双向广度优先搜索

  广度优先搜索遵循从初始结点开始一层层扩展直到找到目标结点的搜索规则,它只能较好地解决状态不是太多的情况,承受力很有限。如果扩展结点较多,而目标结点又处在较深层,采用前文叙述的广度搜索解题,搜索量巨大是可想而知的,往往就会出现内存空间不够用的情况。双向搜索和A算法对广度优先的搜索方式进行了改良或改造,加入了一定的“智能因素”,使搜索能尽快接近目标结点,减少了在空间和时间上的复杂度。

  (1)搜索过程
  有些问题按照广度优先搜索法则扩展结点的规则,既适合顺序,也适合逆序,于是我们考虑在寻找目标结点或路径的搜索过程中,初始结点向目标结点和目标结点向初始结点同时进行扩展—,直至在两个扩展方向上出现同一个子结点,搜索结束,这就是双向搜索过程。出现的这个同一子结点,我们称为相交点,如果确实存在一条从初始结点到目标结点的最佳路径,那么按双向搜索进行搜索必然会在某层出现“相交”,即有相交点,初始结点一相交点一目标结点所形成的一条路径即是所求路径。
  例如:移动一个只含字母A和B的字符串中的字母,给定初始状态为(a)表,目标状态为(b)表,给定移动规则为:只能互相对换相邻字母。请找出一条移动最少步数的办法。

[AABBAA]  [BAAAAB]
(a)       (b)

  解题分析:从初始状态和目标状态均按照深度优先搜索扩展结点,当达到以下状态时,出现相交点,如图1(a),结点序号表示结点生成顺序。
双向扩展结点:

                   顺序                           逆序
1                              1
___AABBAA___                      BAAAAB
2   /            /  3                2 /    / 3
__ABABAA__            AABABA           ABAAAB  BAAABA
4 /    |5    / 6       7 /    / 8       4 /
ABBAAA  BAABAA  ABAABA  AAABBA  AABAAB    AABAAB
(a)            图1               (b)

  顺序扩展的第8个子结点与逆序扩展得到的第4个子结点就是相交点,问题的最佳路径如图2。

[AABBAA]—[AABABA]—[AABAAB]—[ABAAAB]—[BAAAAB]
                          图2

  从搜索的结点来看,双向广度要简单得多。假设每一个子结点可以扩展的子结点数是X,不计约束条件,以完全X叉树计算,那么用广度优先搜索一个长度为I的最佳路径的解,共需要扩展结点X(XL-1)÷(X-1)。从双向搜索来看,设正个方向的搜索在第y层找到向交点,那么正向共搜索了X(XY-1)÷(X-1),逆向扩展的结点数为(XL-y-1)÷(X-1),两个方向共搜索了 X(XY+XL-Y-2)÷(X-1)。我们假设L为偶数,则Y=L/2,双向搜索扩展的结点数约为单向搜索的2÷(XL/2+1)*100%,相对减少(XL/2-1)÷(XL/2+1)*100%。
  当然这里只是作个粗略的比较,事实上在其它一般情况下,双向搜索搜索量都要比单向搜索来的少。

  (2)结点扩展顺序
    双向扩展结点,在两个方向的扩展顺序上,可以轮流交替进行,但由于大部分的解答树并不是棵完全树,在扩展完一层后,下一层则选择结点个数较少的那个方向先扩展,可以克服两个方向结点生成速度不平衡的状态,明显提高搜索效率。

  (3)数据结构
    单向广度优先搜索需建立两个表OPEN和CLOSED,用来存储生成结点和已扩展结点,双向搜索从两个方向进行扩展,我们建立两个二维表OPEN,CLOSED,OPEN[1],CLOSED[1], OPEN[2],CLOSED[2]分别存储两个方向上的生成结点和已扩展结点,OPEN仍然是具有“先进先出”的队列结构。为编程方便,我们采用基于广度优先搜索算法的双向,建立三个二维指针:Q1,Q2,Q3其作用如下:
    Q1[1],Q1[2]:分别指向两个方向上当前待扩展层的第一个结点。
    Q2[1],Q2[2]:分别指两个方向上队尾新产生的结点。
    Q3[1],Q3[2]:分别指向两个方向上下一层的第一个结点位置。
    为了区分当前搜索方向,设方向标志:
    t=1表示处于正向搜索,t=2表示处于逆向搜索。
    Fail—有一个方向搜索失败时,为真,并且结束搜索过程,否则为假。
    I—全局变量,指向当前要扩展的结点。

  (4)算法描述

