generating permunation——全排列(算法彙總)

本文一共提供了4種全排列的方法,包括遞歸非字典序版本、遞歸字典序版本、標準庫版本和BFS字典序版本,固然BFS非字典序實現相對於BFS字典序版本更加簡潔,稍加修改便可。ios

說明:遞歸版本基於網上現有代碼修改而成,標準庫版本參照msdn sample修改而成,最後的BFS版本是由本人在看到題目後思考而來,並實現之(遞歸版本好久以前寫過),全部四種算法都加了模板。固然BFS版本效率相對於遞歸要快,相對於STL版本則較慢,僅僅提供一種思路而已算法

 

注:對於這種小算法,本身主動思考能夠開闊思路,並且想出一種新思路感受會很不錯;對於已成型的經典或者複雜算法,新思路的空間會很是小,因此能夠以掌握爲主。數組



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


#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版本算法思路及修改後的代碼(僅僅爲了可讀性):code

 

思路blog

給定已知序列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)。由於由前面第一、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; } }}
相關文章
相關標籤/搜索