将一组大小为 N 的组拆分为 M 组,这些组的总和之间的差异最小

时间:2021-02-10 14:45:17

标签: algorithm graph dynamic-programming

有一组大小为 N <= 50 的正数。 此集合无法排序或更改!!!

S = {n1, n2, n3, ..., nN}

我需要在 S 组上拆分集合 M <= 25

Groups = {{n1, n2}, {n3, n4, n5...},...,{... nN}} -> len(Groups) = M

两个相邻组之间的最大差异应尽可能小。 差值是这样计算的:

diff = abs(sum(G1) - sum(G2)) = abs((n1 + n2) - (n3 + n4 + n5))

我可以生成大小为 N 的所有向量,其中只包含 M 非零数字(如 1),但我将不得不考虑使用 50! / (25! * 25!) 进行暴力破解这是错误的:(

示例:

6, 13, 10, 2
case 1: {6}, {13, 10, 2} -> difference = 25
case 2: {6, 13}, {10, 2} -> difference = 5
case 3: {6, 13, 10}, {2} -> difference = 29
The best case is second -> difference = 5!

我可以用什么想法来解决这个问题?

1 个答案:

答案 0 :(得分:2)

我结合动态规划和 A* 搜索的修改解决了这个问题。

import heapq

def best_division (numbers, pieces):
    cumulative_sums = [0]
    for number in numbers:
        cumulative_sums.append(cumulative_sums[-1] + number)

    # We need this to be somewhat bigger than the maximum sum.
    # Making it + pieces/2 will result in a better
    # expected_piece_size.
    sum_bound = cumulative_sums[-1] + (pieces // 2)

    # We expect our pieces to be somewhere close to this size.
    expected_piece_size = (cumulative_sums[-1] + (pieces//2)) // pieces

    # This data structure will include:
    #
    #   By completed segments
    #       By start of segment
    #           By end of segment
    #               (lowest maximum difference, prev_start)
    #
    # It will actually be an array of dictionaries of dictionaries.
    #
    # It starts off empty.
    best_path_info = []

    # The queue is a minimum priority queue.
    #
    #   [
    #       lowest maximum difference,
    #       abs(sum of segment - expected_piece_size),
    #       count_pieces,
    #       start,
    #       end,
    #       prev_start
    #   ]
    #
    # This causes us to first look at the lowest maximum difference,
    # and then at the segment sum being close to the expected.
    #
    # It also means we can trace backwards once we have an answer.
    queue = []

    for i in range(len(cumulative_sums)):
        heapq.heappush(
            queue, [
                0,
                abs(cumulative_sums[i] - expected_piece_size),
                0,
                0,
                i,
                None
            ])

    while len(queue):
        max_diff, _, count_completed, start, end, prev_start = heapq.heappop(queue)
        if count_completed + 1 == pieces: # The current piece is not in the count
            if end == len(numbers):
                answer = [numbers[start:end]]
                end = start
                start = prev_start
                while start is not None:
                    answer.append(numbers[start:end])
                    count_completed -= 1
                    prev_start = best_path_info[count_completed][start][end][1]
                    end = start
                    start = prev_start
                ### RETURN HERE ###
                return list(reversed(answer))

            else:
                continue # Not a solution

        if len(best_path_info) <= count_completed:
            best_path_info.append({})

        path = best_path_info[count_completed]
        if start not in path:
            path[start] = {}
        path = path[start]
        if end not in path or max_diff < path[end][0]:
            path[end] = (max_diff, prev_start)
            for next_end in range(end, len(cumulative_sums)):
                this_sum = cumulative_sums[end] - cumulative_sums[start]
                next_sum = cumulative_sums[next_end] - cumulative_sums[end]
                next_max_diff = max(max_diff, abs(next_sum - this_sum))
                heapq.heappush(
                    queue, [
                        next_max_diff,
                        abs(next_sum - expected_piece_size),
                        count_completed + 1,
                        end,
                        next_end,
                        start
                    ])
相关问题