1. 程式人生 > >非遞迴列舉排列組合(C++)

非遞迴列舉排列組合(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 permutation
11 *@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(const
std::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下編譯通過。