找回密码
 立即注册→加入我们

QQ登录

只需一步,快速开始

搜索
热搜: 下载 VB C 实现 编写
查看: 2756|回复: 5

【C++】排列生成算法

[复制链接]
发表于 2021-3-22 20:11:23 | 显示全部楼层 |阅读模式

欢迎访问技术宅的结界,请注册或者登录吧。

您需要 登录 才可以下载或查看,没有账号?立即注册→加入我们

×
本帖最后由 usr 于 2021-3-23 13:11 编辑

这篇帖子主要介绍一种排列生成算法,这种算法不同于C++ STL库中的排列生成算法。
读者可以自行分析它的效率。
  1. #include <iostream>
  2. #include <algorithm>
  3. using namespace std;

  4. template <class T>
  5. class CIntPair
  6. {
  7. public:
  8.     static constexpr bool LEFT = false;
  9.     static constexpr bool RIGHT = true;
  10.     bool direction = LEFT;
  11.     T value = 0;
  12. };

  13. template <class T>
  14. class CArrangement
  15. {
  16. private:
  17.     class CRtn
  18.     {
  19.     public:
  20.         bool found;
  21.         CIntPair<T> pair;
  22.         size_t index;
  23.         CRtn(size_t i, CIntPair<T> & p) : pair(p), index(i), found(false) {}
  24.     };
  25.     size_t n;
  26.     unsigned long long counter = 1;
  27.     CIntPair<T> * arr;
  28.     // Function: MaxMovableIndex
  29.     // Description:
  30.     //     A number(CIntPair) is movable, only if the adjacent number that the direction of the movable number pointed is less than
  31.     //     the removable number.
  32.     //     Therefor this procedure is to find the maximum number of all removable numbers.
  33.     //     If there were the maximun number exist, permutation would be able to go on, otherwise there wouldn't be the next permuatation.
  34.     //     And CRtn().found shows whether we could find the max movable number.
  35.     //     Simultaneously, CRtn().index stores the index of removable number, CRtn().pair stores the movable number itself then.
  36.     CRtn MaxMovableIndex()
  37.     {
  38.         size_t i;
  39.         CIntPair<T> t;
  40.         CRtn r(0, t);
  41.         for (i = 0; i < n; ++i)
  42.         {
  43.             if (arr[i].direction == CIntPair<T>::LEFT)
  44.             {
  45.                 if (i > 0 && arr[i - 1].value < arr[i].value && r.pair.value < arr[i].value)
  46.                 {
  47.                     r.pair = arr[i];
  48.                     r.index = i;
  49.                     r.found = true;
  50.                 }
  51.             }
  52.             else
  53.             {
  54.                 if (i < n - 1 && arr[i + 1].value < arr[i].value && r.pair.value < arr[i].value)
  55.                 {
  56.                     r.pair = arr[i];
  57.                     r.index = i;
  58.                     r.found = true;
  59.                 }
  60.             }
  61.         }
  62.         return r;
  63.     }
  64.     // Function: Swap
  65.     // Description: Swap two adjacent numbers by index and direction.
  66.     void Swap(size_t i)
  67.     {
  68.         if (arr[i].direction == CIntPair<T>::LEFT)
  69.             swap(arr[i], arr[i - 1]);
  70.         else
  71.             swap(arr[i], arr[i + 1]);
  72.     }
  73.     // Function: ChangeDirection
  74.     // Description: Change all directions which belong to p, such that p > r.pair.value.
  75.     void ChangeDirection(CRtn & r)
  76.     {
  77.         size_t i;
  78.         for (i = 0; i < n; ++i)
  79.             if (arr[i].value > r.pair.value)
  80.                 arr[i].direction = !arr[i].direction;
  81.     }
  82. public:
  83.     CArrangement(size_t k) : n(k)
  84.     {
  85.         size_t i;
  86.         arr = new CIntPair<T>[n];
  87.         for (i = 0; i < n; ++i)
  88.         {
  89.             arr[i].direction = CIntPair<T>::LEFT;
  90.             arr[i].value = i + 1;
  91.         }
  92.     }
  93.     CArrangement(const T & initial, size_t k) : n(k)
  94.     {
  95.         size_t i;
  96.         arr = new CIntPair<T>[n];
  97.         for (i = 0; i < n; ++i)
  98.         {
  99.             arr[i].direction = CIntPair<T>::LEFT;
  100.             arr[i].value = static_cast<T>(i + initial);
  101.         }
  102.     }
  103.     ~CArrangement()
  104.     {
  105.         delete[] arr;
  106.         n = 0;
  107.     }
  108.     bool Permute()
  109.     {
  110.         if (n <= 1)
  111.             return false;
  112.         // 1st. find the maximun movable number in arr list as r.
  113.         CRtn r = MaxMovableIndex();
  114.         if (r.found)
  115.         {
  116.             // 2nd. Swap r with the number adjacent to r.
  117.             Swap(r.index);
  118.             // 3rd. Change all directions of p, such that p > r.pair.value.
  119.             ChangeDirection(r);
  120.             ++counter;
  121.         }
  122.         return r.found;
  123.     }
  124.     template <class TX>
  125.     friend ostream & operator << (ostream & o, const CArrangement<TX> & ra);
  126. };

  127. template <class T>
  128. ostream & operator << (ostream & o, const CArrangement<T> & ra)
  129. {
  130.     size_t i;
  131.     o << ra.counter << ":\t";
  132.     for (i = 0; i < ra.n; ++i)
  133.         o << ra.arr[i].value << " ";
  134.     return o;
  135. }

  136. int main(void)
  137. {
  138.     CArrangement<int> a(4);
  139.     cout << a << endl;
  140.     while (a.Permute())
  141.         cout << a << endl;
  142.     return 0;
  143. }
