C/C++:全排列问题
啥叫全排列?
从n个不同元素中任取m(m≤n)个元素,按照一定的顺序排列起来,叫做从n个不同元素中取出m个元素的一个排列。
当m=n时所有的排列情况叫全排列。
假设现在有三个数字:0 1 2,将其全排列结果为:
0 1 2
0 2 1
1 0 2
1 2 0
2 0 1
2 1 0
我们有几种方式可以实现全排列。
最简单,最暴力的是枚举。
【例1】
#include <stdio.h>
#include <stdlib.h>
void Permutations()
{
int i, j, k;
for (i = 0; i < 3; i++)
for (j = 0; j < 3; j++)
for (k = 0; k < 3; k++)
{
if (i != j && i != k && j != k)
printf("%d %d %d\n", i, j, k);
}
}
int main()
{
Permutations();
return 0;
}
其实我们可以优化一点:
【例2】
#include <stdio.h>
#include <stdlib.h>
void Permutations()
{
int i, j, k;
for (i = 0; i < 3; i++)
{
for (j = 0; j < 3; j++)
{
if (i == j)
{
continue;
}
for (k = 0; k < 3; k++)
{
if (i == k || j == k)
{
continue;
}
printf("%d %d %d\n", i, j, k);
}
}
}
}
int main()
{
Permutations();
return 0;
}
哈哈,只是换汤不换药,但是在嵌套循环很多的时候性能会有很大的提高,毕竟可以提前发现,少做很多的无用功。
另外一种可以使用mark标记数组,当需要全排列的数字很多时比较好用。
假设现在需要对0 1 2 3进行全排列:
【例3】
#include <stdio.h>
#include <stdlib.h>
int Permutations()
{
int total = 0;
int a[4];
for (a[0] = 0; a[0] < 4; a[0]++)
for (a[1] = 0; a[1] < 4; a[1]++)
for (a[2] = 0; a[2] < 4; a[2]++)
for (a[3] = 0; a[3] < 4; a[3]++)
{
int mark[4] = {0, 0, 0, 0};
int i;
for (i = 0; i < 4; i++)
{
mark[a[i]]++;
}
int sum = 0;
for (i = 0; i < 4; i++)
{
if (mark[i])
{
sum++;
}
}
if (sum == 4)
{
printf("%d %d %d %d\n", a[0], a[1], a[2], a[3]);
total ++;
}
}
return total;
}
int main()
{
int result = Permutations();
printf("result is %d\n", result);
return 0;
}
[test1280@localhost ~]$ !g
gcc -o main main.c
[test1280@localhost ~]$ ./main
0 1 2 3
0 1 3 2
0 2 1 3
0 2 3 1
0 3 1 2
……
上述方法其实也是枚举,虽然能在数字多时看起来简洁,但是个人认为,性能上还不如第二种方法,毕竟第二种可以减少很多不必要的循环,可以预先检查。
总结上面三点,都是枚举,都很暴力。
有没有更好一点的方法?
我们可以使用DFS来对全排列进行计算。
【例4】
#include <stdio.h>
#include <stdlib.h>
/*****************************************************
* 函数名称:DFS
* 参数列表:mark-标记数组;step-当前为第几步;all-一共有多少步;path-踪迹数组
* 函数描述:深度优先,遍历step步时可能的情况总数
* 返回值 :step步可能出现的情况总数
* 备注 :
* Author :test1280
* History :2017/05/14
* ***************************************************/
int DFS(int *mark, int step, int all, int *path)
{
if (step == all)
{
int i;
for (i = 0; i < all; i++)
{
printf("%d ", path[i]);
}
printf("\n");
return 1;
}
int i;
int total = 0;
for (i = 0; i <= all; i++)
{
if (mark[i])
{
continue;
}
mark[i] = 1;
path[step] = i;
total = total + DFS(mark, step + 1, all, path);
mark[i] = 0;
}
return total;
}
/*******************************************************
* 函数名称:Permutations
* 参数列表:all-共有all个数进行全排列
* 函数描述:全排列all个数字,从0到all-1
* 返回值 :全排列all个数字的情况总数
* 备注 :
* Author :test1280
* History :2017/05/14
* ***************************************************/
int Permutations(int all)
{
int *mark = (int *)malloc(sizeof(int) * all);
int *path = (int *)malloc(sizeof(int) * all);
int total = 0;
int i;
for (i = 0; i < all; i++)
{
mark[i] = 0;
}
for (i = 0; i < all; i++)
{
mark[i] = 1;
// now-step is 0
path[0] = i;
total = total + DFS(mark, 1, all, path);
mark[i] = 0;
}
return total;
}
int main()
{
int all = 10;
int result = Permutations(all);
printf("%d\n", result);
return 0;
}
输出可能会有一会~
我们将那些输出都干掉,只是求结果总数:
【例5】
#include <stdio.h>
#include <stdlib.h>
/*****************************************************
* 函数名称:DFS
* 参数列表:mark-标记数组;step-当前为第几步;all-一共有多少步;
* 函数描述:深度优先,遍历step步时可能的情况总数
* 返回值 :step步可能出现的情况总数
* 备注 :
* Author :test1280
* History :2017/05/14
* ***************************************************/
int DFS(int *mark, int step, int all)
{
if (step == all)
{
return 1;
}
int i;
int total = 0;
for (i = 0; i <= all; i++)
{
if (mark[i])
{
continue;
}
mark[i] = 1;
total = total + DFS(mark, step + 1, all);
mark[i] = 0;
}
return total;
}
/*******************************************************
* 函数名称:Permutations
* 参数列表:all-共有all个数进行全排列
* 函数描述:全排列all个数字,从0到all-1
* 返回值 :全排列all个数字的情况总数
* 备注 :
* Author :test1280
* History :2017/05/14
* ***************************************************/
int Permutations(int all)
{
int *mark = (int *)malloc(sizeof(int) * all);
int total = 0;
int i;
for (i = 0; i < all; i++)
{
mark[i] = 0;
}
for (i = 0; i < all; i++)
{
mark[i] = 1;
// now-step is 0
total = total + DFS(mark, 1, all);
mark[i] = 0;
}
return total;
}
int main()
{
int all = 10;
int result = Permutations(all);
printf("%d\n", result);
return 0;
}
输出如下:
[test1280@localhost ~]$ !g
gcc -o main main.c
[test1280@localhost ~]$ ./main
3628800
注意一点,DFS在这里面,总是递归调用,从全局程序运行生长来看,他做的也是对all个数,用了all次循环,区别在于,其将【例2】中的预先判断做了极大的发挥,使其结合mark标记数组,再加上递归的特性,使得性能大大增加。
大家不妨将【例3】改为10个数字的全排列,只输出结果,然后和【例4】进行性能比较。
有没有别的方法呢?