举例:
6,7,8,4,3,2
从右找到第一个非升序的数字7
从右找到第一个比7大的数字8
调换7和8,得到6,8,7,4,3,2
将8之后的数字全部翻转得到6,8,2,3,4,7此即为下一个字典序的序列。
bool next_permutation(int a[],int n){
int pivot = -1;
for(int i = n-1;i>0;i--){
if(a[i-1]<a[i]){
pivot = i-1;
break;
}
}
if(pivot == -1){
reverse(a,n);
return false;
}
int biggerThanPivot = -1;
for(int i = n-1;i>0;i--){
if(a[i]>a[pivot]){
biggerThanPivot = i;
break;
}
}
swap(a[pivot],a[biggerThanPivot]);
reverse(a+pivot+1,n-pivot-1);
return true;
}
调用next_permutaion之前,要先对原数组排序,得到从小到大排列的数组,然后调用next_permutaion直到此函数返回false,就得到了全部的排列,且不重复。
void permutation(int a[],int n){
sort(a,a+n);
while(true){
printArray(a,n);
if(!next_permutation(a,n))
break;
}
}
2. 递归实现
递归实现比较复杂,首先,为了得到所有不重复的集合,我们可以这么理解这个问题,把数组看成是不同球的编号,数组的大小为球的个数,不同的数字为不同的球,比如数组【1,1,2】就代表有两个标号为1的球和一个标号为2的球,现在的问题是一共要从中取出n个球,有多少种不同的顺序。我们可以先把不同标号的球放到不同的袋子里,标号为1的球都放到标号为1的袋子里,标号为2的球都放到标号为2的袋子里,以此类推,最后我们假设得到了m个不同的带子,里面分别放了若干个球,接着我们要做的只是从这些袋子里面取n个球,一共有多少种中取法就是所有的不重复的集合数,取的过程中要注意的一个问题是从某个袋子里面取球时,要保证这个袋子里面还有剩余的球可取。
先看主函数代码:
void permutations2(int a[],int n){
sort(a,a+n);//可有可无,排序的话可以得到每个排列重点数字都是递增排列的
unordered_map<int,int> mymap;
for(int i = 0;i<n;i++){
if(mymap.find(a[i]) != mymap.end())
mymap[a[i]]++;
else
mymap[a[i]] = 1;
}
vector<pair<int,int>> elems;
for(auto i = mymap.begin();i != mymap.end();i++)
elems.push_back(pair<int,int>(i->first,i->second));
//以上为把每个球放到相应的袋子中,并记录袋子中球的个数。
vector<int> path;
permutations2Helper(elems,path,n);
}
主要的取球算法在permutations2Helper函数中,接下来看这个函数的实现:
void permutations2Helper(vector<pair<int,int>>& elems,vector<int>& path,int n){
if(path.size() == n){
printVec(path);
return;
}
for(size_t i = 0;i<elems.size();i++){
int count = 0;
for(size_t j = 0;j<path.size();j++)
if(path[j] == elems[i].first)
count++;
if(count < elems[i].second){
path.push_back(elems[i].first);
permutations2Helper(elems,path,n);
path.pop_back();
}
}
}
path是临时变量,记录当前路径即当前取球次序的变量。每次取球时,可以从任何一个袋子中取,只要袋子中的球还没被取完。当取到的球的总数等于数组大小即总球数时,返回。
本人水平有限,文章有错误的地方欢迎大家指出,有问题也希望大家能够留言交流,一起学习进步