算法基础——递归

简介

  • 一个函数调用其自身,就是递归
  • 递归和普通函数调用一样——是通过栈实现的

作用:

  1. 替代多重循环(N皇后)
  2. 解决本来就是用递归形式定义的问题(逆波兰表达式、表达式计算)
  3. 将问题分解为规模更小的子问题进行求解(上台阶,汉诺塔,放苹果,算24,印章)

例题——求阶乘

int Factorial(int n){
    if(n==0){
        return 1;
    }
    return n*Factorial(n-1);
}

例题——汉诺塔问题

void Hanoi(int n,char a,char b, char c){
    if(n==1){
        cout<<a<<"->"<<c<<endl;
    }else{
        Hanoi(n-1,a,c,b);
        cout<<a<<"->"<<c<<endl;
        Hanoi(n-1,b,a,c);
    }
}

例题——N皇后问题

  • 展示用递归替代多重循环

输入N,找到N个皇后可放置的列数;

#include<iostream>
#include<cmath>
using namespace std;
int N,queenPos[100];
//N个皇后,queenPos用来放算好的皇后位置
void NQueen(int k){//假设0~k-1行皇后都放置好的前提下,摆放第K行及其往后的皇后
    int i;
    if(k==N){//N个皇后都摆好了,递归出口
        for(i=0;i<N;i++){
            cout<<queenPos[i]+1<<" ";
        }
        cout<<endl;
        return;
    }
    for(i=0;i<N;i++){//尝试第K个皇后的位置
        int j;//i是第K个皇后的可选择位置,j是前面第j行皇后,queenpos[j]是第j行皇后的列数;
        for(j=0;j<k;j++){//检查和已经摆好的k个皇后的位置比较,看是否冲突
            if(queenPos[j]==i||abs(queenPos[j]-i)==abs(k-j)){
                break;//冲突,测试下一个位置->abs(queenPos[j]-i)==abs(k-j):
                //符合斜着杀到的条件:行的绝对差值和列的绝对差值相等
            }
        }//检测是否冲突的循环
        if(j==k){//说明当前第i列不冲突,第k行皇后可以放置在第i列
            queenPos[k]=i;
            NQueen(k+1);
        }
    }
}
int main(){
    cin>>N;
    NQueen(0);//从第0行皇后开始放
    return 0;
}

例题——逆波兰表达式

  • 用递归解决递归形式的问题

逆波兰表达式是把运算符前置的算术表达式;eg:* + 2 3 4

意思是:有一个*运算,表达式是 + 2 3 和 4

​ (+ 2 3)* 4

​ (+ 2 3 )意思就是(2+3)

本题求解逆波兰表达式的值,其中运算符包括±*/

  • 一个数是一个逆波兰表达式,值为该数——在此处可以理解为递归出口
  • 运算符 逆波兰表达式 逆波兰表达式
  • 一个exp()就会读入一个字符,读到运算符就递归读入数值
  • atof(s)是将字符型的数值,变成double类型
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cmath>
using namespace std;
double exp(){
    char s[20];
    cin>>s;
    switch(s[0]){
        case '+': return exp()+exp();
        case '-': return exp()-exp();
        case '*': return exp()*exp();
        case '/': return exp()/exp();
        default : return atof(s);
        break;
    }
}
int main(){
    printf("%lf\n",exp());
    return 0;    
}

例题——表达式计算

输入为四则运算表达式,仅由**数字、+、-、‘×’、/,()**组成,没有空格,要求求其值

解题思想

  • 表达式由项组成:项+项;项-项
  • 项由因子组成:因子*因子;因子/因子
  • 因子有两种可能:
    • ( 表达式 )
    • 整数

理解思想:(2+3)*5+9/3

  • 这个最外层的表达式有两个项:
    • (2+3)*5
    • 9/3
  • 第一个项里面有两个因子:
    • (2+3)
      • 这个因子是表达式形式:
      • 又有两个项:2和3
    • 5
  • 第二个项有两个因子 9和3
  • 你中引用我,我中引用你;(我中有你,你中有我)
#include<iostream>
#include<cstring>
#include<cstdlib>
using namespace std;
int factor_value();//读入一个因子,并且返回其值
int term_value();//读入一个项,并且返回其值
int expression_value();//读入一个表达式,并返回其值
int main(){
    cout<<expression_value()<<endl;
    return 0;
}
int expression_value(){
    //求一个表达式
    int result=term_value();//求第一项的值
    bool more=true;
    while(more){//判断后面还有没有项
        char op=cin.peek();//看一个字符,不取走
        if(op=='+'||op=='-'){
            cin.get();//从输入中取走一个字符
            int value=term_value();
            if(op=='+') result+=value;
            else result-=value;
        }else more=false;
    }
    return result;
}
int term_value(){//求一个项的值
    int result=factor_value();//求第一个因子的值
    while(true){
        char op=cin.peek();
        if(op=='*'||op=='/'){
            cin.get();
            int value=factor_value();
            if(op=='*') result*=value;
            else result/=value;
        }else break;
    }
    return result;
}
int factor_value(){
    int result=0;
    char op=cin.peek();
    if(op=='('){
        cin.get();
        result=expression_value();
        cin.get();//去除左右括号
    }else{
        while(isdigit(op)){//判断是不是数字
            result=10*result+op-'0';
            cin.get();
            op=cin.peek();
        }
    }
    return result;
}

