【暖*墟】#全排列# 解答树算法与next_permutation函数 2.【下一个排列】next_permutation函数

#全排列的两种方法#

1.【解答树】

假设序列为{1,2,3,4},形成全排列。解答树显示出了递归函数的调用过程。

可以近似看成深搜,一层层的解决问题。

这棵树和前面介绍过的二叉树不同。第0层(根)结点有n个子结点,第1层结点各有n-1个子结点,第2层结点各有n-2个子结点,第3层结点各有n-3个子结点,……,第n层结点都没有子结点(即都是叶子),而每个叶子对应于一个排列,共有n!个叶子。由于这棵树展示的是从“什么都没做”逐步生成完整解的过程,因此将其称为解答树。

提示:如果某问题的解可以由多个步骤得到,而每个步骤都有若干种选择(这些候选方案集可能会依赖于先前作出的选择),且可以用递归枚举法实现,则它的工作方式可以用解答树来描述。

这棵解答树一共有多少个结点呢?可以逐层查看:第0层有1个结点,第1层n个,第2层有n*(n-1)个结点(因为第1层的每个结点都有n-1个结点),第3层有n*(n-1)*(n-2)个(因为第2层的每个结点都有n-2个结点),……,第n层有n*(n-1)*(n-2)*…*2*1=n!个。将其求和。

由于叶子近似有n!个,倒数第二层也有n!个结点,因此上面的各层全部加起来也不到n!。

这是一个很重要的结论:在多数情况下,解答树上的结点几乎全部来源于最后一两层。

和它们相比,上面的结点数可以忽略不计。

利用解答树的深度优先遍历实现数列的全排序。

例如 1 2 3 ------>1 2 3,1 3 2,2 1 3,2 3 1,3 1 2,3 2 1共6种不同的排序

参考代码如下:

    #include <string.h>
    #include <iostream>
     
    using namespace std;
    const int N = 99999999;     //输入排序的个数的最大值
    int record[N];              //记录每次排序的序列
    int visited[N];             //标记节点是否被访问过
    int n;                      //输入节点的数目
    int totalSize = 0;
    void DFS(int start){
        if(start>=n){           //递归出口
            for(int i=0;i<n;i++){
                cout<<record[i]<<" ";
            }
            totalSize++;
            cout<<endl;
            return;
        }
        for(int i=1;i<=n;i++){      //深度遍历节点,并标记已经访问过的节点
            if(visited[i]==0){
                visited[i] = 1;
                record[start] = i;
                DFS(start+1);       //递归遍历
                visited[i] = 0;     //回退时标记回退的节点为未被访问节点
            }
        }
    }
     
    int main()
    {
        cin>>n;
        memset(visited,0,n);
        DFS(0);
        cout<<"totalSize = "<<totalSize<<endl;
        return 0;
    }


2.【下一个排列】next_permutation函数

枚举所有排列的另一个方法是从字典序最小排列开始,不停调用“求下一个排列”的过程。

如何求下一个排列呢?C++的STL中提供了一个库函数next_permutation。看看下面的代码片段,就会明白如何使用它了。

    #include<cstdio>
    #include<algorithm> //包含next_permutation
    using namespace std;
    int main( ) {
      int n, p[10];
      scanf("%d", &n);
      for(int i = 0; i < n; i++) scanf("%d", &p[i]);
      sort(p, p+n);                                         //排序,得到p的最小排列
      do {
        for(int i = 0; i < n; i++) printf("%d ", p[i]);     //输出排列p
        printf("\n");
      } while(next_permutation(p, p+n));                    //求下一个排列
      return 0;
    }

需要注意的是,上述代码同样适用于可重集,但只能用于全排列。


枚举所有排列的另一个方法是从字典序最小排列开始,不停调用“求下一个排列”的过程。

如何求下一个排列呢?C++的STL中提供了一个库函数next_permutation。看看下面的代码片段,就会明白如何使用它了。

    #include<cstdio>
    #include<algorithm> //包含next_permutation
    using namespace std;
    int main( ) {
      int n, p[10];
      scanf("%d", &n);
      for(int i = 0; i < n; i++) scanf("%d", &p[i]);
      sort(p, p+n);                                         //排序,得到p的最小排列
      do {
        for(int i = 0; i < n; i++) printf("%d ", p[i]);     //输出排列p
        printf("\n");
      } while(next_permutation(p, p+n));                    //求下一个排列
      return 0;
    }

需要注意的是,上述代码同样适用于可重集,但只能用于全排列。

猜你喜欢

转载自blog.csdn.net/flora715/article/details/80967551