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. ...
随机推荐
- Net Memory Profiler 分析.Net程序内存泄露
Net Memory Profiler 分析.Net程序内存泄露 Haozes's Tech Space 人類的全部才能無非是時間和耐心的混合物 使用.Net Memory Profiler 分析.N ...
- 【UVA272】TEX Quotes
A - TEX Quotes Time Limit:3000MS Memory Limit:0KB 64bit IO Format:%lld & %llu Submitcid=80 ...
- zoj3829 Known Notation --- 2014 ACM-ICPC Asia Mudanjiang Regional Contest
根据规则,可以发现,一*之前必须有至少2数字.一(11*)这反过来可以被视为一数字. 因此,总位数必须大于*号码,或者你会加入数字. 添加数字后,,为了确保该解决方案将能够获得通过交流.那么肯定是*放 ...
- 集合hashCode()方法和equals()办法
1.哈希码: Object中的HashCode方法会返回该对象的的内存真实地址的整数化表示,这个形象的不是真正抵制的整数值就是哈希码. 2.利用哈希码向集合中插入数据的顺序? ...
- Windows PowerShell 简介
Powershell 是运行在windows机器上实现系统和应用程序管理自动化的命令行脚本环境.微软之所以将Powershell 定位为Power,并不是夸大其词,因为它完全支持对象.其可读性,易用性 ...
- robot framework 使用三:他们主动浏览器的兼容性
robot framework 浏览器兼容性测试 上图中黄色圈的地方默认什么都不写,是firefox浏览器.写上ie就是ie浏览器了 firefox最新版本号即可,ie须要设置: 1. IE选项设置的 ...
- Hibernate一个简短的引论
我们从几个方面进行阐述Hibernate When? What ? How? When? Hibernate由来是因为当时EJBBean1.1在处理entittBean架构时,花费的时间要比业务逻辑很 ...
- ZOJ 2412 Farm Irrigation(DFS 条件通讯块)
意甲冠军 两个农田管内可直接连接到壳体 他们将能够共享一个水源 有11种农田 管道的位置高于一定 一个农田矩阵 问至少须要多少水源 DFS的连通块问题 两个相邻农田的管道能够直接连接的 ...
- xCAT在多卡的物理机上装rhel6当需要人工选择网卡
问题叙述性说明 今天装了双网卡的物理机器上rhel5如果一切顺利.但是,在安装rhel6时间不能选择安装自己主动网卡,它会弹出一个窗口,让选择em1依然是em2. 问题原因 原因是我在加入节点的时候使 ...
- ASP.NET vNext or .NET vNext?
ASP.NET vNext or .NET vNext? 从概念和基础开始 vNext在曝光以来绝大多数以ASP.NET vNext这样的的字眼出现,为什么这边会提及.NET vNext?原因是我认为 ...