非遞迴列舉排列組合(C++)
原文地址:http://www.25kx.com/art/1441000
最近心血來潮,想自己寫個求解排列組合的小東西。以2個數組為例: arr1 = {'a', 'b', 'c'}; arr2={'1', '2'}; ,將陣列中元素的所有排列組合枚舉出來:a1 , a2, b1, b2, c1, c2, 1a, 1b.............。這裡僅僅是個例子,需要解決的問題域是:陣列個數是不定的,陣列元素個數也是不定的。 先將問題分解,排列組合嘛,先求排列(陣列位置)再對每一種排列求組合。
求排列
如果有n個數組,那麼排列數為n!,這裡需要將這些排列都枚舉出來。我的解法如下:
假設有序列012那麼,它的排列依次為012,102,120,210,201,021。不難發現規律,就是不斷的將第一個元素向後
1 #pragma once 2 #include <vector> 3 #include <string> 4 #include <algorithm> 5 6 typedef std::vector<size_t> size_t_vector; 7 typedef std::vector<std::string> result_vector; ///< 存放排列組合的結果 8 9 /** 10 *class permutation11 *@brief 求排列 12 * 13 *@see 14 *@author fubingheng 15 *@date 2012-05-10 16 */ 17 class permutation 18 { 19 public: 20 typedef std::vector<size_t_vector> permutation_vector; 21 22 /** 23 *@brief 比較兩個vector是否相等 24 */ 25 template< typename T> 26 static bool compare_vector(conststd::vector<T> &a, const std::vector<T> &b) 27 { 28 if (a.size() != b.size()) 29 { 30 return false; 31 } 32 33 for (size_t i = 0; i < a.size(); ++i) 34 { 35 if (a[i] != b[i]) 36 { 37 return false; 38 } 39 } 40 41 return true; 42 } 43 44 45 permutation(size_t n) 46 { 47 //求n的排列數 48 if (n == 0) 49 { 50 throw std::exception("can't 0"); 51 } 52 53 //構造第一個排列 54 size_t_vector first; 55 for (size_t i = 0; i < n; ++i) 56 { 57 first.push_back(i); 58 } 59 permutations_.push_back(first); 60 61 size_t_vector next = first; 62 size_t_vector::iterator pos = next.begin(); 63 while (1) 64 { 65 size_t_vector::iterator temppos = pos++; 66 if (pos != next.end()) 67 { 68 //元素移動一個位置,得到新的排列 69 std::swap(*temppos, *pos); 70 if (compare_vector(first, next)) 71 { 72 break; 73 } 74 //如果排列是有效的(和第一個不相等),放入容器中(拷貝構造) 75 permutations_.push_back(next); 76 } 77 else 78 { 79 //移動到末尾了,從頭再來 80 pos = next.begin(); 81 } 82 } 83 } 84 85 permutation::permutation_vector get_permutation_vector(){return permutations_;} 86 private: 87 permutation_vector permutations_; ///< 存放列舉的排列 88 };
求組合
通過排列已經能夠確定陣列的位置了。假設arr1在位置0,arr2在位置1的排列,那麼求這個排列下的出所有組合數。
1 std::string str; 2 for (int i = 0; i < arr1_size; ++i) 3 { 4 str = ""; 5 str += arr1[i]; //確定第一個元素 6 for(int j = 0; j < arr2_size; ++j) 7 { 8 str += arr1[j]; 9 std::cout<<" "<<str; 10 } 11 }
如果再有個陣列arr3在位置2,那麼只能在最後一個for裡面再嵌入一個for了,依次類推.........。而陣列個數恰恰是動態變化的,所以不能如此“硬編碼”,那麼還有什麼動態的方法麼。等等看見這樣的情形是不是立刻讓人想到了遞迴?恩,是的遞迴,還有模板元。的確,遞迴能解決這個問題,而且也比較容易寫出(最接近一般的思路),這裡就不實現了。但大家都知道,遞迴通常只能有17層。記得讀書的時候,課本上說過,所有遞迴的方式都能用非遞迴替代。這裡先不管是否有必要用非遞迴的方式實現,只是個人興趣而已。這裡不難發現,難點是for的巢狀層級是由陣列陣列個數n決定的,而且不能硬編碼至程式中。那麼我們只能換一種思路。根據數學知識,我們知道,排列數y =arr1_size*arr2_size*..........等號的右邊是不是正好對應了上面偽碼的巢狀結構?每個巢狀for正好迴圈一個數組長度,那我們從等號左邊思考會如何?
for (int i = 0; i < y; ++i) { ............... }
用一個總的迴圈降解了巢狀迴圈的結構。但新的問題也來了,那就是元素的確定問題。你怎麼確定每個位置的元素?這個問題讓我想了好久。設有0~n個數組,陣列對應的元素個數為S0~Sn,也就是陣列具有下面這種形式
arr0 = {x0, x1, x2........xn} (Xn為元素下標) size = S0。
組合數為y, f為某種函式關係:那麼Y和X
Y0 = f(arr0.x0, arr1.x0, arr2.x0,.........arrn-1.x0, arrn.x0)
Y1 = f(arr0.x0, arr1.x0, arr2.x0,.........arrn-1.x0, arrn.x1)
Y2 = f(arr0.x0, arr1.x0, arr2.x0,.........arrn-1.x0, arrn.xn)
...............................................................
Yn = f(arr0.x0, arr1.x0, arr2.x0,.........arrn-1.x0, arrn.xn)
Yn+1 = f(arr0.x0, arr1.x0, arr2.x0,........., arrn-1.x1, arrn.x0)
看到規律了麼?對,從最後一個數組arrn的下標不斷加1,當arrn加到最後一位時(加了Sn次,因為這個陣列的大小為Sn),向前它一個數組(arrn-1)進1。接著繼續從最後一個數組取,取到Xn後,再向arrn-1進1。由於最後一位arrn不斷的向arrn-1進位(每加Sn次進一位),所以arrn-1會加到arrn-1.Xn(也就是arrn-1的大小Sn-1),這時arrn-1會向arrn-2進1,同時arrn-1.Xn會復位回arrn-1.X0,依次反覆直到arr0.Xn為止。記住,每次加1都是最後一個座標。這種加1以後要進行y =arr1_size*arr2_size*..........次。
1 class coordinate 2 { 3 public: 4 //建構函式 5 coordinate(const size_t_vector & array_size_vecotor) 6 :array_sizes_(array_size_vecotor) 7 { 8 if(array_size_vecotor.empty()) 9 { 10 throw std::exception("can't empty vector"); 11 } 12 13 for (size_t i = 0; i < array_size_vecotor.size(); ++i) 14 { 15 coordinates_.push_back(0); 16 } 17 } 18 //拷貝構造 19 coordinate(const coordinate & other) 20 { 21 if (this != &other) 22 { 23 this->coordinates_ = other.coordinates_; 24 this->array_sizes_ = other.array_sizes_; 25 } 26 } 27 //賦值 28 coordinate & operator = (const coordinate & other) 29 { 30 if (this != &other) 31 { 32 this->coordinates_ = other.coordinates_; 33 this->array_sizes_ = other.array_sizes_; 34 } 35 36 return *this; 37 } 38 39 //前++ 40 coordinate & operator ++ () 41 { 42 size_t pos = array_sizes_.size() - 1;//最後一個座標 43 do 44 { 45 size_t temp = coordinates_[pos]; 46 ++temp;//最後一個座標加1 47 48 if (temp == array_sizes_[pos]) // 判斷加1後是否應該進位 49 { 50 //應該進位 51 coordinates_[pos] = 0; 52 coordinates_[pos - 1] = coordinates_[pos - 1] + 1; 53 } 54 else 55 { 56 //不進位 57 coordinates_[pos] = temp; 58 break; 59 } 60 --pos; 61 } while (pos > 0); 62 63 return *this; 64 } 65 //後++ 66 const coordinate operator ++ (int) 67 { 68 coordinate t(*this); 69 size_t pos = array_sizes_.size() - 1; 70 do 71 { 72 size_t temp = coordinates_[pos]; 73 ++temp; 74 75 if (temp == array_sizes_[pos]) 76 { 77 coordinates_[pos] = 0; 78 //coordinates_[pos_ - 1] = coordinates_[pos_ - 1] + 1; 79 } 80 else 81 { 82 coordinates_[pos] = temp; 83 break; 84 } 85 --pos; 86 } while (pos > 0); 87 88 return t; 89 } 90 91 92 size_t_vector get_coordinates() { return coordinates_;} 93 private: 94 size_t_vector coordinates_;///< 存放X0, X1.......Xn 95 size_t_vector array_sizes_;///< 存放S0,S2.......Sn 96 };
將兩個模組合併
1 //求排列組合 2 template <typename T> 3 void permutation_and_combination(const std::vector<std::vector<T>> & inVal, result_vector & outVal) 4 { 5 typedef std::vector<std::vector<T>> arr_vector; 6 permutation _permutation(inVal.size()); 7 permutation::permutation_vector all_permutation = _permutation.get_permutation_vector(); 8 9 for (size_t i = 0; i < all_permutation.size(); ++i) 10 { 11 //對每一種排列求組合 12 size_t_vector array_size; 13 std::vector<std::vector<T> > carray; 14 for (size_t j = 0; j <all_permutation[i].size();++j) 15 { 16 array_size.push_back(inVal[all_permutation[i][j]].size()); 17 carray.push_back(inVal[all_permutation[i][j]]); 18 } 19 20 coordinate _coordinate(array_size); 21 size_t total = 1; 22 std::for_each(array_size.begin(), array_size.end(), [&total](size_t_vector::value_type &val) 23 { 24 total *= val; 25 }); 26 27 for (size_t i = 0; i < total; ++ i) 28 { 29 if (i != 0 ) 30 { 31 ++_coordinate; 32 } 33 size_t_vector _coordinates = _coordinate.get_coordinates(); 34 std::string str; 35 for (size_t j = 0; j < carray.size(); ++j) 36 { 37 str += carray[j][_coordinates[j]]; 38 } 39 outVal.push_back(str); 40 } 41 } 42 }
對應的main函式
1 #include "stdafx.h" 2 #include <iostream> 3 #include "permutation_combination.h" 4 int _tmain(int argc, _TCHAR* argv[]) 5 { 6 // 動態構造兩個陣列 7 std::vector<char> arr1; 8 arr1.push_back('a'); 9 arr1.push_back('b'); 10 arr1.push_back('c'); 11 12 std::vector<char> arr2; 13 arr2.push_back('1'); 14 arr2.push_back('2'); 15 16 std::vector<std::vector<char>> inVal; 17 inVal.push_back(arr1); 18 inVal.push_back(arr2); 19 result_vector outVal;//計算結果 20 permutation_and_combination<char>(inVal, outVal); 21 return 0; 22 }
以上程式碼均在VS2010下編譯通過。