递归算法详解

http://blog.csdn.net/effective_coder/article/details/8742979
                                                                                递归算法详解

C语言通过运行时堆栈来支持递归的调用,在我们刚接触递归的时候,国内很多教材都采用求阶乘和菲波那契数列来描述该思想,就如同深受大家敬爱的国产的C语言程序设计,老谭也用了阶乘来描述递归,以至于很多新手一看见阶乘就理所当然的认为是递归,坑了不少人,说实在的,描述这个思想还是可以,但是利用递归求阶乘可是没有一点好处,递归解决菲波那契数列效率更是低得惊人,这点是显而易见的!废话不多说,接下来我们进入正题!(不过说实话,我很讨厌接下来这些太理论的东西,说到底就是那么个意思,大家懂就好了,也可以当看看故事!我主要说的就是各种各样递归的实例)

1:递归算法的思想

递归算法是把问题转化为规模缩小了的同类问题的子问题。然后递归调用函数(或过程)来表示问题的解。在C语言中的运行堆栈为他的存在提供了很好的支持,过程一般是通过函数或子过程来实现。

递归算法:在函数或子过程的内部,直接或者间接地调用自己的算法。

2:递归算法的特点

递归算法是一种直接或者间接地调用自身算法的过程。在计算机编写程序中,递归算法对解决一大类问题是十分有效的,它往往使算法的描述简洁而且易于理解。

递归算法解决问题的特点:

(1) 递归就是在过程或函数里调用自身。

(2) 在使用递归策略时,必须有一个明确的递归结束条件,称为递归出口。

(3) 递归算法解题通常显得很简洁,但递归算法解题的运行效率较低。所以一般不提倡用递归算法设计程序。

(4) 在递归调用的过程当中系统为每一层的返回点、局部量等开辟了栈来存储。递归次数过多容易造成栈溢出等。所以一般不提倡用递归算法设计程序。

3:递归算法的要求

递归算法所体现的“重复”一般有三个要求:

一是每次调用在规模上都有所缩小(通常是减半);

二是相邻两次重复之间有紧密的联系,前一次要为后一次做准备(通常前一次的输出就作为后一次的输入);

三是在问题的规模极小时必须用直接给出解答而不再进行递归调用,因而每次递归调用都是有条件的(以规模未达到直接解答的大小为条件),无条件递归调用将会成为死循环而不能正常结束。

4:各式各样利用递归的问题

1:首先看看那些传统的问题吧,如使用递归来解决斐波那契数列的第n个数是多少?(开始从1开始)

[cpp] view plain copy

print?

  1. #include <iostream>
  2. using namespace std;
  3. int Fib(int index);
  4. int main(int argc, char* argv[])
  5. {
  6. cout<<Fib(12)<<endl;
  7. system("pause");
  8. return 0;
  9. }
  10. int Fib(int index)
  11. {
  12. if(index==1 || index==2)
  13. return index;
  14. else
  15. return Fib(index-1) + Fib(index-2);    //开始递归调用
  16. }

写程序的时候我测试了一下,假如要第100个数字,那时间可不知道等了多久,调用函数达到了上千次,速度太慢,对于这种情况,我们对比一下不使用的递归的时候时间消耗,这里只需要多加一个函数即可

[cpp] view plain copy

