解决分区问题的递归回溯算法

Posted

技术标签:

【中文标题】解决分区问题的递归回溯算法【英文标题】:Recursive-backtracking algorithm for solving the partitioning problem 【发布时间】:2011-08-15 00:54:54 【问题描述】:

嘿,我正在寻找一些帮助来找到一种算法,该算法将正数数组划分为 k 部分,以便每个部分具有(大约)相同的总和......假设我们有

1,2,3,4,5,6,7,8,9 en k=3 那么算法应该像这样划分它 1,2,3,4,5|6,7|8,9 元素的顺序不能改变......找到一个贪心算法很容易,但我正在寻找一个总是返回最优解的回溯版本......

谁有任何提示?

【问题讨论】:

【参考方案1】:

最佳解决方案是什么意思?我相信您的意思是将每个分区距离之和最小化到最佳分区的那个。最佳分区是它的元素总和等于总和除以分区数的分区。

如果您不介意效率,那么也许这种粗略的方法对您来说已经足够了。我还没有测试算法来检查它的正确性,所以要小心。

void FindPartitions(int[] numbers, int i, IList<int>[] partitions, int currentPartition, IList<int>[] bestSolution, ref int minDistance)

    if (i == numbers.Length)
    
        int sum = numbers.Sum();
        int avg = sum / partitions.Length;
        int currentDistance = 0;
        foreach (var partition in partitions)
            currentDistance += Math.Abs(partition.Sum() - avg);
        if (currentDistance < minDistance)
        
            minDistance = currentDistance;
            for (int j = 0; j < partitions.Length; j++)
                bestSolution[j] = new List<int>(partitions[j]);
        
    
    else
    
        partitions[currentPartition].Add(numbers[i]);
        FindPartitions(numbers, i + 1, partitions, currentPartition, bestSolution, ref minDistance);
        partitions[currentPartition].RemoveAt(partitions[currentPartition].Count - 1);
        if (currentPartition < partitions.Length - 1)
            FindPartitions(numbers, i, partitions, currentPartition + 1, bestSolution, ref minDistance);
    

【讨论】:

【参考方案2】:

这是一个不使用任何动态数据结构(例如列表)的解决方案。它们完全没有必要,实际上会使算法比必要的慢得多。

设 K 为此处的分区数,N 为数组中的元素数。

int start[K];

void register() 
   /* calculate the error between minimum and maximum value partitions */
   /* partition boundaries are start[0], start[1], start[2], ... */
   /* if lower error than previously stored, remember the best solution */


void rec(int s, int k) 
  if (k == K) register();
  for (int i = s; i < N; i++) 
    start[k] = i;
    rec(i + 1, k + 1);
  


/* entry */
start[0] = 0;
rec(1, 1);
/* then output the best solution found at register() */

注意:这是一个 O(nK) 算法。它是次指数的,因为这等同于一般的 NP 完全分区问题,在这里您正在寻找线性数组的连续段,而不是给定总集的任意子集。

【讨论】:

【参考方案3】:

这是 javascript 中的递归算法。此函数返回将分配给每个工作人员的总数。假设输入数组 bookLoads 是一个正数数组,您希望将其尽可能公平地划分为 k 个部分(假设在 k 个工作人员之间)

这是一个有效的小提琴: https://jsfiddle.net/missyalyssi/jhtk8vnc/3/

function fairWork(bookLoads, numWorkers = 0) 
    // recursive version

    // utilities
    var bestDifference = Infinity;
    var bestPartition = ;
    var initLoads = ;
    var workers = Array.from(length: numWorkers, (val, idx) => 
      initLoads[idx] = 0;
      return idx;
    );
    var bookTotal = bookLoads.reduce((acc, curr) => return acc + curr, 0); 
    var average = bookTotal / bookLoads.length;

    // recursive function
    function partition(books = bookLoads, workers, loads=) 

      // if only one worker give them all the books
      if (workers.length == 1 && books.length > 0) 
        var onlyWorker = workers[0];
        loads[onlyWorker] += books.reduce((acum, curr, idx, arr) => 
                               return acum + curr;
                             ,0);
        books = [];
      

      // base case
      if (books.length == 0) 
        var keys = Object.keys(loads);
        var total = 0;
        for (let key = 0; key < keys.length; key++) 
          // square so that difference shows 
          total += Math.pow(Math.abs(average - loads[keys[key]]), 2);
        
        if (total < bestDifference) 
          bestDifference = total;
          bestPartition= loads;
        
        return bestPartition;
      

      var currBookLoad = books[0];

      // add book to curr worker 1
      var newWorker1Loads = Object.assign(, loads);
      var worker1 = workers[0]; 
      newWorker1Loads[worker1] = newWorker1Loads[worker1] + currBookLoad || currBookLoad;
      partition(books.slice(1), workers, newWorker1Loads)

      // give to next worker
      var newNextWorkerLoads = Object.assign(, loads);
      var worker2 = workers[1]; 
      newNextWorkerLoads[worker2] = newNextWorkerLoads[worker2] + currBookLoad || currBookLoad;
      partition(books.slice(1), workers.slice(1), newNextWorkerLoads)

      return bestPartition;
    
    // function call
    return partition(bookLoads, workers, initLoads)

fairWork([3,1,2,3], 3)
//Result will be: Object 0: 3, 1: 3, 2: 3
fairWork([1,2,3,4,5,6,7,8,9], 3)
//Result will be: Object 0: 15, 1: 13, 2: 17

【讨论】:

以上是关于解决分区问题的递归回溯算法的主要内容,如果未能解决你的问题,请参考以下文章

递归回溯算法期间变量未更改

怎样才能深刻理解递归和回溯?

算法——迷宫问题(回溯递归)

有人可以帮助解释这个回溯算法中的递归吗?

算法入门(回溯算法)

Java数据结构与算法——递归与回溯