生成全排列算法详解
2015-10-17 17:20
316 查看
以下所有算法均基于n个不同的元素。
算法一:递归+回溯+SWAP。
忽略输出,每次递归满足: T(n) = n*T(n-1) + O(n)
令G(n) = T(n)/n!我们得到: G(n) = G(n-1) + O(1/(n-1)!)
所以: G(n) = Theta(1).
即: T(n) = Theta(n!).
所以时间复杂度就是 Theta(n!)
每个排列对应一个数组,空间复杂的O(n!)。
关于这个算法为什么可行,给出证明,基于式子#:n!=n*(n-1)(n-2)…*2*1
我们假设一个全排列初始由n个空格组成,求出全排列就是从第一个空格开始将所有空格填满。每此调用函数GetPermutation(int *a,int first,int end)有3个主要步骤:
假设这是调用函数GetPermutation(a,0,n-1)
步骤一:从区间[0,n-1]找出一个数TEMP1填到排列的第一个空格,有n种(即#的n项),即for循环的意义。
步骤二:在步骤一的基础上,考虑区间[0+1,n-1]上的排列,对于该区间重复步骤一,属于递归调用(即选出一个数作为该区间的第一个数,有n-1种情况,即#中的(n-1)项)。
步骤三:将步骤一的TEMP1放回原位置,属于回溯。再从剩下的n-1个数中重新找一个数TEMP2填到排列的第一个空格,即for循环中的i++。
算法二:字典序算法+SWAP。该算法的时间复杂度要基于输入串的字典序大小。空间复杂度O(n)。
我们知道n个不同元素的字典序一共也有n!种,这是巧合吗?通过观察:1,2,3的全排列:
1,2,3;1,3,2;2,1,3,;2,3,1;3,1,2;3,2,1 我们发现这种排序刚好就是按字典序增长的。难道求全排列就是求所有的字典序吗?答案是肯定的。
假设已给的串是排列中字典序最小的(比如:1,2,3,4,5)。我们的操作是每次寻找当前排列的下一个排列(“下一个排列”指所有比当前排列大的排列中最小的那个),最后输出的按字典序一次递增的,所以我们要从当前排列的最后往前搜索。
假设搜索到ai>ai+1,那么通过交换两数只能得到字典序更小的排列,不可能得到下一个排列。所以我们需要找到一对数ai小于ai+1,而且是第一次搜索到的,即ai+1后面是递减的。为了得到下一个全排列,我们需要用一个更大的数代替ai,即与它交换位置,那当然要从它后面去找这个数(因为全排列按递增输出,所以它前面的数不能改动),而且要找最小的那个,而且必定能找到,至少有ai+1。然后将交换后i+1到n的所有数按递增排列。此时就得到下一个排列。
一直循环重复上面的操作,直到找不到ai小于ai+1的情况。
该算法只能求出字典序比给出的串大的所有排列,要求出n个不同元素的全排列必须先对其进行升序排序。
算法一:递归+回溯+SWAP。
忽略输出,每次递归满足: T(n) = n*T(n-1) + O(n)
令G(n) = T(n)/n!我们得到: G(n) = G(n-1) + O(1/(n-1)!)
所以: G(n) = Theta(1).
即: T(n) = Theta(n!).
所以时间复杂度就是 Theta(n!)
每个排列对应一个数组,空间复杂的O(n!)。
关于这个算法为什么可行,给出证明,基于式子#:n!=n*(n-1)(n-2)…*2*1
我们假设一个全排列初始由n个空格组成,求出全排列就是从第一个空格开始将所有空格填满。每此调用函数GetPermutation(int *a,int first,int end)有3个主要步骤:
假设这是调用函数GetPermutation(a,0,n-1)
步骤一:从区间[0,n-1]找出一个数TEMP1填到排列的第一个空格,有n种(即#的n项),即for循环的意义。
步骤二:在步骤一的基础上,考虑区间[0+1,n-1]上的排列,对于该区间重复步骤一,属于递归调用(即选出一个数作为该区间的第一个数,有n-1种情况,即#中的(n-1)项)。
步骤三:将步骤一的TEMP1放回原位置,属于回溯。再从剩下的n-1个数中重新找一个数TEMP2填到排列的第一个空格,即for循环中的i++。
#include <iostream> #include<cstdio> using namespace std; void SWAP(int &a, int &b){ int tmp; tmp = a; a = b; b = tmp; } int sum=0; void GetPermutation(int *a,int first,int end){//从[first,end]中选一个作为区间[first,end]上的first if(first == end){//递归到最深层 sum++; for(int i = 0; i <= end; i++) cout<<a[i]<<" "; cout<<endl; } else { GetPermutation(a,first+1,end);//first本身作为区间[first,end]的第一个数,从区间[first+1,end]中选一个作为first+1 for(int i = first+1; i <= end; i++){//该循环表示区间[first+1,end]上每个数都依次会作为[first+1,end]上的第一个数 SWAP(a[first],a[i]);//区间[first,end]上的第i个数作为[first,end]的第一个数 GetPermutation(a,first+1,end);//从区间[first+1,end]中选一个作为[first+1,end]的第一个数 SWAP(a[first],a[i]);//将第i个数放回原位置,第i+1个将作为区间[first,end]的第一个数 } } } int main() { freopen("input.txt","r",stdin); freopen("output.txt","w",stdout); int n; int a[100]; cout<<"Input the size of the array:"<<endl; cin>>n; cout<<"Input elements of the array:"<<endl; for(int i=0;i<n;i++) { cin>>a[i]; } GetPermutation(a,0,n-1); cout<<"一共"<<sum<<"种排列"; return 0; }
算法二:字典序算法+SWAP。该算法的时间复杂度要基于输入串的字典序大小。空间复杂度O(n)。
我们知道n个不同元素的字典序一共也有n!种,这是巧合吗?通过观察:1,2,3的全排列:
1,2,3;1,3,2;2,1,3,;2,3,1;3,1,2;3,2,1 我们发现这种排序刚好就是按字典序增长的。难道求全排列就是求所有的字典序吗?答案是肯定的。
假设已给的串是排列中字典序最小的(比如:1,2,3,4,5)。我们的操作是每次寻找当前排列的下一个排列(“下一个排列”指所有比当前排列大的排列中最小的那个),最后输出的按字典序一次递增的,所以我们要从当前排列的最后往前搜索。
假设搜索到ai>ai+1,那么通过交换两数只能得到字典序更小的排列,不可能得到下一个排列。所以我们需要找到一对数ai小于ai+1,而且是第一次搜索到的,即ai+1后面是递减的。为了得到下一个全排列,我们需要用一个更大的数代替ai,即与它交换位置,那当然要从它后面去找这个数(因为全排列按递增输出,所以它前面的数不能改动),而且要找最小的那个,而且必定能找到,至少有ai+1。然后将交换后i+1到n的所有数按递增排列。此时就得到下一个排列。
一直循环重复上面的操作,直到找不到ai小于ai+1的情况。
该算法只能求出字典序比给出的串大的所有排列,要求出n个不同元素的全排列必须先对其进行升序排序。
#include <iostream> #include<cstdio> using namespace std; char a[100]; int len; int sum=0; void _qsort(char* str,int Start,int End){ if(Start==End) return;//出口 char tmp=str[Start]; int i=Start,j=End; while(i<j){ while(i<j&&str[j]>tmp) j--; str[i]=str[j];//补上i空位,j成为新空位 while(i<j&&str[i]<tmp) i++; str[j]=str[i];//补上j空位,i成为新空位 } str[i]=tmp;//补上i空位 //此时一定有i=j if(i-1>Start)_qsort(str,Start,i-1); if(j<End) _qsort(str,i+1,End); } void GetPermutation(char* str) { if(!str) return; while(true) { sum++; cout <<str<<endl; int j=len-2,k=len-1; while(j>=0 && str[j]>str[j+1]) --j;//找到第一对数 str[j]<str[j+1] if(j<0) break;//找不到str[j]<str[j+1]的情况,即排列数已经求完 while(str[k]<str[j]) --k;//找到后面所有比str[j]大的数中最小的 char temp=str[k];//交换两数 str[k]=str[j]; str[j]=temp; int a,b;//str[j]后面的数按递增的顺序排列 for(a=j+1,b=len-1;a<b;++a,--b) { temp=str[a]; str[a]=str[b]; str[b]=temp; } } cout<<sum<<"种情况"<<endl; } int main() { freopen("input.txt","r",stdin); freopen("output.txt","w",stdout); cin>>len; for(int i=0;i<len;i++){ cin>>a[i]; } _qsort(a,0,len-1); GetPermutation(a); return 0; }
相关文章推荐
- 动易2006序列号破解算法公布
- Ruby实现的矩阵连乘算法
- C#插入法排序算法实例分析
- 超大数据量存储常用数据库分表分库算法总结
- C#数据结构与算法揭秘二
- C#通过yield实现数组全排列的方法
- C#冒泡法排序算法实例分析
- 算法练习之从String.indexOf的模拟实现开始
- C#算法之关于大牛生小牛的问题
- C#实现的算24点游戏算法实例分析
- c语言实现的带通配符匹配算法
- 浅析STL中的常用算法
- 算法之排列算法与组合算法详解
- C++实现一维向量旋转算法
- Ruby实现的合并排序算法
- C#折半插入排序算法实现方法
- 基于C++实现的各种内部排序算法汇总
- C++线性时间的排序算法分析
- C++实现汉诺塔算法经典实例
- PHP实现克鲁斯卡尔算法实例解析