print?

  1. #include <iostream>
  2. #include <ctime>
  3. using namespace std;
  4. int Fib2(int index);
  5. int Fib1(int index);
  6. int main(int argc, char* argv[])
  7. {
  8. clock_t start,finish;
  9. cout<<"不使用递归:"<<endl;
  10. start = clock();
  11. cout<<"所得结果为 "<<Fib2(40)<<endl;
  12. finish = clock();
  13. cout<<"时间消耗为 "<<finish - start<<"毫秒"<<endl;
  14. cout<<endl;
  15. cout<<"使用递归:"<<endl;
  16. start = clock();
  17. cout<<"所得结果为 "<<Fib1(40)<<endl;
  18. finish = clock();
  19. cout<<"时间消耗为 "<<finish - start<<"毫秒"<<endl;
  20. system("pause");
  21. return 0;
  22. }
  23. int Fib1(int index)
  24. {
  25. if(index==1 || index==2)
  26. return index;
  27. else
  28. return Fib1(index-1) + Fib1(index-2);    //开始递归调用
  29. }
  30. int Fib2(int index)
  31. {
  32. if(index == 1 || index ==2)
  33. return index;
  34. int *array = new int [index+1];
  35. array[1]=1;                //第0个元素没有使用
  36. array[2]=2;
  37. for(int i=3;i<=index;++i)
  38. array[i] = array[i-1] + array[i-2];
  39. return array[index];
  40. }

运行结果:

结果显而易见,差距太明显,在这里我们同时求第40个斐波那契数字比较时间消耗,所以大家可以看到递归的时间消耗是非常严重,而且效率非常低下,上面已经说了,在可以不用递归的时候尽量不用,那么递归是不是一无是处勒?答案是否定的,在很多程序设计大赛中,有很多题用一般的思路是很难解的,或者是过程繁琐,如果适当的利用递归,结果将事半功倍!!!

2:递归的汉诺塔

这个程序以及说明在分治算法那一节已经说了,递归和分治通常都是结合在一起使用的,一次次的缩小范围,而且子问题和原问题具有相同的结构!  这里我直接把汉诺塔代码拷贝过来,就不多说了!

[cpp] view plain copy

print?

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. static int count = -1;
  4. void move(char x,char y);                             // 对move函数的声明
  5. void hanoi(int n,char one,char two,char three)       ;// 对hanoi函数的声明\
  6. int main()
  7. {
  8. int m;
  9. printf("请输入一共有多少个板子需要移动:");
  10. scanf("%d",&m);
  11. printf("以下是%d个板子的移动方案:\n",m);
  12. hanoi(m,‘A‘,‘B‘,‘C‘);
  13. system("pause");
  14. return 0;
  15. }
  16. void hanoi(int n,char one,char two,char three)        // 定义hanoi函数
  17. // 将n个盘从one座借助two座,移到three座
  18. {
  19. if(n==1)
  20. move(one,three);
  21. else
  22. {
  23. hanoi(n-1,one,three,two);                   //首先把n-1个从one移动到two
  24. move(one,three);                            //然后把最后一个n从one移动到three
  25. hanoi(n-1,two,one,three);                   //最后再把n-1个从two移动到three
  26. }
  27. }
  28. void move(char x,char y)                           //  定义move函数
  29. {
  30. count++;
  31. if( !(count%5) )
  32. printf("\n");
  33. printf("%c移动至%c  ",x,y);
  34. }

3:兔子繁殖问题(递归实现)

一对小兔子一年后长成大兔子,一对大兔子每半年生一对小兔子,大兔子的繁殖期为4年,兔子的寿命为6年,假定第一年年初投放了一对小兔子,请编程实现,第N年年末总共有多少只兔子,N由键盘输入!

解析,这个题目比较好懂,也就是一对小兔子前一年长大,然后每半年产一对小兔子,持续4年,然后最后一年不生殖了,再过一年死亡,题目看似简单,其实要想递归起来可不是那么容易的,大家可以想一下!

代码如下:

4:整数的划分问题

将一个整数分解为若干个整数之和的形式,比如 n = n1+n2+n3+n4··········!不同划分的个数称为N的划分数。

例如对于6而言:

6;

5+1;

4+2,4+1+1;

3+3;3+2+1;3+1+1+1;

2+2+2;2+2+1+1;2+1+1+1+1;

1+1+1+1+1+1     一共有6种!

