分享
 
 
 

排列组合问题的通用算法

王朝other·作者佚名  2007-01-18
窄屏简体版  字體: |||超大  

尽管排列组合是生活中经常遇到的问题,可在程序设计时,不深入思考或者经验不足都让人无从下手。由于排列组合问题总是先取组合再排列,并且单纯的排列问题相对简单,所以本文仅对组合问题的实现进行详细讨论。以在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]表示候选集,m表示一个组合的元素个数。

/// b[1..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 (*i < *ii) // 如果前一個元素小於後一個元素

{

BidirectionalIterator j = last; // 令 j指向尾端

while (!(*i < *--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求出的排列是按排列从小到大的顺序进行的。

Copyright@戴维 2006.5 于北京

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有