BFS、双向BFS和A*

BFS、双向BFS和A*

Table of Contents

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

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

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

设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

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

时间: 2024-10-24 05:19:45

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

POJ 1915-Knight Moves (单向BFS &amp;&amp; 双向BFS 比较)

题目链接:Knight Moves 研究了一下双向BFS,不是很难,和普通的BFS一样,双向BFS不过是从 起点和终点同时开始搜索,可减少搜索时间 当两条搜索路线相遇时,结束. 貌似有一年百度的招聘 笔试,就是双向BFS.... 下面,比较一下BFS 和 双向BFS的用时: BFS STL的queue可能会浪费一点时间 #include <iostream> #include <cstdio> #include <cstdlib> #include <cstrin

POJ1915Knight Moves(单向BFS + 双向BFS)

题目链接 单向bfs就是水题 1 #include <iostream> 2 #include <cstring> 3 #include <cstdio> 4 #include <algorithm> 5 #include <queue> 6 using namespace std; 7 const int INF = 0x3f3f3f3f; 8 const int Max = 300 + 5; 9 struct Node 10 { 11 int

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

题意 :w*h(w,h≤16)网格上有n(n≤3)个小写字母(代表鬼).要求把它们分别移动到对应的大写字母里.每步可以有多个鬼同时移动(均为往上下左右4个方向之一移动),但每步结束之后任何两个鬼不能占用同一个位置,也不能在一步之内交换位置.输入保证所有空格连通,所有障碍格也连通,且任何一个2*2子网格中至少有一个障碍格.输出最少的步数.输入保证有解. 分析 :可以将当前所有小鬼的位置作为一个状态,然后模拟小鬼移动BFS拓展出其他可行状态并且顺带记录步数,直到所有的小鬼都到达终点.首先状态如何表示

LeetCode 5282. 转化为全零矩阵的最少反转次数 bfs 双向bfs

地址 https://leetcode-cn.com/submissions/detail/39277402/ 题目描述给你一个 m x n 的二进制矩阵 mat. 每一步,你可以选择一个单元格并将它反转(反转表示 0 变 1 ,1 变 0 ).如果存在和它相邻的单元格,那么这些相邻的单元格也会被反转.(注:相邻的两个单元格共享同一条边.) 请你返回将矩阵 mat 转化为全零矩阵的最少反转次数,如果无法转化为全零矩阵,请返回 -1 . 二进制矩阵的每一个格子要么是 0 要么是 1 . 全零矩阵是

Hdu1401-Solitaire(双向bfs)

Solitaire is a game played on a chessboard 8x8. The rows and columns of the chessboard are numbered from 1 to 8, from the top to the bottom and from left to right respectively.There are four identical pieces on the board. In one move it is allowed to

UVA-1604 Cubic Eight-Puzzle (双向BFS+状态压缩+限制搜索层数)

题目大意:立体的八数码问题,一次操作是滚动一次方块,问从初始状态到目标状态的最少滚动次数. 题目分析:这道题已知初始状态和目标状态,且又状态数目庞大,适宜用双向BFS.每个小方块有6种状态,整个大方格有9*6^8个状态.每个小方块用一位6进制数表示即可. 注意:状态转移时要谨慎,否则会出现意想不到的错误: 这道题的末状态有256(2^8)个,如果对搜索层数不加限制,即使双向BFS也会TLE的,当限制正向搜索15层逆向搜索15层至正向搜索27层反向搜索3层时都能AC(我下面贴出的程序是这样的),其

HDU1195 双向BFS(或BFS)

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1195 , 双向BFS或者直接BFS也可以过. 其实这道题只是单向BFS就可以过的,但是为了练算法,所以还是用了双向BFS来写. 算法: 先预处理一下,从1111到9999的所有点进行构图(由于是1~9的,所以除去含有0元素的数字),能进行一次变换变成的数字则表示两点之间连通.然后从初态与目态两个点进行BFS,如果有轨迹重合的就返回路程和. 这里注意双向BFS要一层一层的进行搜索,不然的话会产生错误,

HDU 1043 Eight(双向BFS+康托展开)

http://acm.hdu.edu.cn/showproblem.php?pid=1043 题意:给出一个八数码,求出到达指定状态的路径. 思路:路径寻找问题.在这道题里用到的知识点挺多的.第一次用双向BFS来做. ①双向BFS 在单向BFS的基础上,多建一个从终止状态开始搜索的队列,当然这个时候需要两个vis[]辅助数组,分别记录两个队列的访问情况,当两个队列相遇时即可终止循环. ②康托展开 X=a[n]*(n-1)!+a[n-1]*(n-2)!+...+a[i]*(i-1)!+...+a[

第六章部分例题 双向bfs邻接表和邻接矩阵实现

Idealpath 双向bfs输出颜色,邻接矩阵实现 1 #include <iostream> 2 #include <cstdio> 3 #include <cstring> 4 #include <queue> 5 #include <map> 6 #include <algorithm> 7 8 using namespace std; 9 10 const int maxn=10000; 11 const int inf=1