1、 q(n,1) = 1 ,n>=1 ;
当最大加数不大于1时,任何正整数n只有一种表示方式:n = 1+1+……+1 。n个1的和。
2、q( n,m ) = q( n,n ),n<=m;  最大加数不能大于n。
3、 q( n,n ) = 1 +  q( n , n-1 );   正整数的划分由n1=n和n1<=n的划分组成。
4、q( n,m ) = q( n,m-1 )+q( n-m,m ), n>m>1;正整数n的最大加数不大于m的划分由 n1=m的划分和n1<m的划分组成。

现在可以依据这个递推原理写出程序:

[cpp] view plain copy

print?

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. int intPart( int n , int m ) ;
  4. int main()
  5. {
  6. int num ;
  7. int partNum = 0 ;
  8. printf("Please input an integer:/n") ;
  9. scanf("%d",&num) ;
  10. partNum = intPart(num,num);
  11. printf("%d/n",partNum) ;
  12. system("pause");
  13. return 0;
  14. }
  15. int intPart( int n , int m )
  16. {
  17. if( ( n < 1 ) ||( m < 1 ) ) return 0 ;
  18. if( ( n == 1 )||( m == 1 ) ) return 1 ;
  19. if( n < m ) return intPart( n , n ) ;
  20. if( n == m ) return intPart( n , m-1 ) + 1 ;
  21. return intPart( n , m-1 ) + intPart( n - m , m ) ;
  22. }

运行结果可以看到一共有11种情况

5 整数的全排列问题:

全排列的递归实现也就是不停的交换两个数的位置,题目描述这里就省了,直接上代码!

[cpp] view plain copy

print?

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <string.h>
  4. void swap(char *a,char *b)
  5. {
  6. char temp = *a;
  7. *a = *b;
  8. *b = temp;
  9. }
  10. //k表示循环到第几个字符,m表示该次循环的总长度
  11. void arrange(char *pizstr,int k,int m)
  12. {
  13. if(k == m)
  14. {
  15. static int m_count = 1;
  16. printf("the %d time:%s\n",m_count++,pizstr);
  17. }
  18. else
  19. {
  20. for(int i=k;i<=m;i++)                          //主要递归球全排列的代码
  21. {
  22. swap(pizstr+k,pizstr+i);
  23. arrange(pizstr,k+1,m);
  24. swap(pizstr+k,pizstr+i);
  25. }
  26. }
  27. }
  28. void foo(char *p_str)
  29. {
  30. arrange(p_str,0,strlen(p_str)-1);
  31. }
  32. int main()
  33. {
  34. char pstr[] = "12345";
  35. printf("%s\n",pstr);
  36. foo(pstr);
  37. system("pause");
  38. return 0;
  39. }

时间紧促,有时间再继续举例!持续更新

时间: 2024-09-30 04:02:08

递归算法详解的相关文章

[转]递归算法详解

计算机科学的新学生通常难以理解递归程序设计的概念.递归思想之所以困难,原因在于它非常像是循环推理(circular reasoning).它也不是一个直观的过程:当我们指挥别人做事的时候,我们极少会递归地指挥他们. Introduction 递归算法是一种直接或者间接调用自身函数或者方法的算法.递归算法的实质是把问题分解成规模缩小的同类问题的子问题,然后递归调用方法来表示问题的解.递归算法对解决一大类问题很有效,它可以使算法简洁和易于理解.递归算法,其实说白了,就是程序的自身调用.它表现在一段程

python中汉诺塔的递归算法详解

请编写move(n, a, b, c)函数,它接收参数n,表示3个柱子A.B.C中第1个柱子A的盘子数量,然后打印出把所有盘子从A借助B移动到C的方法,例如: def move(n, a, b, c): pass 答案: def move(n,a,b,c): if n==1: print(a,'->',c) else: move(n-1,a,c,b) move(1,a,b,c) move(n-1,b,a,c) 理解的关键不需要管每一步是怎么解决的.重点是实现你的目的.我们可以这么理解: move

Python装饰器详解,详细介绍它的应用场景

