一种有效的方法,可以生成所有可能的方法来配对数据集中的项目

时间:2016-05-25 21:11:54

标签: algorithm grouping combinatorics

这有点像组合问题;我正试图找出一种有效的方法来配对数据集中的所有项目。

例如,我有一个长度为6的数组:[1,2,3,4,5,6],我想在数组中做所有可能的内容配对:

[1,2],[3,4],[5,6]  
[1,2],[3,5],[4,6]  
[1,2],[3,6],[4,5]  
[1,3],[2,4],[5,6]  
[1,3],[2,5],[4,6]  
...

等等。在这个例子中,总共有15个组合。一般来说,这个解决方案的可能性应该是(N-1)!!其中N是数组的长度或要配对的项目数。在这种情况下,N将始终是偶数。理想情况下,算法将连续生成所有可能性,而无需存储非常大的数组。

例如,一种方法有点像'循环'调度算法,你将数组拆分为N / 2:

[1,2,3]  
[4,5,6]  

并顺时针旋转[5,3,6]以产生3个唯一配对(垂直计数),[1,2,4]固定为:

[1,2,3]  
[4,5,6]

[1,2,5]  
[4,6,3]  

[1,2,6]  
[4,3,5]

然后顺时针旋转[4,2,3,6,5]以生成5个唯一配对,[1]固定,从最小的最里面的情况(N = 4)向外直到结束,但递归。因为我不确定如何最好地将其表达为代码,或者是否有更有效的方法来实现这一点,因为N可能非常大。

2 个答案:

答案 0 :(得分:3)

哇。现在有一个过去的爆炸。我在1993年写过这篇文章,并为它提供了Pascal源代码。令人惊讶的是,它出现的文章可以在http://www.drdobbs.com/database/algorithm-alley/184409099#02e5_000d在线获得。

基本上,我采用了选择排序算法:

for x = 0 to n-2
    for y = x+1 to n-1
        write x, y

该方法的问题在于它生成{1,2},{1,3},{1,4},{2,3},{2,4}...

事实证明,您可以通过维护在外部循环的每次迭代后操作的交换数组来修改该输出。这是我在文章中提供的Pascal源代码。

(* ----------------------------------------------------------- *(
**  pairings.pas -- Select sports-event team pairings          **
** ------------------------------------------------------------**
**   This program generates team pairings for sports events.   **
**   Each team is guaranteed to play each other team exactly   **
**   once. No team will play more than one game per day.       **
**   An asterisk ('*') means a day off for that team.          **
**   For example, 5 teams produces this output:                **
**     Day 1 - 12 34 5*                                        **
**     Day 2 - 13 25 4*                                        **
**     Day 3 - 14 2* 35                                        **
**     Day 4 - 15 3* 24                                        **
**     Day 5 - 1* 45 23                                        **
** ------------------------------------------------------------**
**   Copyright (c) 1993 by Jim Mischel. All rights reserved.   **
)* ----------------------------------------------------------- *)

program pairings;
const
  TEAMCOUNT = 5;
var
  TeamNames: Array [1 .. TEAMCOUNT + 1] of Char;
  SwapArray: Array [1 .. TEAMCOUNT + 1] of Integer;
  x, Temp, Day: Integer;
  TempChar: Char;
const
  NTeams: Integer = TEAMCOUNT;
begin
{ Set up team names. Normally read from a file. }
  for x := 1 to NTeams do
    TeamNames[x] := Chr(x + Ord('0'));
  if Odd(NTeams) then
  begin
    NTeams := NTeams + 1;
    TeamNames[NTeams] := '*'
  end;
{ Set up the array that controls swapping. }
  for x := 1 to NTeams do
    SwapArray[x] := x;
  for Day := 1 to NTeams - 1 do
  begin
    Write('Day ', Day, ' -');
{ Write the team pairings for this day }
    x := 1;
    while x < NTeams do
    begin
      Write(' ', TeamNames[x], TeamNames[x + 1]);
      x := x + 2;
    end;
    WriteLn;
{ Perform swaps to prepare array for next day's pairings. }
    if Odd(Day)
      then x := 2
      else x := 3;
    while x < NTeams do
    begin
      TempChar := TeamNames[SwapArray[x]];
      TeamNames[SwapArray[x]] := TeamNames[SwapArray[x + 1]];
      TeamNames[SwapArray[x + 1]] := TempChar;
      Temp := SwapArray[x];
      SwapArray[x] := SwapArray[x + 1];
      SwapArray[x + 1] := Temp;
      x := x + 2
    end
  end
end.

答案 1 :(得分:2)

您可以使用标准recursive algorithm for generating permutations of a list生成对,但是每次递归时都会在列表中前进2个元素,并且列表中的第一个剩余元素始终是该对中的第一个元素在每次递归时输出,其中第二个是其他剩余元素。

始终选择第一个剩余元素作为对中的第一个项目,避免以不同顺序生成与对相同的配对。

与标准算法一样,您可以在不制作数组副本的情况下生成排列,方法是将元素交换到位,递归然后重新交换。

这是一些用于演示算法的C代码(我意识到这个问题被标记为Fortran但只是将其视为伪代码)。此代码在递归时传递列表并计数,但您可以使用itemsitemcount作为全局变量来实现它:

// start is the current position in the list, advancing by 2 each time
// pass 0 as start when calling at the top level
void generatePairings(int* items, int itemcount, int start)
{
    if(itemcount & 1)
        return; // must be an even number of items

    // is this a complete pairing?
    if(start == itemcount)
    {
        // output pairings:
        int i;
        for(i = 0; i<itemcount; i+=2)
        {
            printf("[%d, %d] ", items[i], items[i+1]);
        }
        printf("\n");
        return;
    }

    // for the next pair, choose the first element in the list for the
    // first item in the pair (meaning we don't have to do anything 
    // but leave it in place), and each of the remaining elements for
    // the second item:
    int j;
    for(j = start+1; j<itemcount; j++)
    {
        // swap start+1 and j:
        int temp = items[start+1];
        items[start+1] = items[j];
        items[j] = temp;

        // recurse:
        generatePairings(items, itemcount, start+2);

        // swap them back:
        temp = items[start+1];
        items[start+1] = items[j];
        items[j] = temp;
    }
}

int main(void) {
    int items[6] = {1, 2, 3, 4, 5, 6};
    generatePairings(items, 6, 0);

    return 0;
}

输出:

[1, 2] [3, 4] [5, 6] 
[1, 2] [3, 5] [4, 6] 
[1, 2] [3, 6] [5, 4] 
[1, 3] [2, 4] [5, 6] 
[1, 3] [2, 5] [4, 6] 
[1, 3] [2, 6] [5, 4] 
[1, 4] [3, 2] [5, 6] 
[1, 4] [3, 5] [2, 6] 
[1, 4] [3, 6] [5, 2] 
[1, 5] [3, 4] [2, 6] 
[1, 5] [3, 2] [4, 6] 
[1, 5] [3, 6] [2, 4] 
[1, 6] [3, 4] [5, 2] 
[1, 6] [3, 5] [4, 2] 
[1, 6] [3, 2] [5, 4] 

如果您在大型对象列表中执行此操作,那么置换索引列表然后使用它们索引到您的对象数组更有效,而不是在大量的对象上执行昂贵的交换操作数据