Algorithm to split an array into P subarrays of balanced sum

前端 未结 10 1734
面向向阳花
面向向阳花 2020-12-08 05:00

I have an big array of length N, let\'s say something like:

2 4 6 7 6 3 3 3 4 3 4 4 4 3 3 1

I need to split this array into P subarrays (in

相关标签:
10条回答
  • 2020-12-08 05:51

    I propose an algorithm based on backtracking. The main function chosen randomly select an element from the original array and adds it to an array partitioned. For each addition will check to obtain a better solution than the original. This will be achieved by using a function that calculates the deviation, distinguishing each adding a new element to the page. Anyway, I thought it would be good to add an original variables in loops that you can not reach desired solution will force the program ends. By desired solution I means to add all elements with respect of condition imposed by condition from if.

    sum=CalculateSum(vector)
    Read P
    sigma=sum/P
    initialize P vectors, with names vector_partition[i], i=1..P
    list_vector initialize a list what pointed this P vectors
    initialize a diferences_vector with dimension of P
    //that can easy visualize like a vector of vectors
    //construct a non-recursive backtracking algorithm
    function Deviation(vector) //function for calculate deviation of elements from a vector
    {
      dev=0
      for i=0 to Size(vector)-1 do
      dev+=|vector[i+1]-vector[i]|
      return dev 
    }
    iteration=0
    //fix some maximum number of iteration for while loop
    Read max_iteration
    //as the number of iterations will be higher the more it will get  
    //a more accurate solution
    while(!IsEmpty(vector))
    {   
       for i=1 to Size(list_vector) do
       {
           if(IsEmpty(vector)) break from while loop
           initial_deviation=Deviation(list_vector[i])
           el=SelectElement(vector) //you can implement that function using a randomized   
                                   //choice of element
           difference_vector[i]=|sigma-CalculateSum(list_vector[i])|
           PutOnBackVector(vector_list[i], el)
           if(initial_deviation>Deviation(difference_vector))
              ExtractFromBackVectorAndPutOnSecondVector(list_vector, vector)
        }
        iteration++
        //prevent to enter in some infinite loop
       if (iteration>max_iteration) break from while loop    
    

    } You can change this by adding in first if some code witch increment with a amount the calculated deviation. aditional_amount=0 iteration=0 while { ... if(initial_deviation>Deviation(difference_vector)+additional_amount) ExtractFromBackVectorAndPutOnSecondVector(list_vector, vector) if(iteration>max_iteration) { iteration=0 aditional_amout+=1/some_constant } iteration++ //delete second if from first version }

    0 讨论(0)
  • 2020-12-08 05:55

    First, let’s formalize your optimization problem by specifying the input, output, and the measure for each possible solution (I hope this is in your interest):

    Given an array A of positive integers and a positive integer P, separate the array A into P non-overlapping subarrays such that the difference between the sum of each subarray and the perfect sum of the subarrays (sum(A)/P) is minimal.

    Input: Array A of positive integers; P is a positive integer.
    Output: Array SA of P non-negative integers representing the length of each subarray of A where the sum of these subarray lengths is equal to the length of A.
    Measure: abs(sum(sa)-sum(A)/P) is minimal for each sa ∈ {sa | sa = (Ai, …, Ai+‍SAj) for i = (Σ SAj), j from 0 to P-1}.

    The input and output define the set of valid solutions. The measure defines a measure to compare multiple valid solutions. And since we’re looking for a solution with the least difference to the perfect solution (minimization problem), measure should also be minimal.

    With this information, it is quite easy to implement the measure function (here in Python):

    def measure(a, sa):
        sigma = sum(a)/len(sa)
        diff = 0
        i = 0
        for j in xrange(0, len(sa)):
            diff += abs(sum(a[i:i+sa[j]])-sigma)
            i += sa[j]
        return diff
    
    print measure([2,4,6,7,6,3,3,3,4,3,4,4,4,3,3,1], [3,4,4,5]) # prints 8
    

    Now finding an optimal solution is a little harder.

    We can use the Backtracking algorithm for finding valid solutions and use the measure function to rate them. We basically try all possible combinations of P non-negative integer numbers that sum up to length(A) to represent all possible valid solutions. Although this ensures not to miss a valid solution, it is basically a brute-force approach with the benefit that we can omit some branches that cannot be any better than our yet best solution. E.g. in the example above, we wouldn’t need to test solutions with [9,…] (measure > 38) if we already have a solution with measure ≤ 38.

    Following the pseudocode pattern from Wikipedia, our bt function looks as follows:

    def bt(c):
        global P, optimum, optimum_diff
        if reject(P,c):
            return
        if accept(P,c):
            print "%r with %d" % (c, measure(P,c))
            if measure(P,c) < optimum_diff:
                optimum = c
                optimum_diff = measure(P,c)
            return
        s = first(P,c)
        while s is not None:
            bt(list(s))
            s = next(P,s)
    

    The global variables P, optimum, and optimum_diff represent the problem instance holding the values for A, P, and sigma, as well as the optimal solution and its measure:

    class MinimalSumOfSubArraySumsProblem:
        def __init__(self, a, p):
            self.a = a
            self.p = p
            self.sigma = sum(a)/p
    

    Next we specify the reject and accept functions that are quite straight forward:

    def reject(P,c):
        return optimum_diff < measure(P,c)
    def accept(P,c):
        return None not in c
    

    This simply rejects any candidate whose measure is already more than our yet optimal solution. And we’re accepting any valid solution.

    The measure function is also slightly changed due to the fact that c can now contain None values:

    def measure(P, c):
        diff = 0
        i = 0
        for j in xrange(0, P.p):
            if c[j] is None:
                break;
            diff += abs(sum(P.a[i:i+c[j]])-P.sigma)
            i += c[j]
        return diff
    

    The remaining two function first and next are a little more complicated:

    def first(P,c):
        t = 0
        is_complete = True
        for i in xrange(0, len(c)):
            if c[i] is None:
                if i+1 < len(c):
                    c[i] = 0
                else:
                    c[i] = len(P.a) - t
                is_complete = False
                break;
            else:
                t += c[i]
        if is_complete:
            return None
        return c
    
    def next(P,s):
        t = 0
        for i in xrange(0, len(s)):
            t += s[i]
            if i+1 >= len(s) or s[i+1] is None:
                if t+1 > len(P.a):
                    return None
                else:
                    s[i] += 1
                return s
    

    Basically, first either replaces the next None value in the list with either 0 if it’s not the last value in the list or with the remainder to represent a valid solution (little optimization here) if it’s the last value in the list, or it return None if there is no None value in the list. next simply increments the rightmost integer by one or returns None if an increment would breach the total limit.

    Now all you need is to create a problem instance, initialize the global variables and call bt with the root:

    P = MinimalSumOfSubArraySumsProblem([2,4,6,7,6,3,3,3,4,3,4,4,4,3,3,1], 4)
    optimum = None
    optimum_diff = float("inf")
    bt([None]*P.p)
    
    0 讨论(0)
  • 2020-12-08 05:58

    @Gumbo 's answer is clear and actionable, but consumes lots of time when length(A) bigger than 400 and P bigger than 8. This is because that algorithm is kind of brute-forcing with benefits as he said.

    In fact, a very fast solution is using dynamic programming.

    Given an array A of positive integers and a positive integer P, separate the array A into P non-overlapping subarrays such that the difference between the sum of each subarray and the perfect sum of the subarrays (sum(A)/P) is minimal.

    Measure: , where is sum of elements of subarray , is the average of P subarray' sums.

    This can make sure the balance of sum, because it use the definition of Standard Deviation.

    Persuming that array A has N elements; Q(i,j) means the minimum Measure value when split the last i elements of A into j subarrays. D(i,j) means (sum(B)-sum(A)/P)^2 when array B consists of the i~jth elements of A ( 0<=i<=j<N ).

    The minimum measure of the question is to calculate Q(N,P). And we find that:

    Q(N,P)=MIN{Q(N-1,P-1)+D(0,0); Q(N-2,P-1)+D(0,1); ...; Q(N-1,P-1)+D(0,N-P)}
    

    So it like can be solved by dynamic programming.

     Q(i,1) = D(N-i,N-1)
    
     Q(i,j) = MIN{ Q(i-1,j-1)+D(N-i,N-i); 
                   Q(i-2,j-1)+D(N-i,N-i+1); 
                   ...; 
                   Q(j-1,j-1)+D(N-i,N-j)}
    

    So the algorithm step is:

     1. Cal j=1:
    
        Q(1,1), Q(2,1)... Q(3,1)
    
     2. Cal j=2:
    
        Q(2,2) = MIN{Q(1,1)+D(N-2,N-2)};
    
        Q(3,2) = MIN{Q(2,1)+D(N-3,N-3); Q(1,1)+D(N-3,N-2)}
    
        Q(4,2) = MIN{Q(3,1)+D(N-4,N-4); Q(2,1)+D(N-4,N-3); Q(1,1)+D(N-4,N-2)}
    
     ... Cal j=...
    
     P. Cal j=P:
    
        Q(P,P), Q(P+1,P)...Q(N,P)
    
    The final minimum Measure value is stored as Q(N,P)! 
    To trace each subarray's length, you can store the 
    MIN choice when calculate Q(i,j)=MIN{Q+D...}
    

    space for D(i,j);

    time for calculate Q(N,P)

    compared to the pure brute-forcing algorithm consumes time.

    0 讨论(0)
  • 2020-12-08 05:58

    Your problem is very similar to, or the same as, the minimum makespan scheduling problem, depending on how you define your objective. In the case that you want to minimize the maximum |sum_i - sigma|, it is exactly that problem.

    As referenced in the Wikipedia article, this problem is NP-complete for p > 2. Graham's list scheduling algorithm is optimal for p <= 3, and provides an approximation ratio of 2 - 1/p. You can check out the Wikipedia article for other algorithms and their approximation.

    All the algorithms given on this page are either solving for a different objective, incorrect/suboptimal, or can be used to solve any problem in NP :)

    0 讨论(0)
提交回复
热议问题