题目: 设计一个递归算法,求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)。
嗯。。。。诚然,这是一段不错的代码注释,但看完还是不会写啊啊啊啊。
就如同网上大部分算法博客一样,很多人喜欢从结结果代码反推逻辑(给代码写满注释)。但看懂前人的代码
自己下次遇见会写。我们要做的不仅仅是分析代码,更重要的是下次自己也可以写出来。所以正确的学习方式应该是从第一个写这个代码的人角度来思考:他(她)是怎么写出来的?
这个题目,我一开始是这么写的(没有函数递归,没有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循环一步步到函数递归
这里的
就是我们要定义的递归函数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]) ,由此我们得出最终结论: