有序全排列生成算法集锦

/*

  Name: 有序全排列生成算法集锦

  Copyright: 始发于goal00001111的专栏;允许自由转载,但必须注明作者和出处

  Author: goal00001111

  Date: 18-11-08 15:50

  

Description: 实现了五种有序全排列生成算法。

有关算法的分析讨论详见拙作《有序全排列生成算法》:

http://blog.csdn.net/goal00001111/archive/2008/11/18/3326619.aspx

*/

#include<iostream>

#include <time.h>

 

using namespace std;

 

void Permutation_1(long long n, long long m);//递归直接模拟法

void Permutation_2(long long n, long long m);//循环直接模拟法

void Permutation_3(long long n, long long m);//邻元素增值法

void Permutation_4(long long n, long long m);//设置中介数的字典序全排列生成法

void Permutation_5(long long n, long long m);//数学分析法

bool Try(long long a[], int left, int right, long long m, long long & s);

void MoveRight(long long a[], int left, int right);

void MoveLeft(long long a[], int left, int right);

bool IsRight(long long a[], int n);

void DiZeng(int dZ[], long long p[], int len, long long m);

void YingShe(int yShe[], int dZJ[], int len);

int Compare(long long p[], int n, long long m);

void Move(long long a[], int left, int pos);

void Reverse(long long a[], int left, int right);

 

int main()

{

    long long m , n;

    time_t startTime;

    time_t endTime;

 

    n = 12;

   

    time(&startTime);

    for(m=1000; m<10000001; m*=100)

        Permutation_1(n, m);  //递归直接模拟法

       time(&endTime);

    cout << "time 1 = " << difftime(endTime, startTime) << endl << endl;

   

    time(&startTime);

    for(m=1000; m<10000001; m*=100)

        Permutation_2(n, m);  //循环直接模拟法

       time(&endTime);

    cout << "time 2 = " << difftime(endTime, startTime) << endl << endl;

   

    time(&startTime);

    for(m=10; m<100001; m*=100)

        Permutation_3(n, m); //邻元素增值法

       time(&endTime);

    cout << "time 3 = " << difftime(endTime, startTime) << endl << endl;

    

    time(&startTime);

    for(m=1000; m<10000001; m*=100)

        Permutation_4(n, m); //设置中介数的字典序全排列生成法

       time(&endTime);

    cout << "time 4 = " << difftime(endTime, startTime) << endl << endl;

   

    time(&startTime);

    for(m=1000; m<10000001; m*=100)

        Permutation_5(n, m); //数学分析法

       time(&endTime);

    cout << "time 5 = " << difftime(endTime, startTime) << endl << endl;

   

    system("pause");

       return 0;

}

/*

函数名称:Permutation

函数功能:输出n个数的第m种全排列

输入变量:long long n123...nn个自然数(1<=n<=20

          long long m:第m种全排列(1<=m<=n!

输出变量:无

*/

void Permutation_1(long long n, long long m)//递归直接模拟法

{

    long long *a = new long long[n];//用来存储n个自然数

    for (int i=0; i<n; i++)

    {

        a[i] = i + 1;

    }

   

    long long s = 0;//记录当前是第几个全排列

    Try(a, 0, n-1, m, s);//递归查找并输出n个数的第m种全排列

   

    delete []a;

}

/*

函数名称:Try

函数功能:递归查找并输出n个数的第m种全排列 ,找到第m种全排列返回true,否则返回false

输入变量:long long a[]:用来存储n个自然数,按照第m种全排列存储

          int left:数组的左界

          int right:数组的右界

          long long m:第m种全排列(1<=m<=n!

          long long & s:记录当前是第几个全排列 

输出变量:找到第m种全排列返回true,否则返回false

*/

bool Try(long long a[], int left, int right, long long m, long long & s)

{

    if (left == right)//已经到达数组的右界,直接输出数组

    {

        s++;

        if (s == m)//找到第m种全排列

        {

            cout << s << ":  ";

            for (int i=0; i<=right; i++)

            {

                cout << a[i] << ' ';

            }

            cout << endl;

 

            return true;

        }

    }

    else

    {   //将当前最左边的元素与其后面的元素交换位置

        //i=left时,与自身交换,相当于不换,直接输出

        for (int i=left; i<=right; i++)

        {

            MoveRight(a, left, i);

            if (Try(a, left+1, right, m, s))

            {

                return true;

            }

            MoveLeft(a, left, i);

        }

    }

    return false;

}

 

//right位置的元素左移到left位置,其他元素按顺序右移

void MoveRight(long long a[], int left, int right)

