BFS、双向BFS和A*

Table of Contents

光说不练是无用的。我们从广为人知的POJ 2243这道题谈起:题目大意:给定一个起点和一个终点。按骑士的走法(走日字),从起点到终点的最少移动多少次

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvd2RraXJjaGhvZmY=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" alt="">

设A为寻路起点,B为目标终点。

1 BFS

BFS事实上是退化的A*算法。由于他没有启示函数做指引

Memory Time
144K 407MS

简单的代码例如以下:

#include<iostream>
#include<queue>
using namespace std;
char ss[3];
char ee[3];
typedef struct node
{
int x;
int y;
int steps;
}node;
int d[8][2]={{-2,1},{-2,-1},{-1,-2},{-1,2},{2,-1},{2,1},{1,-2},{1,2}};
int visited[8][8];
node s;
node e;
int in(node n)
{
if(n.x<0||n.y<0||n.x>7||n.y>7)
return 0;
return 1;
}
void bfs()
{
queue<node>q;
memset(visited,0,sizeof(visited));
q.push(s);
visited[s.x][s.y]=1;
while(!q.empty())
{
node st=q.front();
q.pop();
if(st.x==e.x&&st.y==e.y)
{
printf("To get from %s to %s takes %d knight moves.\n",ss,ee,st.steps);
break;
}
for(int i=0;i<8;++i)
{
node t;
t.x=st.x+d[i][0];
t.y=st.y+d[i][1];
if(in(t)&&visited[t.x][t.y]==0)
{
visited[t.x][t.y]=1;
t.steps=st.steps+1;
q.push(t);
}
}
}
}
int main(int argc, char *argv[])
{
while(scanf("%s %s",ss,ee)==2)
{
s.x=ss[0]-'a';
s.y=ss[1]-'1';
e.x=ee[0]-'a';
e.y=ee[1]-'1';
bfs();
}
return 0;
}

2 双向BFS

双向bfs就是用两个队列。一个队列保存从起点開始的状态,还有一个保存从终点開始向前搜索的状态,双向bfs主要是区分每一个格子是从起点開始搜索到的还是从终点開始搜索到的.每一个经过的格子结点保存到达该格子经过的步数。这样两边要是相交了相加就是结果

Memory Time
144K 141MS

明显的省时间

#include<iostream>
#include<queue>
using namespace std;
char ss[3];
char ee[3];
typedef struct node
{
int x;
int y;
int steps;
}node;
int d[8][2]={{-2,1},{-2,-1},{-1,-2},{-1,2},{2,-1},{2,1},{1,-2},{1,2}};
int visited[8][8];
int color[8][8];//区分当前位置是哪个队列查找过了
node s;
node e;
int in(node n)
{
if(n.x<0||n.y<0||n.x>7||n.y>7)
return 0;
return 1;
}
int bfs()
{
queue<node>qf; //我发现假设把qf和qb放在外面的话。节省的时间挺惊人的,耗时16MS
queue<node>qb;
memset(visited,0,sizeof(visited));
memset(color,0,sizeof(color));
qf.push(s);
qb.push(e);
visited[s.x][s.y]=0;
visited[e.x][e.y]=1;
color[s.x][s.y]=1;//着色
color[e.x][e.y]=2;
while(!qf.empty()||!qb.empty())
{
if(!qf.empty())
{
node st=qf.front();
qf.pop();
for(int i=0;i<8;++i)
{
node t;
t.x=st.x+d[i][0];
t.y=st.y+d[i][1];
if(in(t))
{
if(color[t.x][t.y]==0){
visited[t.x][t.y]=visited[st.x][st.y]+1;
color[t.x][t.y]=1;
qf.push(t);
}
else if(color[t.x][t.y]==2){
return visited[st.x][st.y]+visited[t.x][t.y];
}
}
} }
if(!qb.empty())
{
node st=qb.front();
qb.pop();
for(int i=0;i<8;++i)
{
node t;
t.x=st.x+d[i][0];
t.y=st.y+d[i][1];
if(in(t))
{
if(color[t.x][t.y]==0){
visited[t.x][t.y]=visited[st.x][st.y]+1;
color[t.x][t.y]=2;
qb.push(t);
}
else if(color[t.x][t.y]==1){
return visited[st.x][st.y]+visited[t.x][t.y];
}
}
}
}
}
}
int main(int argc, char *argv[])
{
// freopen("in.txt","r",stdin);
while(scanf("%s %s",ss,ee)==2)
{
s.x=ss[0]-'a';
s.y=ss[1]-'1';
e.x=ee[0]-'a';
e.y=ee[1]-'1';
s.steps=0;
e.steps=1;
if(s.x==e.x&&s.y==e.y)
printf("To get from %s to %s takes 0 knight moves.\n",ss,ee);
else
printf("To get from %s to %s takes %d knight moves.\n",ss,ee,bfs());
}
return 0;
}

