Алгоритм для генерации всех уникальных перестановок целочисленных разбиений фиксированной длины? - PullRequest
6 голосов
/ 10 ноября 2010

Я ищу алгоритм, который генерирует все перестановки секций фиксированной длины целого числа.Порядок не имеет значения.

Например, для n = 4 и длины L = 3:

[(0, 2, 2), (2, 0, 2), (2, 2, 0),
 (2, 1, 1), (1, 2, 1), (1, 1, 2),
 (0, 1, 3), (0, 3, 1), (3, 0, 1), (3, 1, 0), (1, 3, 0), (1, 0, 3),
 (0, 0, 4), (4, 0, 0), (0, 4, 0)]

Я возился с целочисленными разбиениями + перестановками для разбиений, длина которых меньше L;но это было слишком медленно, потому что я получал один и тот же раздел несколько раз (потому что [0, 0, 1] может быть перестановкой [0, 0, 1]; -)

Любая помощь приветствуется, и нет, это не домашняя работа - личнаяпроценты: -)

Ответы [ 6 ]

3 голосов
/ 06 апреля 2011

Учитывая, что вы спрашиваете об этом из интереса, вам, вероятно, будет интересен авторский ответ!Это можно найти в «7.2.1.2 - Генерация всех перестановок» Кнута Искусство компьютерного программирования ( подобъем 4A ).

Также 3 конкретных алгоритмаможно найти здесь .

2 голосов
/ 04 февраля 2013

Как отмечает @pbarranis, код @rlibby не включает все списки, когда n равно k . Ниже приведен код Python, который включает в себя все списки. Этот код не является рекурсивным, что может быть более эффективным в отношении использования памяти.

def successor(n, l):
  idx = [j for j in range(len(l)) if l[j] < l[0]-1]
  if not idx:
    return False

  i = idx[0]
  l[1:i+1] = [l[i]+1]*(len(l[1:i+1]))
  l[0] = n - sum(l[1:])
  return True

def partitions(n, k):
  l = [0]*k
  l[0] = n
  results = []
  results.append(list(l))
  while successor(n, l):
    results.append(list(l))
  return results

Списки создаются в коллексографическом порядке (алгоритм и подробное описание здесь ).

2 голосов
/ 05 февраля 2011

Хорошо.Во-первых, забудьте о перестановках и просто сгенерируйте разбиения длины L (как предложено @Svein Bringsli).Обратите внимание, что для каждого раздела вы можете наложить порядок элементов, например>.Теперь просто «посчитайте», поддерживая ваш порядок.Для n = 4, k = 3:

(4, 0, 0)
(3, 1, 0)
(2, 2, 0)
(2, 1, 1)

Итак, как это реализовать?Это выглядит следующим образом: вычитая 1 из позиции i и добавляя ее к следующей позиции, сохраняет наш порядок, вычитая 1 из позиции i, прибавляя 1 к позиции i + 1, и переходя к следующей позиции.Если мы в последней позиции, сделайте шаг назад.

Вот маленький питон, который делает именно это:

def partition_helper(l, i, result):
    if i == len(l) - 1:
        return 
    while l[i] - 1 >= l[i + 1] + 1:
        l[i]        -= 1
        l[i + 1]    += 1
        result.append(list(l))
        partition_helper(l, i + 1, result)

def partition(n, k):
    l = [n] + [0] * (k - 1)
    result = [list(l)]
    partition_helper(l, 0, result)
    return result

Теперь у вас есть список списков (на самом деле список мультимножеств)и генерация всех перестановок каждого мультимножества списка дает вам решение.Я не буду вдаваться в подробности, есть рекурсивный алгоритм, который в основном говорит, что для каждой позиции выбирайте каждый уникальный элемент в мультимножестве и добавляйте перестановки мультимножества, полученные в результате удаления этого элемента из мультимножества.

1 голос
/ 12 июля 2017

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

Итак, вот нерекурсивное решение на C ++, которое создает разделы в отсортированном порядке (что также идеально подходит для перестановок).Я обнаружил, что это в 10 раз быстрее, чем, казалось бы, умных и кратких рекурсивных решений, которые я пробовал для длин разделов 4 или больше, но для длин 1-3 производительность не обязательно лучше (и мне не нужны короткиедлины, потому что они бывают быстрыми при любом подходе).

// Inputs
unsigned short myInt = 10;
unsigned short len = 3;

// Partition variables.
vector<unsigned short> partition(len);
unsigned short last = len - 1;
unsigned short penult = last - 1;
short cur = penult; // Can dip into negative value when len is 1 or 2.  Can be changed to unsigned if len is always >=3.
unsigned short sum = 0;

// Prefill partition with 0.
fill(partition.begin(), partition.end(), 0);

do {
    // Calculate remainder.
    partition[last] = max(0, myInt - sum); // Would only need "myInt - sum" if partition vector contains signed ints.

    /*
     *
     * DO SOMETHING WITH "partition" HERE.
     *
     */

    if (partition[cur + 1] <= partition[cur] + 1) {
        do {
            cur--;
        } while (
            cur > 0 && 
            accumulate(partition.cbegin(), partition.cbegin() + cur, 0) + (len - cur) * (partition[cur] + 1) > myInt
        );

        // Escape if seeked behind too far.
        // I think this if-statement is only useful when len is 1 or 2, can probably be removed if len is always >=3. 
        if (cur < 0) {
            break;
        }

        // Increment the new cur position.
        sum++;
        partition[cur]++;

        // The value in each position must be at least as large as the 
        // value in the previous position.            
        for (unsigned short i = cur + 1; i < last; ++i) {
            sum = sum - partition[i] + partition[i - 1];
            partition[i] = partition[i - 1];
        }

        // Reset cur for next time.
        cur = penult;
    }
    else {
        sum++;
        partition[penult]++;
    }

} while (myInt - sum >= partition[penult]);

