usr 发表于 2021-3-22 20:11:23

【C++】排列生成算法

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

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

template <class T>
class CIntPair
{
public:
    static constexpr bool LEFT = false;
    static constexpr bool RIGHT = true;
    bool direction = LEFT;
    T value = 0;
};

template <class T>
class CArrangement
{
private:
    class CRtn
    {
    public:
      bool found;
      CIntPair<T> pair;
      size_t index;
      CRtn(size_t i, CIntPair<T> & p) : pair(p), index(i), found(false) {}
    };
    size_t n;
    unsigned long long counter = 1;
    CIntPair<T> * arr;
    // Function: MaxMovableIndex
    // Description:
    //   A number(CIntPair) is movable, only if the adjacent number that the direction of the movable number pointed is less than
    //   the removable number.
    //   Therefor this procedure is to find the maximum number of all removable numbers.
    //   If there were the maximun number exist, permutation would be able to go on, otherwise there wouldn't be the next permuatation.
    //   And CRtn().found shows whether we could find the max movable number.
    //   Simultaneously, CRtn().index stores the index of removable number, CRtn().pair stores the movable number itself then.
    CRtn MaxMovableIndex()
    {
      size_t i;
      CIntPair<T> t;
      CRtn r(0, t);
      for (i = 0; i < n; ++i)
      {
            if (arr.direction == CIntPair<T>::LEFT)
            {
                if (i > 0 && arr.value < arr.value && r.pair.value < arr.value)
                {
                  r.pair = arr;
                  r.index = i;
                  r.found = true;
                }
            }
            else
            {
                if (i < n - 1 && arr.value < arr.value && r.pair.value < arr.value)
                {
                  r.pair = arr;
                  r.index = i;
                  r.found = true;
                }
            }
      }
      return r;
    }
    // Function: Swap
    // Description: Swap two adjacent numbers by index and direction.
    void Swap(size_t i)
    {
      if (arr.direction == CIntPair<T>::LEFT)
            swap(arr, arr);
      else
            swap(arr, arr);
    }
    // Function: ChangeDirection
    // Description: Change all directions which belong to p, such that p > r.pair.value.
    void ChangeDirection(CRtn & r)
    {
      size_t i;
      for (i = 0; i < n; ++i)
            if (arr.value > r.pair.value)
                arr.direction = !arr.direction;
    }
public:
    CArrangement(size_t k) : n(k)
    {
      size_t i;
      arr = new CIntPair<T>;
      for (i = 0; i < n; ++i)
      {
            arr.direction = CIntPair<T>::LEFT;
            arr.value = i + 1;
      }
    }
    CArrangement(const T & initial, size_t k) : n(k)
    {
      size_t i;
      arr = new CIntPair<T>;
      for (i = 0; i < n; ++i)
      {
            arr.direction = CIntPair<T>::LEFT;
            arr.value = static_cast<T>(i + initial);
      }
    }
    ~CArrangement()
    {
      delete[] arr;
      n = 0;
    }
    bool Permute()
    {
      if (n <= 1)
            return false;
      // 1st. find the maximun movable number in arr list as r.
      CRtn r = MaxMovableIndex();
      if (r.found)
      {
            // 2nd. Swap r with the number adjacent to r.
            Swap(r.index);
            // 3rd. Change all directions of p, such that p > r.pair.value.
            ChangeDirection(r);
            ++counter;
      }
      return r.found;
    }
    template <class TX>
    friend ostream & operator << (ostream & o, const CArrangement<TX> & ra);
};

template <class T>
ostream & operator << (ostream & o, const CArrangement<T> & ra)
{
    size_t i;
    o << ra.counter << ":\t";
    for (i = 0; i < ra.n; ++i)
      o << ra.arr.value << " ";
    return o;
}

int main(void)
{
    CArrangement<int> a(4);
    cout << a << endl;
    while (a.Permute())
      cout << a << endl;
    return 0;
}

以上代码执行结果如下:
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类型的数据:

int main(void)
{
    CArrangement<char> a('A', 3);
    cout << a << endl;
    while (a.Permute())
      cout << a << endl;
    return 0;
}
结果如下:
1:      A B C
2:      A C B
3:      C A B
4:      C B A
5:      B C A
6:      B A C

watermelon 发表于 2021-3-22 20:57:17

这算法,真“一波三折”啊!牛

usr 发表于 2021-3-23 08:42:44

watermelon 发表于 2021-3-22 20:57
这算法,真“一波三折”啊!牛

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

template <class BidirectionalIterator>
void HeapPermutation(BidirectionalIterator first, size_t size, size_t n)
{
        if (1 == size)
        {
                BidirectionalIterator i = first;
                size_t j;
                for (j = 0; j < n; ++j)
                        cout << *(i + j) << " ";
                cout << endl;
        }
        else
        {
                size_t i;
                for (i = 0; i < size; ++i)
                {
                        HeapPermutation(first, size - 1, n);
                        if (size & 0x1)
                                iter_swap(first, first + size - 1);
                        else
                                iter_swap(first + i, first + size - 1);
                }
        }
}

int main(void)
{
        int a[] = { 1,2,3,4 };
        int n = sizeof a / sizeof a;
        HeapPermutation(a, n, n);
        return 0;
}

结果如下:
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

usr 发表于 2021-3-23 08:48:57

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

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

template <class BidirectionalIterator>
bool stl_next_permutation(BidirectionalIterator first,
    BidirectionalIterator last) {
    if (first == last) return false;
    BidirectionalIterator i = first;
    ++i;
    if (i == last) return false;
    i = last;
    --i;

    for (;;) {
      BidirectionalIterator ii = i--;
      if (*i < *ii) {
            BidirectionalIterator j = last;
            while (!(*i < *--j));
            iter_swap(i, j);
            reverse(ii, last);
            return true;
      }
      if (i == first) {
            reverse(first, last);
            return false;
      }
    }
}

int main(void)
{
        int a[] = { 1,2,3,4 };
        int n = sizeof a / sizeof a;
    do
    {
      for (auto i : a)
            cout << i << " ";
      cout << endl;
    } while (stl_next_permutation(a, a + n));
        return 0;
}

运行结果如下(读者可以自行比较以上排列算法生成排列的不同):
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$$

0xAA55 发表于 2021-3-23 12:38:23

有注释就好了

usr 发表于 2021-3-23 12:43:41

0xAA55 发表于 2021-3-23 12:38
有注释就好了

感谢反馈,抽时间添加。
页: [1]
查看完整版本: 【C++】排列生成算法