递归几例,思想重要,举一反三!
1. n!递归求解
从后(n)出发,n!=n(n-1)! n>1.
若定义函数fac(n)为求n!,那么(n-1)!怎么求,仍给子函数,但去写子函数时发现这也是阶层问题,故可调用fac(n-1)这个函数,只不过调用的函数不是别人,恰好是自己,规模为n-1。C++语言的C程序如下:
#include <iostream> #include <stdio.h> #include <stdlib.h> using namespace std; int fac(int n);//声明 int main() { cout<<fac(4); return 0; } int fac(int n)//求n的阶乘 { if(n==1) return 1; //n=1, n!=1;等价 else return n* fac(n-1);//等价关系式n(n-1)!,其中(n-1)!可以调用fac(n-1); 一定要等价转化,转化的子问题和原问题同质 }
2. 递归求解数组最大值
正常是假设第一个元素值为最大值,依次和后面的值进行比较,发现更大的话更新max;而递归,从后往前思考,原问题(a[n]的最大值)等价于数组(前n-1)的最大值(和原问题同质,规模为n-1的数组,可递归)和数组第n个值的最大值。注意上下文环境,n是数组个数,也可以说是数据元素的个数,下标是1开始的。数组(前n-1)最大值--->MAX_R(int a[],n-1)(递归调用), 第n个值--->a[n-1],不要搞混。
#include<stdio.h>
int MAX_R(int a[], int n);
int main() {
int i,max,a[10] = { 3,5,7,9,10,6,2,1,4,8 };
max = MAX_R(a, 10);
printf("max=%d\n", max);
}
int MAX_R(int a[], int n) {//求含n个数的数组里面的最大值
if (n == 1) {//边界条件,当n==1时数组只有a[0],最大值自然是它,数组只有1个元素
return a[0];
}
else
{
// 递归等价式:原问题=MAX_R(a, n - 1)与a[n-1](第n个值)中的最大值,不断递归,直到n==1停止递归,此时max==a[0],然后一层层返回,执行下面代码
if ( MAX_R(a, n - 1)> a[n - 1]) {
return MAX_R(a, n - 1);
}
else {
return a[n - 1];
}
}
//return MAX_R(a, n - 1)> a[n - 1]?MAX_R(a, n - 1):a[n - 1];//三目运算直接替代上面if分支
}
//#include<bits/stdc++.h>
//using namespace std;
//int main(){//非递归代码
// int max,j,t,a[10]={3,5,7,9,10,6,2,1,4,8};//下标0-9
// max=a[0];
// for(j=1; j<10; j++)
// if(max<a[j])
// max=a[j];
// cout<<"数组中的最大值是"<<max<<endl;
// return 0;
//}
拓展:求单链表中的最大值,长度、遍历(不再是顺序存储了)?int GetMax(Linklist L) p=L->next;
分类讨论:n==1,return p->data;n>=2, return p->data>GetMax(p->next)?p->data:GetMax(p->next);

Status TraverseLinkStack(LinkStack S)//栈底至栈顶遍历链栈,按a1a2...an顺序输出,栈是后进先出,可递归解决 { if (S->next==NULL) { cout << S->data << " "; return OK; } TraverseLinkStack(S->next); cout << S->data << " ";//注意先递归后输出,写到递归函数前面就是栈顶至栈底输出了。后面图、二叉树的先中后序遍历、创建、结点个数 深度 叶子结点个数、快速及归并排序 都是类似递归求解 }
3. 台阶问题
假设有N个台阶,每次可以跨一个或者两个台阶,请问走完这个台阶有多少种走法?
举例说明:假如有3个台阶,那么总计就有3种走法:①每次上1个台阶,上3次;②先上2个台阶,再上1个台阶;③为先上1个台阶,再上2个台阶。
分类讨论:第一类是先走1个台阶后,n-1个台阶的走法;...1
第二类是先走2个台阶后,n-2个台阶的走法;...2
推 出:原问题就等于1+2两步之和,即f(n) = f(n-1) + f(n-2)。
f(1)=1,能否作为终止条件?
n = 2时, f(2) = f(1) +f(0), 如果终止条件只有一个 f(1) = 1, f(2) 就无法求解,因f(0) 的值无法确定。
故,f(2) = 2也应作为一个终止条件,即终止条件有两个:
f(1) = 1;
f(2) = 2;
n = 3, f(3) = f(2) + f(1) = 3
n = 4, f(4) = f(3) + f(2) = 5
f(n) = f(n-1) + f(n-2). 本问题本质实际为斐波那契数列,构建一个递归函数.
#include <iostream>
using namespace std;
int Step(int n)//建立调用函数
{
if (n == 1 || n == 2)//递归结束条件
return n;
return Step(n - 1) + Step(n - 2);//计算公式,step(n)=step(n-1)+step(n-2)
}
int main()
{
int i;
cin >> i;
int n = Step(i);
cout << n << endl;
return 0;
}
4. 汉诺塔递归求解
递归思想就是先移动最大盘(第n个盘)。要移动第n个盘,则其上n-1层盘需按规则移动到b柱上(也是Hanoi问题,只不过是n-1且起a终b)...1;然后,第n个盘从a移动到c(也为Hanoi问题,只不过是1层且起a终c)...2;若b柱上n-1层盘移动到c柱上(显然也是Hanoi问题,只不过n-1层且起b终c)...3,则显然问题求解,亦原n层Hanoi问题=1+2+3三步之和。
3.1递归表达式:Hanoi(n.a,b,c) //我们定义第二个形参为起始柱,第四个为目的柱,红灯停 表示 起,绿灯行 表示 终,因你定义,a才是起,c为终,a不是天生就是起,你也可定义c为起,但你这样定义,后面就要按你定义的规则走就行。
=Hanoi(n-1.a,c,b)+Hanoi(1.a,b,c)+Hanoi(n.b,a,c) //还得等价转化才行,子问题中理解形参的含义,参数列表中,第一个位置参数是规模,第二个是起始柱...,根据等价图示填进去即可,不用死记硬背,要灵活运用。

