所谓双向广度搜索指的是搜索沿两个方向同时进行:(1)正向搜索:从初始结点向目标结点方向搜索;(2)逆向搜索:从目标结点向初始结点方向搜索;当两个方向的搜索生成同一子结点时终止此搜索过程。

广度双向搜索通常有两种方法:(1)两个方向交替扩展;(2)选择结点个数较少的那个方向先扩展。方法(2)克服了两方向结点的生成速度不平衡的状态,可明显提高效率。

【例1】Knight Moves (POJ 1915)

Description

Background
Mr Somurolov, fabulous chess-gamer indeed, asserts that no one else but him can move knights from one position to another so fast. Can you beat him?
The Problem
Your task is to write a program to calculate the minimum number of moves needed for a knight to reach one point from another, so that you have the chance to be faster than Somurolov.
For people not familiar with chess, the possible knight moves are shown in Figure 1.

Input

The input begins with the number n of scenarios on a single line by itself.
Next follow n scenarios. Each scenario consists of three lines containing integer numbers. The first line specifies the length l of a side of the chess board (4 <= l <= 300). The entire board has size l * l. The second and third line contain pair of integers {0, ..., l-1}*{0, ..., l-1} specifying the starting and ending position of the knight on the board. The integers are separated by a single blank. You can assume that the positions are valid positions on the chess board of that scenario.
Output

For each scenario of the input you have to calculate the minimal amount of knight moves which are necessary to move from the starting point to the ending point. If starting point and ending point are equal,distance is zero. The distance must be written on a single line.
Sample Input

3
8
0 0
7 0
100
0 0
30 50
10
1 1
1 1
Sample Output

5
28
0

(1)编程思路1。

先采用一般的单向广度优先搜索方法。设置数组step[305][305]记录走到某个位置需要的步数,数组vis[305][305]来记录某个位置是否已访问过(值0代表未访问,1代表已访问过)。

用数组q[]来模拟队列,front和rear分别为队头和队尾指针。单向广度优先搜索的框架可写为:

队列初始化,即front=rear=0;

起点坐标入队列;

while (front<rear)      // 队列不为空

{

队头元素出队,送cur结点;

若cur结点的坐标等于终点坐标,则搜索完成,返回;

将cur结点按规则展出新结点next(即用循环进行8个方向的新结点生成);

若next结点未访问过,则置相应的值,并将next结点入队;

}

(2)采用单向广度优先搜索的源程序。

#include <stdio.h>

int vis[305][305], step[305][305];

int dx[] = {-2, -2, -1, 1, 2, 2, 1, -1};

int dy[] = {-1, 1, 2, 2, 1, -1, -2, -2};

struct point

{

int x, y;

};

int BFS(int start_x,int start_y,int end_x,int end_y,int n)

// 在n*n的棋盘中搜索从起点(start_x,strat_y)到终点(end_x,end_y)所需的最少步数

{

int front,rear,i;

point cur,next,q[90005];

front=rear=0;

cur.x = start_x;

cur.y = start_y;

vis[start_x][start_y] = 1;   // 设置探索标记为1

q[rear++] = cur;              // 起始坐标入队

while (front < rear)

{

cur = q[front++];       // 队头结点坐标出队

for (i=0; i<8; ++i)

{

next.x = cur.x + dx[i];

next.y = cur.y + dy[i];

if (next.x<0 || next.x>=n || next.y<0 || next.y>=n)

continue;

if (next.x==end_x && next.y==end_y)   // 到达目标位置

return step[cur.x][cur.y]+1;

if (!vis[next.x][next.y])

{

vis[next.x][next.y] = 1;

step[next.x][next.y] = step[cur.x][cur.y] + 1; // 记录步数

q[rear++] = next; // 当前合法坐标位置入队

}

}

}

return -1;  // 若搜索不成功,表示不可达

}

int main()

{

int nCase,sx,sy,tx,ty,size,i,j;

scanf("%d", &nCase);

while (nCase--)

{

scanf("%d", &size);

for (i=0;i<size;i++)

for (j=0;j<size;j++)

vis[i][j]=step[i][j]=0;

scanf("%d %d", &sx, &sy);

scanf("%d %d", &tx, &ty);

if (sx==tx && sy==ty)

{

printf("0\n");

}

else

{

printf("%d\n",BFS(sx,sy,tx,ty,size));

}

}

return 0;

}

