前面的篇幅占了太多,再次新开一章,讲述BFS求最短路的问题

注意此时DFS就没有BFS好用了,因为DFS更适合求全部解,而BFS适合求最优解

这边再次提醒拓扑变换的思想在图形辨认中的重要作用,需要找寻不同图形在进行拓扑变换时候的不变性

假设有一个网格迷宫,由n行m列的单元格组成,每个单元格要么是空地,要么是障碍物,如何找到从起点到终点的最短路径?

回想二叉树的BFS,节点的访问顺序恰好是他们到根节点距离从小到大的顺序,类似的可以用BFS来按照到起点的距离顺序遍历迷宫图(因为BFS保证了后面遍历到的点离起点的距离是不严格单调递增的)

易知道如果将前一步指向后一步这一种关系当作树的祖孙层次关系,那么就可以创建出来一棵树,很明显这棵树的一大特点在于如果路权相等的情况下,局部最优会达到全局最优,即贪心的利用,使得除根节点之外,其他节点一定有且只有一个父亲节点,这棵树被称为最短路树,或者BFS树

Abbott's_Revenge题解

点击查看代码
#include<iostream>
#include<cstring>
#include<queue>
#include<vector>
using namespace std; const int maxl = 11; const int maxword = 20+5;
int toward[4][2] = {-1,0,0,1,1,0,0,-1}, dic[maxl][maxl][4][3]; struct Point{
int x, y;
Point(int x = 0, int y = 0) : x(x), y(y) {}
}; struct Node{
int x, y, from;//0 1 2 3 nesw 012 go l f r
vector<Point> v[4];
}node[maxl][maxl]; int main() {
// freopen("test.in", "r", stdin);
// freopen("test.out", "w", stdout);
char name[maxword];
while(scanf("%s", name) == 1 && strcmp(name, "END")) {
int sx, sy, ex, ey;
char sdic;
cin >> sx >> sy >> sdic >> ex >> ey;
memset(node, 0, sizeof(node));
memset(dic, 0, sizeof(dic));
int tx, ty;
while(scanf("%d", &tx) == 1 && tx) {
scanf("%d", &ty);
node[tx][ty].x = tx;
node[tx][ty].y = ty;
char buf[10];
while(scanf("%s", buf)==1 && strcmp(buf, "*")) {
int d;
switch(buf[0]) {
case 'N': d=0; break;
case 'E': d=1; break;
case 'S': d=2; break;
case 'W': d=3; break;
default: break;
}
for(int i = 1; i < strlen(buf); i++) {
switch(buf[i]) {
case 'L': dic[tx][ty][d][0] = 1; break;
case 'F': dic[tx][ty][d][1] = 1; break;
case 'R': dic[tx][ty][d][2] = 1; break;
default : break;
}
}
}
}
int d;
switch(sdic) {
case 'N': d=0; break;
case 'E': d=1; break;
case 'S': d=2; break;
case 'W': d=3; break;
default: break;
}
dic[sx][sy][d][1] = 0;
int sx1 = sx+toward[d][0], sy1 = sy+toward[d][1];
node[sx1][sy1].x = sx1;
node[sx1][sy1].y = sy1;
node[sx1][sy1].v[d].push_back(Point(sx, sy));
node[sx1][sy1].v[d].push_back(Point(sx1, sy1));
node[sx1][sy1].from = d;
queue<Node> q;
q.push(node[sx1][sy1]);
vector<Point> ans;
while(!q.empty()) {
Node n = q.front();
q.pop();
if(n.x == ex && n.y == ey) {
ans = n.v[n.from];
break;
}
int temp = n.from;
for(int i = 0; i < 3; i++) {
if(dic[n.x][n.y][temp][i]) {
int ndis = (temp+4+i-1)%4, sum = 10;
int px = n.x+toward[ndis][0], py = n.y+toward[ndis][1];
// for(int j = 0; j < 3; j++) {
// sum += dic[px][py][ndis][j];
// }
if(sum) {
Node insert;
insert.x = px;
insert.y = py;
insert.from = ndis;
insert.v[ndis] = n.v[n.from];
insert.v[ndis].push_back(Point(px, py));
q.push(insert);
}
dic[n.x][n.y][temp][i] = 0;
}
}
}
cout << name << endl;
if(ans.empty()) cout << " No Solution Possible" << endl;
else{
for(int i = 0; i < ans.size(); i++) {
if(i%10 == 0) cout << " ";
cout << "(" << ans[i].x << "," << ans[i].y << ")";
if(i%10 == 9) cout << endl;
else if(i != ans.size()-1) cout << " ";
}
if(ans.size()%10) cout << endl;
}
}
return 0;
}