{

    long long temp = a[right];

   

    for (int i=right; i>left; i--)

    {

        a[i] = a[i-1];

    }

    a[left] = temp;

}

 

//left位置的元素右移到right位置,其他元素按顺序左移

void MoveLeft(long long a[], int left, int right)

{

    long long temp = a[left];

   

    for (int i=left; i<right; i++)

    {

        a[i] = a[i+1];

    }

    a[right] = temp;

}

//

void Permutation_2(long long n, long long m)//循环直接模拟法

{

    long long *a = new long long[n];//用来存储n个自然数

    for (int i=0; i<n; i++) //存储全排列的元素值,并计算全排列的数量

    {

        a[i] = i + 1;

    }

    cout << m << ":   "; //输出n个数的第m种全排列

    int left, right;

    long long temp;

    for (int i=1; i<m; i++)

    {

        left = n - 2; //设置需要改变的元素的左边界,保证此时左边界右边的元素是一个递减排列 

        while (left >= 0 && a[left] > a[left+1])

            left--;

        right = n - 1;

        while (a[right] < a[left])//找到左边界右边数组中比a[left]大的最小的元素,这个就是要取代a[left]的元素

            right--;

        temp = a[left]; a[left] = a[right]; a[right] = temp; //交换a[right]a[left]

        left++; //左边界增1,保证此时左右边界之间的元素是一个递减排列

        right = n -1;

        while (left < right)//逆置左右边界之间的元素,使其按增序排列

        {

            temp = a[left]; a[left] = a[right]; a[right] = temp;

            left++;

            right--;

        }

    } 

   

    for (int i=0; i<n; i++)

    {

        cout << a[i] << " ";

    }

    cout << endl;

   

    delete []a;

}

//

void Permutation_3(long long n, long long m)//邻元素增值法

{

    long long *a = new long long[n];//用来存储n个自然数

    for (int i=0; i<n; i++) //存储全排列的元素值

    {

        a[i] = i + 1;

    }

   

    cout << m << " : ";

    long long s = 1;

    int pos;

    while (s < m)

    {

        pos = n - 1; //最后一位加n-1

        a[pos] = (a[pos] + pos);

        while (pos > 0 && a[pos] > n)//若有进位,前一元素加1,直到没有进位为止 

        {

           a[pos--] -= n;//大于n则取余数,实际上a[pos]一定比2*n

           a[pos] += 1;  //前一元素加1

        }

        if (a[0] > n)//当第一个元素的值>n则结束

            break;

        if (IsRight(a, n))//若数组a中不存在重复元素,说明得到了一个正确的全排列

        {

            s++;

        }

    }

    for (int i=0; i<n; i++)

        cout << a[i] << ' ';

    cout << endl;

   

    delete []a;

}

 

bool IsRight(long long a[], int n)

{

    for (int i=0; i<n; i++)

    {

        for (int j=i+1; j<n; j++)

        {

            if (a[i] == a[j])

                return false;

        }

    }

    return true;

}

void Permutation_4(long long n, long long m)//设置中介数的字典序全排列生成法

{

    int *a = new int[n];//用来存储n个自然数,也可以看成是初始全排列1234...n

    for (int i=0; i<n; i++)

    {

        a[i] = i + 1;

    }

 

    long long s = 1;

    long long *p = new long long[n];//用来存储各个全排列的值,p[i]=i!

    for (int i=0; i<n; i++)

    {

        s *= a[i];

        p[i] = s;

    }

    cout << m << ":   "; //输出n个数的第m种全排列

    int *diZ = new int[n-1]; //用来存储序号m的递增进进制数,设所有元素的初值为0

    for (int i=0; i<n-1; i++)

    {

        diZ[i] = 0;

    }

    DiZeng(diZ, p, n-1, m-1);//获取序号m-1的递增进进制数

    int *zhongJie = new int[n-1];

    for (int i=0; i<n-1; i++)//设初始全排列的中介数为0

        zhongJie[i] = 0;

    YingShe(zhongJie, diZ, n);//将原中介数加上序号m-1的递增进进制数得到新的映射

   

    //将映射还原,得到新的全排列b

    int *b = new int[n];//用来存储新的全排列

    for (int i=0; i<n-1; i++) //获取前n-1个数字

    {

        s = 0;

        int j = 0;

        while (s < zhongJie[i])

        {

            if (a[j] != -1)

                s++;

            j++;

        }

        while (a[j] == -1)

            j++;

        b[i] = a[j];

        a[j] = -1; //-1表示该位置已经被填充

    }

    for (int i=0; i<n; i++)//填充最后一个数字

    {

        if (a[i] != -1)

        {

            b[n-1] = a[i];

            break;

        }

    }

   

    for (int i=0; i<n; i++)

    {

        cout << b[i] << ' ';

    }

    cout << endl;

   

    delete []a;

    delete []b;

    delete []p;

    delete []diZ;

    delete []zhongJie;

}

 