复制代码

以上代码执行结果如下:
1:      1 2 3 4
2:      1 2 4 3
3:      1 4 2 3
4:      4 1 2 3
5:      4 1 3 2
6:      1 4 3 2
7:      1 3 4 2
8:      1 3 2 4
9:      3 1 2 4
10:     3 1 4 2
11:     3 4 1 2
12:     4 3 1 2
13:     4 3 2 1
14:     3 4 2 1
15:     3 2 4 1
16:     3 2 1 4
17:     2 3 1 4
18:     2 3 4 1
19:     2 4 3 1
20:     4 2 3 1
21:     4 2 1 3
22:     2 4 1 3
23:     2 1 4 3
24:     2 1 3 4
本算法来自:Richard A. Brualdi Introductory Combinatorics, Fifth Edition
以上源代码已经经过了修改。引入模板类,目的是为了能像STL的函数一样对任意整数类型进行排列。
这样就能排列char类型的数据:

  1. int main(void)
  2. {
  3.     CArrangement<char> a('A', 3);
  4.     cout << a << endl;
  5.     while (a.Permute())
  6.         cout << a << endl;
  7.     return 0;
  8. }
复制代码

结果如下:
1:      A B C
2:      A C B
3:      C A B
4:      C B A
5:      B C A
6:      B A C
回复

使用道具 举报

发表于 2021-3-22 20:57:17 | 显示全部楼层
这算法,真“一波三折”啊!牛
回复 赞! 靠!

使用道具 举报

 楼主| 发表于 2021-3-23 08:42:44 | 显示全部楼层
watermelon 发表于 2021-3-22 20:57
这算法,真“一波三折”啊!牛

再发一种比较直观的排列生成算法。
下面这种算法是Heap算法:
  1. #include <iostream>
  2. #include <algorithm>
  3. using namespace std;

  4. template <class BidirectionalIterator>
  5. void HeapPermutation(BidirectionalIterator first, size_t size, size_t n)
  6. {
  7.         if (1 == size)
  8.         {
  9.                 BidirectionalIterator i = first;
  10.                 size_t j;
  11.                 for (j = 0; j < n; ++j)
  12.                         cout << *(i + j) << " ";
  13.                 cout << endl;
  14.         }
  15.         else
  16.         {
  17.                 size_t i;
  18.                 for (i = 0; i < size; ++i)
  19.                 {
  20.                         HeapPermutation(first, size - 1, n);
  21.                         if (size & 0x1)
  22.                                 iter_swap(first, first + size - 1);
  23.                         else
  24.                                 iter_swap(first + i, first + size - 1);
  25.                 }
  26.         }
  27. }

  28. int main(void)
  29. {
  30.         int a[] = { 1,2,3,4 };
  31.         int n = sizeof a / sizeof a[0];
  32.         HeapPermutation(a, n, n);
  33.         return 0;
  34. }
