【六十】【算法分析与设计】用一道题目解决dfs深度优先遍历,dfs中节点信息,dfs递归函数模板进入前维护出去前回溯,唯一解的剪枝飞升返回值true

路径之谜

题目描述

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

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

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

1e8908abd7eb470083d000bb49a7bab3.png

按习俗,骑士要从西北角走到东南角。可以横向或纵向移动,但不能斜着音走,也不能跳跃。每走到一个新方格,就要向正北

方和正西方各射一箭。(城堡的西墙和北墙内各有12个靶子)同一个方格只允许经过一次。但不必走完所有的方格。如果只

给出靶子上箭的数目,你能推断出骑士的行走路线吗?有时是可以的,比比如上图中的例子。

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

输入描述

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

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

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

输出描述

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

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

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

0 1 2 3

4 5 6 7

8 9 10 11

12 13 14 15

输入输出样例

示例

输入

4

2 4 3 4

4 3 3 3

输出

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

运行限制

最大运行时间:5s

最大运行内存:256M

12c651f1c7234b9988867f9e74ddcf2e.png

暴力递归

1.

定义dfs(i,j)表示当前节点坐标。

假设入口位置坐标是(1,1),往下是row行,往右是col列。

定义row记录从入口到当前节点这条路径西边靶子的数量,row[1]表示西边第一个靶子上箭的数量,row[2]表示西边第二个靶子上箭的数量...以此类推。

定义col记录从入口到当前节点这条路径北边靶子的数量,col[1]表示北边第一个靶子上箭的数量,col[2]表示北边第二个靶子上箭的数量...以此类推。

定义path记录从入口到当前节点的路径信息。

定义visit记录从入口到当前节点,这条路径,当前所有位置访问情况,访问过为true,没有被访问过false。

2.

也就是当前节点的信息不止有(i,j)还有path,row,col,visit四个变量共同组成。

3.

dfs内部逻辑,进入当前节点的时候,维护当前节点的所有信息。

离开当前节点返回之前,回溯,消除当前节点维护的所有信息。

4.

夹在中间的就是计算逻辑,写代码的时候先把首位维护和回溯写掉,然后在中间加主要逻辑。

 
void dfs(int i, int j) {visit[i][j] = true;row[i]++;col[j]++;path.push_back((i - 1) * n + (j - 1));//数学推导不细说,找规律//添加主要逻辑path.pop_back();row[i]--;col[j]--;visit[i][j] = false;
}

 

5.

06e70e01efad4a72a6044d401aaf9bae.png

对于当前节点,下一个遍历的节点位置有四个,上下左右,但是有一些位置需要剪枝。

规则是,第一,这四个位置首先不能越界,第二,这四个位置不能被访问过,被访问过表示已经是路径上的点,已经走过了。如果满足要求就可以dfs进入下一节点。

 
void dfs(int i, int j) {visit[i][j] = true;row[i]++;col[j]++;path.push_back((i - 1) * n + (j - 1));for (int k = 0; k < 4; k++) {int x = i + dx[k], y = j + dy[k];if (x >= 1 && x <= n && y >= 1 && y <= n && !visit[x][y]) {dfs(x, y);}}path.pop_back();row[i]--;col[j]--;visit[i][j] = false;
}

 

6.

思考递归出口,递归出口是当你走到出口的时候,即i==n&&j==n。

此时遍历row和col,看看靶子上箭的数量是不是和aim_row,aim_col目标值对上。

如果对上了此时path里面存放的就是我们要的路径,打印出来即可。

如果没有对上就返回,不需要再进入下一节点了。

返回前注意需要回溯,消除维护操作。

 
void dfs(int i, int j) {visit[i][j] = true;row[i]++;col[j]++;path.push_back((i - 1) * n + (j - 1));if (i == n && j == n) {int flag = 1;for (int i = 1; i <= n; i++) {if (row[i] != aim_row[i] || col[i] != aim_col[i]) flag = 0;}if (flag == 1) {for (auto& x : path) cout << x << " ";}path.pop_back();row[i]--;col[j]--;visit[i][j] = false;return;}for (int k = 0; k < 4; k++) {int x = i + dx[k], y = j + dy[k];if (x >= 1 && x <= n && y >= 1 && y <= n && !visit[x][y]) {dfs(x, y);}}path.pop_back();row[i]--;col[j]--;visit[i][j] = false;
}

 