(3)编程思路2。

用同一个队列来保存正向和逆向扩展的结点。开始时,将起点坐标和终点坐标同时入队列。这样,第1个出队的坐标是起点,正向搜索扩展队列;第2个出队的坐标是终点,逆向搜索扩展队列。…,两个方向的扩展依次交替进行。

由于采用双向搜索,如何知道某个结点是正向还是逆向扩展来的呢?

简单修改vis[][]数组元素的置值方法即可。初始时,vis数组的全部元素值为0,由正向扩展来的结点的vis对应元素值置为1,由逆向扩展来的结点的vis对应元素值置为2。

设当前结点为cur,由cur可以扩展出新结点next。若vis[next.x][next.y]==0,则next结点未访问过,将next结点入队并进行相应设置;若vis[next.x][next.y]!=0,则next结点已访问过。由于vis[cur.x][cur.y]记录的是当前扩展方向(1代表正向,2代表逆向),若vis[next.x][next.y] != vis[cur.x][cur.y],则表示next结点以前按相反的方向访问过,正向和反向遇到了同一个结点,搜索成功。

(4)采用双向广度优先搜索的源程序。

#include <stdio.h>

int vis[305][305], step[305][305];

int dx[] = {-2, -2, -1, 1, 2, 2, 1, -1};

int dy[] = {-1, 1, 2, 2, 1, -1, -2, -2};

struct point

{

int x, y;

};

int BFS(int start_x,int start_y,int end_x,int end_y,int n)

// 在n*n的棋盘中搜索从起点(start_x,strat_y)到终点(end_x,end_y)所需的最少步数

{

int front,rear,i;

point cur,next,q[90005];

front=rear=0;

cur.x = start_x;

cur.y = start_y;

vis[start_x][start_y] = 1;  // 从起始位置开始的探索标记为1

q[rear++] = cur;         // 起始坐标入队

next.x = end_x;

next.y = end_y;

vis[end_x][end_y] = 2;   // 从终点位置开始的探索标记为 2

q[rear++] = next;    // 终点坐标入队

while (front < rear)

{

cur = q[front++];     /* 队首结点坐标出队 */

for (i=0; i<8; ++i)

{

next.x = cur.x + dx[i];

next.y = cur.y + dy[i];

if (next.x<0 || next.x>=n || next.y<0 || next.y>=n)

continue;

if (!vis[next.x][next.y])

{

vis[next.x][next.y] = vis[cur.x][cur.y];     // 设为与当前探索路径相同的标记

step[next.x][next.y] = step[cur.x][cur.y] + 1; // 记录步数

q[rear++] = next; // 当前合法坐标位置入队

}

else if (vis[cur.x][cur.y] != vis[next.x][next.y])

{   // 说明从起点出发的探索与从终点出发的探索重合

return step[cur.x][cur.y]+step[next.x][next.y]+1;

}

}

}

return -1;  // 若搜索不成功,表示不可达

}

int main()

{

int nCase,sx,sy,tx,ty,size,i,j;

scanf("%d", &nCase);

while (nCase--)

{

scanf("%d", &size);

for (i=0;i<size;i++)

for (j=0;j<size;j++)

vis[i][j]=step[i][j]=0;

scanf("%d %d", &sx, &sy);

scanf("%d %d", &tx, &ty);

if (sx==tx && sy==ty)

{

printf("0\n");

}

else

{

printf("%d\n",BFS(sx,sy,tx,ty,size));

}

}

return 0;

}

(5)编程思路3。

定义两个队列q1[]和q2[]分别用于两个方向的扩展,两个队列的队头指针和队尾指针分别为front1、front2和rear1、rear2。双向广度优先搜索的框架还可写成:
      void BFS()
      {

将起始节点放入队列q1,将目的节点放入队列q2;

当两个队列都未空时,作如下循环
           {
                 如果队列q1里的未处理节点比q2中的少(即rear1-front1 < rear2-front2),

则扩展队列q1;

否则扩展队列q2;
             }

}

