全排列问题 以51nod1384为例(字典序且重复)


参考:http://blog.csdn.net/laojiu_/article/details/51115352


1.非字典序:

首先是非字典序不能重复递归实现:

以 1 , 2 , 3 , 4 为例  , 想要得到全排列,必定是 先以 一个数字开头,然后求剩下数字的全排列:

以 1 开头,下面跟着 {2, 3, 4} 的全排列;

  • 以 2 开头,下面跟着 {1, 3, 4} 的全排列;

  • 以 3 开头,下面跟着 {1, 2, 4} 的全排列;

  • 以 4 开头,下面跟着 {1, 2, 3} 的全排列。

    得到递归算法:

    /**********非字典序不可以重复**************/
    #include <bits/stdc++.h>
    using namespace std; 
    char a[10];                                    //这里我用的是char类型的示范
    
    void solve(char a[] , int left , int right ){
    	if( left == right ){
    		for(int i = 0 ; i <= right ; i ++ ){
    			cout<< a[i] ;
    		}
    		cout<<endl;
    	}else{
    		for( int i = left ; i <= right ; i++ ){
    			swap( a[i] , a[left] ) ; 
    			solve(a , left + 1 , right );
    			swap( a[i] , a[left] );
    		}
    	}
    }
    
    int main(){
    	scanf("%s",a);
    	solve(a , 0 , strlen(a)-1);
    	return 0;
    }
    接着是非字典序不可以重复:
    只需要判断交换是否相等, 不相等才能交换

    为什么那样判断?举个例子:对于 1abc2xyz2 这样的排列,我们交换1与第一个2,变成2abc1xyz2,按照递归的顺序,接下来对 abc1xyz2进行全排列;但是1是不能和第二个2交换的,如果交换了,变成了2abc2xyz1,按照递归的顺序,接下来对 abc2xyz1进行全排列,那么问题来了,注意我红色突出的两个地方,这两个全排列进行的都是同样的工作,也就是如果1和第二个2交换必然会和前面重复。

    /*************非字典序可以重复*****************/
    #include <iostream>
    #include <cstdio>
    #include <string.h>
    using namespace std;
    
    char a[10];
    
    bool judge(int s ,int e){                    //不相等才能交换
    	for( int i = s ; i < e; i ++ ){
    		if( a[i] == a[e] ) return false;
    	}
    	return true;
    }
    
    void permutation(int left , int right){
    
    	if(left == right){
    		for( int i = 0 ; i <= right ; i++ ){
    			cout<<a[i];
    		}
    		cout<<endl;
    	}
    
    	for( int i = left ;i <= right ; i++ ){
    
    		if( judge( left ,  i ) ){         //不想等才交换
    			swap( a[i] , a[left] );
    			permutation(left + 1 , right);
    			swap( a[i] , a[left] );
    		}
    	}
    }
    
    int main(){
    	scanf("%s",a);
    	int len = strlen(a);
    	permutation(0 , len-1);
    	return 0;
    }

    2.字典序算法

    首先看什么叫字典序,顾名思义就是按照字典的顺序(a-z, 1-9)。以字典序为基础,我们可以得出任意两个数字串的大小。比如 "1" < "12"<"13"。 就是按每个数字位逐个比较的结果。对于一个数字串,“123456789”, 可以知道最小的串是 从小到大的有序串“123456789”,而最大的串是从大到小的有序串“*987654321”。这样对于“123456789”的所有排列,将他们排序,即可以得到按照字典序排序的所有排列的有序集合。
    如此,当我们知道当前的排列时,要获取下一个排列时,就可以范围有序集合中的下一个数(恰好比他大的)。比如,当前的排列时“123456879”, 那么恰好比他大的下一个排列就是“123456897”。 当当前的排列时最大的时候,说明所有的排列都找完了。

    于是可以有下面计算下一个排列的算法:
    设P是1~n的一个全排列:p=p1p2......pn=p1p2......pj-1pjpj+1......pk-1pkpk+1......pn
      1)从排列的右端开始,找出第一个比右边数字小的数字的序号j(j从左端开始计算),即 j=max{i|pi<pi+1}
      2)在pj的右边的数字中,找出所有比pj大的数中最小的数字pk,即 k=max{i|pi>pj}(右边的数从右至左是递增的,因此k是所有大于pj的数字中序号最大者)
      3)对换pi,pk
      4)再将pj+1......pk-1pkpk+1......pn倒转得到排列p'=p1p2.....pj-1pjpn.....pk+1pkpk-1.....pj+1,这就是排列p的下一个排列。



    下面以51nod1384为例(含有重复)

    基准时间限制: 1 秒 空间限制: 131072 KB 分值: 0 难度:基础题
    收藏
    关注
    取消关注
    给出一个字符串S(可能有重复的字符),按照字典序从小到大,输出S包括的字符组成的所有排列。例如:S = "1312",
    输出为:

    1123
    1132
    1213
    1231
    1312
    1321
    2113
    2131
    2311
    3112
    3121
    3211
    Input
    输入一个字符串S(S的长度 <= 9,且只包括0 - 9的阿拉伯数字)
    Output
    输出S所包含的字符组成的所有排列
    Input示例
    1312
    Output示例
    1123
    1132
    1213
    1231
    1312
    1321
    2113
    2131
    2311
    3112
    3121
    3211

    /***********字典序可以重复*********************/
    
    #include <iostream>
    #include <cstdio>
    #include <algorithm>
    #include <string.h>
    using namespace std;
    
    char a[10];
    int  c[10];
    
    void permutation(){
    
    	int j , k ;
    	while(true){
    
    		for( int i = 0 ; i < strlen(a) ; i++ )  cout<<c[i];
    			cout<<endl;
    
    		for( j = strlen(a) - 2 ; j >=0 && c[ j ] >= c[ j + 1 ] ; j -- );       //只需要加个=号
    
    			if( j < 0 ) return ;
    
    		for( k = strlen(a) - 1 ; k > j  && c[k] <= c[j] ; k -- );           //只需要加个=号
    
    			swap( c[j] , c[k] );
    
    		for( int l = j + 1 , r = strlen(a) - 1 ; l < r ; l ++ , r -- )
    			swap( c[l] , c[r] );
    	}
    }
    
    int main(){
    	scanf("%s",a);
    	int len = strlen(a);
    	for( int i = 0 ; i < len ; i++ ){
    		c[i] = a[i] - '0';
    	}
    	//cout<<len;
    	sort( c, c+len );
    	permutation();
    	return 0;
    }
    

    另外也可以用STL容器实现(自己写的快了1倍)。

    #include <iostream>
    #include <algorithm>
    #include <cstdio>
    #include <string.h>
    using namespace std;
    char a[10];
    int c [10];
    void FullPermutation(int c[]){
    	do{
    		for( int i = 0 ; i < strlen(a) ;i ++ ){
    			cout<<c[i];
    		}
    		cout<<endl;
    	}while( next_permutation( c , c + strlen(a) ) );
    }
    int main(){
    	scanf("%s",a);
    	for( int i = 0 ; i < strlen(a) ; i++ ){
    		c[i] = a[i] - '0';
    	}
    	sort(c,c+strlen(a));
    	FullPermutation(c);
    	return 0;
    }
  • STL 的两个全排列函数。不用考虑重复与否且为字典序,但是因为实现复杂效率低
  • next_permutation,首先,从最尾端开始往前寻找两个相邻元素,令第一元素为*i,第二元素为*ii,且满足*i<*ii,找到这样一组相邻元素后,再从最尾端开始往前检验,找出第一个大于*i的元素,令为*j,将 i,j 元素对调,再将 ii 之后的所有元素颠倒排列,此即所求之 “下一个” 排列组合。
  • prev_permutation,首先,从最尾端开始往前寻找两个相邻元素,令第一元素为*i,第二元素为*ii,且满足*i>*ii,找到这样一组相邻元素后,再从最尾端开始往前检验,找出第一个小于*i的元素,令为*j,将 i,j 元素对调,再将 ii 之后的所有元素颠倒排列,此即所求之 “上一个” 排列组合。

    bool next_permutation(int * first, int * last)
    {
        if (first == last) return false;  //空区间
        int * i = first;
        ++i;
        if (i == last) return false;  //只有一个元素
        i = last;
        --i;
    
        for (;;)
        {
            int * ii = i;
            --i;
            if (*i < *ii)
            {
                int * j = last;
                while (!(*i < *--j))  //由尾端往前找,直到遇上比*i大的元素
                    ;
                swap(*i, *j);
                reverse(ii, last);
                return true;
            }
        }
    
        if (i == first)  //当前排列为字典序的最后一个排列
        {
            reverse(first, last);  //全部逆向排列,即为升序
            return false;
        }
    }
    
    bool prev_premutation(int * first, int * last)
    {
        if (first == last) return false;  //空区间
        int * i = first;
        ++i;
        if (i == last) return false;  //只有一个元素
        i = last;
        --i;
    
        for (;;)
        {
            int * ii = i;
            --i;
            if (*i > *ii)
            {
                int * j = last;
                while (!(*i > *--j))  //由尾端往前找,直到遇上比*i大的元素
                    ;
                swap(*i, *j);
                reverse(ii, last);
                return true;
            }
        }
    
        if (i == first)  //当前排列为字典序的第一个排列
        {
            reverse(first, last);  //全部逆向排列,即为降序
            return false;
        }
    }



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值