BFS、双向BFS和A*
BFS、双向BFS和A*
光说不练是无用的。我们从广为人知的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*的基本原理。因为原理不是十分难懂又有图解过程,所以能够一次性掌握原理(尽管文字介绍相当简要,只是好像也没有什么要说的)。剩下的动手的问题。
假设你有不论什么建议或者批评和补充,请留言指出,不胜感激,很多其它參考请移步互联网。
版权声明:本文博主原创文章,博客,未经同意不得转载。
BFS、双向BFS和A*的更多相关文章
- UVa 1601 || POJ 3523 The Morning after Halloween (BFS || 双向BFS && 降维 && 状压)
题意 :w*h(w,h≤16)网格上有n(n≤3)个小写字母(代表鬼).要求把它们分别移动到对应的大写字母里.每步可以有多个鬼同时移动(均为往上下左右4个方向之一移动),但每步结束之后任何两个鬼不能占 ...
- POJ1915Knight Moves(单向BFS + 双向BFS)
题目链接 单向bfs就是水题 #include <iostream> #include <cstring> #include <cstdio> #include & ...
- POJ 3126 Prime Path 解题报告(BFS & 双向BFS)
题目大意:给定一个4位素数,一个目标4位素数.每次变换一位,保证变换后依然是素数,求变换到目标素数的最小步数. 解题报告:直接用最短路. 枚举1000-10000所有素数,如果素数A交换一位可以得到素 ...
- UVA - 1601 The Morning after Halloween (BFS/双向BFS/A*)
题目链接 挺有意思但是代码巨恶心的一道最短路搜索题. 因为图中的结点太多,应当首先考虑把隐式图转化成显式图,即对地图中可以相互连通的点之间连边,建立一个新图(由于每步不需要每个鬼都移动,所以每个点需要 ...
- POJ1915 BFS&双向BFS
俩月前写的普通BFS #include <cstdio> #include <iostream> #include <cstring> #include <q ...
- bfs(双向bfs加三维数组)
http://acm.hdu.edu.cn/showproblem.php?pid=2612 Find a way Time Limit: 3000/1000 MS (Java/Others) ...
- 洛谷 P1379 八数码难题(map && 双向bfs)
题目传送门 解题思路: 一道bfs,本题最难的一点就是如何储存已经被访问过的状态,如果直接开一个bool数组,空间肯定会炸,所以我们要用另一个数据结构存,STL大法好,用map来存,直接AC. AC代 ...
- 双向BFS和启发式搜索的应用
题目链接 P5507 机关 题意简述 有12个旋钮,每个旋钮开始时处于状态 \(1\) ~ \(4\) ,每次操作可以往规定方向转动一个旋钮 (\(1\Rightarrow2\Rightarrow ...
- HDU 3085 Nightmare II 双向bfs 难度:2
http://acm.hdu.edu.cn/showproblem.php?pid=3085 出的很好的双向bfs,卡时间,普通的bfs会超时 题意方面: 1. 可停留 2. ghost无视墙壁 3. ...
随机推荐
- 乐在其中设计模式(C#) - 建造者模式(Builder Pattern)
原文:乐在其中设计模式(C#) - 建造者模式(Builder Pattern) [索引页][源码下载] 乐在其中设计模式(C#) - 建造者模式(Builder Pattern) 作者:webabc ...
- 理解JavaScript的闭包
在JS这块,免不了被问什么是闭包. 从一个常见的循环问题说起. 有一个ul列表, 里面有5个li标签,我希望点击每个li标签的时候,弹出每个li标签对应的索引值(第一个弹出0,第二个弹出1...). ...
- 【转】c#引用类型与值类型的区别大盘点
解析:CLR支持两种类型:值类型和引用类型.用Jeffrey Richter(<CLR via C#>作者)的话来说,“不理解引用类型和值类型区别的程序员将会把代码引入诡异的陷阱和诸多性能 ...
- LINK : fatal error LNK1181: 无法打开输入文件“..\..\lib\Release\opencv_ocl249.lib”
最近想要编译什么OpenCV资源.查看源代码调试执行. 按照网上的文章<Win7x64+VS2012+OpenCV2.4.3+CMake2.8.10+TBB41重编译OpenCV> 进行配 ...
- DataGridView绑定数据源
给DataGridView绑定数据源比較简单,方法主要有两种: 1.直接在控件属性中绑定数据源,这样的方法最简单,但它是直接连接数据库的,这样就和传DataTable的后果差点儿相同了,所以还是尽量避 ...
- leetcode第一刷_Minimum Path Sum
能够用递归简洁的写出,可是会超时. dp嘛.这个问题须要从后往前算,最右下角的小规模是已知的,边界也非常明显,是最后一行和最后一列,行走方向的限制决定了这些位置的走法是唯一的,能够先算出来.然后不断的 ...
- c# 用正则表达式在指定的字符串中每隔指定个数的文字插入指定字符串
public static string AddNewLine(string inString,int num,string addString="\r\n") { return ...
- HDU4540+DP
简单题... dp[ i ][ j ] 表示第 i 行取第 j 个数的MinVal /* DP&简单题 */ #include<stdio.h> #include<strin ...
- 【IPC第二个进程间通信】管道Pipe
IPC进程间通信+管道Pipe IPC(Inter-Process Communication,进程间通信). 管道用于进程间共享数据,事实上质是共享内存 ...
- 汉字Collection
只是上一行Demo private static string[] HanZis = new string[]{ "啊阿呵吖嗄腌锕爱矮挨哎碍癌艾唉哀蔼隘埃皑呆嗌嫒瑷暧捱砹嗳锿霭按安暗岸俺案鞍 ...