深度优先算法——蓝桥杯省赛算法

深度优先搜索——蓝桥杯省赛

主要原理

从起点到终点,每一条路径进行尝试,走不到则回头重新换路,直到走到想要到达的终点,如果所有路线都走不到,在会一直回退到起点。

主要工具

递归

例题讲解1

题目

如图 1所示,3*3 的格子中填写了一些整数。

我们沿着图中的红色线剪开,得到两个部分,每个部分的数字和都是 60

本题的要求就是请你编程判定:对给定的 m*n 的格子中的整数,是否可以分割为两个部分,使得这两个区域的数字和相等。

如果存在多种解答,请输出包含左上角格子的那个区域包含的格子的最小数目。

如果无法分割,则输出 0

输入格式

程序先读入两个整数 mn 用空格分割 (m,n<10) 表示表格的宽度和高度。

接下来是 n 行,每行 m 个正整数,用空格分开。每个整数不大于 10000

输出格式

程序输出:在所有解中,包含左上角的分割区可能包含的最小的格子数目。

样例

样例输入 #1

 3 3
 10 1 52
 20 30 1
 1 2 3

样例输出 #1

 3

样例 #2

样例输入 #2

 4 3
 1 1 1 1
 1 30 80 2
 1 1 1 100

样例输出 #2

 10

提示

第二个用例中:

时限 5 秒, 64M。蓝桥杯 2013 年第四届省赛

代码

 #include<bits/stdc++.h>
 using namespace std;
 ​
 //搜索算法 
 ​
 int n;
 int m;
  
 int ans;   //最终的格子数结果 
 int number[11][11];
 bool vis[11][11];   //默认为0 false 用于标志你这个位置是否走过 便于后退
 ​
 int sum;   //整个格子的总和  因为要对半分,只要从1,1开始搜查相加和为sum总和的一半 
 int direct[4][2] = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}};
 //分别对应右走一步, 左走一步, 上, 下
 ​
 ​
 void find(int x, int y, int tmpans, int tmpsum)  {
     // tmpans 每一次搜索的时候对于格子数的记录
     // tmpsum 每一次搜索的和 
     
     if(tmpsum == sum){       //如果某次的搜索和等于总和的一半 
         ans = tmpans;        //则最终的结果就是这次搜索的格子数 
         return;
     } 
     
     if(tmpsum > sum){    //如果搜索的结果大于一半了 表明走超了 退回到上一个 所以直接return
         return;              //返回到上一层 进入ans的判断  如果ans == 0  则把刚刚走入的格子恢复,                              //去走另一个格子 
     } 
     
     //否则就是还没有到总和的一半 所以用递归调用去不断搜索
     for(int i = 0; i < 4; i++){
         //前两步现在水平移动 如果continue了再进行上下移动 
         //(x,y)是目前的格子坐标位置
         int xx = x + direct[i][0];
         int yy = y + direct[i][1];
         if(xx < 1 || xx > n || yy < 1 || yy > m || vis[xx][yy]){  
           //去除超出边界值  去除已经来到过的格子 如果确实是这种非法情况则调换位置后再进入下一格子 
             continue;
         } 
         vis[xx][yy] = 1;      //表示来到过这个格子,已经加进来了 前1 在这条路径线中
          //递归入口
         find(xx, yy, tmpans + 1, tmpsum + number[xx][yy]);
         //递归操作  1是找到出口 2是找到递归的公式
         //这就是递归的出口 只要ans找到了有值了 一路返回到主函数中输出结果
         if(ans != 0){
             return;
         }
         vis[xx][yy] = 0;      //后0  去恢复状态 可能换一条路径还能抵达
     } 
 }
 ​
 int main(){
     //简单的读入数据
     cin >> n >> m;
     for(int i = 1; i <= n; i++){
         for(int j = 1; j <= m; j++){
             cin >> number[i][j];
             sum += number[i][j]; 
         }
     }
     
     if(sum % 2 != 0){      //当总和是奇数的时候直接表示无法分隔
         cout << 0;
         return 0;   
     }
     
     sum /= 2;
     
     find(1,1,1,number[1][1]);        //图的起点 从第一个位置开始进入函数调用的入口 
     
     cout << ans;
     return 0;
 }

例题讲解2

题目

小明冒充 X 星球的骑士,进入了一个奇怪的城堡。

城堡里边什么都没有,只有方形石头铺成的地面。

假设城堡地面是 n* n 个方格。如图所示。

按习俗,骑士要从西北角走到东南角。

可以横向或纵向移动,但不能斜着走,也不能跳跃。

每走到一个新方格,就要向正北方和正西方各射一箭。

(城堡的西墙和北墙内各有 n 个靶子)

同一个方格只允许经过一次。但不必做完所有的方格。

如果只给出靶子上箭的数目,你能推断出骑士的行走路线吗?

有时是可以的,比如如图中的例子。

本题的要求就是已知箭靶数字,求骑士的行走路径(测试数据保证路径唯一)

输入格式

