全排列--从for循环一步步到函数递归

题目: 设计一个递归算法,求n个不同字符的所有全排列

先上最终代码。

#include <iostream>
#include <algorithm>
#include<string.h>
using namespace std;
void printa(char a[]){
	int i=0;
	while(a[i]!='\0'){
		cout<<a[i]<<" ";
		i++;
	}	
	cout<<endl;
}
void allsort(char a[],int k){
	int i = 0;char wzq;
	if(k==0){
		printa(a);
	}else{
		for(int j=0;j<=k;j++){ 
			
			swap(a[k],a[j]);
			allsort(a,k-1);
			swap(a[k],a[j]);
		}
	}	
}

int main(){
   char a[3]={'a','b','c'};
    allsort(a,2);
	return 0;
}

对于上述代码,王道书上是这么解释的:

设a是含有n个不同字符的字符串,allsort(a,k-1)为a[0]~ a[k-1]的全排列,allsort(a,k)为a[0]~ a[k]的全排列,allsort(a,k-1)比allsort(a,k)处理的字符少一个。假设allsort(a,k-1)可求,对于a[k]位置可以取a[0] ~a[k]范围的任何值,再组合allsort(a,k-1),则可以得到allsort(a,k)。

嗯。。。。诚然,这是一段不错的代码注释,但看完还是不会写啊啊啊啊。
就如同网上大部分算法博客一样,很多人喜欢从结结果代码反推逻辑(给代码写满注释)。但看懂前人的代码 \ne 自己下次遇见会写。我们要做的不仅仅是分析代码,更重要的是下次自己也可以写出来。所以正确的学习方式应该是从第一个写这个代码的人角度来思考:他(她)是怎么写出来的?

这个题目,我一开始是这么写的(没有函数递归,没有for循环),我相信这代表大部分刚刚入门小白的逻辑,所以请大神勿喷。

#include <iostream>
#include <algorithm>
#include<string.h>
using namespace std;
void printa(char a[]){
	int i=0;
	while(a[i]!='\0'){
		cout<<a[i]<<" ";
		i++;
	}	
	cout<<endl;
}
void allsort(char a[]){
	swap(a[0],a[0]);
	
		swap(a[1],a[1]);
			swap(a[2],a[2]);
				printa(a);
			swap(a[2],a[2]);
		swap(a[1],a[1]);
			
		swap(a[1],a[2]);
			swap(a[2],a[2]);
				printa(a);
			swap(a[2],a[2]);
		swap(a[1],a[2]);
	swap(a[0],a[0]);
	
	swap(a[0],a[1]);
	
		swap(a[1],a[1]);
			swap(a[2],a[2]);
				printa(a);
			swap(a[2],a[2]);
		swap(a[1],a[1]);
			
		swap(a[1],a[2]);
			swap(a[2],a[2]);
				printa(a);
			swap(a[2],a[2]);
		swap(a[1],a[2]);
	swap(a[0],a[1]);
	
	swap(a[0],a[2]);
	
		swap(a[1],a[1]);
			swap(a[2],a[2]);
				printa(a);
			swap(a[2],a[2]);
		swap(a[1],a[1]);
			
		swap(a[1],a[2]);
			swap(a[2],a[2]);
				printa(a);
			swap(a[2],a[2]);
		swap(a[1],a[2]);
	swap(a[0],a[2]);
}

int main(){
   char a[3]={'a','b','c'};
    allsort(a);
	return 0;
}

明显,这个allsort函数只能解决N=3的情况,手敲一遍就会有感觉,如果敲完,相信大部分人都可以敲出来N=4的allsort函数!也可以敲出来N=5、6、7…的情况下的allsort函数。
然后,笔者学习了for循环,对代码进行了改进。

#include <iostream>
#include <algorithm>
#include<string.h>
using namespace std;
void printa(char a[]){
	int i=0;
	while(a[i]!='\0'){
		cout<<a[i]<<" ";
		i++;
	}	
	cout<<endl;
}
void allsort(char a[]){
	 for(int i=0;i<3;i++){
		swap(a[0],a[i]);
		for(int j=1;j<3;j++){
			swap(a[1],a[j]);
			//	swap(a[2],a[2]);
					printa(a);
			//	swap(a[2],a[2]);
			swap(a[1],a[j]);
		}
		swap(a[0],a[i]);
	 }
	
}

int main(){
   char a[3]={'a','b','c'};
    allsort(a,2);
	return 0;
}

别的不说,至少长度短了不少^ _ ^但依然没有解决对任意长度字符串的问题,N=4、5、6…依然需要我们手工重新编写allsort函数。
到这里相信大部分大学c语言期末考试的知识都是足以解决的,所以不详细介绍改进过程,下面详细讲一下(也就是标题):如何从for循环一步步到函数递归
在这里插入图片描述
这里的 a n a_n 就是我们要定义的递归函数allsort!!
接下来确定函数里面的参数。
每次循环,其中只有一个变量k和数组a在不断改变,N是问题的规模,也就是多少个字符的全排列,所以allsort应该申明为allsort(char a[],int N,int k)。

void allsort(char a[],int N,int k){
	for(int i=k;i<N;i++){ 
			swap(a[k],a[i]);
			allsort(a,N,k+1);
			swap(a[k],a[i]);
	}
}

接着思考函数何时停止递归(总不能一直循环下去吧!)。回头看之前的代码,k一开始等于0,当k增加到N-1的时候,就可以执行输出函数,不需要继续向下循环了。
于是乎,我们还需要给函数加一个判断语句:如果k=N-1,就直接打印,不再往里面循环。

void allsort(char a[],int N,int k){
	if(k==N-1){
		printa(a);
	}else{
		for(int i=k;i<N;i++){ 
			swap(a[k],a[i]);
			allsort(a,N,k+1);
			swap(a[k],a[i]);
		}
	}
}

完整代码:

#include <iostream>
#include <algorithm>
#include<string.h>
using namespace std;
void printa(char a[]){
	int i=0;
	while(a[i]!='\0'){
		cout<<a[i]<<" ";
		i++;
	}	
	cout<<endl;
}
void allsort(char a[],int N,int k){
	if(k==N-1){
		printa(a);
	}else{
		for(int i=k;i<N;i++){ 
			swap(a[k],a[i]);
			allsort(a,N,k+1);
			swap(a[k],a[i]);
		}
	}
}

int main(){
   char a[3]={'a','b','c'};
    allsort(a,3,0);
	return 0;
}

这段代码和最终代码还是有所不同的,但是已经能够运行,并完美解决问题,对于任意N都可以给出结果。但和文章开头给出的代码(以下简称:文开代码)依然有所差别,并且我们还比开头代码多了一个参数k。
对比两个代码运行结果。
我们的代码结果:
在这里插入图片描述
文开代码结果:
在这里插入图片描述
明显可以看出,我们是从前往后排,先固定a[0]剩下全排列,再固定a[1]。而文开代码先固定a[n](也就是a[2])剩下的全排列,再固定a[n-1](也就是a[1]) ,由此我们得出最终结论:

递归问题,从n往前排,可以节省一个参数。(好像没啥用,但可以让代码变美观啊^ _ ^)

猜你喜欢

转载自blog.csdn.net/weixin_39529891/article/details/106008730