generating permunation——全排列(算法汇总)

本文一共提供了4种全排列的方法,包括递归非字典序版本、递归字典序版本、标准库版本和BFS字典序版本,当然BFS非字典序实现相对于BFS字典序版本更加简洁,稍加修改即可。

说明:递归版本基于网上现有代码修改而成,标准库版本参照msdn sample修改而成,最后的BFS版本是由本人在看到题目后思考而来,并实现之(递归版本很久之前写过),所有四种算法都加了模板。当然BFS版本效率相对于递归要快,相对于STL版本则较慢,仅仅提供一种思路而已

 

注:对于这种小算法,自己主动思考可以开阔思路,而且想出一种新思路感觉会很不错;对于已成型的经典或者复杂算法,新思路的空间会非常小,所以可以以掌握为主。



 /**
 @description: generating permunation
 @author: seiyagoo
 @create: 2013.10.10
 @modified: 2013.10.11
 **/


#include <iostream> #include <string> #include <vector> #include <list> #include <queue> #include <iterator> #include <algorithm> using namespace std; #define MAX 10 int used[MAX]; //用来标记数字是否已经在前面使用过 int result[MAX]; //存放结果 int array[MAX] = {1,2,3,4,5,6,7,8,9,10}; void swap(int x, int y){ int temp = array[x]; array[x]=array[y]; array[y]=temp; return; } template<typename T> void printArray(T array[], int size){ int i; for (i=0;i<size;i++) cout << array[i] << " "; cout << endl; return; } /*递归(非字典序)*/ template<typename T> void recur_permute(T array[], int begin, int end) { int i; if (begin==end) { printArray(array, end+1); return; } else{ //for循环遍历该排列中第一个位置的所有可能情况 for (i=begin; i<=end; i++){ swap(begin, i); //循环变换第一个位置 recur_permute(array, begin+1, end); //DFS swap(begin, i); //回溯,保持原排列 } } } /*递归(字典序)*/ template<typename T> void lexi_recur_permute(T array[], int begin, int end) { int i; if (begin == end+1) { printArray(result, end+1); return; } else{ for (i=0; i<=end; i++){ if(!used[i]) //没有标记 { used[i]=1; //标记 result[begin]=array[i]; //记录结果 lexi_recur_permute(array, begin+1, end); used[i]=0; //回溯 } } } } /*STL(字典序)*/ template<typename T> void stl_permute(T array[], int size) { vector<T>::iterator begin, end; vector<T> Pattern(size) ; ostream_iterator<T> out_it(cout, " ") ; //int size=sizeof(array)/sizeof(T); for(int i=0; i<size; i++) Pattern[i]=array[i]; begin = Pattern.begin() ; end = Pattern.end() ; do { copy(begin, end, out_it) ; cout << endl ; }while ( next_permutation(begin, end) ); } int get_factorial(int n) { if(1==n || 0==n) return 1; else return n*get_factorial(n-1); } /*给定元素个数n,以及数组p,返回全排列的序号*/ template<typename T> int perm2num(int n, T *p){ int i, j, num=0,k=1; for (i=n-2;i>=0;i--)//注意下标从0开始 { for (j=i+1;j<n;j++) if (p[j]<p[i]) num+=k;//是否有逆序,如有,统计 k*=n-i; //每一轮后k=(n-i)!, } return num; } /*BFS(字典序)*/ template<typename T> void bfs_permute(T array[], int size) { int idx=0; int cnt=get_factorial(size); list<T> ls; queue<list<T>> q; ls.push_back(array[0]); q.push(ls); while(!q.empty()) { list<T> cur_perm = q.front(); if(cur_perm.size() == size) //第n层的第一个元素长度等于size,循环结束 break; if(cur_perm.size() != idx) //不相等 idx++; q.pop(); list<T>::iterator it = cur_perm.end(); while( it!=cur_perm.begin() ) { cur_perm.insert(it, array[idx]); //插入 q.push(cur_perm); it=cur_perm.erase(--it); //还原 --it; //向前一步找插入点 if( it==cur_perm.begin() ) //第一个插入点特殊处理并结束 { cur_perm.push_front(array[idx]); q.push(cur_perm); cur_perm.clear(); break; } } } print_queue(q, size, cnt); } template<typename T> void print_queue(queue<list<T>> q, int size, int cnt) { vector<list<T>> vec(cnt); T* perm=new T[size]; //存储当前排列 /*映射*/ while(!q.empty()) { list<T> cur_perm=q.front(); q.pop(); list<T>::iterator it=cur_perm.begin(); int idx=0,i=0; int n=size; while(it!=cur_perm.end()) { perm[i++]=*it++; } //当前排列放入全排列对应位置 idx=perm2num(size, perm); vec[idx]=cur_perm; } delete []perm; /*输出*/ vector<list<T>>::iterator vit=vec.begin(); for(;vit!=vec.end();vit++) { list<T> cur_perm=*vit; list<T>::iterator lit=cur_perm.begin(); for(;lit!=cur_perm.end();lit++) { cout<<*lit<<" "; } cout<<endl; } } int main(){ recur_permute(array, 0, 3); lexi_recur_permute(array, 0,3); stl_permute(array, 4); bfs_permute(array, 4); return 0; }

 

最后再附上STL版本算法思路及修改后的代码(仅仅为了可读性):

 

思路

给定已知序列P =  A1A2A3.....An
对P按字典排序,得到P的一个最小排列Pmin = A1A2A3....An ,满足Ai > A(i-1) (1 < i <= n)
从Pmin开始,找到刚好比Pmin大的一个排列P(min+1),再找到刚好比P(min+1)大的一个排列,如此重复。
1.从后向前(即从An->A1),找到第一对为升序的相邻元素,即Ai < A(i+1)。
  若找不到这样的Ai,说明已经找到最后一个全排列,可以返回了。
2.从后向前,找到第一个比Ai大的数Aj,交换Ai和Aj。
3.将排列中A(i+1)A(i+2)....An这个序列的数逆序倒置,即An.....A(i+2)A(i+1)。因为由前面第1、2可以得知,A(i+1)>=A(i+2)>=.....>=An,这为一个升序序列,应将该序列逆序倒置,所得到的新排列才刚刚好比上个排列大。
4.重复步骤1-3,直到返回。
这个算法是C++ STL算法next_permutation的思想。
 
代码
template <class BidirectionalIterator>
bool next_permutation(BidirectionalIterator first,
                                  BidirectionalIterator last) {
    BidirectionalIterator  i=last;
    if (first == last || first==--i) return false;  //单个元素或空则无下一个排列,且使i指向最后一个元素
    for(;;) {
        BidirectionalIterator ii = i--; //i在前,ii在后,循环查找直至*i>=*ii
        if (*i <*ii) {         //第一个非降序的元素(即*i)
                BidirectionalIterator j = last;
                while (!(*i <*--j)); //在降序序列[ii,last)中从后往前查找第一个大于*i的数
                iter_swap(i, j);      //交换*i,*j,则[ii,last)依然为降序序列
                reverse(ii, last);    //翻转[ii,last)为升序
                return true;
        }
        if (i == first) {        //没找到连续的两个升序数,则已经是降序序列,直接翻转全部序列
                reverse(first, last);
                return false;
        }
    }
}

 

posted on 2013-09-30 00:39  Seiyagoo  阅读(1292)  评论(1编辑  收藏  举报