代码1

7.

此时得到的代码如下,

 
#include <iostream>
#include<bits/stdc++.h>
using namespace std;int n; // 定义n表示城堡地面是n×n个方格
vector<int> path; // 用于记录骑士的行走路径
vector<int> row; // 用于记录每行射出的箭的数量
vector<int> col; // 用于记录每列射出的箭的数量
vector<vector<int>> visit; // 记录某个格子是否被访问过
int dx[4] = { 1,-1,0,0 }; // 方向数组,用于实现上下左右移动
int dy[4] = { 0,0,1,-1 };
vector<int> aim_col; // 存储每列应该射出的箭的目标数量
vector<int> aim_row; // 存储每行应该射出的箭的目标数量// 深度优先搜索(DFS)函数,用于尝试所有可能的路径
void dfs(int i, int j) {visit[i][j] = true; // 标记当前格子为已访问row[i]++; // 当前行的箭数增加col[j]++; // 当前列的箭数增加path.push_back((i - 1) * n + (j - 1)); // 将当前格子编号加入路径// 如果到达东南角,并且每行每列的箭数都符合目标if (i == n && j == n) {int flag = 1;for (int i = 1; i <= n; i++) {if (row[i] != aim_row[i] || col[i] != aim_col[i]) flag = 0;}if (flag == 1) {for (auto& x : path) cout << x << " "; // 如果路径有效,输出这条路径}path.pop_back();row[i]--;col[j]--;visit[i][j] = false;return;}// 尝试向四个方向移动for (int k = 0; k < 4; k++) {int x = i + dx[k], y = j + dy[k];if (x >= 1 && x <= n && y >= 1 && y <= n && !visit[x][y]) {dfs(x, y);}}// 回溯,撤销当前步骤的影响path.pop_back();row[i]--;col[j]--;visit[i][j] = false;
}int main() {cin >> n; // 读入n的大小aim_col.resize(n + 1);for (int i = 1; i <= n; i++) cin >> aim_col[i]; // 读入每列的目标箭数aim_row.resize(n + 1);for (int i = 1; i <= n; i++) cin >> aim_row[i]; // 读入每行的目标箭数row.resize(n + 1);col.resize(n + 1);visit = vector<vector<int>>(n + 1, vector<int>(n + 1));dfs(1, 1); // 从西北角开始进行深度优先搜索return 0;
}

运行结果如下,

09945d723f644fc8a58e3ff7d6749755.png

剪枝1:唯一解找到即返回true(飞升)

8.

大部分运行超时,说明代码整体逻辑没有问题,但是剪枝操作没有做好。

重新思考剪枝操作。

题目中测试数据保证了路径的唯一性,说明我们只要找到了最终答案,就不需要回溯,后面的操作都不需要,找到最终答案就一路飞升回到第一层递归返回。

修改dfs的返回值,不使用void返回值,而使用int或者bool,意思是如果当前找到了就返回true,没有找到就返回false。

修改完返回值还需要修改进入下一层递归的代码,不能直接是dfs,而是if(dfs(x, y)) return true;

如果返回值是true说明找到了,如果找到了什么都不用管,直接返回true。

每一层节点都接收这个信息,有没有完成工作,有没有找到路径?找到了就可以不用再工作了,直接返回。

还需要修改递归出口的逻辑,flag==1说明找到了,打印完路径后直接返回true。

 
int dfs(int i, int j) {visit[i][j] = true;row[i]++;col[j]++;path.push_back((i - 1) * n + (j - 1));if (i == n && j == n) {int flag = 1;for (int i = 1; i <= n; i++) {if (row[i] != aim_row[i] || col[i] != aim_col[i]) flag = 0;}if (flag == 1) {for (auto& x : path) cout << x << " ";return true;}path.pop_back();row[i]--;col[j]--;visit[i][j] = false;return;}for (int k = 0; k < 4; k++) {int x = i + dx[k], y = j + dy[k];if (x >= 1 && x <= n && y >= 1 && y <= n && !visit[x][y]) {if(dfs(x, y)) return true;}}path.pop_back();row[i]--;col[j]--;visit[i][j] = false;return false;
}

 