3 A*算法

选择路径中经过哪个方格的关键是以下这个等式:F = G + H这里:

  • G = 从起点A。沿着产生的路径,移动到网格上指定方格的移动耗费。
  • H = 从网格上那个方格移动到终点B的预估移动耗费。

    这常常被称为启示式的,可能会让你有点迷惑。

    这样叫的原因是由于它仅仅是个推測。我们没办法事先知道路径的长度,由于路上可能存在各种障碍(墙,水。等等)。

A*算法步骤为:

  • 把起始格加入到开启列表。

  • 反复例如以下的工作:
    • 寻找开启列表中F值最低的格子。我们称它为当前格。
    • 把它切换到关闭列表。
    • 对相邻的格中的每个?
      • 假设它不可通过或者已经在关闭列表中,略过它。反之例如以下。
      • 假设它不在开启列表中,把它加入进去。

        把当前格作为这一格的父节点。记录这一格的F,G,和H值。

      • 假设它已经在开启列表中。用G值为參考检查新的路径是否更好。更低的G值意味着更好的路径。假设是这样,就把这一格的父节点改成当前格,而且又一次计算这一格的G和F值。假设你保持你的开启列表按F值排序,改变之后你可能须要又一次对开启列表排序。
    • 停止。当你
      • 把目标格加入进了关闭列表。这时候路径被找到。或者
      • 没有找到目标格,开启列表已经空了。

        这时候,路径不存在。

  • 保存路径。从目标格開始,沿着每一格的父节点移动直到回到起始格。

    这就是你的路径。

能够这样说,BFS是A*算法的一个特例。

对于一个BFS算法,从当前节点扩展出来的每个节点(假设没有被訪问过的话)都要放进队列进行进一步扩展。也就是说BFS的预计函数h永远等于0。没有一点启示式的信息。能够觉得BFS是“最烂的”A*算法。

选取最小估价:假设学过数据结构的话。应该能够知道,对于每次都要选取最小估价的节点。应该用到最小优先级队列(也叫最小二叉堆)。在C++的STL里有现成的数据结构priorityqueue。能够直接使用。当然不要忘了重载自己定义节点的比較操作符。

Memory Time
154K 47MS

只是上面优化的双向BFS(16MS)