Program DOUBFS;
初始化,初始结点,和目标结点分别进入OPEN[1]和OPEN[2]表;
Q1[1]:=1;Q2[1]:=1;Q1[2]:=1;Q2[2]:=1;
repeat
if (Q2[1]-Q1[1])<=(Q2[2]-Q1[2]) then t:=1
else t:=2;
for I:=Q1[t] to Q2[t] do
EXPEND(t);{扩展第1个结点}
Q1[t]:=Q3[t];
until(Q1[t]>Q2[t]);
其中过程EXPEND(t)的结构如下:
Procedure expand(t:integer);
Var j:integer;
begin
for j:=1 to n do {n为最多后继状态数}
begin
产生i点的第j个后继状态,将它加入到队尾(Q2[t]+1);
if新结点未与其上一层以上的所有点重复
then if isans(t) then [输出解;halt;] else
else将新点从队列中去掉;(Q2[t]-1)
end;    -
end;
判断是否是相交点的过程isans(t)如下:
function isans(t:integer):Boolean;
var j,t1:integer;
begin
if t=1 then t1:=2 else t1:=1;
isans:=false;
forj:=Q1[t1] to Q2[t1] do
if Q2[t]=j {Q2[t]新结点是相交点}
then [isans:=true;exit];
end;

(5)例题应用
    【例1】魔方问题
    在魔方风靡全球之后,Rubik先生发明了它的简化版——魔板。魔板由8个同样大小的方块组成,每个方块的颜色均不相同,本题中以数字1—8分别表示,可能出现在魔板的任一位置,任一时刻魔板的状态可以用方块的颜色序列表示:从魔板的左上角开始,按顺时针方向依次写下各方块的颜色代号,得到的数字序列即可表示此时魔板的状态。
    例如,序列(1,2,3,4,5,6,7,8)表示题中魔板的初始状态。

 1 2 3 4
8 7 6 5

  对于魔板,可以施加三种不同的操作,分别以A,B,C标识。
    具体操作方法如下:
    A:上下行互换,如上图可以变换为状态87654321
    B:每行同时循环右移一格,如上图可以变换为41236785
    C:中间4个方块顺时针旋转一格,上图可以变换为17245368。
    应用这三种基本操作,可以由任一状态达到任意另一状态。
    子任务A:
    请编一程序,对于输入的一个目标状态,寻找一种操作的序列,使得从初始状态开始,经过此操作序列后使该魔板变为目标状态。
    子任务B:
    如果你的程序寻找到的操作序列在300步以内,会得到子任务B的分数。
    输入数据:
    文件名INPUT.TXT,第一行包含8个以一个空格相隔的正整数,表示目标状态。
    输出数据:
    输出文件名为OUTPUT.TXT,在第一行输出你的程序寻找到的操作序列的步数L,随后L行是相应的操作序列,每行的行首写一个字符,代表相应的操作。
    【算法分析】
A.空间复杂度如果解的步数为n,则状态表示空间约占3 n
B.基本算法本题是典型的广度优先算法题,很自然的想到能否构造启发算法。但本题不同于八数码,很难找到一个估价函数。因为每一种状态的达到都有三种本质不同的方法,因此在计算某一状态的估价值时,容易将状态各个数字的最少移动步数重复计算,或忽略计算,不能够构造出一个恰当函数f*使得f*< f。因此不宜采用启发算法得出最优解,而只能得可行解。现在考虑双向广度优先搜索。
双向搜索与单向广度搜索相比的优点在于节省了存储空间,避免搜索出更多的无用结点,提高丁搜索速度,如果采用动态数组存储(655 350Byte)可以做到大约21~22步,甚至可以更多。
    【参考程序】

