字符串的全排列详解,递归+非递归

问题:

输入一个字符串,打印出该字符串中字符的所有排列。例如输入字符串abc,则输出由字符a,b,c所能排列出来的所有字符串abc,acb,bac,bca,cab和cba

1.全排列的递归实现

思路

  1. 首先,我们固定第一个字符a,求后面两个字符bc的排列
  2. 当两个字符bc排列求好之后,我们把第一个字符a和后面的b交换,得到bac,接着我们固定第一个字符b,求后面两个字符ac的排列
  3. 现在是把c放在第一个位置的时候了,但是记住前面我们已经把原先的第一个字符a和后面的b做了交换,为了保证这次c仍是和原先处在第一个位置的a交换,我们在拿c和第一个字符交换之前,先要把b和a交换回来。在交换b和a之后,再拿c和处于第一位置的a进行交换,得到cba。我们再次固定第一个字符c,求后面两个字符b、a的排列
  4. 既然我们已经知道怎么求三个字符的排列,那么固定第一个字符之后求后面两个字符的排列,就是典型的递归思路了

#include"stdafx.h"
#include"iostream"
#include <string.h>
using namespace std;
   

int count;  //统计全排列的个数
   
void swap(char *str, int a, int b)  
{  
    char temp;  
    temp = str[a];  
    str[a] = str[b];  
    str[b] = temp;  
}  
   
void permutation_process(char *str, int begin, int end) {  
    int k;  
   
    if (begin == end - 1) {  
        cout<<"第"<<count++<<"个全排列"<<str<<endl;
<span style="white-space:pre">	</span>}else {  
        for (k = begin; k < end; k ++) {  
            swap(str, k, begin);  
            permutation_process(str, begin + 1, end);  
            swap(str, k, begin);  
        }  
    }  
}  
   
int _tmain(int argc, _TCHAR* argv[])
{
<span style="white-space:pre">	</span>char str[7];  
<span style="white-space:pre">	</span>int i, len;  
<span style="white-space:pre">	</span>cin>>str;
<span style="white-space:pre">	</span>count = 1;  
        len = strlen(str);  
        permutation_process(str, 0, len);  
          
    return 0;  
}  
下面这张图给出了递归的过程


如果字符串中有重复字符的话,上面的那个方法肯定不会符合要求的,因此现在要想办法来去掉重复的数列。

2、去掉重复的全排列的递归实现

由于全排列就是从第一个数字起每个数分别与它后面的数字交换。我们先尝试加个这样的判断——如果一个数与后面的数字相同那么这两个数就不交换了。例如abb,第一个数与后面两个数交换得bab,bba。然后abb中第二个数和第三个数相同,就不用交换了。但是对bab,第二个数和第三个数不同,则需要交换,得到bba。由于这里的bba和开始第一个数与第三个数交换的结果相同了,因此这个方法不行。
换种思维,对abb,第一个数a与第二个数b交换得到bab,然后考虑第一个数与第三个数交换,此时由于第三个数等于第二个数,所以第一个数就不再用与第三个数交换了。再考虑bab,它的第二个数与第三个数交换可以解决bba。此时全排列生成完毕!
这样,我们得到在全排列中去掉重复的规则:
去重的全排列就是从第一个数字起,每个数分别与它后面非重复出现的数字交换。
#include "stdafx.h"
#include"iostream"
#include <string.h>
using namespace std;   
int count;  //统计全排列的个数
  
void swap(char *str, int a, int b)  
{  
    char temp;  
    temp = str[a];  
    str[a] = str[b];  
    str[b] = temp;  
}  

int is_swap(char *str, int begin, int k)  
{  
    int i, flag;  
      for (i = begin, flag = 1; i < k; i ++) {  
        if (str[i] == str[k]) {  
            flag = 0;  
            break;  
        }  
    }    
    return flag;  
}  
   
void permutation_process(char *str, int begin, int end) {  
    int k;  
     if (begin == end - 1) {  
          cout<<"第"<<count++<<"个全排列"<<str<<endl;
	}else {  
       <span style="white-space:pre">	</span>	for (k = begin; k < end; k ++) {  
          <span style="white-space:pre">	</span>  if(is_swap(str,  begin, k) ){
<span style="white-space:pre">			</span>swap(str, k, begin);  
           <span style="white-space:pre">		</span>permutation_process(str, begin + 1, end);  
          <span style="white-space:pre">		</span>swap(str, k, begin);  
		  }
		}
  <span style="white-space:pre">	</span>  }  
    }  
   

int _tmain(int argc, _TCHAR* argv[])
{
    int i, len;  
    cin>>str;
    count = 1;  
    len = strlen(str);  
    permutation_process(str, 0, len);  
    return 0;  
}  
	

3、全排列非递归实现

思路

要考虑全排列的非递归实现,先来考虑如何计算字符串的下一个排列。如"1234"的下一个排列就是"1243"。只要对字符串反复求出下一个排列,全排列的也就迎刃而解了。
如何计算字符串的下一个排列了?来考虑"926520"这个字符串,我们从后向前找第一双相邻的递增数字,"20"、"52"都是非递增的,"26 "即满足要求,称前一个数字2为替换数,替换数的下标称为替换点,再从后面找一个比替换数大的最小数(这个数必然存在),0、2都不行,5可以,将5和2交换得到"956220",然后再将替换点后的字符串"6220"颠倒即得到"950226"。
如果达到这个数的最大,比如1234-4321,这个时候就结束整个循环。
如果输入是一个非最小数,如1324,则将它转换为最小数,如1234,再进行排序。排序算法用快排,可以自己写一个,如果快排不会的话,就先看会再来接着看,或者自己想一个靠谱的算法,也可以直接用VC库中的qsort(s , n , sizeof(s[0]) , cmp);各参数是什么意思就自己在下面多花点时间吧。
OK,下面看代码分析
//全排列非递归
#include"stdafx.h"
#include<iostream>  
#include<algorithm>  
#include<cstring>  
using namespace std;  
#include<assert.h>  
  
//反转区间  
void Reverse(char* pBegin , char* pEnd)  
{  
    while(pBegin < pEnd)  
        swap(*pBegin++ , *pEnd--);  
}  
//下一个排列  
bool Next_permutation(char a[])  
{  
    assert(a);  
    char *p , *q , *pFind;  
    char *pEnd = a + strlen(a) - 1;  
    if(a == pEnd)  
        return false;  
    p = pEnd;  
    while(p != a)  
    {  
        q = p;  
        p--;  
        if(*p < *q)  //找降序的相邻2数,前一个数即替换数    
        {  
             //从后向前找比替换点大的第一个数  
            pFind = pEnd;  
            while(*pFind < *p)  
                --pFind;  
            swap(*p , *pFind);  
            //替换点后的数全部反转  
            Reverse(q , pEnd);  
            return true;  
        }  
    }  
    Reverse(a , pEnd);   //如果没有下一个排列,全部反转后返回false     
    return false;  
}  
  
int cmp(const void *a,const void *b)  
{  
    return int(*(char *)a - *(char *)b);  
}  
int main(void)  
{  
    char str[] = "bac";  
    int num = 1;  
    qsort(str , strlen(str),sizeof(char),cmp);  
    do  
    {  
        printf("第%d个排列\t%s\n",num++,str);   
    }while(Next_permutation(str));  
    return 0;  
}  


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值