void YingShe(int yShe[], int dZJ[], int len)

{

    int pos = len - 2;

    int r = 0;

    while (pos >= 0)

    {

        yShe[pos] += dZJ[pos] + r;

        if (yShe[pos] >= len - pos)

        {

            yShe[pos] -= len - pos;

            r = 1;

        }

        else

            r = 0;

        pos--;

    }

}

 

void DiZeng(int dZ[], long long p[], int len, long long m)

{

    int pos = 0;

    while (m > 0)

    {

        while (m < p[len-1-pos])

        {

            dZ[pos++] = 0;

        }

        if (m == p[len-1-pos])

        {

            dZ[pos++] = 1;

            while (pos < len)

                dZ[pos++] = 0;

            break;

        }

        else

        {

            dZ[pos] = m / p[len-1-pos];

            m %= p[len-1-pos];

            pos++;

        }

    }

}

/

void Permutation_5(long long n, long long m)//数学分析法

{

    long long *a = new long long[n];//用来存储n个自然数

    for (int i=0; i<n; i++)

    {

        a[i] = i + 1;

    }

   

    long long s = 1;

    long long *p = new long long[n];//用来存储各个全排列的值,p[i]=i!

    for (int i=0; i<n; i++)

    {

        s *= a[i];

        p[i] = s;

    }

   

    cout << m << ":   "; //输出n个数的第m种全排列

   

    while (m > 1)//通过判断m的值排列数组a的元素

    {

        //m=1,什么也不做,直接输出数组

        if (m == 2) //若只要求输出第2个排列,直接交换数组最后两个元素

        {

            long long temp = a[n-1];

            a[n-1] = a[n-2];

            a[n-2] = temp;

            break; //跳出循环

        }

        else if (m > 2)

        {

            int pos = Compare(p, n, m);//查找pos,使得p[pos-1] < m <= p[pos] 

            int left = n - 1 - pos;//由于m<p[pos],所以前n-1-pos个元素不必移动,故让数组的左界移至n-1-pos

          

            if (p[pos] == m)//如果p[pos] == m,直接逆置数组

            {

                Reverse(a, left, n-1);//逆置数组

                break; //跳出循环

            }

            else

            {

                int r = m / p[pos-1] + left;//计算排列在left位的元素位置

                m %= p[pos-1];   //改变m的值----这是本算法的关键!

             

                if (m == 0)//如果mp[pos-1]的倍数,将第r-1位元素移动到left

                {

                    Move(a, left, r-1);

                    Reverse(a, left+1, n-1);//逆置数组

                }  

                else//否则将第r位元素移动到left位,并排列剩下的数组元素 

                {

                    Move(a, left, r);

                }

            }

        }

    }

   

    for (int i=0; i<n; i++)

    {

        cout << a[i] << ' ';

    }

    cout << endl;

   

    delete []a;

    delete []p;

}

/*

函数名称:Reverse

函数功能:逆置数组

输入变量:long long a[]:逆置前的数组

          int left:数组的左界

          int right:数组的右界

输出变量:long long a[]:逆置后的数组

*/

void Reverse(long long a[], int left, int right)

{

    while (left < right)

    {

        long long temp = a[left];

        a[left++] = a[right];

        a[right--] = temp;

    }

}

/*

函数名称:Compare

函数功能:寻找使得p[pos-1] < m <= p[pos]的数组下标pos,确保m<=p[n-1]

输入变量:long long p[]:数组

          int n:数组的长度

          int m:用来比较的数m

输出变量:满足条件的数组下标pos

*/

int Compare(long long p[], int n, long long m)

{

    for (int i=0; i<n; i++)//确保m<=p[n-1]

    {

        if (p[i] >= m)

        {

            return i;

        }

    }

}

/*

函数名称:Move

函数功能:将pos位的元素向左移动到left位,而left~pos-1)位的元素则依次右移一位

输入变量:long long p[]:数组

          int left:数组的左界

          int pos:需要左移的位置

输出变量:满足条件的数组下标pos

*/

void Move(long long a[], int left, int pos)

{

    long long temp = a[pos];//先将a[pos]存储起来

   

    for (int i=pos; i>left; i--)//left~pos-1)位的元素则依次右移一位 

    {

        a[i] = a[i-1];

    }

    a[left] = temp;//pos位的元素移动到left

}

//

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值