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. effective c++ 条款5 c++ 默默实现的函数

    当写一个空类c++ 会为我们自动提供四个函数 1 默认构造函数 2 默认析构函数 3 拷贝构造函数 4 默认赋值运算符

  2. 使用python+django+twistd 开发自己的操作和维护系统的一个

    许多开源操作系统和维护系统,例nagios.zabbix.cati等等,但是,当他们得到的时间自己的个性化操作和维护需求,始终无力! 最近的一项研究python.因此,我们认为python+djang ...

  3. JavaEE SSH集成框架(两) struts2 本地加载dtd文件,action组态

    1. 载入中struts2的dtd文件.使struts.xml网络无法验证,和eclipse有技巧 在src在创建struts.xml: <? xmlversion="1.0" ...

  4. 用友财务总帐(GL)模BI数据ETL分析

    业务需求,如下面的: 现在用友总帐一家公司BI分析案例. /* Sql Server2012使用作业设置定时任务,为了保证有一天运行时间 */ /* 意temp1表里一定要保证要有记录,否则以temp ...

  5. php+sqlite cms

    1 phpSQLiteCMS 最新版本 phpSQLiteCMS 2.0.4 http://phpsqlitecms.net/ 2 taoCMS  最新版本 [2.5Beta5下载地址] 需要php ...

  6. cocos2d-x 3.1.1 学习笔记[21]cocos2d-x 创建过程

    文章出自于  http://blog.csdn.net/zhouyunxuan RootViewController.h #import <UIKit/UIKit.h> @interfac ...

  7. Linux互斥和同步应用程序(四):posix互斥信号和同步

           [版权声明:尊重原创.转载请保留源:blog.csdn.net/shallnet 要么 .../gentleliu,文章仅供学习交流,请勿用于商业用途]          在前面讲共享内 ...

  8. 华为OJ:查找字符的第一个字符串只出现一次

    您可以使代码有点写得真好,不要直接写双循环,如果,是可能的写函数调用,非常高的可重用性. import java.util.Scanner; public class findOnlyOnceChar ...

  9. iphone6 plus有什么办法

    在苹果9月9号最新的秋季公布会上苹果官方公布了最新的iPhone6 plus,可能非常多朋友不知道plus是什么意思,这样命名有什么特殊的意义呢?本次Ly经典家居小编就为大家带来这方面的具体解答,一起 ...

  10. 左右sqlplus一些方法用于汇总

    SQL> select * from v$version where rownum=1; BANNER --------------------------------------------- ...