递归的定义:子程序(或函数)直接调用自己或通过一系列调用语句间接调用自己,是一种描述问题和解决问题的基本方法。
首先,看一个简单的递归例子:
#include <iostream> void countdown (int n);
int main() { countdown (4); return 0; } void countdown (int n) { using namespace std; cout << "Counting down ..." << n << endl; if (n > 0) countdown (n-1); cout << n << ": Kaboom!/n"; } |
输出结果为:
入栈、出栈顺序:4—3—2—1—0—0—1—2—3—4
递归定义由两部分组成:
第一部分锚:列出产生集合中其他元素的基本元素
第二部分给出有基本元素或已有对象产生新对象的构造规则,
一次次地应用这些规则来产生新对象
在程序设计中递归程序的简单定义是一个能调用自身的程序,但一个递归程序不能总是调用自身,否则它将无法停止;所以递归还必须要有一个终止条件,使得程序能够停止调用自身
求阶乘的递归:
int Factorial(int n) { if (n == 0) return 1; else return n*Factorial(n-1);
} |
我们来剖析下面这个递归函数
double power(double x, unsigned int n) { if (n == 0) return 1.0; else return x * power(x,n-1); } |
2的4次方
第1次调用: power(2,4)
第2次调用: power(2,3)
第3次调用: power(2,2)
第4次调用: power(2,1)
第5次调用: power(2,0)
第5次调用: 1
第4次调用: x
第3次调用: x*x
第2调用: x*x*x
第1次调用: x*x*x*x
void reverse() { char ch; cin.get(ch); if (ch != '/n') { reverse(); cout.put(ch); } } |
用main调用reverse()函数
输入ABCDEF得到结果为:FEDCBA
首先ABCDEF被依次压入栈中,当不满足递归条件ch != '/n'时,就按照FEDCBA的顺序出栈
调用cout.put(ch);
通过这个函数可以实现以相反的顺序打印输入行
与下面的程序进行比较?
void simpleIterativeReverse() { char stack[80]; register int top = 0; cin.getline(stack,80); for (top = strlen(stack) - 1; top >= 0; cout.put(stack[top--])); } |
上面的reverse函数如果改成下面两种情况?
void reverse() { char ch; cin.get(ch); if (ch != '/n') reverse2(); cout.put(ch); } | void reverse() { static char ch; cin.get(ch); if (ch != '/n') { reverse3(); cout.put(ch); } } |
间接递归
Receive(buffer)
Decode(buffer);
Decode(buffer)
Store(buffer);
Store(buffer)
Receive(buffer);
嵌套递归
int h(int n,int m) { if (n == 0) return 0; else if (n > 0 && m == 0) return h(n-1,1); else return h(n-1,h(n,m-1)); } |
不合理递归:
Fibonacci数列:
Fib(n) = n n<2
Fib(n)= Fib(n-2) + Fib(n-1) 其他
说明:声明前两个数为0和1,数列中其他的数都是起两个前驱的和,依此类推:
0,1,1,2,3,5,8,13,21,34,55,99…………………
递归算法:
unsigned int Fib(unsigned int n) { if (n < 2) return n; else return Fib(n-2) + Fib(n-1); } |
分析Fib(6)
Fib(6) = Fib(4) + Fib(5)
= Fib(2) + Fib(3) + Fib(5)
= Fib(0) + Fib(1) + Fib(3) + Fib(5)
=0 + 1 + Fib(1) + Fib(2) + Fib(5)
=0 + 1 + Fib(1) + Fib(0) + Fib(1) + Fib(5)
要计算Fib(6)需要调用Fib()函数25次
可以证明,利用递归定义求Fib(n)的加法次数是Fib(n+1)-1。每个加法都带有两次调用,还要加上最开始的调用,所以计算Fib(n)需要调用2*Fib(n+1)-1次Fib()函数
所以上述函数的效率极低;
使用迭代算法可以实现
unsigned int Fib(unsigned int n) { if (n < 2) return n; else { register int i = 2, tmp, current = 1, last = 0; for (; i <= n; ++i) { tmp = current; current +=last; last = tmp; } return current; } } |
看完前面的例子,下面研究汉诺塔问题
传说在古代印度的贝拿勒斯圣庙里,
安放了一块黄铜板,板上插了三根宝石柱,在其中一根宝石柱上,
自上而下按由小到大的顺序串有64个金盘。
这就是汉诺塔游戏。要求将左边柱子上的64个
金盘按照下面的规则移到右边的柱子上。
规则:
1.有三根杆子A,B,C。B杆上有若干碟子
2.每次移动一块碟子,小的只能叠在大的上面
3.把所有碟子从A杆全部移到B杆上
! ! !
! ! !
! ! !
A B C
思路:
设盘子只有一个,则本问题可简化为A→B。
对于大于一个盘子的情况,逻辑上可分为两部分:
第n个盘子和除n个以外的n-1个盘子。如果将除n以外的n-1个盘子看成一个整体,则要解决本问题,可按以下步骤:
a、将A杆上n-1个盘子借助于B先移到c杆; A→C (n-1,A,C,B)
b、将A杆上第n个盘子从A移到B杆; A→B
c、将C杆上n-1个盘子借助A移到B杆。 C→B (n-1,C,B,A)
void Hanoi (int n, char A, char B,char C) { if (n == 1) Move(A,B); else { Hanoi (n-1,A,C,B); Move(A,B); Hanoi (n-1,C,B,A); } } |
继续看下面一个递归的例子
#include <iostream> const int Len = 66; const int Divs = 6; void subdivide(char ar[], int low, int high, int level); int main() { char ruler[Len]; int i; for (i = 1; i < Len - 2; i ++) ruler[i] = ' '; ruler[Len-1] = '/0'; int max = Len - 2; int min = 0; ruler[min] = ruler[max] = '|'; std::cout << ruler << std::endl; for (i = 1; i <= Divs; i ++) { subdivide(ruler, min, max, i); std::cout << ruler << std::endl; for (int j = 1; j < Len - 2; j ++) ruler[j] = ' '; } return 0; } void subdivide(char ar[], int low, int high, int level) { if (level == 0) return; int mid = (high + low) / 2; ar[mid] = '|'; subdivide(ar,low, mid, level-1); subdivide(ar,mid,high, level-1); } |
思路基本和汉诺塔类似