算法笔记 - 递归

第九章 递归

9.1 斐波那契数列 见动态规划专题|| 汉诺塔问题

9.2 波兰式求值

这里写图片描述
dfs() 表示读取一个数字或者运算符并进行相应的atof转换或者递归运算
逆波兰式不需要递归只需要维护一个栈,读到数字就存入(全局)栈中,读到运算符就将栈顶两个元素进行运算,结果存入栈顶

#include<cstdio>
#include<math.h>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<string>
#include<map>
#include<set>
#include<vector>
#define MAX 10
#define  num 32767
#define INF 0x7f7f7f
#define eps 1e-5
using namespace std;
double dfs()
{
    string s;//读入一个数字或者运算符
    cin>>s;
    if(s[0]=='+')//对运算符进行递归
        return dfs()+dfs();
    else if(s[0]=='-')
        return dfs()-dfs();
    else if(s[0]=='*')
        return dfs()*dfs();
    else if(s[0]=='/')
        return dfs()/dfs();
    else//对数字进行转换
        return atof(s.data());
}
int main()
{
   //freopen("input.txt","r",stdin);
    printf("%f\n",dfs());
   return 0;
}

小技巧: string类型转换成char *

//string ->char * 需要调用函数
const char *c = s.data();//或者 char *c = (char *)s.data();
const char *c = s.c_str();//或者 char *c = (char *)s.c_str();
//char * -> string 直接复赋值
string s = c;

9.3 递归解决排列组合问题

9.3.1 从起点开始递增的排列组合(可重复或者不可重复,且与顺序无关)
可重复 与顺序无关

注意
1 枚举所有起点(main中的for)
2 枚举所有后继(dfs中的for)
3 dfs(出口参数 , 条件参数1 , 条件参数2…)
出口
剪枝
递增枚举

2749 分解因数(乘法分解)
这里写图片描述
dfs(int 当前积, int 上一个因子) 表示当积为dangqianji 且上一个因子是shangyigeyinzi的情况下能否完成分解

#include<cstdio>
#include<math.h>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<string>
#include<map>
#define MAX 53
#define  num 32767
#define INF 0x7f7f7f
#define eps 1e-5
using namespace std;
int n,a,ans;
void dfs(int k,int pre)//限定条件:递增 由参数pre实现(k用来判递归出口)
{
    if(k==a)
    {
        ans++;
        return ;
    }
    if(k>a) return ;
    for( int i=pre;i<=a;i++)//如果是不重复递增则i=pre+1
    {
        if(k*i<=a)
            dfs(k*i,i);
        else break;
    }
}
int main()
{
   //freopen("input.txt","r",stdin);
    cin>>n;
    for(int i=0;i<n;i++)
    {
        ans=0;
        cin>>a;
        for(int i=2;i<=a;i++) dfs(i,i);//枚举所有起点
        cout<<ans<<endl;
    }
   return 0;
}

1664 放苹果(加法分解) 可以看做是和确定的递增排列,且限定了加的次数(上一题因子分解没限定乘的次数,这里递归函数的参数要多加一个)
这里写图片描述
dfs(int 当前第几个盘 , int 总苹果 ,int 上一个盘放的苹果数) 表示该情况继续分解

#include<cstdio>
#include<math.h>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<string>
#include<map>
#include<set>
#include<vector>
#define MAX 10
#define  num 32767
#define INF 0x7f7f7f
#define eps 1e-5
using namespace std;
int m,n,k,t;
void dfs(int dangqianpan , int zongpingguo,int 上一个盘放的苹果数 )//这里多加了一个限定条件(加的次数也就是盘子总数)
{
        if(zongpingguo==m&&dangqianpan<n)
        {
            k++;
            return ;
        }
        if(zongpingguo >m||dangqianpan>n) return;

        for(int i=上一个盘放的苹果数;i<=m;i++)
            dfs(dangqianpan+1,zongpingguo+i,i);
}
int main()
{
   //freopen("input.txt","r",stdin);
   cin>>t;
   for(int i=0;i<t;i++)
   {
       cin>>m>>n;
       k=0;
       for(int i=1;i<=m;i++)//遍历可能的起点
           dfs(0,i,i);
        cout<<k<<endl;
   }
   return 0;
}