(6)采用两个队列的双向广度优先搜索方法的源程序。

#include <stdio.h>

int vis[305][305], step[305][305];

int dx[] = {-2, -2, -1, 1, 2, 2, 1, -1};

int dy[] = {-1, 1, 2, 2, 1, -1, -2, -2};

struct point

{

int x, y;

};

int BFS(int start_x,int start_y,int end_x,int end_y,int n)

// 在n*n的棋盘中搜索从起点(start_x,strat_y)到终点(end_x,end_y)所需的最少步数

{

int front1,rear1,front2,rear2,i,flag;

point cur,next,q1[45001],q2[45001];

front1=rear1=0;

front2=rear2=0;

cur.x = start_x;

cur.y = start_y;

vis[start_x][start_y] = 1;  // 设置正向探索标记为1

q1[rear1++] = cur;           // 起始坐标入正向队列

next.x = end_x;

next.y = end_y;

vis[end_x][end_y] = 2;      // 设置逆向探索标记为2

q2[rear2++] = next;          // 终点坐标入逆向队列

while (front1 < rear1 && front2<rear2)

{

if (rear1-front1 < rear2-front2)

{

cur = q1[front1++]; flag=1;    // 扩展正向队列

}

else

{

cur = q2[front2++]; flag=2;    // 扩展逆向队列

}

for (i=0; i<8; ++i)

{

next.x = cur.x + dx[i];

next.y = cur.y + dy[i];

if (next.x<0 || next.x>=n || next.y<0 || next.y>=n)

continue;

if (!vis[next.x][next.y])

{

vis[next.x][next.y] = flag;

step[next.x][next.y] = step[cur.x][cur.y] + 1;

if (flag==1)

q1[rear1++] = next;

else

q2[rear2++] = next;

}

else if (vis[cur.x][cur.y] != vis[next.x][next.y])

{

return step[cur.x][cur.y]+step[next.x][next.y]+1;

}

}

}

return -1;  // 若搜索不成功,表示不可达

}

int main()

{

int nCase,sx,sy,tx,ty,size,i,j;

scanf("%d", &nCase);

while (nCase--)

{

scanf("%d", &size);

for (i=0;i<size;i++)

for (j=0;j<size;j++)

vis[i][j]=step[i][j]=0;

scanf("%d %d", &sx, &sy);

scanf("%d %d", &tx, &ty);

if (sx==tx && sy==ty)

{

printf("0\n");

}

else

{

printf("%d\n",BFS(sx,sy,tx,ty,size));

}

}

return 0;

}

