комбинация без повторения N элементов без использования for..to..do

Я хочу загрузить в список комбинацию числа N без повторения, давая вводить элементы и группу. Например, с 4 элементами [1,2,3,4] у меня есть для:

Group 1: [1][2][3][4]; 
Group 2: [1,2][1,3][1,4][2,3][2,4][3,4];
Group 3: [1,2,3][1,2,4][1,3,4][2,3,4]
Group 4: [1,2,3,4]

Теперь я решил это, используя вложенный цикл, например, для группы 2, я пишу:

  for x1 := 1 to 3 do
    for x2 := Succ(x1) to 4 do
      begin
        // x1, x2 // 
      end

или для группы 3 я написал:

  for x1 := 1 to 2 do
    for x2 := Succ(x1) to 3 do
      for x3 := Succ(x2) to 4 do
      begin
        // x1, x2, x3 // 
      end

и так для других групп. В общем, если я хочу сделать это для группы N, как я могу, без записи N процедур с вложенными циклами? Я подумал о двойном цикле while..do один для использования для счетчика и один для использования для подсчета групп, но это немного сложно, я хотел знать, есть ли какое-то решение более простое и быстрое, тоже с использованием логического оператора или что-то в этом роде . Кто может мне что-нибудь подсказать? Большое спасибо.


person Marcello Impastato    schedule 29.11.2011    source источник
comment
Я думаю, вы пропустили [2,3], [2,4], [3,4].   -  person 500 - Internal Server Error    schedule 29.11.2011
comment
Имя набора, который вы хотите, называется Перестановки набора или чисел. Прочтите эту статью в Википедии: en.wikipedia.org/wiki/Permutation   -  person DwB    schedule 29.11.2011
comment
@AndreasRejbrand 'annidate' = вложенный   -  person RRUZ    schedule 29.11.2011
comment
@DwB нет, перестановки имеют то же количество элементов, что и исходный набор.   -  person David Heffernan    schedule 29.11.2011
comment
да извини, я уже забыл :( просто было отвлечься.   -  person Marcello Impastato    schedule 29.11.2011
comment
Помогает ли это: stackoverflow.com / questions / 728972 /   -  person David Heffernan    schedule 29.11.2011
comment
Приведенный вами пример цикла for работает только со значениями инкрементного набора, у которых нет пробелов посередине. Это твой случай?   -  person Marcus Adams    schedule 30.11.2011
comment
@ Дэвид, k-перестановки могут иметь меньше элементов. DwB, вероятно, думал, что Марчелло хотел вычислить все k-перестановки для k ∈ {1..N}, когда ему действительно нужны k-комбинации для того же диапазона.   -  person Rob Kennedy    schedule 30.11.2011


Ответы (6)


Похоже, вы ищете быстрый алгоритм для вычисления всех k-комбинаций. Следующий код Delphi является прямым переводом кода C, найденного здесь: Создание Комбинации. Я даже исправил ошибку в этом коде!

program kCombinations;

{$APPTYPE CONSOLE}

// Prints out a combination like {1, 2}
procedure printc(const comb: array of Integer; k: Integer);
var
  i: Integer;
begin
    Write('{');
    for i := 0 to k-1 do
  begin
    Write(comb[i]+1);
    if i<k-1 then
      Write(',');
  end;
    Writeln('}');
end;

(*
Generates the next combination of n elements as k after comb
  comb => the previous combination ( use (0, 1, 2, ..., k) for first)
  k => the size of the subsets to generate
  n => the size of the original set

  Returns: True if a valid combination was found, False otherwise
*)
function next_comb(var comb: array of Integer; k, n: Integer): Boolean;
var
  i: Integer;
begin
    i := k - 1;
    inc(comb[i]);
    while (i>0) and (comb[i]>=n-k+1+i) do
  begin
    dec(i);
        inc(comb[i]);
    end;

    if comb[0]>n-k then// Combination (n-k, n-k+1, ..., n) reached
  begin
    // No more combinations can be generated
    Result := False;
    exit;
  end;

    // comb now looks like (..., x, n, n, n, ..., n).
    // Turn it into (..., x, x + 1, x + 2, ...)
    for i := i+1 to k-1 do
        comb[i] := comb[i-1]+1;

  Result := True;
end;

procedure Main;
const
    n = 4;// The size of the set; for {1, 2, 3, 4} it's 4
    k = 2;// The size of the subsets; for {1, 2}, {1, 3}, ... it's 2
var
  i: Integer;
  comb: array of Integer;
begin
  SetLength(comb, k);// comb[i] is the index of the i-th element in the combination

    //Setup comb for the initial combination
  for i := 0 to k-1 do
        comb[i] := i;

    // Print the first combination
    printc(comb, k);

    // Generate and print all the other combinations
    while next_comb(comb, k, n) do
        printc(comb, k);
end;

begin
  Main;
  Readln;
end.

Вывод

{1,2}
{1,3}
{1,4}
{2,3}
{2,4}
{3,4}
person David Heffernan    schedule 30.11.2011
comment
Спасибо, это решение более быстрое. Очень-очень хорошо, еще раз спасибо :) - person Marcello Impastato; 01.12.2011
comment
Он выравнивает выходные данные в ожидаемом вами порядке. Хороший алгоритм. - person David Heffernan; 01.12.2011

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

