当前位置: 首页 > news >正文

BFS、双向BFS和A*

BFS、双向BFS和A*

Table of Contents

  • 1. BFS
  • 2. 双向BFS
  • 3. A*算法

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


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

1 BFS

BFS其实是退化的A*算法,因为他没有启发函数做指引

MemoryTime
144K407MS

简单的代码如下:

#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主要是区分每个格子是从起点开始搜索到的还是从终点开始搜索到的.每个经过的格子结点保存到达该格子经过的步数,这样两边要是相交了相加就是结果

MemoryTime
144K141MS

明显的省时间

#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,可以直接使用。当然不要忘了重载自定义节点的比较操作符。

MemoryTime
154K47MS

不过上面优化的双向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)

Validate

相关文章:

  • 二分的模板(花式二分)
  • STL之set集合容器
  • NOIP2016#模拟考试 Day.1# T1 洗澡
  • NOIP2016#模拟考试 Day.1# T3 导航软件
  • [hdu 4405] Aeroplane chess [概率DP 期望]
  • NOIP2016#模拟考试 Day.2# T2 网络修复 【LCA + 并查集】
  • NOIP2016#模拟考试 Day.2# T3 王位继承
  • [hdu 2826] The troubles of lmy [简单计算几何 - 相似]
  • [hdu 2896] 病毒侵袭 [ac自动机][病毒特征码匹配]
  • [hdu 3065] 病毒侵袭持续中 [AC自动机] [病毒特征码匹配]
  • TCP/IP协议讲解 一
  • #NOIP 2014# day.1 生活大爆炸版 石头剪刀布
  • #NOIP 2014# day.1 T2 联合权值
  • #NOIP 2014# day.1 T3 飞扬的小鸟 bird
  • #NOIP 2014#day.2 T1 无限网络发射器选址
  • Apache的80端口被占用以及访问时报错403
  • canvas 绘制双线技巧
  • JavaScript 基础知识 - 入门篇(一)
  • JavaScript 一些 DOM 的知识点
  • Python - 闭包Closure
  • 初识MongoDB分片
  • 使用 @font-face
  • 栈实现走出迷宫(C++)
  • 如何用纯 CSS 创作一个菱形 loader 动画
  • 专访Pony.ai 楼天城:自动驾驶已经走过了“从0到1”,“规模”是行业的分水岭| 自动驾驶这十年 ...
  • ​ 全球云科技基础设施:亚马逊云科技的海外服务器网络如何演进
  • #define
  • #前后端分离# 头条发布系统
  • (附源码)ssm高校运动会管理系统 毕业设计 020419
  • (生成器)yield与(迭代器)generator
  • (一) storm的集群安装与配置
  • (一)使用Mybatis实现在student数据库中插入一个学生信息
  • (转)自己动手搭建Nginx+memcache+xdebug+php运行环境绿色版 For windows版
  • (转载)hibernate缓存
  • ./configure、make、make install 命令
  • .NET Core跨平台微服务学习资源
  • .NET Core引入性能分析引导优化
  • .net6解除文件上传限制。Multipart body length limit 16384 exceeded
  • .Net8 Blazor 尝鲜
  • .Net的C#语言取月份数值对应的MonthName值
  • .Net小白的大学四年,内含面经
  • @ 代码随想录算法训练营第8周(C语言)|Day57(动态规划)
  • @RequestParam @RequestBody @PathVariable 等参数绑定注解详解
  • @基于大模型的旅游路线推荐方案
  • [1127]图形打印 sdutOJ
  • [20181219]script使用小技巧.txt
  • [28期] lamp兄弟连28期学员手册,请大家务必看一下
  • [AutoSar]BSW_Com07 CAN报文接收流程的函数调用
  • [BZOJ 3282] Tree 【LCT】
  • [C#]C#学习笔记-CIL和动态程序集
  • [cb]UIGrid+UIStretch的自适应
  • [CUDA手搓]从零开始用C++ CUDA搭建一个卷积神经网络(LeNet),了解神经网络各个层背后算法原理
  • [Django 0-1] Core.Email 模块
  • [Dxperience.8.*]报表预览控件PrintControl设置
  • [GYCTF2020]Ez_Express