Где я написал ДЕЛАЙТЕ ЧТО-ЛИБО С «разделом» ЗДЕСЬ. - это место, где вы на самом деле потребляете значение.(На последней итерации код продолжит выполнение оставшейся части цикла, но я обнаружил, что это лучше, чем постоянная проверка условий выхода - он оптимизирован для более крупных операций)

0,0,10
0,1,9
0,2,8
0,3,7
0,4,6
0,5,5
1,1,8
1,2,7
1,3,6
1,4,5
2,2,6
2,3,5
2,4,4
3,3,4

О, я использовал "unsigned short", потому что я знаю, что моя длина и целое число не превысят определенные пределы, измените это, если это не подходит для вас :) Проверьте комментарии;одна переменная там (cur) должна была быть подписана для обработки длины 1 или 2, и есть соответствующий if-оператор, который идет с этим, и я также отметил в комментарии, что если ваш вектор разбиения имеет подписанные целые, есть другая строкаэто можно упростить.

Чтобы получить все композиции, в C ++ я бы использовал эту простую стратегию перестановок, которая, к счастью, не дает дубликатов:

do {
    // Your code goes here.
} while (next_permutation(partition.begin(), partition.end()));

Гнездо, которое в ДЕЛАЙТЕ ЧТО-ТО С «разделом» ЗДЕСЬ spot, и все готово.

Альтернатива поиску композиций (на основе Java-кода здесь https://www.nayuki.io/page/next-lexicographical-permutation-algorithm) заключается в следующем. IВы обнаружили, что это работает лучше, чем next_permutation ().

// Process lexicographic permutations of partition (compositions).
composition = partition;
do {

    // Your code goes here.

    // Find longest non-increasing suffix
    i = last;
    while (i > 0 && composition[i - 1] >= composition[i]) {
        --i;
    }
    // Now i is the head index of the suffix

    // Are we at the last permutation already?
    if (i <= 0) {
        break;
    }

    // Let array[i - 1] be the pivot
    // Find rightmost element that exceeds the pivot
    j = last;
    while (composition[j] <= composition[i - 1])
        --j;
    // Now the value array[j] will become the new pivot
    // Assertion: j >= i

    // Swap the pivot with j
    temp = composition[i - 1];
    composition[i - 1] = composition[j];
    composition[j] = temp;

    // Reverse the suffix
    j = last;
    while (i < j) {
        temp = composition[i];
        composition[i] = composition[j];
        composition[j] = temp;
        ++i;
        --j;
    }
} while (true);

Там вы заметите некоторые необъявленные переменные, просто объявите их ранее в коде перед всеми вашими циклами: i, j, pos и temp (шорты без знака) и composition (того же типа и длины, что и partition). Вы можете повторно использовать объявление i для его использования в цикле for в разделахтрескаэлектронный фрагмент.Также обратите внимание на используемые переменные, такие как last, которые предполагают, что этот код вложен в код разделов, указанный ранее.

Снова "Ваш код идет сюда" - это место, где вы используете композицию для своих собственных целей.

Для справки вот мои заголовки.

#include <vector> // for std::vector
#include <numeric> // for std::accumulate
#include <algorithm> // for std::next_permutation and std::max
using namespace std;

Несмотря на значительное увеличение скорости с использованием этих подходов, для любых значительных целых чисел и длин разделов это все равно заставит вас злиться на ваш процессор:)

0 голосов
/ 20 декабря 2016

Многие поиски привели к этому вопросу. Вот ответ, который включает перестановки:

#!/usr/bin/python
from itertools import combinations_with_replacement as cr
def all_partitions(n, k):
    """
    Return all possible combinations that add up to n
    i.e. divide n objects in k DISTINCT boxes in all possible ways
    """
    all_part = []
    for div in cr(range(n+1), k-1):
        counts = [div[0]]
        for i in range(1, k-1):
            counts.append(div[i] - div[i-1])
        counts.append(n-div[-1])
        all_part.append(counts)
    return all_part

Например, all_part (4, 3) по запросу OP дает:

[[0, 0, 4],
 [0, 1, 3],
 [0, 2, 2],
 [0, 3, 1],
 [0, 4, 0],
 [1, 0, 3],
 [1, 1, 2],
 [1, 2, 1],
 [1, 3, 0],
 [2, 0, 2],
 [2, 1, 1],
 [2, 2, 0],
 [3, 0, 1],
 [3, 1, 0],
 [4, 0, 0]]
0 голосов
/ 02 июля 2012

Как я уже упоминал выше, я не мог заставить код @ rlibby работать для моих нужд, и мне нужен был код, где n = l, поэтому просто подмножество ваших потребностей. Вот мой код ниже, в C #. Я знаю, что это не совсем ответ на вопрос выше, но я считаю, что вам нужно всего лишь изменить первый метод, чтобы он работал для разных значений l; в основном добавьте тот же код, что и @rlibby, создавая массив длины l вместо длины n.

public static List<int[]> GetPartitionPermutations(int n)
{
    int[] l = new int[n];

    var results = new List<int[]>();

    GeneratePermutations(l, n, n, 0, results);
    return results;
}

private static void GeneratePermutations(int[] l, int n, int nMax, int i, List<int[]> results)
{
    if (n == 0)
    {
        for (; i < l.Length; ++i)
        {
            l[i] = 0;
        }
        results.Add(l.ToArray());
        return;
    }

    for (int cnt = Math.Min(nMax, n); cnt > 0; --cnt)
    {
        l[i] = cnt;
        GeneratePermutations(l, (n - cnt), cnt, i + 1, results);
    }
}
...