代码2

9.

 
#include <iostream>
#include<bits/stdc++.h>
using namespace std;int n; // 城堡地面为n×n个方格的大小
vector<int> path; // 存储骑士的行走路径
vector<int> row; // 每行射箭的次数
vector<int> col; // 每列射箭的次数
vector<vector<int>> visit; // 标记方格是否访问过
int dx[4] = { 1,-1,0,0 }; // x方向的移动:下,上,不动,不动
int dy[4] = { 0,0,1,-1 }; // y方向的移动:不动,不动,右,左
vector<int> aim_col; // 目标,每列应射箭的次数
vector<int> aim_row; // 目标,每行应射箭的次数// 深度优先搜索(DFS)算法,i和j表示当前位置
int dfs(int i, int j) {visit[i][j] = true; // 标记当前方格为已访问row[i]++; // 当前行的射箭次数增加col[j]++; // 当前列的射箭次数增加path.push_back((i - 1) * n + (j - 1)); // 将当前方格的编号加入到路径中// 检查是否到达右下角,并且所有行和列的射箭次数都符合目标if (i == n && j == n) {int flag = 1; // 用于检查是否所有行和列的射箭次数都匹配for (int i = 1; i <= n; i++) {if (row[i] != aim_row[i] || col[i] != aim_col[i]) flag = 0;}if (flag == 1) {for (auto& x : path) cout << x << " "; // 如果匹配,输出路径cout << endl; // 输出换行return true; // 返回找到有效路径}}// 尝试四个可能的移动方向for (int k = 0; k < 4; k++) {int x = i + dx[k], y = j + dy[k];if (x >= 1 && x <= n && y >= 1 && y <= n && !visit[x][y]) {if (dfs(x, y)) return true; // 递归调用dfs}}// 回溯,撤销当前步骤path.pop_back();row[i]--;col[j]--;visit[i][j] = false;return false; // 没有找到有效路径
}int main() {cin >> n;aim_col.resize(n + 1);for (int i = 1; i <= n; i++) cin >> aim_col[i]; // 输入每列的目标射箭次数aim_row.resize(n + 1);for (int i = 1; i <= n; i++) cin >> aim_row[i]; // 输入每行的目标射箭次数row.resize(n + 1);col.resize(n + 1);visit = vector<vector<int>>(n + 1, vector<int>(n + 1, false)); // 初始化访问标记数组dfs(1, 1); // 从(1,1)开始深度优先搜索return 0;
}

运行结果如下,

a94fedcc6cc74cd2bcb5c4451d3fd699.png

剪枝2:靶子箭数量小于目标靶子箭数量

10.

说明这个剪枝操作微不足道,没什么用。

继续思考其他剪枝操作,我们发现如果到了一个节点,如果row,col中有一个靶子的箭数量大于目标靶子的箭数量,后面的路径都不可能是最终答案。

因为靶子上箭的数量不可能减少只能增加,所以到出口之前,如果是正确路径,靶子数量一定小于目标靶子数量。

所以如果靶子箭数量大于目标靶子箭数量,直接返回。

 
int dfs(int i, int j) {visit[i][j] = true;row[i]++;col[j]++;path.push_back((i - 1) * n + (j - 1));int flag1 = 1;for (int i = 1; i <= n; i++) {if (row[i] > aim_row[i] || col[i] > aim_col[i]) {flag1 = 0;break;}}if (flag1 == 0) {path.pop_back();row[i]--;col[j]--;visit[i][j] = false;return false;}if (i == n && j == n) {int flag = 1;for (int i = 1; i <= n; i++) {if (row[i] != aim_row[i] || col[i] != aim_col[i]) {flag = 0;break;}}if (flag == 1) {for (auto& x : path) cout << x << " ";return true;}else{path.pop_back();row[i]--;col[j]--;visit[i][j] = false;return false;}}for (int k = 0; k < 4; k++) {int x = i + dx[k], y = j + dy[k];if (x >= 1 && x <= n && y >= 1 && y <= n && !visit[x][y]) {if (dfs(x, y)) return true;}}path.pop_back();row[i]--;col[j]--;visit[i][j] = false;return false;
}

 