Вывод находится не в том порядке, в котором вы хотите, но это достаточно легко исправить, если это важно для вас.

program VisitAllSubsetsDemo;

{$APPTYPE CONSOLE}

procedure PrintBitset(Bitset: Cardinal; Size: Integer);
var
  i: Integer;
  Mask: Cardinal;
  SepNeeded: Boolean;
begin
  SepNeeded := False;
  Write('{');
  for i := 1 to Size do begin
    Mask := 1 shl (i-1);
    if Bitset and Mask<>0 then begin
      if SepNeeded then begin
        Write(',');
      end;
      Write(i);
      SepNeeded := True;
    end;
  end;
  Writeln('}');
end;

procedure EnumerateSubsets(Size: Integer);
var
  Bitset: Cardinal;
begin
  for Bitset := 0 to (1 shl Size)-1 do begin
    PrintBitset(Bitset, Size);
  end;
end;

begin
  EnumerateSubsets(4);
end.

Вывод

{}
{1}
{2}
{1,2}
{3}
{1,3}
{2,3}
{1,2,3}
{4}
{1,4}
{2,4}
{1,2,4}
{3,4}
{1,3,4}
{2,3,4}
{1,2,3,4}

А вот вариант, который просто перечисляет подмножества указанной мощности:

function SetBitCount(Bitset: Cardinal; Size: Integer): Integer;
var
  i: Integer;
  Mask: Cardinal;
begin
  Result := 0;
  for i := 1 to Size do begin
    Mask := 1 shl (i-1);
    if Bitset and Mask<>0 then begin
      inc(Result);
    end;
  end;
end;

procedure EnumerateSubsets(Size, NumberOfSetBits: Integer);
var
  Bitset: Cardinal;
begin
  for Bitset := 0 to (1 shl Size)-1 do begin
    if SetBitCount(Bitset, Size)=NumberOfSetBits then begin
      PrintBitset(Bitset, Size);
    end;
  end;
end;

begin
  EnumerateSubsets(4, 2);
end.

Вывод