笔者终于自行编出这段代码了,不过还是借用了udebug的调试,自己的思想还是不够严谨,不过笔者并没有看过他人代码参考,这题除了模拟比较高人心态之外,其思想是比较简单的,即不重不漏走完全图,有bfs路的出现就有答案,否则没有答案,需要注意的是对于各个结点的存储方式以及如何判断朝向,这两大问题解决了,本题就会较为简单了,本题的难点是对多数据的存储以及处理

需要注意的是在这题貌似PE的显示是WA,笔者只是修改了输出格式就AC了,只能说UVAOJyyds,这边读者有兴趣的话一定要注意这个坑

这道题是作者强烈要求搞懂细节,并且能够独立编写程序的题目,因此笔者会尽量将作者原先的代码写满注释,方便后来的读者能够更加方便的理解本道题目

这边小插入一下关于unique的使用,注意一定要先排序再使用,否则会出现很多意想不到的错误,同时这部分的内容感觉和迭代器有很大的关系,什么时候作者补充了这方面的知识,再在后面填坑吧

点击查看代码
/*
本题和普通的迷宫在本质上是一样的,但是由于“朝向”也起到了关键作用,所以需要用一个三元组(注意这边的三元组的利用,这是对于线性代数
的一个高级理解,直接跳过结构体,直接用简单的数组来模拟,不过本题确实可以直接通过数组来模拟,使用结构体,只是为了形式上的统一罢了)
(r,c,dir)表示位于(r,c)面朝dir这个状态,这边是笔者认为最妙的地方,这种存储方阵数据的思想值得学习。假设入口位置为(r0,c0),朝向为dir
则初始状态并不是(r0,c0,dir),而是(r1,c1,dir),其实这边的dir表示的是上一步是从那个方向来的(描述不够严谨),所以从起点开始,会使得
这边的递归思想不够完备,递归思想的一大要求就是需要里面的对象在一定程度上是等价的,也就是各方面的状态属性应该是近似一致的
此处用d[r][c][dir]表示初始状态到(r,c,dir)的最短路长度,并且用p[r][c][dir]保存了状态(r,c,dir)的BFS树中的父节点,这样就可以不用特定
建立一个vector数组来实现该功能了,这边的结构成立的条件之一就是r,c,dir的父节点最多只有一个
*/ const char* dirs = "NESW"; //顺时针旋转 //注意这边的深意,一般来讲确定一个顺序顺时针逆时针可以根据个人喜好,因为这样子才能通过一种类似圆排列的方式来进行抽象的统一
const char* turns = "FLR"; //一样的目的,笔者这边就不再赘述了
int dir_id(char c) { return strchr(dirs, c) - dirs; } //这个函数的作用是返回c所在的第几个位置,范围是[0, strlen(dirs))
int turn_id(char c) { return strchr(turns, c) - turns; }//与上面的函数作用是一样的
//接下来是行走函数,根据当前状态和转弯方式,计算出后续状态
const int dr[] = {-1, 0, 1, 0};//这边作者用两个一维数组实现笔者二维数组的效果,分别是NESW
const int dc[] = {0, 1, 0, -1}; Node walk(const Node& u, int turn) {//这边就是通过012控制,0不改变方向,1l本质上就是逆时针-1,2r本质上就是顺时针+1
int dir = u.dir;
if(turn == 1) dir = (dir + 3) % 4; //逆时针
if(turn == 2) dir = (dir + 1) % 4; //顺时针
return Node(u.r + dr[dir], u.c + dc[dir], dir); //返回刷新过的新节点(nr,nc,ndir)
}
//输入函数比较简单,作用就是读取r0,c0,dir,并且计算出r1,c1,然后读入has_edge数组,其中has_edge[r][c][dir][turn]表示当前状态是(r,c,dir),是否可以沿着转弯方向turn行走,接下来就是最常见的BFS的主过程了
void solve() {
queue<Node> q;
memset(d, -1, sizeof(q));
Node u(r1, c1, dir);//注意是r1不是r0
d[u.r][u.c][u.dir] = 0;//这边开始计数
//笔者认为这边有一个bug,这边忘了记录r1,c1的父节点了,不知道是不是作者打漏了
q.push(u);
while(!q.empty()) {//BFS开始
Node u = q.front(); q.pop();//q.pop()不要忘掉,不然就会成为一个死循环,或者爆栈了
if(u.r == r2 && u.c == c2) { print_ans(u); return; }//如果到了最终的结点进行输出
for(int i = 0; i < 3; i++) {
Node v = walk(u, i);//这边会先进行行走,笔者认为这边先判断能否走可能效率更高?
if(has_edge[u.r][u.c][u.dir][i] && inside(v.r, v.c) && d[v.r][v.c][v.dir] < 0) {//第一个条件是是否可以往这边走,第二个条件是判断是否有这个点?作者没有给出任何信息,qaq,第三个条件判断的是该方向并没有来过,也就是变相的保证了每个结点最多有一个父节点
d[v.r][v.c][v.dir] = d[u.r][u.c][u.dir] + 1;//如果前面的条件都符合,那么开始建立新节点,同时计数
p[v.r][v.c][v.dir] = u;//记录父节点,BFS树
q.push(v);//老BFS了
}
}
}
printf("No Solution Possible\n");//如果没有找到才会执行该段代码
} //最后是解的打印过程,它也可以写成递归函数,不过用vector保存节点可以避免递归时出现栈溢出,并且更加灵活,注意如果递归的话,注意先序遍历的写法,应该是先递归后输出
//使用BFS求出图的最短路之后,可以用递归方式打印最短路的具体路径。如果最短路非常长,递归可能会引起栈溢出,此时可以改用循环,用vector保存路径 void print_ans(Node u) {
//从目标结点逆序追朔到初始节点
vector<Node> nodes;
for(;;) {
nodes.push_back(u);
if(d[u.r][u.c][u.dir] == 0) break;//开始追溯上一个父节点的位置,最后输出的时候需要逆序输出,同时注意这边的边界条件就是0,这个在最初的设置中很容易想到
u = p[u.r][u.c][u.dir];//我愿称之为伪递归
}
nodes.push_back(Node(r0, c0, dir));//好吧,笔者前面的论断是错误的,作者还是作者,将这边最初的结点给加入进去了 //打印解,每行10个
int cnt = 0;
for(int i = nodes.size()-1; i >= 0; i--) {//最后就是经典环节打印输出环节了,如果对这个还不熟悉的读者,可以去前几张找几套simulate题练练手
if(cnt % 10 == 0) print(" ");//保证开头是" "
printf(" (%d,%d)", nodes[i].r, nodes[i].c);//记住是逆序输出,就可以了
if(++cnt % 10 == 0) printf("\n");//注意这边对于\n的输出的控制
}
if(nodes.size() % 10 != 0) printf("\n");
}

