【c语言】递归习题小结

    最近在学习递归,下面是近期遇见的一些习题,都可以使用递归法来解决的,总结如下,希望加深自己对递归的理解。

    【题1】 字母全排列,假设给出字符串“abc”,求出它的所有排列,如abc,acb,bac,bca,等等。

    扩展要求:不能出现重复的字符串,例如"abb",只能输出abb,bab,bba。(我还没做出来,^_^)

    方法一,基于分治的递归法,见代码中arrange_div函数;方法二,基于回溯的递归方法,见代码中arrange_dfs函数。

#include <stdio.h>

#include <string.h>



#if 1     //将 1 改为 0 ,就可以隐藏下述代码

#define N 3

#define NUL '\0'



int num=0;

int locate[N+1]={0};

char charLocate[N+1];



void arrange_dfs(char *str);



int main(){



        char a[]="abc";

        arrange_dfs(a);

        printf("%d\n",num);

        return 0;



}





void arrange_dfs(char *str){

        int i,j;



        if(*str!=NUL){

                for(i=1;i<=N;i++){

                        if(locate[i]!=0 )

                                continue;



                        locate[i]=1;

                        charLocate[i]=*str;

                        arrange_dfs(str+1);

                        locate[i]=0;

                }

        }

        else{

                num++;

                for(j=1;j<=N;j++)

                printf("%c",charLocate[j]);

                printf("\n");

        }

}

#endif



#if 0    //将 0 改为 1 ,就可以运行下列代码



void arrange_div(char *str,int len);

int num=0;

void inv(char *a ,char *b){

        char temp;

        temp=*a;

        *a=*b;

        *b=temp;

}



int main(){

        char a[]="abc";

        arrange_div(a,3);

        printf("%d\n",num);

        return 0;

}



void arrange_div(char *str,int len){

        int i;



                if(0==len){

                        num++;

                        printf("%s\n",str);

                }else

                {

        for(i=0;i<len;i++)

        {

                inv(&(*(str+i)),&(str[len-1]));

                arrange_div(str,len-1);

                inv(&(*(str+i)),&(str[len-1]));

        }

        }

}



#endif

  

     此外,还想记录在写回溯法的递归函数时出现的一个非常隐蔽的错误。之所以出现这个错误,在于我对递归的理解不够深入,记在此处,时时提醒自己。

 #include <stdio.h>

#include <string.h>





#define N 3

#define NUL '\0'



int num=0;

int locate[N+1]={0};

char charLocate[N+1];



void arrange_dfs(char *str);



int main(){



        char a[]="abc";

        arrange_dfs(a);

        printf("%d\n",num);

        return 0;



}





void arrange_dfs(char *str){

        int i,j;



        if(*str!=NUL){

                for(i=1;i<=N;i++){

                        if(locate[i]!=0 )

                                continue;



                        locate[i]=1;

                        charLocate[i]=*str;

                        arrange_dfs(str+1);

                }

                        locate[i]=0;  //错误在这里,没有回溯

        }

        else{

                num++;

                for(j=1;j<=N;j++)

                printf("%c",charLocate[j]);

                printf("\n");

        }

}

 

    这道题目很简单,但又很典型。充分展现了两种截然不同的递归方法——回溯法和分治法。分治法以目标为驱动,在求解著名的斐波那契数列问题时,有一个公式淋漓尽致地诠释了分治法的思路,f(n)=f(n-1)+f(n-2)(n>2,n为正整数);分治法从最终的问题出发,倒推以前的状态,建立两者之间的关系。与此不同,回溯法从最初的状态,一步步试探,然后得到最终我们想求的结果。或许这又有点玄了。没事,继续来看习题。

 

 

    【题目2】上\下楼梯问题,假设有h个楼梯,一次只允许走一步,两步,或者三步,求有多少种下楼梯的方法?

    扩展要求:将每一种下楼梯的方案打印在屏幕。

    方法一,基于分治的递归法,缺点是打印不了每一种下楼梯方案;方法二,基于回溯的递归方法,可以打印每种下楼方法。代码见下:

#include <stdio.h>

#include <stdlib.h>



#if 1

#define MAX 100

void trystep_hs(int n,int s);



int pace[MAX]={0};

int num=0;



int main(){



        trystep_hs(5,1);

        printf("%d\n",num);

        return 0;



}



/*回溯法*/

void trystep_hs(int i,int s){

        int j,k;



        for(j=1;j<=3;j++){

        if(i<j) continue;

        else{

                pace[s]=j; //如果将这一句放在后面的trystep_hs(i-j,s+1)前面,将会出错。

                if(i==j){

                        num++;

                        for(k=1;k<=s;k++)

                                printf("%d  ",pace[k]);

                        printf("\n");

                }else{

                     // pace[s]=j; 试一试 

                        trystep_hs(i-j,s+1);

                }

        }

        pace[s]=0;



        }

}

#endif



#if 0

int  trystep(int n);



int main(){

        printf("%d\n",trystep(5));

        return 0;



}



/*分治法*/



int trystep(int n){



        if(n<=0){

                printf("wrong input\n");

                exit(1);

        }



        if(1==n)

                return 1;

        else if(2==n)

                return 2;

        else if(3==n)

                return 4;

        else

                return trystep(n-1)+trystep(n-2)+trystep(n-3);



}

#endif

  

     或许,上述内容仍然没有清楚地阐述分治法与回溯法的具体思路。那只好祭出经典习题了,第一个习题是快速排序算法,它是分治法的经典代表;第二个是迷宫问题,它是回溯法的经典代表。看见这两道习题,就会令人自然而然地想起回溯法和分治法。

 【2014/4/12补充】

