今天看剑指offer,看到里面的全排列算法,研究了一下。终于搞懂怎么个递归法了。
如果我们有一个序列:a,b, c,d。
当我们输出这四个字符的全排列时,输出的格式肯定时
4个字符:a1,a2,a3,a4。这种形式
先考察第一个字符a1的位置。
在输出全排列的时候,a1取值依次为:a1 = a或b或c或d。
好了,下面是重点了,想通了全排列的递归就很直观了
①当a1取定为a的时候,余下序列为b,c,d。
我们发现a和序列b,c,d的全排列的组合 {a,bdc全排列}。就构成了以a开头的序列的全排列。子问题就是求bcd序列的全排列了。可以发现此时子问题的规模减小了1。那么用递归就很直观了。
②好了,然后我们把a1取定为b的时候。那么问题的求解就编程了{b,adc全排列}
如此类推,直到所有a1的可能的取值下的子问题都解决了,就能得到全排列了。而且序列的初始序列并不影响全排列的过程。
好了说那么多,还是通过代码来分析好点。
代码
#include<iostream>
#include<string.h>
using namespace std;
//交换
void swap_m(char*& a,char*& b)
{
if(*a != *b)
{
*a = *a ^ *b;
*b = *a ^ *b;
*a = *a ^ *b;
}else if(*a == *b){
cout<<"发生相同交换啦"<<endl;
}
}
void pernum(char* per,char* begin)
{
if(per == NULL) return ;
if(*begin == '\0')
{
cout<<per<<endl;
}else{
for(char* pCh = begin;*pCh != '\0';pCh++)
{
//函数刚进入的时候看这里,递归第0层
//pch指针了当前序列:abcd的第一个a,然后不断自增
//然后不断的和begin(此时指向了a)的值交换
//第0层的循环遍历完了后就是把,a1的所有可能取值就列举完了
//函数进入递归后看这里,递归第1层
//从第0层进入到第1层后,就变成求解子问题了
//此时pch指向了当前序列:bcd的第一个b,然后不断自增
//特别注意,特别注意,特别注意!!!
//第一层递归的子问题是bcd,不是acd。
//因为for循环开始的时候pch=begin,
//所以for循环中第一次调用swap()本质上是交换了两个一样的值。交换完自己后就进入了深一层的递归,求解bcd的子问题
//函数进入递归第二层后,求解的就是bdc的子问题cd了
//如此类推,直到子问题的规模为0,即begin指向的子问题序列为空了,就可以输出了。
//要注意第i层递归的begin的指向总在,第i-1层递归的begin的指向的后一个,因为传值操作!!发生的是拷贝
swap(pCh,begin);
pernum(per,(begin+1));
//这里的交换是为了把序列恢复成初始的序列
swap(pCh,begin);
}
}
}
int main()
{
char per[] ="abcd";
pernum(per,per);
return 0;
}
好了,我们来看看输出
可以看到,函数刚刚调用的时候,发生了4次相同交换,递归了四层,子问题的规模终于为0了。到d交换完自己后,再进一层递归,此时begin值向为’\0’了,就输出abcd了。退出一层,cd交换成dc,进入递归,c自身交换,在进入递归,输出abdc,退出,恢复的时又发生一次c自身交换,退出。后面的就不慢慢分析了。
同理数组中的全排列也是类似的,其实全排列的本质是一个DFS的过程。
#include<bits/stdc++.h>
using namespace std;
void premu(int array[],int index,int length)
{
if(index == length-1)
{
for(int i=0;i<length;i++){
cout<<array[i]<<" ";
}
cout<<endl;
return ;
}
for(int i=index;i<length;i++)
{
swap(array[i],array[index]);
if(array[i]!=array[index]);
premu(array,index+1,length);
swap(array[i],array[index]);
}
}
int main()
{
int array[]={1,2,3,4};
premu(array,0,sizeof(array)/sizeof(*array));
return 0;
}