递归几例,思想重要,举一反三!

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(nf(n-1f(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)

回溯算法解决此问题的具体思路是:

  1. 从当前位置开始,分别判断是否可以向 4 个方向(上、下、左、右)移动;
  2. 选择一个方向并移动到下个位置。判断此位置是否为终点,如果是就表示找到了一条移动路线;如果不是,在当前位置继续判断是否可以向 4 个方向移动;
  3. 如果 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的值大就是后理解很重要,见猴子摘桃例)。

posted @ 2022-10-19 19:04  师大无雨  阅读(2236)  评论(0)    收藏  举报