其实无需刻意地写成分治或回溯的形式,使用一般的递归也可以求出此题,而且由此而写出的代码易于理解。

#include <stdio.h>



void walk_floor(int now, int h);

#define MAX 100

void trystep_hs(int n,int s);



int pace[MAX]={0};



int cases_num;

int num;



int main()

{



	int h;



	while(1){

		printf("\n");

	scanf("%d", &h);

	



	walk_floor(0, h);



	printf("%d\n", cases_num);



	trystep_hs(h, 1);

	printf("%d\n", num);



	}

	

	return 0;

}





void walk_floor(int now, int h)

{

	int j;



	for( j = 1; j < 4; j++)

	{

		

		if( now + j >=  h)

		{

			if( now+j == h)

				cases_num++;

		}

		else

		{

			walk_floor(now+j, h);

		}

	}

}





/*回溯法*/

void trystep_hs(int i,int s){

        int j,k;



        for(j=1;j<=3;j++){

        if(i<j) continue;

        else{

                pace[s]=j; //如果将这一句放在后面的trystep_hs(i-j,s+1)前面,将会出错。

                if(i==j){

                        num++;

						/*

                        for(k=1;k<=s;k++)

                                printf("%d  ",pace[k]);

                        printf("\n");

						*/

                }else{

                        trystep_hs(i-j,s+1);

                }

        }

        pace[s]=0;



        }

}

  

 

    【题目3】假设有一个数组int a[]={1,2,3,4,7,2,3,8,9},将这个数组,由大到小,进行排序。(分治法)

    c代码:

View Code
 1 #include <stdio.h>

 2 

 3 

 4 int part(int a[],int low,int high);

 5 

 6 void quicksort(int a[],int low,int high);

 7 

 8 

 9 int main(){

10         int i;

11         int a[]={1,1,1,3,99,23,3,4,7,9,3,2,11,13,22,2,1};

12         int len=sizeof(a)/sizeof(int);

13         quicksort(a,0,len-1);

14         for(i=0;i<len;i++)

15                 printf("%d ",a[i]);

16         printf("\n");

17         return 0;

18 }

19 

20 /*函数part选定一个数X作为基准,对数组进行筛选,比X大的,放在X右边,反之,放在左边*/

21 

22 int part(int a[],int low,int high){

23         int i,j,flag;

24 

25         int partion=a[low];  //选定a[low]作为基准,这样便于处理

26 

27         i=low;j=high;

28 /*循环查找比基准大的数和小的数,并移位。*/

29         while(i<j){

30                 while(i<j&&a[j]>=partion) //此处一定要有等号"="

31                         j--;

32                         a[i]=a[j];

33                 while(i<j&&a[i]<=partion)

34                         i++;

35                         a[j]=a[i];

36         }

37 

38 /*找到基准后,将其在数组上的位置返回,作为下一步递归使用*/

39         a[i]=partion;

40         flag=i;

41         return flag;

42 

43 }

44 

45 

46 /*进行分治法,递归处理*/

47 void quicksort(int a[],int low,int high){

48 

49         if(low<high){

50                 int mid=part(a,low,high);  //以基准为中心,开始分治处理

51                 quicksort(a,low,mid);      

52                 quicksort(a,mid+1,high);

53         }

54 }

 

 

    【题4】迷宫问题(回溯法)

    扩展要求:除了递归实现以外,还可以用堆栈的形式非递归实现。此处不再赘述,感兴趣的话,可以参考:http://blog.csdn.net/lixiang99410/article/details/6179826

View Code
#include <stdio.h>





#define MAX_SIZE 512



int maze[5][5]={

        0,1,0,0,0,

        0,0,0,0,0,

        0,0,0,0,0,

        0,1,1,1,0,

        0,0,0,1,0,

};





struct point{int row,col;} trace[MAX_SIZE];



struct point status[2]={[0].row=1,[0].col=0,[1].row=0,[1].col=1};



void maze_tr(int row,int col,int s);



int num=0;

int main(){

        maze[0][0]=2;

        maze_tr(0,0,1);

}





void maze_tr(int row,int col,int s){



        int i,k;

        if(4==row&&4==col){

                        for(k=0;k<s;k++)

                                printf("(%d,%d) ",trace[k].row,trace[k].col);

                        printf("\n");

        }else{

                for(i=0;i<2;i++){

                        if((row+status[i].row)>4||(col+status[i].col)>4||(row+status[i].row)<0 ||(col+status[i].col)<0||2==maze[row+status[i].row][col+status[i].col]|| 1==maze[row+status[i].row][col+status[i].col])

                                continue;

                        maze[row+status[i].row][col+status[i].col]=2;

                        trace[s].row=row+status[i].row;

                        trace[s].col=col+status[i].col;

                        maze_tr(row+status[i].row,col+status[i].col,s+1);

                        maze[row+status[i].row][col+status[i].col]=0;



                }

        }

}

    

 

 

    暂时告一段落,递归的方法千变万化,上面几种形式只是其中一小部分,而且递归算法效率未必高,存在优化问题,有空再来补充一些内容。递归还有许多应用,比如八皇后问题,归并排序算法,古老的打子弹计环数问题(见http://blog.csdn.net/hehe9737/article/details/7008552),每一种应用都灵活使用了递归思想,也都值得去做一做。

    附件:清华大学湛卫军老师的c语言递归课件,非常不错。http://wenku.baidu.com/view/c744dd4333687e21af45a90a.html

你可能感兴趣的:(C语言)