组合排列的通用算法

转载 2007年09月18日 14:38:00

 尽管排列组合是生活中经常遇到的问题,可在程序设计时,不深入思考或者经验不足都让人无从下手。由于排列组合问题总是先取组合再排列,并且单纯的排列问题相对简单,所以本文仅对组合问题的实现进行详细讨论。以在n个数中选取m(0<m<=n)个数为例,问题可分解为:
1. 首先从n个数中选取编号最大的数,然后在剩下的n-1个数里面选取m-1个数,直到从n-(m-1)个数中选取1个数为止。
2. 从n个数中选取编号次小的一个数,继续执行1步,直到当前可选编号最大的数为m。
很明显,上述方法是一个递归的过程,也就是说用递归的方法可以很干净利索地求得所有组合。
下面是递归方法的实现:

/**//// 求从数组a[1..n]中任选m个元素的所有组合。
/// a[1..n]表示候选集,n为候选集大小,n>=m>0。
/// b[1..M]用来存储当前组合中的元素(这里存储的是元素下标),
/// 常量M表示满足条件的一个组合中元素的个数,M=m,这两个参数仅用来输出结果。

void combine( int a[], int n, int m,  int b[], const int M )
...
 
for(int i=n; i>=m; i--)   // 注意这里的循环范围
 ...{
  b[m
-1= i - 1;
  
if (m > 1)
   combine(a,i
-1,m-1,b,M);
  
else                     // m == 1, 输出一个组合
  ...{   
   
for(int j=M-1; j>=0; j--)
    cout 
<< a[b[j]] << " ";
   cout 
<< endl;
  }

 }

}

因为递归程序均可以通过引入栈,用回溯转化为相应的非递归程序,所以组合问题又可以用回溯的方法来解决。为了便于理解,我们可以把组合问题化归为图的路径遍历问题,在n个数中选取m个数的所有组合,相当于在一个这样的图中(下面以从1,2,3,4中任选3个数为例说明)求从[1,1]位置出发到达[m,x](m<=x<=n)位置的所有路径:
1  2  3  4
    2  3  4
        3  4
上图是截取n×n右上对角矩阵的前m行构成,如果把矩矩中的每个元素看作图中的一个节点,我们要求的所有组合就相当于从第一行的第一列元素[1,1]出发,到第三行的任意一列元素作为结束的所有路径,规定只有相邻行之间的节点,并且下一行的节点必须处于上一行节点右面才有路径相连,其他情况都无路径相通。显然,任一路径经过的数字序列就对应一个符合要求的组合。
下面是非递归的回溯方法的实现:

/**//// 求从数组a[1..n]中任选m个元素的所有组合。
/// a[1..n]表示候选集,m表示一个组合的元素个数。
/// 返回所有排列的总数。

int combine(int a[], int n, int m)
...{   
 m 
= m > n ? n : m;

 
int* order = new int[m+1];    
 
for(int i=0; i<=m; i++)
  order[i] 
= i-1;            // 注意这里order[0]=-1用来作为循环判断标识
 
 
int count = 0;                                
 
int k = m;
 
bool flag = true;           // 标志找到一个有效组合
 while(order[0== -1)
 
...{
  
if(flag)                   // 输出符合要求的组合
  ...{   
   
for(i=1; i<=m; i++)                    
    cout 
<< a[order[i]] << " ";
   cout 
<< endl;
   count
++;
   flag 
= false;
  }


  order[k]
++;                // 在当前位置选择新的数字
  if(order[k] == n)          // 当前位置已无数字可选,回溯
  ...{
   order[k
--= 0;
   
continue;
  }
     
  
  
if(k < m)                  // 更新当前位置的下一位置的数字          
  ...{
   order[
++k] = order[k-1];
   
continue;
  }

  
  
if(k == m)
   flag 
= true;
 }


 delete[] order;
 
return count;
}


下面是测试以上函数的程序:
int main()
...{
 
const int N = 4;
 
const int M = 3;
 
int a[N];
 
for(int i=0;i<N;i++)
  a[i] 
= i+1;

 
// 回溯方法
 cout << combine(a,N,3<< endl; 

 
// 递归方法
 int b[M];
 combine(a,N,M,b,M); 

 
return 0;
}

由上述分析可知,解决组合问题的通用算法不外乎递归和回溯两种。在针对具体问题的时候,因为递归程序在递归层数上的限制,对于大型组合问题而言,递归不是一个好的选择,这种情况下只能采取回溯的方法来解决。

    n个数的全排列问题相对简单,可以通过交换位置按序枚举来实现。STL提供了求某个序列下一个排列的算法next_permutation,其算法原理如下:
1. 从当前序列最尾端开始往前寻找两个相邻元素,令前面一个元素为*i,后一个元素为*ii,且满足*i<*ii;
2. 再次从当前序列末端开始向前扫描,找出第一个大于*i的元素,令为*j(j可能等于ii),将i,j元素对调;
3. 将ii之后(含ii)的所有元素颠倒次序,这样所得的排列即为当前序列的下一个排列。
其实现代码如下:

template <class BidirectionalIterator>
bool next_permutation(BidirectionalIterator first, BidirectionalIterator last) 
...{
  
if (first == last) return false;   // 空範圍
  BidirectionalIterator i = first;
  
++i;
  
if (i == last) return false;       // 只有一個元素
  i = last;                          // i 指向尾端
  --i;

 
for(;;) 
 
...{
  BidirectionalIterator ii 
= i;
  
--i;
  
// 以上,鎖定一組(兩個)相鄰元素
  if (*< *ii)                     // 如果前一個元素小於後一個元素
  ...
   BidirectionalIterator j 
= last;  // 令 j指向尾端
   while (!(*< *--j));            // 由尾端往前找,直到遇上比 *i 大的元素
   iter_swap(i, j);                 // 交換 i, j
   reverse(ii, last);               // 將 ii 之後的元素全部逆向重排
   return true;
  }

  
if (i == first)                   // 進行至最前面了
  ...
   reverse(first, last);            
// 全部逆向重排
   return false;
  }

 }

}
 

下面程序演示了利用next_permutation来求取某个序列全排列的方法:
int main()
...{
 
int ia[] = ...{1,2,3,4};
 vector
<int> iv(ia,ia+sizeof(ia)/sizeof(int));

 copy(iv.begin(),iv.end(),ostream_iterator
<int>(cout," "));
 cout 
<< endl;
 
while(next_permutation(iv.begin(),iv.end()))
 
...{
  copy(iv.begin(),iv.end(),ostream_iterator
<int>(cout," "));
  cout 
<< endl;
 }


 
return 0;
}

注意:上面程序中初始序列是按数值的从小到大的顺序排列的,如果初始序列无序的话,上面程序只能求出从当前序列开始的后续部分排列,也就是说next_permutation求出的排列是按排列从小到大的顺序进行的。

排列组合问题的通用算法

尽管排列组合是生活中经常遇到的问题,可在程序设计时,不深入思考或者经验不足都让人无从下手。由于排列组合问题总是先取组合再排列,并且单纯的排列问题相对简单,所以本文仅对组合问题的实现进行详细讨论。以在n...

排列与组合的JAVA通用算法

排列算法1.例如,输入n,求1-n的全排列。一共有n*(n-1)*(n-2)…*1种排列(n个数两两不相同)//采用递归地方式实现,就是回朔法 public class Rank { priv...

Java实现通用组合算法

Java实现通用组合算法

Java实现通用组合算法

转自:http://itlab.idcquan.com/Java/advance/873134.html  Java实现通用组合算法,存在一个类似{31311133,33113330}这样的...

字符串的全排列和组合算法

http://blog.csdn.net/hackbuteer1/article/details/7462447   hackbuteer1 全排列在笔试面试中很热门,因为它难度适中,既可以考察递归...

Java排列组合算法

  • 2016年07月31日 15:56
  • 95KB
  • 下载

C++和java中关于全排列和组合的有关算法

收集的排列加组合的各种算法,还有待完善: 一.利用二进制状态法求排列组合,此种方法比较容易懂,但是运行效率不高,小数据排列组合可以使用 import java.util.Arrays; //利用二...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:组合排列的通用算法
举报原因:
原因补充:

(最多只允许输入30个字)