Алгоритм рекурсивного возврата для решения проблемы разбиения - PullRequest
7 голосов
/ 29 апреля 2011

Эй, я ищу некоторую помощь, чтобы найти алгоритм, который делит массив положительных чисел на k-части так, чтобы каждая часть имела (приблизительно) одинаковую сумму ... скажем, у нас есть

1,2,3,4,5,6,7,8,9 en k = 3 th алгоритм должен разделить его следующим образом: 1,2,3,4,5 | 6,7 | 8,9 порядкаэлементы не могут быть изменены ... Найти жадный алгоритм легко, но я ищу версию с возвратом, которая всегда возвращает оптимальное решение ...

У кого-нибудь есть подсказки?

Ответы [ 3 ]

5 голосов
/ 29 апреля 2011

Вот решение, которое не использует динамические структуры данных, такие как списки.Они совершенно не нужны и на практике могут сделать алгоритм намного медленнее, чем необходимо.

Пусть K будет числом разделов здесь, а N будет количеством элементов в вашем массиве.Примечание: это алгоритм O (n K ).Оно является субэкспоненциальным, поскольку оно не эквивалентно общей задаче разбиения, полной NP-полной, здесь вы ищете непрерывные сегменты линейного массива вместо произвольных подмножеств данного общего набора.

5 голосов
/ 29 апреля 2011

Что вы подразумеваете под оптимальным решением? Я полагаю, что вы имеете в виду тот, который сводит к минимуму сумму расстояния каждого раздела до оптимального раздела. Оптимальным разделом будет раздел, в котором сумма его элементов равна общей сумме, разделенной на количество разделов.

Если вы не беспокоитесь об эффективности, то, возможно, этот грубый подход вам подойдет. Я не проверял алгоритм на предмет его корректности, поэтому будьте осторожны.

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);
    }
}
0 голосов
/ 17 января 2017

Вот рекурсивный алгоритм в 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}
...