{1,2}
{1,3}
{2,3}
{1,4}
{2,4}
{3,4}
person David Heffernan    schedule 29.11.2011
comment
Очень хорошо, но для отображения только набора из N элементов с группой M? Например, при group = 2 отображать только: {1,2} {1,3} {1,4} {2,3} {2,4} {3,4}, как я могу это настроить? Большое спасибо. - person Marcello Impastato; 30.11.2011
comment
Большое спасибо, это решило мою проблему. Еще раз спасибо. Есть только небольшая проблема, пока до 20 элементов все в порядке, намного быстрее, но с 25 или более элементами в наборе это все медленнее :( Можно ли решить это? Ищете код, не понимаете, что занимает много времени. Еще раз спасибо . - person Marcello Impastato; 01.12.2011
comment
Что ж, если вам нужны все подмножества, то здесь сложно пойти намного быстрее, чем базовый цикл. Просто существует огромное количество подмножеств. Если вам нужны подмножества только определенной мощности, тогда это становится более послушным. Дай мне подумать об этом. - person David Heffernan; 01.12.2011
comment
@Marcello Попробуйте другой ответ. Думаю, это именно то, что вам нужно! - person David Heffernan; 01.12.2011

Кажется, это вопрос, который возникает снова и снова, и несколько фрагментов кода пытаются решить эту проблему. Был написан очень хороший алгоритм в некотором коде, но он не был строго чистым C и не переносился в UNIX, Linux или любую систему POSIX, поэтому я очистил его и добавил предупреждающие сообщения, использование и возможность предоставить установленный размер и размер под_набора в командной строке. Также comb [] был переведен на более общий указатель на массив целых чисел и calloc, используемый для обнуления памяти, необходимой для любого размера набора, который может потребоваться.

Следующее - ISO IEC 9899: 1999 C clean:

/*********************************************************************
 * The Open Group Base Specifications Issue 6
 * IEEE Std 1003.1, 2004 Edition
 *
 *    An XSI-conforming application should ensure that the feature 
 *    test macro _XOPEN_SOURCE is defined with the value 600 before 
 *    inclusion of any header. This is needed to enable the 
 *    functionality described in The _POSIX_C_SOURCE Feature Test 
 *    Macro and in addition to enable the XSI extension.
 *
 * Compile with c99 or with gcc and CFLAGS to include options 
 * -std=iso9899:199409 -pedantic-errors in order to ensure compliance
 * with ISO IEC 9899:1999 C spec. 
 *
 * Code cleanup and transition to comb as a pointer to type ( int * ) 
 * array by Dennis Clarke [email protected]  28 Dec 2012 
 *
 *********************************************************************/
#define _XOPEN_SOURCE 600

#include <stdio.h>
#include <stdlib.h>

/* Prints out a combination like {1, 2} */
void printc( int *comb, int k) {

    int j;
    printf("{ ");

    for ( j = 0; j < k; ++j )
        printf("%d , ", *( comb + j ) + 1 );

    printf( "\b\b}\n" );

} /* printc */

/**********************************************************************
    next_comb(int comb[], int k, int n)
    Generates the next combination of n elements as k after comb

    comb => the previous combination ( use (0, 1, 2, ..., k) for first)
    k => the size of the subsets to generate
    n => the size of the original set

    Returns: 1 if a valid combination was found
    0, otherwise
**********************************************************************/

int next_comb( int *comb, int k, int n) {

    int i = k - 1;
    ++*( comb + i );
    while ( ( i >= 0 ) && ( *( comb + i ) >= n - k + 1 + i ) ) {
        --i;
        ++*( comb + i );
    }

    if ( *comb > n - k) /* Combination (n-k, n-k+1, ..., n) reached */
        return 0; /* No more combinations can be generated */

    /* comb now looks like (..., x, n, n, n, ..., n).
     * Turn it into (..., x, x + 1, x + 2, ...) */
    for (i = i + 1; i < k; ++i)
        *( comb + i ) = *( comb + ( i - 1 ) ) + 1;

    return 1;

} /* next_comb */

int main(int argc, char *argv[]) {

    int *comb, i, n, k;

    n = 9; /* The size of the set; for {1, 2, 3, 4} it's 4 */
    k = 6; /* The size of the subsets; for {1, 2}, {1, 3}, .. it's 2 */

    if ( argc < 3 ) { 
        printf ( "\nUSAGE : %s n k\n", argv[0] );
        printf ( "      : Where n is the set size and k the sub set size.\n" );
        printf ( "      : Note that k <= n\n" );
        return ( EXIT_FAILURE );
    }

    n = atoi ( argv[1] );
    k = atoi ( argv[2] );

    if ( k > n ) {
        printf ( "\nWARN  : k > n is not allowed.\n" );
        printf ( "USAGE : %s n k\n", argv[0] );
        printf ( "      : Where n is the set size and k the sub set size.\n" );
        printf ( "      : Note that k <= n\n" );
        return ( EXIT_FAILURE );
    }

    comb = ( int * ) calloc( (size_t) k, sizeof(int) );

    for ( i = 0; i < k; ++i)
        *( comb + i ) = i;

    /* Print the first combination */
    printc( comb, k );

    /* Generate and print all the other combinations */
    while ( next_comb( comb, k, n ) )
        printc( comb, k );

    free ( comb );

    return ( EXIT_SUCCESS );

}

Вышеупомянутое можно скомпилировать на машине на базе Opteron следующим образом:

$ echo $CFLAGS 
-m64 -g -malign-double -std=iso9899:199409 -pedantic-errors -mno-mmx 
-mno-sse -fexceptions -fpic -fvisibility=default -mtune=opteron 
-march=opteron -m128bit-long-double -mpc80 -Wl,-q
$ gcc $CFLAGS -o combinations combinations.c 

Быстрый тривиальный тест с размером набора 10 и подмножеством 6 будет таким:

$ ./combinations 10 6 | wc -l 
210

Математика верна:

 ( 10 ! ) / ( ( 10 - 6 )!  *  ( 6! ) )  =   210 unique combinations. 

Теперь, когда гребешок целочисленных массивов основан на системе указателей, мы ограничены только доступной памятью и временем. Следовательно, мы имеем следующее:

$ /usr/bin/time -p ./combinations 20 6 | wc -l 
real 0.11
user 0.10
sys 0.00
38760

Выглядит правильно:

( 20 ! ) / ( ( 20 - 6 )!  *  ( 6! ) )  = 38,760 unique combinations

Теперь мы можем немного расширить границы таким образом:

$ ./combinations 30 24 | wc -l 
593775

Математика снова согласуется с результатом:

( 30 ! ) / ( ( 30 - 24 )!  *  ( 24! ) )  =  593 775 unique combinations

Не стесняйтесь расширять пределы своей системы:

$ /usr/bin/time -p ./combinations 30 22 | wc -l  
real 18.62
user 17.76
sys 0.83
5852925

Мне еще предстоит пробовать что-то большее, но математика выглядит правильной, как и результат. Не стесняйтесь сообщить мне, если потребуется какое-то исправление.

Деннис Кларк [email protected] 28 декабря 2012 г.

person Dennis Clarke    schedule 29.12.2012
comment
Этот вопрос помечен тегом delphi, и в этом вопросе нет никакого упоминания о c. Ваш ответ похож на публикацию Си, сеньор, когда кто-то спрашивает Как вы скажете «Да, сэр» по-китайски? :-) (И мы не используем здесь подписи в вопросах или ответах; у вас есть профиль пользователя, в котором вы можете указать такие вещи, как адрес электронной почты, если хотите, но не делайте этого в своих сообщениях. < href = "http://stackvoerflow.com/faq" rel = "nofollow noreferrer"> FAQ упоминает об этом, если вы не уверены. Вопросы и ответы также автоматически имеют дату и время, поэтому вы их тоже не нужно добавлять.) - person Ken White; 30.12.2012