#include<iostream>
#include<queue>
#include<stdlib.h>
using namespace std;
char ss[3];
char ee[3];
typedef struct node
{
int x;
int y;
int steps;
int g;
int h;
int f;
friend bool operator < (const node & a,const node &b);
}node;
inline bool operator < (const node & a,const node &b)
{
return a.f>b.f;
}
int d[8][2]={{-2,1},{-2,-1},{-1,-2},{-1,2},{2,-1},{2,1},{1,-2},{1,2}};
int visited[8][8];
node s;
node e;
int in(node n)
{
if(n.x<0||n.y<0||n.x>7||n.y>7)
return 0;
return 1;
}
int Heuristic(const node &a){
return (abs(a.x-e.x)+abs(a.y-e.y))*10;
}//曼哈顿(manhattan)估价函数
priority_queue<node> q; //最小优先级队列(开启列表) 这里有点优化策略,由于我发现假设把q
//放在Astar函数里头的话。代码跑起来是157MS,放在外面的话是47MS。有显著的差别
int Astar()
{
while(!q.empty())q.pop();
memset(visited,0,sizeof(visited));
q.push(s);
while(!q.empty())
{
node front=q.top();
node t;
q.pop();
visited[front.x][front.y]=1;
if(front.x==e.x && front.y==e.y)
return front.steps;
for(int i=0;i<8;i++){
t.x=front.x+d[i][0];
t.y=front.y+d[i][1];
if(in(t) && visited[t.x][t.y]==0){
t.g=23+front.g;
t.h=Heuristic(t);
t.f=t.g+t.h;
t.steps=front.steps+1;
q.push(t);
}
}
}
}
int main(int argc, char *argv[])
{
//freopen("in.txt","r",stdin);
while(scanf("%s %s",ss,ee)==2)
{
s.x=ss[0]-'a';
s.y=ss[1]-'1';
e.x=ee[0]-'a';
e.y=ee[1]-'1';
s.steps=0;
s.g=0;
s.h=Heuristic(s);
s.f=s.g+s.h;
if(s.x==e.x&&s.y==e.y)
printf("To get from %s to %s takes 0 knight moves.\n",ss,ee);
else
printf("To get from %s to %s takes %d knight moves.\n",ss,ee,Astar());
}
return 0;
}

本篇文章摘录了最主要的BFS和双向BFS的实现以及A*的基本原理。因为原理不是十分难懂又有图解过程,所以能够一次性掌握原理(尽管文字介绍相当简要,只是好像也没有什么要说的)。剩下的动手的问题。

假设你有不论什么建议或者批评和补充,请留言指出,不胜感激,很多其它參考请移步互联网。

Author: kirchhoff

Created: 2014-11-14 Fri 17:43

Emacs 24.4.1 (Org mode 8.2.10)

uri=referer">Validate

版权声明:本文博主原创文章,博客,未经同意不得转载。