1 #include <iostream> 2 #include <stdio.h> 3 #include <stdlib.h> 4 using namespace std; 5 void Hanoi(int n,char A,char B,char C); 6 int main() 7 { 8 Hanoi(3,'a','b','c'); 9 return 0; 10 } 11 void Hanoi(int n,char A,char B,char C)//定义第二个参数为起,第四个参数为终,才有函数功能为将A柱移至C柱,实际上A B C三个形参是占位符,逻辑含义需要你指定 12 { 13 if(n==1) 14 { 15 cout<<A<<"--->"<<C<<endl; 16 return; 17 } 18 Hanoi(n-1,A,C,B); 19 Hanoi(1,A,B,C); //cout<<A<<"--->"<<C<<endl; 20 Hanoi(n-1,B,A,C); 21 }
5. 迷宫问题(0表示可走-白,1表示墙-黑)
从(0,0)到(row-1,cow-1)找一条路。递归从(0,0)求解,原问题等价于(不妨假设是向右)(0,0)->(0,1)+新坐标(row,col+1)--->(row-1,cow-1),两步之和。注意迷宫(矩阵)大小不变,起点前进了一步(可上可下可左可右(具体需判断是否可行),是或者的关系),=1步(上/下/左/右)+新点到终点(也是迷宫问题,只不过起点变了),=1+2。迷宫(矩阵)二维数组,起点坐标row,col, 终点坐标outrow, outcol,注意需要5个参数,不妨定义maze_puzzle(char maze[ROW][COL], int row, int col, int outrow, int outcol)这个函数是求解迷宫问题,其等价于若(右)走一步+maze_puzzle(char maze[ROW][COL], int row, int col+1, int outrow, int outcol)。
回溯算法解决此问题的具体思路是:
- 从当前位置开始,分别判断是否可以向 4 个方向(上、下、左、右)移动;
- 选择一个方向并移动到下个位置。判断此位置是否为终点,如果是就表示找到了一条移动路线;如果不是,在当前位置继续判断是否可以向 4 个方向移动;
- 如果 4 个方向都无法移动,则回退至之前的位置,继续判断其它的方向;
- 重复 2、3 步,最终要么成功找到可行的路线,要么回退至起点位置,表明所有的路线都已经判断完毕。
#include <stdio.h>
//typedef enum {false,true}bool; C++编译不过,因C++有bool关键字,不用定义了。C语言需要定义。
#define ROW 5
#define COL 5
//假设当前迷宫中没有起点到终点的路线
bool find = false;
//回溯算法查找可行路线
void maze_puzzle(char maze[ROW][COL], int row, int col, int outrow, int outcol);
//判断 (row,col) 区域是否可以移动
bool canMove(char maze[ROW][COL], int row, int col);
//输出行走路线
void printmaze(char maze[ROW][COL]);
int main()
{
char maze[ROW][COL] = {
{'1','0','1','1','1'},
{'1','1','1','0','1'},
{'1','0','0','1','1'},
{'1','0','0','1','0'},
{'1','0','0','1','1'} };
maze_puzzle(maze, 0, 0, ROW - 1, COL - 1);
if (find == false) {
printf("未找到可行线路");
}
return 0;
}
//(row,col) 表示起点,(outrow,outcol)表示终点
void maze_puzzle(char maze[ROW][COL], int row, int col, int outrow, int outcol) {
//如果行走至终点,表明有从起点到终点的路线
if (row == outrow && col == outcol) {
find = true;
printf("成功走出迷宫,路线图为:\n");
printmaze(maze);
return;
}
maze[row][col] = 'Y'; // 等价式1+2子中的 第1步(上/下/左/右),走了一步, 这步要标记走过,即将各个走过的区域标记为 Y(已走进该点,先置Y),不妨按照右下左上顺序。
//尝试向右移动
if (canMove(maze, row, col + 1)) {
maze_puzzle(maze, row, col + 1, outrow, outcol);
//如果程序不结束,表明此路不通,恢复该区域的标记,上面等价式中的1。是不是有点类似栈的顺序遍历,先递归后输出 :>
maze[row][col + 1] = '1';
}
//尝试向下移动
if (canMove(maze, row + 1, col)) {
maze_puzzle(maze, row + 1, col, outrow, outcol);
//如果程序不结束,表明此路不通,恢复该区域的标记
maze[row + 1][col] = '1';
}
//尝试向左移动
if (canMove(maze, row, col - 1)) {
maze_puzzle(maze, row, col - 1, outrow, outcol);
//如果程序不结束,表明此路不通,恢复该区域的标记
maze[row][col - 1] = '1';
}
//尝试向上移动
if (canMove(maze, row - 1, col)) {
maze_puzzle(maze, row - 1, col, outrow, outcol);
//如果程序不结束,表明此路不通,恢复该区域的标记
maze[row - 1][col] = '1';
}
}
//判断 (row,col) 区域是否可以移动
bool canMove(char maze[ROW][COL], int row, int col) {
//如果目标区域位于地图内,不是黑色区域,且尚未行走过,返回 true:反之,返回 false
return row >= 0 && row <= ROW - 1 && col >= 0 && col <= COL - 1 && maze[row][col] != '0' && maze[row][col] != 'Y';
}
//输出可行的路线,二维数组的输出
void printmaze(char maze[ROW][COL]) {
int i, j;
for (i = 0; i < ROW; i++) {
for (j = 0; j < COL; j++) {
printf("%c ", maze[i][j]);
}
printf("\n");
}
}//DEV C++可执行;由于迷宫矩阵出口固定为row-1,col-1,故递归函数中的形参int outrow, int outcol可不要。
//起点若定义结构体变量{x,y},则该递归函数只要两个形参即可(结构体变量,二维数组),只不过用时变成了S.x S.y.
//其他语言代码见http://c.biancheng.net/algorithm/maze-puzzle.html.
6. 猴子吃桃
猴子第一天摘下N个桃子,当时就吃了一半,还不过瘾,就又多吃了一个。第二天又将剩下的桃子吃掉一半,又多吃了一个。以后每天都吃前一天剩下的一半零一个。到第10天在想吃的时候就剩一个桃子了,问第一天共摘下来多少个桃子?
根据题目意思可得:
f(1)=N;(未知)
f(2)=f(1)/2-1 ------>f(1)=(f(2)+1)*2
f(3)=f(2)/2-1 ------>f(2)=(f(3)+1)*2
f(n)=f(n-1)/2-1------>f(n-1)=(f(n)+1)*2 n要+1去触碰终止条件
结束递归条件:n=10时,f(10)=1。
推出:原问题等价于第二天桃子数+1之后乘2 这一步;而后面每一天都是等于后一天+1之后乘2,n--->10止,故可递归求解。
#include <iostream> using namespace std; int getPeachNumber(int n){//计算第n天桃子的数量 if(n==10){ return 1; }else{ return (getPeachNumber(n+1)+1)*2;//递归等价式,注意规模是n+1,反而相对原问题是规模变小,朝着终止条件前进 } } int main() { cout<<getPeachNumber(1)<<endl; return 0; }
递归等价关系式(情形之一 向右走):(0.0)->(row-1,col-1)==(0,1)+(0,1)->(row-1,col-1)。n!=n*(n-1)!、数组最大值只有一个方向。一个走路问题,子问题也是走路,一个阶乘问题,子问题也是阶乘问题;一个求数组最大值,子问题也是求数组最大值。阶乘问题:原问题=1;台阶问题:原问题=1+2;汉诺塔:原问题=1+2+3 ;迷宫问题:原问题=1/2/3/4。分析出他们的异同,积累,以便举一反三。
递归算法所体现的“重复”一般有三个要求:
一是每次调用在规模上都有所缩小(通常是减半,-1,+1);
二是相邻两次重复之间有紧密的联系,前一次要为后一次做准备(通常前一次的输出就作为后一次的输入);
三是在问题的规模极小时必须用直接给出解答而不再进行递归调用(终止条件不唯一),因而每次递归调用都要向着终止条件前进(不管该条件是天(大)还是地(小)),否则死循环而不能正常结束。
四是递归是从后出发,规模变小,这个“后”指什么,不难发现所谓的“后”是对“前”的,“前”是终止条件,故而不能直观地理解成n的值大就是后(理解很重要,见猴子摘桃例)。

浙公网安备 33010602011771号