最近通过学习算法, 了解了全排列实现的几种方式,在此记录总结一下。
方式一:DFS深度优先搜索
该算法的实现可以当成模板来套用,当然本菜鸡习惯的写法不一定适合你,不过个人觉得还是比较好理解的,你可以多看看,总结总结,嘿嘿。
#include<iostream>
#include<stdio.h>
using namespace std;
const int eleNum = 5;
bool flag[eleNum] = {false};
int data[eleNum] = {0,1,2,3,4};
int count = 0;
void dfs(int depth, int path[])
{
if(depth == eleNum)
{
for(int i = 0;i < eleNum;i++)
{
printf("%d ", path[i]);
}
printf("\n");
count++;
return;
}
for(int i = 0; i< eleNum;i++)
{
if(!flag[i])
{
path[depth] = data[i];
flag[i] = true;
dfs(depth+1,path);
flag[i] = false;
}
}
}
int main()
{
int path[5];
dfs(0, path);
printf("共计%d种排列方式",count);
return 0;
}
方式二:next_permutation()
想看原文档的,请移步next_permutation
在这里只介绍两种常见的用法
注:next_permutation包含于头文件algorithm,用的时候别忘了。
#include<iostream>
#include<algorithm>
#include<string.h>
#include<stdio.h>
using namespace std;
int main()
{
//用法1:对字符串全排列
string s = "01234";
int count = 0;
//int *data = new int[3]{0,1,2};
do{
cout << s << endl;
count++;
} while(next_permutation(s.begin(),s.end()));
cout << "方式一共有" << count << "种情况" << endl;
//用法2:对整形数组全排列
int data[5] = {0,1,2,3,4};
count = 0;
do{
cout << data[0] << data[1] << data[2] << data[3] << data[4] << endl;
count++;
}while(next_permutation(data,data+5));
cout << "方式二共有" << count << "种情况" << endl;
return 0;
}
对于全排列中重复元素的处理
在待排列元素存在重复时,例如{0,0,0,0,0,1,1,1,1}在排列时,第一位和第二位元素交换位置之后,仍表示为一种情况,因此需要条件限制来给dfs减支,但待排列元素必须是有序的,否则就会有问题,问题在最后进行说明。
先看一下对于上面两种方式的重复处理
- 对于dfs,去重方式为在for循环下做条件补充,在上面例子中做的补充应是
if(i>0)&&data[i]!=data[i-1]&&!flag[i-1]) continue;
,表示的含义为判断当前数是否与上一个元素重复,并且上一个元素还没被使用,此时跳过,不再递归。从而能达到减支的效果。 - 对与全排列next_permutation()其本身就包含了重复元素的处理方式,很方便,其排列的顺序为序列的字典序的前后。
说明
- 当待排列元素无序但不重复时
dfs算法仍然有效,而next_permutation就会少很多次数,因为next_permutation总是从当前元素的字典序往下继续进行搜索,例如三个元素0,1,2
初始为0,1,2时,有6种顺序,即012,021,102,120,201,210。这就意味着,如果从120开始的话,就只能获取到120及以后的情况,即只得到4种情况。
因此在使用next_permutation时一定要小心,分清楚情况。 - 当待排列元素无序且重复时
此时,dfs和全排列函数得到的情况都会不正确,只有先排序才能进一步处理。
这间接说明,做全排列时,数组通常是自己构造的。