目录
先看下《算法竞赛: 从入门到精通》这本书里面关于全排列的代码
例题1:求全排列
题目:按照字典序从小到大打印出n个数的全排列(1~n)
比如:n=3,则结果为:
123
132
213
231
312
321
打印全排列有很多种方法,单纯的打印全排列可以使用递归或者BFS
先看下《算法竞赛: 从入门到精通》这本书里面关于全排列的代码
int g_n = 0;
int g_data[11];
void Swap(int *dataA, int *dataB)
{
int m = *dataA;
*dataA = *dataB;
*dataB = m;
}
void PrintData()
{
for (int i = 0; i < g_n; i++) {
printf("%d ", g_data[i]);
}
printf("\n");
}
void Perm(int begin, int end)
{
if (begin == end) {
PrintData();
return;
}
for (int i = begin; i <= end; i++) {
Swap(&g_data[i], &g_data[begin]);
Perm(begin + 1, end);
Swap(&g_data[i], &g_data[begin]);
}
}
int main()
{
g_n = 3;
for (int i = 0; i < g_n; i++) {
g_data[i] = i + 1;
}
Perm(0, g_n - 1);
return 0;
}
如果n=3则结果为:
1 2 3
1 3 2
2 1 3
2 3 1
3 2 1
3 1 2
运行之后会发现最后两行的字典序是颠倒的,接下来让我们一起分析下颠倒的原因:
代码递归的思路还是比较好理解的,大致的步骤可以分为如下几个步骤:
1. 由123找到最后来个数,交换2和3得到结果:132
把132还原为123
2. 由123开始找,交换1和2得到结果:213
由213开始找到最后两个数,交换1和3,得到的结果为:231
把231还原为213
把213还原为123
3. 由123开始,交换1和3,得到的结果为321
由321开始找到最后两个数,交换1和2,得到的结果为312
把312还原为321
把321还原为123
通过这个思想我们可以把交换操作变换为平移操作
而如果要得到按字典序的排序,则需要进行平移操作,最简单的思路为:
1. 由123找到最后来个数,把3提到2的前面,得到结果:132
把132还原为123,还原方法为:把3放到2的后面
2. 由123开始找,把2提到1的前面,得到结果:213
由213开始找到最后两个数,把3提到1的前面,得到的结果为:231
把231还原为213,还原方法为:把3放到1的后面
把213还原为123,还原方位为:把2放到1的后面
3. 由123开始,把3提到1的前面,得到的结果为312
由312开始找到最后两个数,把2提到1的前面,得到的结果为321
把321还原为312,还原方法为:把2放到1的后面
把312还原为123,还原方法为:把3放到2的后面
例题2 求n个数中的任意m个数的全排列
题目:求n个数中的任意3个数的全排列
则只需要把递归的终止条件进行如下改动即可:
if (begin == 2) {
PrintData();
return;
}
例题3 求n个数中,任意m个数的组合
题目:求n个数中,任意3个数的组合
思路:借用二进制,n位的二进制中1的个数
借用二进制思考
假如有三个数,我们用{A0, A1, A3}表示,它的子集和二进制的关系如下表格所示:
子集 | 空 | A0 | A1 | A1,A0 | A2 | A2,A0 | A2,A1 | A2,A1,A0 |
二进制 | 000 | 001 | 010 | 011 | 100 | 101 | 110 | 111 |
拆分来看,3个数的所有子集:只包含1个数的集合,只包含2个数的集合,只包含三个数的集合
只包含1个数的集合:A0; A1; A2,对应二进制为:001, 010, 100(只包含1个1)
只包含2个数的集合:A2,A1; A2, A0; A1, A0,对应的二进制为:110,101,011(只包含2个1)
只包含3个数的集合:A2,A1,A0,对应的二进制为:111(只包含3个1)
因此若求n个数中任意m个数的组合,我们只需要判断所有n位组成的二进制数中,包含多少个m个1,即可判断几个数的组合(组合无序)
代码如下:
void Print_subset(int n)
{
// 大于由n位组成的数最小的那个数
int max = (1 << n);
// 遍历所有的由n位组成的数
for (int i = 0; i < max; i++) {
// 判断每一位
for (int j = 0; j < n; j++) {
// 如果第j位为1,则打印
if (i & (1 << j)) {
printf("%d", j);
}
}
}
}
直接定位二进制数中1的位置,跳过中间的0
while (kk) {
kk = kk & (kk - 1);
num++;
}