zoukankan      html  css  js  c++  java
  • 排列的生成算法(一)

      组合数学课程上,介绍到了排列的生成算法。而其中第一个算法——翻转算法,竟是由本课程的任课老师殷奶奶发现的,瞬间感觉到了殷奶奶的强大。殷奶奶在课堂上说,这个算法,是她盯着一个排列数看了两年,同时结合平时她的学生的课程设计想出来的。

      殷奶奶在课堂上说了算法组成的三要素:

    •   算法处理后结果出来的是一张表
    •   算法只能由当前一个情况生成下一个情况
    •   算法应该是循环的

      关于这个算法组成的三要素,现在还不是很理解。

      在这里,由于对这个翻转算法比较敢兴趣,于是将这个算法进行一下简单的介绍,同时用程序实现该算法,通过计算机来实现输出n位数的所有排列。

      翻转算法:

      假设一个集合{1,2,…,n},构造这个集合的所有排列可以按照下面两步进行:

      (1)将整数n插入集合{1,2,…,n-1}的每一个排列后,得到集合{1,2,…,n}的(n-1)!个不同的全排列;

      (2)选定(1)中的一个排列,依次将该排列的前i位(i=1,2…,n-1)调到该排列的尾部,得到集合{1,2,…,n}的n个不同排雷。

      通过以上两步,由乘法原则可知,{1,2,…,n}的(n-1)!*n=n!个不同的排列。

      对于以上的构造过程,举个例子进行解释:

      假设已经知道了{1,2,3}的所有的全排列为:

      {1,2,3}、{2,3,1}、{3,1,2}、{2,1,3}、{1,3,2}、{3,2,1}  

      根据{1,2,3}得到{1,2,3,4}的所有排列的过程如下:

      

      图片中第一列是如何生成的?

      首先选择{1,2,3},然后将4插入到这个排列的末尾,得到{1,2,3,4},再然后,每次进行一次循环左移,就可以得到2341、3412、4123这几个排列了。对于每一列,进行的步骤都统第一列。就这样,得到了{1,2,3,4}的所有排列情况。

      一开始,感觉这很简单啊,没什么了不起的地方嘛。但是,真正厉害的地方不在这,而在与如何从4123变化到2314(即第一列的末尾变到第二列的头)?如何从4231变化到3124?这其中的规律是什么?这就是翻转算法的精华的所在。不然,这个就不是一个算法了,因为不满足“循环”这个条件。

      我们用P4、P3、P2、P1分别表示四个数的位置,P4表示一个排列的第一位,P3表示第二位,其他依次类推。

      我们从左到右扫描一个排列,当发现第一个Pk(k表示下标)不等于k的时候,即找出第一个该位置上的数不等于他的下标的位置。我们将该位置(包括该位置本身)以前的所有的数翻转后,连接到该排列的末尾,得到一个新的排列。通过这个方法,可以解决上面说的如何从第一列末尾跳到第二列开头的问题。也正是因为这个方法,我们可以生成任意n个数的所有的排列。该算法总结起来如下:

      (1)从左到右找到第一个这样的位置:他的下标和他的数的值不一样。

      (2)将包括该数在内的之前的数进行翻转,并接到该排列的末尾,得到一个新的排列

      (3)直到所有数都满足该位置的数的值与下标的值一致时程序结束。

      其代码实现如下所示:

    /*
    Author:xiongmao
    Date:20151126 16:26
    Title:翻转算法实现n个数的排列的生成 
    */
    #include <iostream> 
    #include <algorithm>
    #include <cstring>
    using namespace std;
    #define N 100
    int c[N];
    int myreverse(int arr[],int i,int j)
    {
        while(i<=j){
            int temp;
            temp=arr[i];
            arr[i]=arr[j];
            arr[j]=temp;
            i++;
            j--;
        }
        return 0;
    }
    int show(int n)
    {
        if(n>=100) return -1;
        //按顺序初始化并输出第一个排列,第一个排列为1~n 
        for(int i=1;i<=n;i++){
            c[i]=i;
            cout<<i<<" "; 
        }
        cout<<endl;
        int flag=false;
        while(!flag){
            int index=n;
            for(int i=1;i<=n;i++){
                if(c[i]!=index){
                    break;
                }
                index--;
            }
            if(index==0){
                for(int i=1;i<=n;i++){
                    cout<<c[i]<<" ";
                }
                cout<<endl;
                flag=true;
                continue;
            }
            myreverse(c,1,n-index+1);
            int temp[N];
            int num=1;
            index=n-index+1;//使得index变为所指向的数的下标 
            for(int i=index+1;i<=n;i++){
                cout<<c[i]<<" ";
                temp[num]=c[i];
                num++;
            }
            for(int i=1;i<=index;i++){
                cout<<c[i]<<" ";
                temp[num]=c[i];
                num++;
            }
            cout<<endl;
            //将当前这个排列重新赋给数组c,用于下次循环 
            memcpy(c,temp,sizeof(c));
        }
    }
    int main()
    {
        int n;
        while(cin>>n&&n!=-1)
        {
            show(n);
        }
    }

      生成的4个数的排列如下图:

      

      

  • 相关阅读:
    UWP中的消息提示框(二)
    UWP中的消息提示框(一)
    使用批处理编写ORACLE设置
    程序员那些事儿
    “程序已停止工作”问题的解决方法
    《第一行代码--Android》阅读笔记之数据持久化
    《第一行代码--Android》阅读笔记之广播
    《第一行代码--Android》阅读笔记之界面设计
    《第一行代码--Android》阅读笔记之Activity
    WinForm中为按钮添加键盘快捷键,超简单,亲测有效
  • 原文地址:https://www.cnblogs.com/xiongmao-cpp/p/4996230.html
Copyright © 2011-2022 走看看