代码3

 
#include <iostream>
#include<bits/stdc++.h>  // 引入常用库,包含STL等
using namespace std;// 定义全局变量
int n;  // 地图大小
vector<int> path;  // 记录路径
vector<int> row;  // 记录每行走过的次数
vector<int> col;  // 记录每列走过的次数
vector<vector<int>> visit;  // 访问标记数组
int dx[4] = { 1,-1,0,0 };  // 方向数组,表示行的移动
int dy[4] = { 0,0,1,-1 };  // 方向数组,表示列的移动
vector<int> aim_col;  // 目标列的箭数
vector<int> aim_row;  // 目标行的箭数// 深度优先搜索函数
int dfs(int i, int j) {visit[i][j] = true;  // 标记当前单元格已访问row[i]++;  // 增加当前行的计数col[j]++;  // 增加当前列的计数path.push_back((i - 1) * n + (j - 1));  // 记录路径int flag1 = 1;// 检查所有行列是否满足条件for (int i = 1; i <= n; i++) {if (row[i] > aim_row[i] || col[i] > aim_col[i]) {flag1 = 0;break;}}if (flag1 == 0) {  // 如果条件不满足,则回退操作path.pop_back();row[i]--;col[j]--;visit[i][j] = false;return false;}// 检查是否到达最后一个方格if (i == n && j == n) {int flag = 1;for (int i = 1; i <= n; i++) {if (row[i] != aim_row[i] || col[i] != aim_col[i]) {flag = 0;break;}}if (flag == 1) {  // 如果满足最终条件,输出路径for (auto& x : path) cout << x << " ";return true;} else {  // 否则,进行回退操作path.pop_back();row[i]--;col[j]--;visit[i][j] = false;return false;}}// 遍历四个方向for (int k = 0; k < 4; k++) {int x = i + dx[k], y = j + dy[k];if (x >= 1 && x <= n && y >= 1 && y <= n && !visit[x][y]) {if (dfs(x, y)) return true;}}// 回退操作path.pop_back();row[i]--;col[j]--;visit[i][j] = false;return false;
}int main() {cin >> n;aim_col.resize(n + 1);for (int i = 1; i <= n; i++) cin >> aim_col[i];  // 输入北边靶子箭数aim_row.resize(n + 1);for (int i = 1; i <= n; i++) cin >> aim_row[i];  // 输入西边靶子箭数row.resize(n + 1);col.resize(n + 1);visit = vector<vector<int>>(n + 1, vector<int>(n + 1));  // 初始化访问矩阵dfs(1, 1);  // 从(1,1)开始搜索return 0;
}

72a4030d74204f56b86d822e926b7774.png

总结结论

1.

递归函数dfs,用同样的函数完成相同的逻辑问题,但是需要用其他遍历辅助判断当前位于那个节点。

vector<int> path;

vector<int> row;

vector<int> col;

vector<vector<int>> visit;

(i,j)

以上都是当前节点的信息。

2.

每一次进入dfs维护当前节点信息,每一次出去之前回溯,消除维护的信息。

 
void dfs(int i, int j) {visit[i][j] = true;row[i]++;col[j]++;path.push_back((i - 1) * n + (j - 1));//数学推导不细说,找规律//添加主要逻辑path.pop_back();row[i]--;col[j]--;visit[i][j] = false;
}

 

3.

如果只需要找唯一解,找到即返回,找到就不用工作,找到就飞升。

修改返回值,进入下一层逻辑,出口逻辑。

7401f170df974efcb1dc440636a9fd40.png