2803 碎纸机 这里切割的位次相当于一个递增序列
这里写图片描述
这里写图片描述
dfs(int 当前总和,int 上一次切的位数)

//定义 vector<int> temp,ans 存临时解和最优解
//输入 int 目标数 ; char 纸片数[MAX];
void dfs(int temp_sum,int index)
{
    //出口
    if(index>=len)//切出最后一位
    {
        //更新ans
        if(fabs(temp_sum-目标数)<最优解) //复制temp到ans; 方案数=0 ; 更新最优解;
        if(==) 方案数++;
        return ;
    }
    //剪枝
    if( temp_sum > 目标数 ) return;
    //递增枚举下一个切口位置
    for(int i=index+1;i<=len;i++)
    {
        //转换从纸片[index]到纸片[i]的数得到 t
        //存储t到temp中
        dfs(temp_sum+t,i);
    }
}
//主函数
//特判1 每一位加起来和仍大与目标数
//特判2 纸片数整体小于目标数
#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
int n,m;
int shu[23333];
int tmp[23333];

bool flag1;
void clr()
{
    memset(shu,0,sizeof(shu));
    flag1=0;
}
int ans1[23333];
int ans;
int lenth;
void dfs(int tot,int num,int cnt)//当前和,上一次切的第几位,temp中保存了几个数(可以省略)
{
    //剪枝
    if(tot>n)
        return;
    //出口
    if(num>m)
    {
        if(tot==ans)
        {
            flag1=1;
            return;
        }
        if(tot>ans)
        {
            flag1=0;
            ans=tot;
            //更新ans
            for(int i=1;i<cnt;i++)
                ans1[i]=tmp[i];
            lenth=cnt-1;
        }
        return;
    }
    //递增枚举
    int hah=0;
    for(int i=num;i<=m;i++)
    {
        hah*=10;
        hah+=shu[i];
        tmp[cnt]=hah;
        dfs(tot+hah,i+1,cnt+1);
    }
}
int main()
{
    while(1)
    {
        clr();
        string a;
        scanf("%d",&n);
        ans=-1;
        cin>>a;
        int hah=0;
        m=a.size();
        if(n==0)
        break; 
        for(int i=0;i<m;i++)
        {
            shu[i+1]=a[i]-'0';
            hah*=10;
            hah+=shu[i+1];
        }
        //特判
        if(hah==n)
        {
            printf("%d %d\n",n,n);
            continue; 
        }
        dfs(0,1,1);
        if(flag1)
        {
            puts("rejected");
            continue; 
        }
        if(ans==-1)
            puts("error");
        else
        {
            printf("%d ",ans);
            for(int i=1;i<=lenth;i++)
                printf("%d ",ans1[i]);
            puts(""); 
        }
    }
    return 0;
}

9.3.2 全排列问题(把1~n按照某种顺序不重复地摆放)
不重复 与顺序有关

注意
1 dfs(递归出口参数) 一般为全排列的总位数n

求n个数的全排列个数 || 求n个数的第k个全排列 可以看作不在同一行同一列的皇后
dfs(int 当前第几行) 表示在当前行放该行的数

//定义 vector<int> ans,temp(临时排列和最终排列);  bool visit[MAX];(某个数是否已经加入本次排列)
 void dfs(int index)
 {
     if(index==n+1)//注意是n+1,因为dfs(1)从1开始
     {
        //计数 或者 按照规则检查temp更新ans(如八皇后)
         return;
     }

     for(int i=1;i<=n;i++)//9.3.1 中因为是递增排列所以不需要一个标记数组visit
     {//这里在全排列中需要从头开始找没用过的数字
         if(!visit[i])
         {
             temp.push_back(i);
             visit[i]=1;
             dfs(index+1);
             visit[i]=0;//全排列问题需要还原状态
         }
     }
 }

