2010-11-10 4 views
6

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

Например, при п = 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] ;-)

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

+0

В случае, если не перестановками (2, 1, 1) быть в этом списке? –

+0

Я знал, что кое-что забыл. Спасибо, добавлено. – deleted77

+3

Разрешения целых разделов называются «композициями». –

ответ

2

Хорошо. Во-первых, забудьте о перестановках и просто сгенерируйте разбиения длины 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

Я попытался запустить это решение, и это не сработало для меня в большинстве случаев; это было n = 4 & l = 3, но немногие другие. Мне нужен алгоритм для подмножества, где n = l, и этот алгоритм не дал решения (1,1,1, ...) для любого случая, кроме n = 2. Я попытался заставить его работать, но в конечном итоге должен был сделать совершенно новое решение (см. Ниже). – pbarranis

3

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

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

+0

Слушайте! Если вы сталкиваетесь с такими проблемами, этот субволокт содержит много и много вариантов. Решения, которые предлагает Кнут, - это праздник для ума: очень элегантный и умный. –

0

Как я уже упоминал выше, я не мог получить код @ 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); 
    } 
} 
2

Как отмечает @pbarranis, код по @rlibby не включает в себя все списки, когда п равна К. Ниже приведен код 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 

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

0

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

#!/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), а на вопрос ОП дает:

[[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

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

Так вот нерекурсивно решения в 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 

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

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

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

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

Альтернативой поиску композиций (на основе кода Java здесь https://www.nayuki.io/page/next-lexicographical-permutation-algorithm) является следующее. Я нашел, что это работает лучше, чем 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; 

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

Смежные вопросы