Перейдя по ссылке, которую разместил Дэвид, и щелкнув по ней, я попал в статью, в которой был введен термин «поиск банкира», который, кажется, соответствует вашему образцу.

В статье представлен пример решения на C ++ с использованием рекурсии:

Эффективное перечисление подмножеств набора

person Marcus Adams    schedule 29.11.2011

Если вы не можете выполнять вызовы функций по каким-либо требованиям, сделайте следующее:

select_n_from_list(int *selected, int n, int *list, int list_size):
    if (n==0) {
        // print all numbers from selected by traversing backward
        // you can set the head to a special value or make the head location
        // a static variable for lookup
    }

    for (int i=0; i<=list_size-n; i++) {
        *selected = list[i];
        select_n_from_list(selected+1, n-1, list+i+1, list_size-i-1);
    }
}

Вам действительно нужна какая-то рекурсия, потому что вам нужно автоматическое хранение промежуточных результатов. Сообщите мне, если есть особые требования, из-за которых это решение не работает.

person syang    schedule 29.11.2011
comment
рекурсия - хорошее решение проблемы, но этот код написан не на том языке - person David Heffernan; 29.11.2011
comment
лол, извините, что не знаю желаемого языка. это похоже на delphi, но я бы предпочел писать на общем языке C, чтобы убедиться, что это правильный код. - person syang; 30.11.2011

Я создал этот скрипт здесь и работал очень хорошо:

$(document).ready(function(){
    $("#search").on('click', function(){
        var value = $("#fieldArray").val().split(",");
        var results = new SearchCombinations(value);
        var output = "";
        for(var $i = 0; $i< results.length;$i++){
        	results[$i] = results[$i].join(",");
            output +="<li>"+results[$i]+"</li>";
        }
        $("#list").html(output);
    });
});

/*Helper Clone*/
var Clone = function (data) {
    return JSON.parse(JSON.stringify(data));
}

/*Script of Search All Combinations without repetitions. Ex: [1,2,3]*/
var SearchCombinations = function (statesArray) {
    var combinations = new Array(),
        newValue = null,
        arrayBeforeLevel = new Array(),
        $level = 0,
        array = new Clone(statesArray),
        firstInteration = true,
        indexFirstInteration = 0,
        sizeValues = array.length,
        totalSizeValues = Math.pow(2, array.length) - 1;
    array.sort();
    combinations = new Clone(array);
    arrayBeforeLevel = new Clone(array);
    loopLevel: while ($level < arrayBeforeLevel.length) {
        for (var $i = 0; $i < array.length; $i++) {
            newValue = arrayBeforeLevel[$level] + "," + array[$i];
            newValue = newValue.split(",");
            newValue.sort();
            newValue = newValue.join(",");
            if (combinations.indexOf(newValue) == -1 && arrayBeforeLevel[$level].toString().indexOf(array[$i]) == -1) {
                if (firstInteration) {
                    firstInteration = false;
                    indexFirstInteration = combinations.length
                }
                sizeValues++;
                combinations.push(newValue);
                if (sizeValues == totalSizeValues) {
                    break loopLevel;
                }
            }
        }
        $level++;
        if ($level == arrayBeforeLevel.length) {
            firstInteration = true;
            arrayBeforeLevel = new Clone(combinations);
            arrayBeforeLevel = arrayBeforeLevel.splice(indexFirstInteration);
            indexFirstInteration = 0;
            $level = 0;
        }
    }
    for (var $i = 0; $i < combinations.length; $i++) {
        combinations[$i] = combinations[$i].toString().split(",");
    }
    return combinations;
}
*{font-family: Arial;font-size:14px;}
small{font-size:11px}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<label for="">
    <input type="text" id="fieldArray">
    <button id="search">Search</button>
    <br><small>Info the elements. Ex: "a,b,c"</small>
</label>
<hr>
<ul id="list"></ul>

person João Júnior    schedule 19.11.2015