生成全排列算法详解

以下所有算法均基于n个不同的元素。

算法一:递归+回溯+SWAP。
忽略输出,每次递归满足: T(n) = n*T(n-1) + O(n)
令G(n) = T(n)/n!我们得到: G(n) = G(n-1) + O(1/(n-1)!)
所以: G(n) = Theta(1).
即: T(n) = Theta(n!).
所以时间复杂度就是 Theta(n!)
每个排列对应一个数组,空间复杂的O(n!)。
关于这个算法为什么可行,给出证明,基于式子#:n!=n*(n-1)(n-2)…*2*1
我们假设一个全排列初始由n个空格组成,求出全排列就是从第一个空格开始将所有空格填满。每此调用函数GetPermutation(int *a,int first,int end)有3个主要步骤:
假设这是调用函数GetPermutation(a,0,n-1)
步骤一:从区间[0,n-1]找出一个数TEMP1填到排列的第一个空格,有n种(即#的n项),即for循环的意义。
步骤二:在步骤一的基础上,考虑区间[0+1,n-1]上的排列,对于该区间重复步骤一,属于递归调用(即选出一个数作为该区间的第一个数,有n-1种情况,即#中的(n-1)项)。
步骤三:将步骤一的TEMP1放回原位置,属于回溯。再从剩下的n-1个数中重新找一个数TEMP2填到排列的第一个空格,即for循环中的i++。

#include <iostream>
#include<cstdio>
using namespace std;
void SWAP(int &a, int &b){
    int tmp;
    tmp = a;
    a = b;
    b = tmp;
}
int sum=0;
void GetPermutation(int *a,int first,int end){//从[first,end]中选一个作为区间[first,end]上的first
    if(first == end){//递归到最深层
        sum++;
        for(int i = 0; i <= end; i++)
        cout<<a[i]<<" ";
        cout<<endl;
    } else {
       GetPermutation(a,first+1,end);//first本身作为区间[first,end]的第一个数,从区间[first+1,end]中选一个作为first+1
        for(int i = first+1; i <= end; i++){//该循环表示区间[first+1,end]上每个数都依次会作为[first+1,end]上的第一个数
            SWAP(a[first],a[i]);//区间[first,end]上的第i个数作为[first,end]的第一个数
           GetPermutation(a,first+1,end);//从区间[first+1,end]中选一个作为[first+1,end]的第一个数
            SWAP(a[first],a[i]);//将第i个数放回原位置,第i+1个将作为区间[first,end]的第一个数
        }
    }
}
int main() {
    freopen("input.txt","r",stdin);
    freopen("output.txt","w",stdout);
    int n;
    int a[100];
    cout<<"Input the size of the array:"<<endl;
    cin>>n;
    cout<<"Input elements of the array:"<<endl;
    for(int i=0;i<n;i++) {
        cin>>a[i];
    }
   GetPermutation(a,0,n-1);
    cout<<"一共"<<sum<<"种排列";
    return 0;
}

算法二:字典序算法+SWAP。该算法的时间复杂度要基于输入串的字典序大小。空间复杂度O(n)。
我们知道n个不同元素的字典序一共也有n!种,这是巧合吗?通过观察:1,2,3的全排列:
1,2,3;1,3,2;2,1,3,;2,3,1;3,1,2;3,2,1 我们发现这种排序刚好就是按字典序增长的。难道求全排列就是求所有的字典序吗?答案是肯定的。
假设已给的串是排列中字典序最小的(比如:1,2,3,4,5)。我们的操作是每次寻找当前排列的下一个排列(“下一个排列”指所有比当前排列大的排列中最小的那个),最后输出的按字典序一次递增的,所以我们要从当前排列的最后往前搜索。
假设搜索到ai>ai+1,那么通过交换两数只能得到字典序更小的排列,不可能得到下一个排列。所以我们需要找到一对数ai小于ai+1,而且是第一次搜索到的,即ai+1后面是递减的。为了得到下一个全排列,我们需要用一个更大的数代替ai,即与它交换位置,那当然要从它后面去找这个数(因为全排列按递增输出,所以它前面的数不能改动),而且要找最小的那个,而且必定能找到,至少有ai+1。然后将交换后i+1到n的所有数按递增排列。此时就得到下一个排列。
一直循环重复上面的操作,直到找不到ai小于ai+1的情况。
该算法只能求出字典序比给出的串大的所有排列,要求出n个不同元素的全排列必须先对其进行升序排序。

#include <iostream>
#include<cstdio>
using namespace std;
char a[100];
int len;
int sum=0;
void _qsort(char* str,int Start,int End){
    if(Start==End) return;//出口
    char tmp=str[Start];
    int i=Start,j=End;
    while(i<j){
        while(i<j&&str[j]>tmp) j--;
        str[i]=str[j];//补上i空位,j成为新空位
        while(i<j&&str[i]<tmp) i++;
        str[j]=str[i];//补上j空位,i成为新空位
    }
    str[i]=tmp;//补上i空位
    //此时一定有i=j
    if(i-1>Start)_qsort(str,Start,i-1);
    if(j<End)  _qsort(str,i+1,End);
}
void GetPermutation(char* str)
{
    if(!str)
        return;
    while(true)
    {
        sum++;
        cout <<str<<endl;
        int j=len-2,k=len-1;
        while(j>=0 && str[j]>str[j+1])
            --j;//找到第一对数 str[j]<str[j+1]
        if(j<0) break;//找不到str[j]<str[j+1]的情况,即排列数已经求完

        while(str[k]<str[j])
            --k;//找到后面所有比str[j]大的数中最小的

        char temp=str[k];//交换两数
        str[k]=str[j];
        str[j]=temp;

        int a,b;//str[j]后面的数按递增的顺序排列
        for(a=j+1,b=len-1;a<b;++a,--b)
        {
            temp=str[a];
            str[a]=str[b];
            str[b]=temp;
        }
    }
    cout<<sum<<"种情况"<<endl;
}
int main()
{
    freopen("input.txt","r",stdin);
    freopen("output.txt","w",stdout);
    cin>>len;
    for(int i=0;i<len;i++){
        cin>>a[i];
    }
    _qsort(a,0,len-1);
    GetPermutation(a);
    return 0;
}
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值