例题——上台阶

  • 用递归将问题分解为规模更小的子问题进行求解
  • n级台阶的走法=先走一级后,n-1级台阶的走法+先走二级后,n-2级台阶的走法
    • f(n)=f(n-1)+f(n-2);
  • 递归出口:
    • n<0 0
    • n=0 1
    • n=1 1
    • n=2 2
  • 一定要存在递归出口,防止无穷递归,栈爆开结束
int f(int n){
    if(n==1){
        return 1;
    }else if(n==2){
        return 2;
    }else{
        return f(n-1)+f(n-2);
    }
}

例题——放苹果

  • 把M个同样的苹果放在N个同样的盘子里,允许有的盘子空着不放;511和151是相同方法

/*
设i个苹果放在k个盘子里放法总数是f(i,k),则:
k>i时,f(i,k)=f(i,i);
k<=i时,总放法=有盘子为空的方法+没盘子为空的方法
k<=i时,f(i,k)=f(i,k-1)+f(i-k,k);f(i-k,k)时先把k个苹果,每个盘子放一个先,再计算i-k个苹果放在k个盘子的放法
*/

#include<iostream>
using namespace std;
int f(int m,int n){
    if(n>m) return f(m,m);
    if(m==0) return 1;
    if(n==0) return 0;
    return f(m,n-1)+f(m-n,n); 
}

递归化DP

#include<iostream>
#include<cstring>
using namespace std;
int main(){
    int m,n,num[100][100],time;
    cin>>time;
    while(time--){
        memset(num,0,sizeof(num));
        num[0][0]=1;
        cin>>m>>n;
        for(int i=0;i<=m;i++){
            for(int j=1;j<=n;j++){
                if(i<j) num[i][j]=num[i][i];
                else num[i][j]=num[i][j-1]+num[i-j][j];
            }
        }
        cout<<num[m][n]<<endl;
    }
}

例题——算24

  • 给4个小于10的正整数,使用加减乘除四种运算以及括号把这四个数连接起来得到一个表达式,使表达式的值等于24
  • 能算出24则YES,反之NO

思路

  • n个数算24,必有两个数要先算。
  • 这两个数算的结果,和剩余n-2个数,就构成了n-1个数求24的问题

注意:浮点数比较是否相等,不能用==

#include<iostream>
#include<cmath>
using namespace std;
double a[5];//存放输入的四个数
#define EPS 1e-6
bool IsZero(double x) {return fabs(x)<=EPS;}//用于浮点数判断是否相等
bool count24(double a[],int n){
    if(n==1){
        if(IsZero(a[0]-24)) return true;
        else return false;
    }//递归出口
    double b[5];//存放中间结果
    for(int i=0;i<n-1;i++){
        for(int j=i+1;j<n;j++){//枚举两个数的组合
            int m=0;//还剩m个数,m=n-2;
            for(int k=0;k<n;k++){
                if(k!=i&&k!=j) b[m++]=a[k];
            }//把挑选后剩余的数字放入b数组里
            b[m]=a[i]+a[j];
            if(count24(b,m+1)) return true;
            b[m]=a[i]-a[j];
            if(count24(b,m+1)) return true;
            b[m]=a[j]-a[i];
            if(count24(b,m+1)) return true;
            b[m]=a[i]*a[j];
            if(count24(b,m+1)) return true;
            if(!IsZero(a[j])){
                b[m]=a[i]/a[j];
                if(count24(b,m+1)) return true;
            }
            if(!IsZero(a[i])){
                b[m]=a[j]/a[i];
                if(count24(b,m+1)) return true;
            }
        }
    }
    return false;
}
int main(){
    int time;
    cin>>time;
    while(time--){
    	for(int i=0;i<4;i++){
        	cin>>a[i];
    	}
    	if(count24(a,4)){
        	cout<<"YES"<<endl;
    	}else{
        	cout<<"NO"<<endl;
    	}
    }
}

蓝桥——印章问题

#include<iostream>
#include<cmath>
using namespace std;
int m,n;
double p;
double res(int i,int j){
    if(i<j) return 0;
    if(j==1) return pow(p,i-1);
    else return res(i-1,j)*j*p+res(i-1,j-1)*(n-j+1)*p;
}
int main(){
    cin>>n>>m;
    p=1.0/n;
    printf("%.4f",res(m,n));
}

递归化DP

#include<iostream>
#include<cmath>
using namespace std;
int main(){
    double num[30][30],p;
    int m,n;
    cin>>n>>m;
    p=1.0/n;
    num[1][1]=1;
    for(int i=1;i<=m;i++){
        for(int j=1;j<=n;j++){
            if(i<j) num[i][j]=0;
            if(j==1) num[i][j]=pow(p,i-1);
            else num[i][j]=num[i-1][j]*j*p+num[i-1][j-1]*(n-(j-1))*p;
        }
    }
    printf("%.4f",num[m][n]);
}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Caaaaaan

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值