BFS(三):双向广度优先搜索的更多相关文章

  1. U1. 广度优先搜索(BFS)和 广度优先搜索(DFS)

    广度优先搜索用栈(stack)来实现,整个过程可以想象成一个倒立的树形: 1.把根节点压入栈中. 2.每次从栈中弹出一个元素,搜索所有在它下一级的元素,把这些元素压入栈中.并把这个元素记为它下一级元素 ...

  2. [MIT6.006] 13. Breadth-First Search (BFS) 广度优先搜索

    一.图 在正式进入广度优先搜索的学习前,先了解下图: 图分为有向图和无向图,由点vertices和边edges构成.图有很多应用,例如:网页爬取,社交网络,网络传播,垃圾回收,模型检查,数学推断检查和 ...

  3. 利用广度优先搜索(BFS)与深度优先搜索(DFS)实现岛屿个数的问题(java)

    需要说明一点,要成功运行本贴代码,需要重新复制我第一篇随笔<简单的循环队列>代码(版本有更新). 进入今天的主题. 今天这篇文章主要探讨广度优先搜索(BFS)结合队列和深度优先搜索(DFS ...

  4. 深度优先搜索DFS和广度优先搜索BFS简单解析(新手向)

    深度优先搜索DFS和广度优先搜索BFS简单解析 与树的遍历类似,图的遍历要求从某一点出发,每个点仅被访问一次,这个过程就是图的遍历.图的遍历常用的有深度优先搜索和广度优先搜索,这两者对于有向图和无向图 ...

  5. 三个水杯——java,广度优先搜索

    题目如下: 21-三个水杯 内存限制:64MB 时间限制:1000ms 特判: No通过数:51 提交数:137 难度:4 题目描述: 给出三个水杯,大小不一,并且只有最大的水杯的水是装满的,其余两个 ...

  6. 数据结构之 图论---基于邻接矩阵的广度优先搜索遍历(输出bfs遍历序列)

    数据结构实验图论一:基于邻接矩阵的广度优先搜索遍历 Time Limit: 1000MS Memory limit: 65536K 题目描述 给定一个无向连通图,顶点编号从0到n-1,用广度优先搜索( ...

  7. 深度优先搜索DFS和广度优先搜索BFS简单解析

    转自:https://www.cnblogs.com/FZfangzheng/p/8529132.html 深度优先搜索DFS和广度优先搜索BFS简单解析 与树的遍历类似,图的遍历要求从某一点出发,每 ...

  8. Leetcode之广度优先搜索(BFS)专题-详解429. N叉树的层序遍历(N-ary Tree Level Order Traversal)

    Leetcode之广度优先搜索(BFS)专题-429. N叉树的层序遍历(N-ary Tree Level Order Traversal) 给定一个 N 叉树,返回其节点值的层序遍历. (即从左到右 ...

  9. Leetcode之广度优先搜索(BFS)专题-994. 腐烂的橘子(Rotting Oranges)

    Leetcode之广度优先搜索(BFS)专题-994. 腐烂的橘子(Rotting Oranges) BFS入门详解:Leetcode之广度优先搜索(BFS)专题-429. N叉树的层序遍历(N-ar ...

随机推荐

  1. 关于 Facebook 的 React 专利许可证

    本文转载自:酷 壳 – CoolShell 作者:陈皓 随着 Apache.百度.Wordpress 都在和 Facebook 的 React.js 以及其专利许可证划清界限,似乎大家又在讨论 Fac ...

  2. 从源码角度看MySQL memcached plugin——0.大纲

    本系列文章介绍MySQL memcached plugin插件.剖析已经完成.先把链接弄好,内容会陆续补上. 大纲如下: 系统结构和引擎初始化(已完成) 线程模型和连接的状态机 containers表 ...

  3. win7在USB该解决方案不健全音箱

    Win7安装后,原XP在正常工作USB小喇叭不工作,重新安装声卡驱动程序仍然是相同的.后来,通过以下的得心应手最后一次尝试. 1.右键右下角喇叭button. 2.点击"播放设备" ...

  4. krpano全球漫游相同的声音和声音添加的场景(文章内容已移至krpano中国网站)

    请关注微信订阅号 krpano   watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvdG1ob2s=/font/5a6L5L2T/fontsize/400/f ...

  5. sql 多列求和

    列相加即可注意Null不可加,先用ISNULL方法验证,设置默认值 SELECT ID, Name, Province, City, District, ISNULL(row1, 0), ISNULL ...

  6. WPF编游戏系列 之九 物品清单再优化

    原文:WPF编游戏系列 之九 物品清单再优化        在"第三篇"和"第四篇"中通过用户控件和数据绑定功能对物品清单进行一些优化减少了部分C#代码,但感觉 ...

  7. Android微信支付SDK

    App对接微信调起微信支付需要在微信平台注册,鉴别的标识就是App的包名,所以将申请的包名单独打包成一个Apk文件,则在其他的App调起此Apk的时候同样可以起到调用微信支付的功能.这样就实现了调起微 ...

  8. 在云中生成和模拟 iOS

    原文:在云中生成和模拟 iOS 1.原文地址 https://msdn.microsoft.com/zh-cn/library/vs/alm/dn858446.aspx

  9. C++和QML混合的QT程序调试方法

    以前调试只是QML或者只是C++的QT程序很简单,断点打上,直接debug按钮一点,喝一口水,自然就停在断点了. 这次遇到C++和QML混合的程序,把CONFIG+=declarative_debug ...

  10. C#获取带汉字的字符串长度

    正常情况下,我们是直接去string的length的,但是汉字是有两个字节的,所以直接用length是错的.如下图: 所以应该用以下代码来获取长度: private void button1_Clic ...