第一行一个整数 N(0<N<20),表示地面有 N * N 个方格。

第二行 N 个整数,空格分开,表示北边的箭靶上的数字(自西向东)

第三行 N 个整数,空格分开,表示西边的箭靶上的数字(自北向南)

输出格式

一行若干个整数,表示骑士路径。

为了方便表示,我们约定每个小格子用一个数字代表,从西北角开始编号 :0,1,2,3 \cdots

比如,图中的方块编号为:

 0  1  2  3
 4  5  6  7
 8  9  10 11
 12 13 14 15

样例 #1

样例输入 #1

 4
 2 4 3 4
 4 3 3 3

样例输出 #1

 0 4 5 1 2 3 7 11 10 9 13 14 15

提示

时限 1 秒, 256M。蓝桥杯 2016 年第七届国赛

代码

#include<bits/stdc++.h>
using namespace std;
#include<vector>

 
const int N=25;
int top[N];
int L[N];
bool visit[N][N];
int direct[4][2]={{-1,0},{1,0},{0,-1},{0,1}};  //走路移动的方向 

vector<int> v;      //向量 
int n;
 
bool empty(){
 
    for(int i=0;i<n;i++){
        if(top[i]!=0){
            return false;
        }
    }
    for(int i=0;i<n;i++){
        if(L[i]!=0){
            return false;
        }
    }
    //当全部为0的时候表示已经走到了终点 
    return true;
}
bool judge(int r,int c){
	//越界的非法数据 
    if(r<0||c<0){
        return false;
    }
    if(r>=n||c>=n){
        return false;
    }
    //如果visit为真 则表明曾经来到过,已经在自己的路线中 不能重复再走 
    if(visit[r][c]){
        return false;
    }
    //   这条路在箭的数量限制下不能走了 
    if(L[r]<=0||top[c]<=0){
        return false;
    }
    // 经过所有考验 可以到达这个新位置 
    return true;
}

void dfs(int row,int col){       //row行 Column列 
    
	//来到这里就需要射箭 
    L[row]--;
    top[col]--; 
	            
    //因为来到这里 就需要给出标志 
    visit[row][col]=true;
    
	//把这个位置对应的序号记录到向量v中 
    v.push_back(row*n+col);   //直接在数组末端添加 
     
	//  首先士兵的位置需要到达终点的位置 
    if(row==n-1 && col==n-1){
    	//其次墙上的箭 数量全部清空 
        if( empty() ){
        	//最后输出真正的最终结果 
            for(int i=0;i<v.size();i++){     //v.size() 是向量v存储内容的长度 
                cout<<v[i]<<" ";
            }           
        }
        //递归的出口  如果沿着这条路走到了最后 但是箭的数量不符合证明这条路不符合
		//需要退后到上一条路 重新选择方向 
        return ;
    }
    
    //如果不能满足最终的结果 ,则需要进行位置移动,
	//一共就四个方向 所以嵌套在for循环框架中
	 
    for(int i=0;i<4;i++){
    	//位置的移动 
        int r=row+direct[i][0];
        int c=col+direct[i][1];
        
        if(judge(r,c)){   //judge判断能否到达新的位置
			//能的话就进行递归调用 
            dfs(r,c);
            
            //被return回来 就要清除刚刚走过的数据 
            L[r]++;
            top[c]++;
            visit[r][c]=false;
            v.pop_back();       //删掉数组的最后一个元素 
            
        }
    }    
}
int main(){
	//输入格子宽度 
    cin>>n;
    //上面北方的靶子箭数量 
    for(int i=0;i<n;i++){
        cin>>top[i];
    }      
    //左侧西方 
    for(int i=0;i<n;i++){
        cin>>L[i];
    }
    //递归函数的入口 
    dfs(0,0);    // 从(0,0)开始 
    
    return 0;
}

总结

深度优先算法的常规解题思路:

  1. 在主函数中有一个起点的递归函数入口。

  2. 进入递归函数之后,先设置出口,就是什么情况下你要退回到上一个节点,什么情况下你找到了最终的结果程序结束,输出结果。

  3. 然后在递归函数中当目前情况无法满足想要的结果,就要在for框架下进行位置移动,调用递归函数,知道满足上面的条件。

基本模板:

  1. 都必须具有标志和位置的声明

    bool visit[N][N];    //是否走到过某个方格
    int direct[4][2]={{-1,0},{1,0},{0,-1},{0,1}};  //走路移动的方向 

  2. 都必须具备递归函数的入口

     dfs(0,0);    // 从(0,0)开始 
    
     find(1,1,1,number[1][1]);        //图的起点 从第一个位置开始进入函数调用的入口 

  3. 都必须具有位置移动的框架

     for(int i=0;i<4;i++){
        	//位置的移动 
            int r=row+direct[i][0];
            int c=col+direct[i][1];
            ...
     }

递归函数的基本使用要求:

  1. 找到递归函数的出口

  2. 找到递归函数的公式

我是哈皮,祝您每天嗨皮,我们下期再见!

  • 2
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值