装饰器的应用场景 附加功能 数据的清理或添加: 函数参数类型验证 @require_ints 类似请求前拦截 数据格式转换 将函数返回字典改为 JSON/YAML 类似响应后篡改 为函数提供额外的数据 mock.patch 函数注册 在任务中心注册一个任务 注册一个带信号处理器的函数 不同应用场景下装饰器实现 函数注册表 简单注册表 funcs = [] def register(func): funcs.append(func) return func @register def a(): r

.NET深入解析LINQ框架(五:IQueryable、IQueryProvider接口详解)

阅读目录: 1.环路执行对象模型.碎片化执行模型(假递归式调用) 2.N层对象执行模型(纵横向对比链式扩展方法) 3.LINQ查询表达式和链式查询方法其实都是空壳子 4.详细的对象结构图(对象的执行原理) 5.IQueryable<T>与IQueryProvider一对一的关系能否改成一对多的关系 6.完整的自定义查询 1]. 环路执行对象模型.碎片化执行模型(假递归式调用) 这个主题扯的可能有点远,但是它关系着整个LINQ框架的设计结构,至少在我还没有搞懂LINQ的本意之前,在我脑海里一直频

二叉树的应用详解 - 数据结构

二叉树的应用详解 - 数据结构 概述: 平衡树——特点:所有结点左右子树深度差≤1 排序树——特点:所有结点“左小右大字典树——由字符串构成的二叉排序树判定树——特点:分支查找树(例如12个球如何只称3次便分出轻重)带权树——特点:路径带权值(例如长度) 最优树——是带权路径长度最短的树,又称 Huffman树,用途之一是通信中的压缩编码. 1. 二叉排序树(二叉查找树 Binary Search Tree): 1.1 二叉排序树: 或是一棵空树:或者是具有如下性质的非空二叉树: (1)若左子树

(转)详解八大排序算法

概述 排序有内部排序和外部排序,内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一次不能容纳全部的排序记录,在排序过程中需要访问外存. 我们这里说说八大排序就是内部排序. 当n较大,则应采用时间复杂度为O(nlog2n)的排序方法:快速排序.堆排序或归并排序序. 快速排序:是目前基于比较的内部排序中被认为是最好的方法,当待排序的关键字是随机分布时,快速排序的平均时间最短: 1.插入排序—直接插入排序(Straight Insertion Sort) 基本思想: 将一个记录插入到

动态规划之最长递增子序列问题详解

最近重新开始看动态规划,动态规划算法的有效性依赖于问题本身所具有的两个重要性质:最优子结构性质和子问题重叠性质. 1.最优子结构:当问题的最优解包含了其子问题的最优解时,称该问题具有最优子结构性质. 2.重叠子问题:在用递归算法自顶向下解问题时,每次产生的子问题并不总是新问题,有些子问题被反复计算多次.动态规划算法正是利用了这种子问题的重叠性质,对每一个子问题只解一次,而后将其解保存在一个表格中,在以后尽可能多地利用这些子问题的解. (二).动态规划算法的基本步骤设计一个标准的动态规划算法,通常

(转)dp动态规划分类详解

dp动态规划分类详解 转自:http://blog.csdn.NET/cc_again/article/details/25866971 动态规划一直是ACM竞赛中的重点,同时又是难点,因为该算法时间效率高,代码量少,多元性强,主要考察思维能力.建模抽象能力.灵活度. ****************************************************************************************** 动态规划(英语:Dynamic programm

二叉树详解

二叉树详解: 采用递归的方式进行遍历,这样做的好处时代码十分简洁. 顺序存储:数组 链表存储:链表 typedef struct bi_t_node{ telemetype data; struct bi_t_node *lchild, *rchild; }bi_t_node, *bi_tree; 前序遍历:第一次到达节点时,自左向右 中序遍历:第二次到达节点时,自左向右 后序遍历:第三次到达节点时,自左向右 层序遍历:自上而下,,自左向右 示例: 1 /* 二叉树前序遍历递归算法 */ 2 v