Program Rubic;
Uses Crt;
Const
n=8;
input = 'input.txt';
Type
dar = record
f: integer;
d: array[1..n] of Integer;
End;
Var
Cab: array[1..2,1..7500] of ^dat;
dat1,dat2: dat;
Procedure Init;
Var
f: text;
i,i: Integer;
Begin
assign(f, input);
reset(f);
new(cab[1,1]);
for I:=1 To n do
read(f,cab[1,I]^.d[i]);
cab[1,1]^.f := 0;
readln(f);
new(cab[2,1 ] );
for I := 1 tondo
read(f,cab[2,1]^.d[i]);
readln(f);
cab[2,1]^.f := 0;
End;
Function Check(x,y: Integer) :boolean;
Var
i,j,k: Integer;
ok: Boolean;
Begin
for i:= 1 to y-1 do
Begin
forj := 1 to n do
if cab[x,i]^.d[j] < > dat1.d[j] then
Begin
ok := true;
Break;
End else Ok:= false;
if not ok then break;
End;
Check := ok;
End;
Function CheckOut(X,Y: Integer;Var a: Integer): Boolean;
Var
i,j,k: Integer;
ok: Boolean;
Begin
a:=0;
fori := 1 to y do
Begin
for j := 1 to n do
if cab[x,i]A.d[j] < > dat1.d[j] then
Begin
ok := true;
Break;
End else Ok: = false;
if not ok then
Begin
a:= i;
break;
End;
End;
CheckOut := ok;
End;
Procedure Print(a,b,c: Integer);
Var
i,j,k,l: Integer;
s1,s2: array[1..30] of Integer;
x,y: Integer;
Begin
fillchar(s1,sizeof(s1), 0);
fillchar(s2,sizeof(s2) ,0);
if a = 1 then
Begin
i:=1;
j:=2;
End else
Begin
i:=2;
j:=1;
End;
k:= O;
Repeat
inc(k);
s1[k] := b;
b := cab[i,b]^.f;
Until b = 0;
l:= 0;
Repeat
inc(l);
s2[l] := c;
c := cab[j,c]^.f;
Until c = 0;
if a = 1 then
begin
for x := k downto 1 do
Begin
for y := 1 to n do
write(cab[1,s1[x]]^.d[y]: 3);
if y mod 4 = 0 then writeln;
End;
writeln('-----');
Readln;
End;
for x := 2 to l do
Begin
for y := 1 to n do
Begin
write(cab[2,s2[x]]^.d[y]: 3);
if y mod 4 = 0 then writdn;
End;
writeln('-----');
Readln;
End;
End
else
Begin
for x := l downto 1 do
Begin
for y := 1 to n do
write(cah[1,s2[x]]^.d[y]: 3);
if y mod 4 = 0 then writdn;
End;
writeln('-----');
Readln;
End;
for x := 2 to k do
Begin
for y:= 1 to n do
begin
write(cab[2,s1[x]]^.d[y]: 3);
if y mod 4 = 0 then writeln;
End;
writeln('-----');
Readln;
End;
End;
Halt;
End;
Procedure Double;
Var
i,j: array[1..2] of Integer;
Out: Boolean;
k,l,kk,s: Integer;
i[1] := 1;
i[2] := 1;
j[1] := 2;
j[2] := 2;
Out := false;
repeat
kk:=2;
k:=1;
{--1--}
dat1.d := Cab[k,i[k]]^.d;
for l := 1 to 4 do
Begin
dat1.d[l] := dat1.d[l+4];
dat1.d[l+4] := cab[k,i[k]]^.d[l];
End;
dat1.f := i[k];
if Check(k,j[k]) then
Begin
new(mb[kd[k]]);
mb[kd[k]]^:= dat1;
inc(j[k]);
if Not CheckOut(kk,j[kk] - 1,s) then Print(k,j[k] - 1 ,s);
End;
{--2--}
dat1.d := Cab[k,i[k]]^.d;
dat1.d[3]: = dat1.d[2];
dat1.d[2] := dat1.d[5];
dat1.d[5] := dat1.d[6];
dat1.d[6] := cab[k,i[k]]^.d[3];
dat1.f: = i[k];
if Check(k,j[k])  then
Begin
new(cab[k,j[k]]);
cab[k,j[k]]^ := dat1;
inc(j[k]);
if NOt CheckOut(kk,j[kk] - 1,s) then Print(k,j[k] - 1,s);
End;
{--3--}
dat1.d:= Cab[k,i[k] ]^.d;
dat1.d[4]: = dat1.d[3];
dat1.d[3]: = dat1.dj2];
dat1.d[2] := dat1.d[1];
dat1.dj1] := cab[k,i[k]]^.d[4];
dat1.f := i[k];
if Check(k,j[k]) then
Begin
new(cab[k,j[k]]);
cab[k,j[k]]^:= dat1;
inc(j[k]);
if Not CheckOut(kk,j[kk]- 1,s) then Print(k,j[k] - 1,s);
End;
Inc(i[k]);
kk:= 1;
k:=2;
{--1--}
dat1.d := Cab[k,i[k]]^.d;
for l := 1 to 4 do
Begin
dat1.d[l] := dat1.d[l+4];
dat1.d[l+4] := cab[k,i[k]]^.d[l];
End;
dat1.f:= i[k];
if Check(k,j[k]) then
Begin
new(cab[k,j[k] ]);
cab[k,j[k]]^:= dat1;
inc(j[k]);
if Not CheckOut(kk,j[kk] - 1 ,s) then Print(k,j[k] - 1 ,s);
End;
{--2--}
dat1.d := Cab[k,i[k]]^.d;
daft. d[2] : = dat1. d[3];
dat1.d[3] := dat1.d[6];
dat1.d[6]: = dat1.d[5];
dat1.d[5] := cab[k,i[k]]^.d[2];
dat1.f: = i[k];
if Check(k,j[k]) then
Begin
new(cab[k,j[k]]);
cab[k,j[k]]^:= dat1;
ine(j[k]);
if Not CheckOut(kk,j[kk] - 1 ,s) then Print(k,j[k] - 1 ,s);
End;
{---3---}
dad.d:= Cab[k,i[k]]^.d;
dat1.d[1] := dat1.d[2];
dat1.d[2] := dat1.d[3];
dat1.d[3] := dat1. d[4];
dat1.dj4] := cab[k,i[k] ]^.d[1];
dat1.f := i[k];
if Check(k,j[k]) then
Begin
new(cab[k,j[k]]);
cab[k,j[k]]^ := dat1;
inc(j[k]);
if Not CheckOut(kk,j[kk] - 1,s) then Prim(k,j[k] - 1,s);
End;
Inc(i[k]);
until Out;
End;
Begin
INit;
clrscr;
Double;
End.