2754 八皇后问题
这里写图片描述
dfs(int 当前第几行) 表示 在当前行放该行的数

#include <iostream>
#include<cstdio>
#include<vector>
#include<string>
#include<map>
#include <math.h>
#include<algorithm>
#include <string.h>
#define eps 1e-10
#define ll long long
#define da    0x3f3f3f3f
#define xiao -0x3f3f3f3f

using namespace std;
int b;
vector<int > ans,temp;
bool visit[9]={false};
int num;
void dfs(int index)
{
    if(cnt==8)//从0开始所以是n
    {
        num++;//代表找到了第num个全排列
        if(num==b)
            ans=temp;
        return ;
    }
    if(index>8) return ;

    for(int i=0;i<8;i++)
    {
        if(!visit[i])
        {
            //遍历之前的皇后(八皇后问题的高校写法是将检查从出口移动到for中)
            //所谓回溯也就是将出口处的判断移动到后面的程序中
            bool flag=1;
            //八皇后只需要检查之前的皇后是否在k=+-1斜线上,因为不在同一行和同一列是必然的
            //index为当前行号,外层for是当前列号,内层for中是之前的行号,temp[]为之前的列号
            for(int j=0;j<index;j++)
                if(abs(index-j)==abs(i-temp[j])) flag=0;//abs保证k=+-1
            if(flag)
            {
                visit[i]=1;
                temp.push_back(i);
                dfs(cnt+1);
                temp.pop_back();
                visit[i]=0;
            }
        }
    }
}
 int main()
{
    //freopen("input.txt","r",stdin);
   int n;
   cin>>n;
   for(int i=0;i<n;i++)
   {
       ans.clear();
       temp.clear();
       memset(visit,0,sizeof(visit));
       num=0;
       cin>>b;
       dfs(0);
       for(int i=0;i<8;i++)
           cout<<ans[i]+1;
       cout<<endl;
   }
    return 0;
}

1321 棋盘问题 全排列计数(相当于在八皇后问题中去掉斜线条件,增加了一个可摆放位置条件和一个总棋子个数条件(皇后中棋子个数一定等于行数,这里可能小于行数因而递归出口))
这里写图片描述
dfs(int 当前行 , int 当前第几个数)表示在当前行放第几个数 与皇后相比多了一个条件参数,因为皇后问题每一行必定放一个数,所以行数等于棋子个数,也就不需要额外判断棋子数,这里棋盘若在当前行没找到符合条件的位置,仍然需要递归到下一行,所以需要额外参数标识

DFS(index+1,dijigeshu+1);//该行(第index行)找到了能放的位置并放入,到下一行
DFS(index+1,dijigeshu); //该行没有符合条件的位置,直接到下一行

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
char a[10][10];     //记录棋盘位置
bool book[10];        //记录一列是否已经放过棋子
int n,k;
int total,m;    //total 是放棋子的方案数 ,m是已放入棋盘的棋子数目

void DFS(int index,int dijigeshu)
{
    if(dijigeshu==k)
    {
        total++;
        return ;
    }
    if(index>k)    //边界
        return ;
    for(int j=0; j<n; j++)
    //判断条件,与八皇后相比这里多了判断是否为棋盘区的条件
        if(book[j]==0 && a[index][j]=='#')
        {
            book[j]=1;
            DFS(index+1,dijigeshu+1);//该行(第index行)找到了能放的位置并放入,到下一行
            book[j]=0;
        }
    DFS(index+1,dijigeshu);  //与八皇后的区别,该行没有符合条件的位置,直接到下一
}

int main()
{
    int i,j;
//freopen("input.txt","r",stdin);
    while(scanf("%d%d",&n,&k)&&n!=-1&&k!=-1) //限制条件
    {
        total=0;
        m=0;
        for(i=0; i<n; i++)
            scanf("%s",&a[i]);
        memset(book,0,sizeof(book));
        DFS(0,0);
        printf("%d\n",total);
    }
    return 0;
}