复制代码

结果如下:
1 2 3 4
2 1 3 4
3 1 2 4
1 3 2 4
2 3 1 4
3 2 1 4
4 2 3 1
2 4 3 1
3 4 2 1
4 3 2 1
2 3 4 1
3 2 4 1
4 1 3 2
1 4 3 2
3 4 1 2
4 3 1 2
1 3 4 2
3 1 4 2
4 1 2 3
1 4 2 3
2 4 1 3
4 2 1 3
1 2 4 3
2 1 4 3
回复 赞! 靠!

使用道具 举报

 楼主| 发表于 2021-3-23 08:48:57 | 显示全部楼层
本帖最后由 usr 于 2021-3-23 13:16 编辑

此处补上STL中的排列生成算法:
  1. #include <iostream>
  2. #include <algorithm>
  3. using namespace std;

  4. template <class BidirectionalIterator>
  5. bool stl_next_permutation(BidirectionalIterator first,
  6.     BidirectionalIterator last) {
  7.     if (first == last) return false;
  8.     BidirectionalIterator i = first;
  9.     ++i;
  10.     if (i == last) return false;
  11.     i = last;
  12.     --i;

  13.     for (;;) {
  14.         BidirectionalIterator ii = i--;
  15.         if (*i < *ii) {
  16.             BidirectionalIterator j = last;
  17.             while (!(*i < *--j));
  18.             iter_swap(i, j);
  19.             reverse(ii, last);
  20.             return true;
  21.         }
  22.         if (i == first) {
  23.             reverse(first, last);
  24.             return false;
  25.         }
  26.     }
  27. }

  28. int main(void)
  29. {
  30.         int a[] = { 1,2,3,4 };
  31.         int n = sizeof a / sizeof a[0];
  32.     do
  33.     {
  34.         for (auto i : a)
  35.             cout << i << " ";
  36.         cout << endl;
  37.     } while (stl_next_permutation(a, a + n));
  38.         return 0;
  39. }
复制代码

运行结果如下(读者可以自行比较以上排列算法生成排列的不同):
1 2 3 4
1 2 4 3
1 3 2 4
1 3 4 2
1 4 2 3
1 4 3 2
2 1 3 4
2 1 4 3
2 3 1 4
2 3 4 1
2 4 1 3
2 4 3 1
3 1 2 4
3 1 4 2
3 2 1 4
3 2 4 1
3 4 1 2
3 4 2 1
4 1 2 3
4 1 3 2
4 2 1 3
4 2 3 1
4 3 1 2
4 3 2 1
当然,SV库中的算法就是STL的算法:【C】计算排列组合
下面给出Stirling公式,该公式用于估算n!的大小:
$$P_n^n = n!$$
$$n! \approx \sqrt{2\pi n}\left(\frac{n}{e}\right)^n$$
回复 赞! 靠!

使用道具 举报

发表于 2021-3-23 12:38:23 | 显示全部楼层
有注释就好了
回复 赞! 靠!

使用道具 举报

 楼主| 发表于 2021-3-23 12:43:41 | 显示全部楼层

感谢反馈,抽时间添加。
回复 赞! 靠!

使用道具 举报

本版积分规则

QQ|Archiver|小黑屋|技术宅的结界 ( 滇ICP备16008837号 )|网站地图

GMT+8, 2024-11-21 17:32 , Processed in 0.034933 second(s), 24 queries , Gzip On.

Powered by Discuz! X3.5

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表