双向广度优先搜索(介绍)相关推荐

  1. 二阶魔方还原 Rubik’s Cube 双向广度优先搜索

    1. 算法简介 使用搜索算法完成二阶魔方从任意初始状态向目标状态的操作转换.         根据已有的研究,二阶魔方的上帝之数为11(进行FTM计数)或14(进行QTM计数),本算法采用QTM计数对 ...

  2. 八数码问题——双向广度优先搜索解决

    八数码问题:在3×3的方格棋盘上,摆放着1到8这八个数码,有1个方格是空的,其初始状态如图1所看到的,要求对空格运行空格左移.空格右移.空格上移和空格下移这四个操作使得棋盘从初始状态到目标状态. 搜索 ...

  3. 小游戏系列算法之五广度优先搜索,双向广搜,八数码,华容道

    前段时间在玩仙五前,遇上了蚩尤冢拼图这个小游戏. 其实就是八数码问题,一直想着如何才能用最少步数求解,于是就写了个程序. Q1:什么是八数码问题? A1:首先假定一个3*3的棋盘(如上图),分别有1, ...

  4. 《算法图解》学习笔记(六):图和广度优先搜索(附代码)

    欢迎关注WX公众号:[程序员管小亮] python学习之路 - 从入门到精通到大师 文章目录 欢迎关注WX公众号:[程序员管小亮] [python学习之路 - 从入门到精通到大师](https://b ...

  5. 【LeetCode 】试题总结:广度优先搜索(BFS)

    [LeetCode ]试题总结:广度优先搜索(BFS) 一.数据结构:二叉树中的 BFS (一).二叉树的堂兄弟节点 试题链接 解题思路 代码 (二).二叉树的层序遍历 II (三).二叉树的锯齿形层 ...

  6. 广度优先搜索解决欧拉回路时间复杂度_迷宫搜索类的双向bfs问题(例题详解)

    前言 文章若有疏忽还请指正! 更多精彩还请关注公众号:bigsai 头条号:一直码农一直爽 在搜索问题中,以迷宫问题最具有代表性,无论是八皇后的回溯问题,还是dfs找出口,bfs找最短次数等等题目的问 ...

  7. 限界分支法(实际上没有剪枝,介绍的是广度优先搜索):01背包问题,队列实现方式(FIFO)

    限界分支法:队列实现方式 前面已经介绍过限界分支法大部分是基于广度优先搜索,广度优先搜索一般借助于队列实现,剪枝的情况可以借助于优先级队列. 实现如下: #%% class FIFO_01_Pack: ...

  8. 深度优先搜索和广度优先搜索的比较与分析

    一)深度优先搜索的特点是: (1)无论问题的内容和性质以及求解要求如何不同,它们的程序结构都是相同的,即都是深度优先算法(一)和深度优先算法(二)中描述的算法结构,不相同的仅仅是存储结点数据结构和产生 ...

  9. 6.3.1广度优先搜索

    ヾ(≧O≦)"好喜欢你 各位同学大家好,上一节课我们介绍了.各位同学大家好,上一节课我们介绍了有关图的基本操作,那么除了上一节课我们介绍的那些相关基本操作之外,还有一种非常重要的操作,就是有 ...

