一、下一個排列
首先,STL提供了兩個用來計算排列組合關系的算法,分別是next_permutation和prev_permutation。
next_permutation(nums.begin(),nums.end());//下一個排列
prev_permutation(nums.begin(),nums.end())//上一個排列
當返回為1時,表示找到了下一全排列;返回0時,表示無下一全排列
1.1下一個排列算法過程
(1)從右到左,找到第一個違反遞增趨勢的分區數;例如下圖的6。
(2)從右到左,找到第一個比分區數大的改變數;例如下圖的7。
(3)交換分區數和改變數;例如下圖的6和7交換。
(4)顛倒分區數索引的右邊所有數字。例如下圖的7之后的元素。
1.2 STL源碼剖析中的算法過程
(1)首先從最尾端開始往前尋找兩個相鄰元素,令第一元素為*i,第二元素為*ii,且滿足*i<*ii。
(2)找到這樣一組相鄰元素后,再從最尾端開始往前檢驗,找出第一個大於*i的元素,令為*j,將i,j元素對調(swap)。
(3)再將ii之后的所有元素顛倒(reverse)排序。
1.3 版本一實現細節(C指針實現)
template<calss BidrectionalIterator> bool next_permutation(BidrectionalIterator first,BidrectionalIterator last) { if(first == lase) return false; /* 空區間 */ BidrectionalIterator i = first; ++i; if(i == last) return false; /* 只有一個元素 */
i = last; /* i指向尾端 */ --i; for(;;) { BidrectionalIterator ii = i; --i; /* 以上鎖定一組(兩個)相鄰元素 */ if(*i < *ii) /* 如果前一個元素小於后一個元素 */ { BidrectionalIterator j = last; /* 令j指向尾端 */ while(!(*i < *--j)); /* 由尾端往前找,直到遇到比*i大的元素 */ iter_swap(i,j); /* 交換i,j */ reverse(ii,last); /* 將ii之后的元素全部逆序重排 */ return true; } if(i == first) /* 進行至最前面了 */ { reverse(first,last); /* 全部逆序重排 */ return false; } } }
1.4版本二實現細節(純STL規范)
1 template<typename BidiIt> 2 bool next_permutation(BidiIt first,BidiIt last) 3 { 4 const auto rfirst=reverse_iterator<BidiIt>(last);+++ 5 const auto rlast=reverse_iterator<BidiIt>(first); 6 7 auto pivot=next(rfirst); 8 9 while( pivot!= rlast && *pivot >= *prev(pivot)) 10 ++pivot;//直到找出第一個違反遞增趨勢的分區數,此時,pivot指向分區數; 11 12 if(pivot == rlast) 13 { 14 reverse(rfirst,rlast);//如果此序列為遞減系列,則下一個排序為顛倒整個序列; 15 return false; 16 } 17 18 auto change=find_if(rfirst,rlast,bindlst(less<int>(),*pivot));//從右到左,找到第一個大於分區數的數,並賦給change; 19 20 swep(*change,*pivot);//交換分區數與改變數; 21 22 reverse(rfirst,pivot);//將分區數之后的序列顛倒; 23 24 return true; 25 }
1.5 前一個排列(prev_permutation)
與next_permutation類似,STL也提供一個版本:
1 int prev_permutation(int *begin, int *end) 2 { 3 int *i=begin, *j, *k; 4 if (i==end || ++i==end) return 0; // 0 or 1 element, no prev permutation 5 for (i=end-1; i!=begin;) { 6 j = i--; // find last decreasing pair (i,j) 7 if (!(*i > *j)) continue; 8 // find last k which less than i, 9 for (k=end; !(*i > *(--k));); 10 iter_swap(i,k); 11 // now the range [j,end) is in ascending order 12 reverse(j,end); 13 return 1; 14 } 15 // current is in ascending order 16 reverse(begin,end); 17 return 0; 18 }
二、全排列
1.1 利用next_permutation求全排列
對初始序列依次求下一個排列,直到沒有下一個序列為止。
舉個實例,假設有序列{0,1,2,3,4},下圖便是套用上述演算法則,一步一步獲得“下一個”排列組合。圖中只框出那符合“一元素為*i,第二元素為*ii,且滿足*i<*ii ”的相鄰兩元素,至於尋找適當的j、對調、逆轉等操作並未顯示出。
代碼如下:
vector<vector<int>> permute(vector<int>& nums) { vector<int>temp; vector<vector<int>>result; sort(nums.begin(),nums.end()); do { temp.clear(); for(int i=0;i<nums.size();i++) temp.push_back(nums[i]); result.push_back(temp); }while(next_permutation(nums.begin(),nums.end()));//do while循環最適合,因為先要打印出初始序列 return result; }
1.2 利用深度優先搜索(DFS)求解,以后待更新。
三、第K個排列
簡單的,可以用暴力枚舉法,調用k-1次next_permutation()(注意一定是k-1次)
代碼如下:
1 string getPermutation(int n, int k) { 2 string str(n,'0'); 3 for(int i=0;i<n;i++) 4 str[i]+=i+1; 5 for(int i=0;i<k-1;i++) 6 next_permutation(str.begin(),str.end()); 7 return str; 8 }