以上就是笔者对于作者代码的解读与思考,只能说作者赛高,思路非常清晰

6.4.2 用BFS求最短路的更多相关文章

  1. 图-用DFS求连通块- UVa 1103和用BFS求最短路-UVa816。

    这道题目甚长, 代码也是甚长, 但是思路却不是太难.然而有好多代码实现的细节, 确是十分的巧妙. 对代码阅读能力, 代码理解能力, 代码实现能力, 代码实现技巧, DFS方法都大有裨益, 敬请有兴趣者 ...

  2. POJ 2251 Dungeon Master --- 三维BFS(用BFS求最短路)

    POJ 2251 题目大意: 给出一三维空间的地牢,要求求出由字符'S'到字符'E'的最短路径,移动方向可以是上,下,左,右,前,后,六个方向,每移动一次就耗费一分钟,要求输出最快的走出时间.不同L层 ...

  3. UVa 816 (BFS求最短路)

    /*816 - Abbott's Revenge ---代码完全参考刘汝佳算法入门经典 ---strchr() 用来查找某字符在字符串中首次出现的位置,其原型为:char * strchr (cons ...

  4. BFS求最短路

    假设有一个n行m列的迷宫,每个单位要么是空地(用1表示)要么是障碍物(用0表示).如和找到从起点到终点的最短路径?利用BFS搜索,逐步计算出每个节点到起点的最短距离,以及最短路径每个节点的前一个节点. ...

  5. UVA 816 -- Abbott's Revenge(BFS求最短路)

     UVA 816 -- Abbott's Revenge(BFS求最短路) 有一个 9 * 9 的交叉点的迷宫. 输入起点, 离开起点时的朝向和终点, 求最短路(多解时任意一个输出即可).进入一个交叉 ...

  6. 利用BFS求最短路

    利用BFS求图的最短路, POJ3984 #define _CRT_SECURE_NO_DEPRECATE #include<iostream> #include<string.h& ...

  7. hdu 3760(2次bfs求最短路)

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=3760 思路:首先是建反图,从点n开始做spfa求出n到各点的最短路,然后从1点开始搜最小序列,对于边( ...

  8. CodeForces - 987D Fair (BFS求最短路)

    题意:有N个城市,M条双向道路连接两个城市,整个图保证连通.有K种物品,但每个城市只有一种,现在它们都需要S种物品来举办展览,可以去其他城市获取该城市的物品,花费是两城市之间的最短路径长度.求每个城市 ...

  9. [codeforces-543B]bfs求最短路

    题意:给一个边长为1的无向图,求删去最多的边使得从a到b距离<=f,从c到d距离<=g,a,b,c,d,f,g都是给定的,求最多删去的边数. 思路:反过来思考,用最少的边构造两条从a到b, ...

随机推荐

  1. 经典!服务端 TCP 连接的 TIME_WAIT 过多问题的分析与解决

    开源Linux 专注分享开源技术知识 本文给出一个 TIME_WAIT 状态的 TCP 连接过多的问题的解决思路,非常典型,大家可以好好看看,以后遇到这个问题就不会束手无策了. 问题描述 模拟高并发的 ...

  2. .Net Core 依赖注入(IOC) 一些简单的使用技巧

    原文链接:https://www.cnblogs.com/ysmc/p/16240534.html .Net Core 在使用IOC后,我们不必再浪费精力在管理实例的生命周期上,交给IOC代替我们管理 ...

  3. zabbix的web界面访问失败问题排查

    现象:用curl访问显示拒绝链接,查看zabbix-server日志也无异常 1.检查防火墙,SElinux是否关闭 2.检查zabbix-server服务是否启动 3.检查80端口是否被占用,比方是 ...

  4. 【mq】从零开始实现 mq-11-消费者消息回执添加分组信息 pull message ack groupName

    前景回顾 [mq]从零开始实现 mq-01-生产者.消费者启动 [mq]从零开始实现 mq-02-如何实现生产者调用消费者? [mq]从零开始实现 mq-03-引入 broker 中间人 [mq]从零 ...

  5. 理解RESTful Api设计

    REST REST(REpresentational State Transfer)是 Roy Fielding 博士于 2000 年在他的博士论文中提出来的一种软件架构风格(一组架构约束条件和原则) ...

  6. 【Java面试】什么是幂等?如何解决幂等性问题?

    一个在传统行业工作了7年的粉丝私信我. 他最近去很多互联网公司面试,遇到的很多技术和概念都没听过. 其中就有一道题是:"什么是幂等.如何解决幂等性问题"? 他说,这个概念听都没听过 ...

  7. AWD平台搭建及遇到的问题分析

    1.安装docker环境 a.使用的是ubuntu系统,通过sudo apt install docker.io进行docker得安装,此方式会自动启动docker服务. b.通过curl -s ht ...

  8. fiddler的安装以及使用同时对Android 与IOS 抓包配置进行分析 进阶 一

    由于工作方向的原因,很久没有用过APP抓包工具了,有那么一天遇到了bug需要协助开发工程师进行定位分析,然后又重新梳理了一下之前常用的抓包工具,这里重点介绍一下目前市面上最流行的几款抓包工具,根据自己 ...

  9. npm init cabloy背后的故事

    背景 我们知道许多框架会提供一个脚手架工具,我们先下载安装脚手架工具,然后再通过脚手架命令行来创建项目.在npm@6.1.0中引入了npm init <initializer>的语法.简单 ...

  10. 使用 .NET MAUI 创建移动应用——Get Start

    大家好,我是张飞洪,感谢您的阅读,我会不定期和你分享学习心得,希望我的文章能成为你成长路上的垫脚石,让我们一起精进. 1.IDE下载安装 如果你还没安装Visual Studio 2022 预览版 你 ...