4.

剪枝操作提前返回,注意返回之前一定要回溯,也就是消除维护当前节点信息的操作!!!

结尾

最后,感谢您阅读我的文章,希望这些内容能够对您有所启发和帮助。如果您有任何问题或想要分享您的观点,请随时在评论区留言。

 

同时,不要忘记订阅我的博客以获取更多有趣的内容。在未来的文章中,我将继续探讨这个话题的不同方面,为您呈现更多深度和见解。

 

谢谢您的支持,期待与您在下一篇文章中再次相遇!

 

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/2490.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ESP32开发

目录 1、简介 1.1 种类 1.2 特点 1.3 管脚功能 1.4 接线方式 1.5 工作模式 2、基础AT指令介绍 2.1 AT指令类型 2.2 基础指令及其描述 2.3 使用AT指令需要注意的事 3、AT指令分类和提示信息 3.1 选择是否保存到Flash的区别 3.2 提示信息 3.3 其他会保存到Flash的A…

基础SQL DQL语句

基础查询 select * from 表名; 查询所有字段 create table emp(id int comment 编号,workno varchar(10) comment 工号,name varchar(10) comment 姓名,gender char(1) comment 性别,age tinyint unsigned comment 年龄,idcard char(18) comment 身份证号,worka…

排序算法:顺序查找

简介 顺序查找&#xff08;也称为线性查找&#xff09;是一种简单直观的搜索算法。按照顺序逐个比较列表或数组中的元素&#xff0c;直到找到目标元素或搜索完整个列表。 应用场景 数据集比较小&#xff0c;无需使用复杂的算法。数据集没有排序&#xff0c;不能使用二分查找…

书生·浦语大模型实战营(第二期):OpenCompass司南大模型评测实战

目录 大语言模型评测中的挑战如何评测大模型模型客观题&主观题提示词工程长文本评测OpenCompass评测流水线CompassHub&#xff1a;高质量评测基准社区 OpenCompass介绍作业&#xff1a;使用OpenCompass评测internlm2-chat-1_8b模型在C-Eval数据集上的性能准备阶段环境配置数…

html--canvas粒子球

<!doctype html> <html> <head> <meta charset"utf-8"> <title>canvas粒子球</title><link type"text/css" href"css/style.css" rel"stylesheet" /></head> <body><script…

element plus:tree拖动节点交换位置和改变层级

图层list里有各种组件&#xff0c;用element plus的tree来渲染&#xff0c;可以把图片等组件到面板里&#xff0c;面板是容器&#xff0c;非容器组件&#xff0c;比如图片、文本等&#xff0c;就不能让其他组件拖进来。 主要在于allow-drop属性的回调函数编写&#xff0c;要理清…

ElasticSearch笔记一

随着这个业务的发展&#xff0c;我们的数据量越来越庞大。那么传统的这种mysql的数据库就渐渐的难以满足我们复杂的业务需求了。 所以在微服务架构下一般都会用到一种分布式搜索的技术。那么今天呢我们就会带着大家去学习分布搜索当中最流行的一种ElasticSearch&#xff0c;Ela…

基于harris角点和RANSAC算法的图像拼接matlab仿真

目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 5.算法完整程序工程 1.算法运行效果图预览 2.算法运行软件版本 MATLAB2022a 3.部分核心程序 ....................................................................... I1_harris fu…

【洛谷 P8605】[蓝桥杯 2013 国 AC] 网络寻路 题解(图论+无向图+组合数学)

[蓝桥杯 2013 国 AC] 网络寻路 题目描述 X X X 国的一个网络使用若干条线路连接若干个节点。节点间的通信是双向的。某重要数据包&#xff0c;为了安全起见&#xff0c;必须恰好被转发两次到达目的地。该包可能在任意一个节点产生&#xff0c;我们需要知道该网络中一共有多少种…

python基础知识三(运算符、while循环、for循环)

目录 运算符&#xff1a; 算术运算符&#xff1a; 比较运算符&#xff1a; 赋值运算符&#xff1a; 逻辑运算符&#xff1a; 位运算符&#xff1a; 成员运算符&#xff1a; while循环&#xff1a; 1. while循环的语法&#xff1a; 2. while循环的执行过程&#xff1a…

232 基于matlab的MIMO雷达模型下一种子空间谱估计方法

基于matlab的MIMO雷达模型下一种子空间谱估计方法&#xff0c;采用过估计的方法&#xff0c;避免了信源数估计的问题&#xff0c;对数据协方差矩阵进行变换&#xff0c;构造信号子空间投影矩阵和噪声子空间投影矩阵&#xff0c;不需要像经典的MUSIC一样对其进行特征分解&#x…

【笔试强训】数字统计|两个数组的交集|点击消除

一、数字统计 链接&#xff1a;[NOIP2010]数字统计_牛客题霸_牛客网 (nowcoder.com) 思路&#xff1a; 枚举数字拆分&#xff08;模10 除10&#xff09; &#x1f4a1; 当前数据范围为10^4可以用int类型解决&#xff0c;如果到了10^9就需要用long类型 代码实现&#xff1a; i…

实验七 智能手机互联网程序设计(微信程序方向)实验报告

请编写一个用户登录界面&#xff0c;提示输入账号和密码进行登录&#xff0c;要求在输入后登陆框显示为绿色&#xff1b; 二、实验步骤与结果&#xff08;给出对应的代码或运行结果截图&#xff09; index.wxml <view class"content"> <view class"a…

绝地求生:16款战术手套,你最钟爱哪一款?

大家好&#xff0c;我是闲游盒&#xff01; 喜迎PUBG七周年生日同时游戏里又迎来了一款新的战术手套&#xff0c;那么就让我们来回顾一下目前出游戏中的16款战术手套吧&#xff0c;看看你最中意的是哪一款&#xff1f; 1、MAZARIN1K 战术手套 2、SPAJKK 战术手套 3、SWAGGER 战…

C++笔记:C++中的重载

重载的概念 一.函数重载 代码演示例子&#xff1a; #include<iostream> using namespace std;//函数名相同&#xff0c;在是每个函数的参数不相同 void output(int x) {printf("output int : %d\n", x);return ; }void output(long long x) {printf("outp…

php 编译安装oracel扩展

第一步安装Oracle客户端 1&#xff0c;需要下载基础包和sdk oracle客户端下载链接&#xff1a;Oracle Instant Client Downloads for Linux x86-64 (64-bit) https://www.oracle.com/database/technologies/instant-client/linux-x86-64-downloads.html 选择最新版本 versi…

11 JavaScript学习:事件

Html事件 HTML 中有很多事件可以用来与用户交互&#xff0c;以下是一些常见的 HTML 事件及其详细解释和举例&#xff1a; click 事件&#xff1a;当用户点击元素时触发。 <button onclick"myFunction()">点击我</button>dblclick 事件&#xff1a;当用…

在Jupyter notebook中添加虚拟环境

通常我们打开Jupyter notebook&#xff0c;创建一个新文件&#xff0c;只有一个Python3&#xff0c;但是我们也会想使用自己创建的虚拟环境&#xff0c;很简单仅需几部即可将自己的conda环境添加到jupyter notebook中。 1. 创建并激活conda环境&#xff08;已有可跳过&#xf…

数据结构基础:链表操作入门

数据结构基础&#xff1a;链表操作入门 数据结构基础&#xff1a;链表操作入门链表的基本概念链表的基本操作输出链表插入节点删除节点查找值 完整的链表操作示例结语 数据结构基础&#xff1a;链表操作入门 在计算机科学中&#xff0c;数据结构是组织和存储数据的方式&#x…

哪些因素影响了PCB电路板切割精度?

PCB电路板切割是电子制造过程中一个至关重要的环节&#xff0c;其精度对后续工序的质量和效率具有决定性影响。因此&#xff0c;了解影响PCB电路板切割精度的原因&#xff0c;对于提高电子产品的质量和生产效率具有重要意义。 1. PCB分板机稳定性 PCB分板机的性能直接影响到切…