BFS、双向BFS和A*的更多相关文章

  1. UVa 1601 || POJ 3523 The Morning after Halloween (BFS || 双向BFS && 降维 && 状压)

    题意 :w*h(w,h≤16)网格上有n(n≤3)个小写字母(代表鬼).要求把它们分别移动到对应的大写字母里.每步可以有多个鬼同时移动(均为往上下左右4个方向之一移动),但每步结束之后任何两个鬼不能占 ...

  2. POJ1915Knight Moves(单向BFS + 双向BFS)

    题目链接 单向bfs就是水题 #include <iostream> #include <cstring> #include <cstdio> #include & ...

  3. POJ 3126 Prime Path 解题报告(BFS & 双向BFS)

    题目大意:给定一个4位素数,一个目标4位素数.每次变换一位,保证变换后依然是素数,求变换到目标素数的最小步数. 解题报告:直接用最短路. 枚举1000-10000所有素数,如果素数A交换一位可以得到素 ...

  4. UVA - 1601 The Morning after Halloween (BFS/双向BFS/A*)

    题目链接 挺有意思但是代码巨恶心的一道最短路搜索题. 因为图中的结点太多,应当首先考虑把隐式图转化成显式图,即对地图中可以相互连通的点之间连边,建立一个新图(由于每步不需要每个鬼都移动,所以每个点需要 ...

  5. POJ1915 BFS&双向BFS

    俩月前写的普通BFS #include <cstdio> #include <iostream> #include <cstring> #include <q ...

  6. bfs(双向bfs加三维数组)

    http://acm.hdu.edu.cn/showproblem.php?pid=2612 Find a way Time Limit: 3000/1000 MS (Java/Others)     ...

  7. 洛谷 P1379 八数码难题(map && 双向bfs)

    题目传送门 解题思路: 一道bfs,本题最难的一点就是如何储存已经被访问过的状态,如果直接开一个bool数组,空间肯定会炸,所以我们要用另一个数据结构存,STL大法好,用map来存,直接AC. AC代 ...

  8. 双向BFS和启发式搜索的应用

    题目链接 P5507 机关 题意简述   有12个旋钮,每个旋钮开始时处于状态 \(1\) ~ \(4\) ,每次操作可以往规定方向转动一个旋钮 (\(1\Rightarrow2\Rightarrow ...

  9. HDU 3085 Nightmare II 双向bfs 难度:2

    http://acm.hdu.edu.cn/showproblem.php?pid=3085 出的很好的双向bfs,卡时间,普通的bfs会超时 题意方面: 1. 可停留 2. ghost无视墙壁 3. ...

随机推荐

  1. 《深入了解mybatis原则》 MyBatis架构设计和案例研究

    MyBatis这是现在很流行ORM框架,这是非常强大.事实上现却比較简单.优雅. 本文主要讲述MyBatis的架构设计思路,而且讨论MyBatis的几个核心部件.然后结合一个select查询实例.深入 ...

  2. 在WPF中处理Windows消息

    在Winform中 处理Windows消息通过重写WndProc方法 在WPF中 使用的是System.Windows. Sytem.Windows.Controls等名字空间,没有WndProc函数 ...

  3. Android 随着输入框控件的清除功能ClearEditText,抄IOS输入框

    今天给大家带来一个非常有用的小控件ClearEditText,就是在Android系统的输入框右边增加一个小图标,点击小图标能够清除输入框里面的内容,IOS上面直接设置某个属性就能够实现这一功能.可是 ...

  4. 【Web探索之旅】第四部分:Web程序员

    内容简介 1.第四部分第一课:什么是Web程序员? 2.第四部分第二课:如何成为Web程序员? 3.第四部分第三课:成为优秀Web程序员的秘诀 第四部分:Web程序员(完结篇) 大家好.终于来到了[W ...

  5. C3P0具体的配置说明(com.mchange.v2.c3p0.ComboPooledDataSource)

    C3P0它是一个开源JDBC连接池,它lib文件夹和Hibernate一起公布,包含了实现jdbc3和jdbc2扩展规范说明的Connection 和Statement 池的DataSources 对 ...

  6. HDU 1061 Rightmost Digit解决问题的方法

    求大量N^N的值最右边的数字,即最低位. 它将能够解决一个简单二分法. 只是要注意溢出,只要把N % 10之后.我不会溢出,代替使用的long long. #include <stdio.h&g ...

  7. Kotlin

    关于Kotlin,网上已有一些介绍的文章,包括Antonio Leiva的这组blog翻译稿.不过,我还是想跟进它们.翻译它们,以锻炼自己的英文翻译.各位高手发现问题,请及时“拍砖”. 原文题目:Ko ...

  8. 第三篇——第二部分——第四文 配置SQL Server镜像——非域环境

    原文:第三篇--第二部分--第四文 配置SQL Server镜像--非域环境 本文为非域环境搭建镜像演示,对于域环境搭建,可参照上文:http://blog.csdn.net/dba_huangzj/ ...

  9. SQL Server高可用——日志传送(4-2)——部署

    原文:SQL Server高可用--日志传送(4-2)--部署 前文再续,书接上一回.本章演示一下日志传送的具体过程 准备工作: 由于时间关系,已经装好了3台虚拟机,且同在一个域里面: SQL01:主 ...

  10. PowerMockito使用详解(转)

    一.为什么要使用Mock工具 在做单元测试的时候,我们会发现我们要测试的方法会引用很多外部依赖的对象,比如:(发送邮件,网络通讯,远程服务, 文件系统等等). 而我们没法控制这些外部依赖的对象,为了解 ...