9.4 草丛问题

dfs
1 访问
2 发散

2816 红与黑 草丛内方块个数
这里写图片描述
dfs(int x ,int y) 表示访问点(x,y)(该点进入函数前未访问)并向可能的方向发散

#include<cstdio>
#include<math.h>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<string>
#include<map>
#include<set>
#include<vector>
#define MAX 21
#define  num 32767
#define INF 0x7f7f7f
#define eps 1e-5
using namespace std;
int w,h,qidianx,qidiany;
int X[]={-1,1,0,0};
int Y[]={0,0,-1,1};
char mp[MAX][MAX];
bool visit[MAX][MAX];
bool check(int x ,int y)
{
    if(x<0||x>=h||y<0||y>=w) return 0;
    if( visit[x][y]||mp[x][y]=='#') return 0;
    return 1;
}
void dfs(int x,int y)
{
    //check(省略)
    //访问
    visit[x][y]=1;
    //四个方向遍历
    for(int i=0;i<4;i++)
    {
        int newx = x+X[i];
        int newy = y+Y[i];
        if(check(newx,newy))  dfs(newx,newy);
    }
    return ;
}

int main()
{
   //freopen("input.txt","r",stdin);
   while(cin>>w>>h&&w!=0&&h!=0)
   {
       memset(visit,0,sizeof(visit));
       for(int i=0;i<h;i++)
       {
           for(int j=0;j<w;j++)
           {
               cin>>mp[i][j];
               if(mp[i][j]=='@')
               {
                   qidianx=i;
                   qidiany=j;
               }
           }
       }
       dfs(qidianx,qidiany);
       int ans =0;
       //只有一个草丛,所有访问过的都是组成草丛的块
       for(int i=0;i<h;i++)
           for(int j=0;j<w;j++)
               if(visit[i][j]) ans++;
       cout<<ans<<endl;
   return 0;
}

2815 城堡问题 草丛内方块个数+草丛丛数
这里写图片描述
dfs(int x,int y)表示访问点(x , y)

注意
1 main中的for记录草丛丛数
2 dfs中的循环记录草丛方块数

#include<cstdio>
#include<math.h>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<string>
#include<map>
#define MAX 53
#define  num 32767
#define INF 0x7f7f7f
#define eps 1e-5
using namespace std;
int dx,nb;
int room=0,fk,maxfk;
int D[MAX][MAX];
bool visit[MAX][MAX]={0};
//这里四个方向能否走通需要额外判断
void dfs(int i, int j)
{
    //检查
    if(visit[i][j]) return;
    if(i<=0||j<=0||i>nb||j>dx) return ;
    //访问
    visit[i][j]=1;//必有
    fk++;
    //向四个方向延申
    if((D[i][j]&1)==0) dfs(i,j-1);//刚开始没给&运算加括号,==优先级高于按位与
    if((D[i][j]&2)==0) dfs(i-1,j);
    if((D[i][j]&4)==0) dfs(i,j+1);
    if((D[i][j]&8)==0) dfs(i+1,j);
}

int main()
{
   //freopen("input.txt","r",stdin);
    cin>>nb>>dx;
    for(int i=1;i<=nb;i++)
        for(int j=1;j<=dx;j++)
            cin>>D[i][j];
    memset(visit,sizeof(visit),0);
    for(int i=1;i<=nb;i++)
    {
        for(int j=1;j<=dx;j++)
        {
            if(!visit[i][j])
            {
                room++;
                fk=0;
                dfs(i,j);
                maxfk=max(maxfk,fk);
            }
        }
    }
    cout<<room<<endl<<maxfk<<endl;
   return 0;
}

9.5 迷宫问题

dfs
终点
剪枝
发散

9.5.1 能否走出迷宫(可达问题)

2790 迷宫
这里写图片描述
dfs(int x,int y ) 表示从(x,y)出发(该点进入函数前已访问)访问可能的发散方向

#include <iostream>
#include<cstdio>
#include<vector>
#include<string>
#include<map>
#include <math.h>
#include<algorithm>
#include <string.h>
#define MAX 105
using namespace std;
int k,n,ha, la, hb, lb,label=1;
char D[MAX][MAX];
bool visit[MAX][MAX];
int X[]={-1,0,0,1};
int Y[]={0,-1,1,0};

bool check(int x,int y)
{
    if(visit[x][y]||D[x][y]=='#') return 0;
    if(x<0||y<0||x>=n||y>=n) return 0;
    return 1;
}

void dfs(int x, int y)
{
    //终点
    if(x==hb&&y==lb)
    {
        label = 0;
        return ;
    }
    //剪枝
    if(x<0||y<0||x>=n||y>=n) return ;
    //发散
    for(int i=0;i<4;i++)
    {
        int newx = x+X[i];
        int newy = y+Y[i];
        if(check(newx,newy))
        {
            visit[newx][newy]=1;
            dfs(newx,newy);
            //visit[newx][newy]=0;
             //问是否可达,dfs回溯时不需要记录数组归零。
            //如果是求最短路径则需要归零。   回溯时是否归零看具体情况而定
        }
    }
}

int main()
{
    //freopen("input.txt","r",stdin);
    cin>>k;
    for(int i=0;i<k;i++)
    {
        cin>>n;
        memset(visit,0,sizeof(visit));
        for(int m=0;m<n;m++)
            for(int mm=0;mm<n;mm++)
                cin>>D[m][mm];
        cin>>ha>>la>>hb>>lb;
        if(D[ha][la]=='#'||D[hb][lb]=='#')
        {
            cout<<"NO"<<endl;
            continue;
        }
        label=1;
        visit[ha][la]=1;
        dfs(ha,la);
        if(!label) cout<<"YES"<<endl;
        else cout<<"NO"<<endl;
    }
    return 0;
}

9.5.2 走出迷宫的最短路径长度问题
3752 走迷宫
这里写图片描述
dfs(int x, int y , int step) 表示从(x,y)出发(该点进入函数前已访问)访问可能的发散方向且已走了step步

//输入char map[MAX][MAX]; 定义 bool visit[MAX][MAX];
void dfs(int x,int y,int step)
{
    //终点检查
    if(x==zhongdianX && y==zhongdianY)
    {
        //更新最短步数
        ans = min(ans,step);
        return ;
    }
    //剪枝
    if(!check(x,y)) return ;
    //发散访问
    for(int i=0;i<4;i++)
    {
        int newx= x+X[i];
        int newy= y+Y[i];
        if(check(newx,newy))
        {
            visit[newx][newy]=1;
            dfs(newx,newy,step+1);
            visit[newx][newy]=0;
        }
    }
}

9.5.3 走出迷宫的最短路径问题
4127 迷宫问题
这里写图片描述

//输入char map[MAX][MAX]; 定义 bool visit[MAX][MAX]; 
//定义 stack<node> temp,ans;   额外增加一个队列或者栈以保存路径(类似全排列保存遍历中得到的某个排列组合,在出口处更新最佳组合)
void dfs(int x,int y,int step)
{
    //终点检查
    if(x==zhongdianX && y==zhongdianY)
    {
        //更新最短步数或者最短路径序列
        min_step = min(ans,step);
        ans = temp;//保存路径
        return ;
    }
    //剪枝
    if(min_step<step) return ;
    //发散访问
    for(int i=0;i<4;i++)
    {
        int newx= x+X[i];
        int newy= y+Y[i];
        if(check(newx,newy))
        {
            visit[newx][newy]=1;
            temp.push(节点);//保存路径
            dfs(newx,newy,step+1);
            temp.pop();//回复路径
            visit[newx][newy]=0;
        }
    }
}
#include <iostream>
#include <cstring>
#include <cstdio>
#include <map>
#include <cmath>
#include <vector>
#include <stack>
#include<queue>
#include <algorithm>
#define MAX 0x3f7f7f7f
using namespace std;
int m[5][5];
bool visit[5][5]={false};

struct node
{
    int x;int y;
}s,e,temp;
long int minstep;
stack<node > q,ans;
int X[]{1,-1,0,0};
int Y[]{0,0,1,-1};

bool check(int x,int y)
{
    if(x<0||x>4||y<0||y>4) return 0;
    if(visit[x][y]==1||(visit[x][y]==0&&m[x][y]==1)) return 0;
    return 1;
}
void dfs(int x,int y,int step)
{
    if(x==e.x&&y==e.y)
    {
        if(step<minstep)
        {
            minstep=step;
            ans=q;
        }
        return;
    }
    if(step>minstep) return ;
    for(int i=0;i<4;i++)
    {
        int newx=x+X[i];
        int newy=y+Y[i];
        if(check(newx,newy))
        {
            temp.x=newx;
            temp.y=newy;
            visit[newx][newy]=1;
            q.push(temp);
            dfs(newx,newy,step+1);
            visit[newx][newy]=0;
            q.pop();
        }
    }
}

int main()
{
   //freopen("input.txt","r",stdin);
    memset(m,0,sizeof(m));
    memset(visit,0,sizeof(visit));
    s.x=4;s.y=4;
    e.x=0;e.y=0;
    minstep=MAX;
    for(int i=0;i<5;i++)
        for(int j=0;j<5;j++)
            cin>>m[i][j];
    visit[s.x][s.y]=1;
    q.push(s);
    dfs(s.x,s.y,0);
    while(!ans.empty())
    {
        cout<<"("<<ans.top().x<<", "<<ans.top().y<<")"<<endl;
        ans.pop();
    }
    return 0;
}

9.6 总的操作步数固定的枚举
2787 算24 重要
这里写图片描述
dfs(int 第几步操作) 表示当前仅进行第几步操作

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<vector>
#include<queue>
#include<map>
#include<set>
#include<ctime>
using namespace std;
typedef long long ll;
#define INF 0x3f3f3f3f
#define maxn 5
#define eps 1e-8
double a[maxn];
int flag,vis[maxn];//visit表示已被合并
int sign(double x)
{
    if(fabs(x)<eps)return 0;
    if(x>eps)return 1;
    return -1;
}
void dfs(int num)//num表示当前正在进行第几步操作
{
    if(flag)return ;
    if(num==4)  //输入的四个数经过三次运算就能得到一个数
    {
        //找到最后被保存的那个数
        for(int i=1;i<=4;i++)
            if(!vis[i]&&sign(a[i]-24.0)==0)flag=1;
        return ;
    }
    //选两个数a[i],a[j]进行操作,结果放到a[i]中
    for(int i=1;i<=4;i++)
        if(!vis[i])
            for(int j=i+1;j<=4;j++)
                if(!vis[j])
                {
                   //标记
                    vis[j]=1;
                    double x=a[i],y=a[j];

                    //六个方向dfs
                    if(sign(y))a[i]=x/y,dfs(num+1);
                    if(sign(x))a[i]=y/x,dfs(num+1);
                    a[i]=x-y,dfs(num+1);
                    a[i]=y-x,dfs(num+1);
                    a[i]=x+y,dfs(num+1);
                    a[i]=x*y,dfs(num+1);

                    //撤销标记
                    a[i]=x;
                    vis[j]=0;
                }
}
int main()
{
    while(1)
    {
        for(int i=1;i<=4;i++)scanf("%lf",&a[i]);
        if(sign(a[1]+a[2]+a[3]+a[4])==0)break;
        flag=0;
        memset(vis,0,sizeof(vis));
        dfs(1);
        printf("%s\n",flag?"YES":"NO");
    }
    return 0;
}
  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值