最新文章

  1. MOSS的CSS样式说明,一个老外总结的
  2. 人为「刷」论文引用量,IEEE高级会员被终身“禁赛”,奖项被撤销
  3. Asp.Net统一前后端提示信息方案
  4. 《JavaScript设计模式与开发实践》原则篇(3)—— 开放-封闭原则
  5. fpga板子怎么和电脑连_windows7台式电脑怎么连接路由器?台式win7电脑连路由器步骤...
  6. getMap(Thread t)
  7. 复旦大学计算机学院专业硕士学费,复旦大学计算机在职研究生学费一年要交的学费多少?...
  8. C++标准转换运算符:const_cast
  9. TAUCS库的编译(vs2010)
  10. django中url 和 path 的区别
  11. java流程控制if_[Java]Java基本语法结构(运算符,流程控制语句,if语句)
  12. 顶级外语学习资源[转] 近600个教学学习资料链接
  13. 动态更换 Shape 的颜色
  14. halcon之屌炸天的自标定(1)
  15. C++ Primer Message和Folder类
  16. VS Code 中解决 C++ 代码编写时的爆红
  17. minus subtract deduct这三个单词的区别
  18. V4L2 驱动框架概览
  19. 7 轮面试后,还是挂了 | Google 中国面经分享
  20. 使用Python来调教我的微信

热门文章

  1. 6月适配进展|优炫数据库与40余款产品完成兼容认证
  2. cv mat 灰度值和_访问OpenCV中灰度图像的像素值
  3. iThoughtsX for Mac 5.9 中文破解版下载 强大的思维导图软件
  4. 使用NATS消息中间件实现云边协同
  5. 广东推进工业机器人进集群 2017年应用机器人将超8万台
  6. Android 布局平分间距
  7. 我用wxPython搭建GUI量化系统之财务选股工具与股票行情界面切换
  8. 数据挖掘之关联规则挖掘之SETM算法实现
  9. STC - 同时外挂扩展RAM和12864时, C库函数失效的问题
  10. vxWorks PCIE控制器驱动解读