广度搜索.docx
《广度搜索.docx》由会员分享,可在线阅读,更多相关《广度搜索.docx(18页珍藏版)》请在冰豆网上搜索。
![广度搜索.docx](https://file1.bdocx.com/fileroot1/2022-12/13/edaa4472-fdfe-46d1-b7a9-eacef097485e/edaa4472-fdfe-46d1-b7a9-eacef097485e1.gif)
广度搜索
第二节广度优先搜索
一、广度优先搜索
在深度优先搜索中,深度越大的结点越先得到扩展;如果将深度越小的结点越先得到扩展,那便是广度优先搜索。
广度优先搜索类似于树的按层次遍历的过程,它从初始点开始,应用算符生成第一层结点,检查目标结点是否在这些后继结点中;若没有,再用算符将所有第一层的结点逐一扩展,生成第二层结点,并逐一检查第二层结点中是否包含目标结点;若没有,再用算符将所有第二层的结点逐一扩展,生成第三层结点,并逐一检查第三层结点中是否包含目标结点;……如此依次扩展、检查下去,直到发现目标结点为止。
广度优先搜索为了满足先生成的结点先扩展的原则,采用队列的数据结构来存贮结点。
队列是一种线性表,对于它所有的进队都在表尾的一端进行,所有的出队都在表首的一端进行。
如同现实生活中的等车、买票的排队,新来的总是加入队尾,每次离开的总是队首的人。
例1-2-1下面是六个城市之间道路联系的示意图,连线表示两城市之间有道路相通。
请编一程序,由计算机找出从C1城到C6城的没有重复城市的所有不同的路径。
const
link:
array[1..5,1..6]ofbyte=((0,1,1,0,0,0),
(1,0,1,1,1,0),
(1,1,0,1,1,0),
(0,1,1,0,1,1),
(0,1,1,1,0,1));
type
ft=setof1..6;
var
qm{队列},pnt{parent父节点}:
array[1..100]ofbyte;
f{走到这里通过的城市}:
array[1..100]offt;
fs:
ft;
i,k,closed,open:
byte;
procedureprint;
var
n,i,j:
byte;
s:
array[1..6]ofbyte;
begin
i:
=open;
n:
=0;
whilei>0dobegin
n:
=n+1;
s[n]:
=i;
i:
=pnt[i];
end;
write('Dep=',n-1,':
1');
forj:
=n-1downto1dobegin
write('->',qm[s[j]]);
end;
readln;
end;
begin
f[1]:
=[1]{城市1已到};qm[1]:
=1{队列的第一个节点是1};pnt[1]:
=0;{没有父辈节点}
closed:
=0;open:
=1;
repeat
inc(closed);
k:
=qm[closed];
ifk<>6thenbegin
fs:
=f[closed];
fori:
=2to6do
if(not(iinfs))and(link[k,i]>0)thenbegin
inc(open);
qm[open]:
=i;
f[open]:
=fs+[i];
pnt[open]:
=closed;
ifi=6thenprint;
end;
end;
untilclosed>=open;
end.
在广度优先搜索中,我们将扩展出来的结点存贮在一个称作qm的数组里,qm数组采用“先进先出”的队列结构,设两个指针closed和open,分别是队首指针和队尾指针。
其中qm[1..closed-1]存贮已扩展的结点(即这些结点的子结点已扩展出);qm[closed..open]存贮待扩展结点(即这些结点的子结点尚待扩展)。
当closed>=open则表示队列空,结束。
pnt为父辈结点数组,它记录了每个结点的父辈结点,当找到目标后,可沿着父辈结点倒串上去,输出路径方案。
在广度优先搜索中,第一个达到目标结点的,即是最短路径。
例1-2-2有一个由四个1和四个0,中间有一个空格组成的字符串‘11110000’,现规定:
①1只能向右运动,0只能向左运动。
②空格左右的1或0可以移动,进入空格。
③1可以跳过一个0,进入空格;0也可以跳过一个1,进入空格。
要求在符合上述规定的方式下,以最少的步骤,将其变为字符串‘00001111’,编程打印输出运动的每一步。
type
a9=string[9];
qt=record
a:
a9;
x,pnt:
byte;
end;
var
qm:
array[byte]ofqt;
temp:
qt;
closed,open:
byte;
Procedureprint;
Var
buf:
array[1..30]ofbyte;
i,j:
byte;
begin
i:
=open;j:
=0
whilei>0dobegin
inc(j);
buf[j]:
=i;
i:
=qm[i].pnt;
end;
fori:
=jdownto1dobegin
write('No.',i-j:
2,':
',qm[buf[i]].a);
readln;
end;
halt;
end;
procedurecomp;
vari:
byte;
begin
fori:
=1toopendo
ifqm[i].a=temp.athenexit;
open:
=open+1;
qm[open].a:
=temp.a;
qm[open].x:
=temp.x;
qm[open].pnt:
=closed;
iftemp.a='00001111'thenprint;
end;
procedureopa;
begin
temp:
=qm[closed];
withtempdo
if(x>1)and(a[x-1]='1')thenbegin
a[x-1]:
='';
a[x]:
='1';
x:
=x-1;
comp;
end;
end;
procedureopb;
begin
temp:
=qm[closed];
withtempdo
if(x<9)and(a[x+1]='0')thenbegin
a[x+1]:
='';
a[x]:
='0';
x:
=x+1;
comp;
end;
end;
procedureopc;
begin
temp:
=qm[closed];
withtempdo
if(x>2)and(a[x-1]='0')and(a[x-2]='1')thenbegin
a[x-2]:
='';
a[x]:
='1';
x:
=x-2;
comp;
end;
end;
procedureopd;
begin
temp:
=qm[closed];
withtempdo
if(x<8)and(a[x+1]='1')and(a[x+2]='0')thenbegin
a[x+2]:
='';
a[x]:
='0';
x:
=x+2;
comp;
end;
end;
begin
withqm[1]dobegin
a:
='11110000';
x:
=5;
pnt:
=0;
end;
closed:
=0;open:
=1;
whileclosedclosed:
=closed+1;
opa;
opb;
opc;
opd;
end;
end.
例1-2-3在魔方风靡全球后,Rubik先生发明了它的简化版——魔板,如下图:
魔板由6个同样大小的方块组成,每个方块的颜色均不相同,本题中用数字1–6分别表示,可能出现在魔板的任一位置。
对魔板可施加三种不同的操作,分别以a、b、c标识,具体操作方法如下:
应用三种基本操作,可由任一状态到达任意另一状态。
编一程序,对于输入的一个初始状态和一个目标状态,寻找一种最少的操作步骤,打印输出从初始状态到目标状态的每一步操作和状态值。
type
qt=record
x,y:
integer;
op:
char;
pnt:
integer;
end;
var
qm:
array[1..720]ofqt;
closed,open,k,l,m,n:
integer;
procedureprint;
var
s:
array[1..30]ofinteger;
i,j:
integer;
begin
j:
=0;
i:
=open;
whilei>0dobegin
j:
=j+1;
s[j]:
=i;
i:
=qm[i].pnt;
end;
writeln(qm[1].x);
writeln(qm[1].y);
writeln;
fori:
=j-1downto1dobegin
writeln('Oprate:
',qm[s[i]].op);
writeln(qm[s[i]].x);
writeln(qm[s[i]].y);
readln;
end;
halt;
end;
procedurecomp(opx:
char);
var
i:
integer;
begin
fori:
=1toopendo
if(m=qm[i].x)and(n=qm[i].y)thenexit;
open:
=open+1;
qm[open].x:
=m;qm[open].y:
=n;
qm[open].op:
=opx;qm[open].pnt:
=closed;
if(m=k)and(n=l)thenprint;
end;
procedureopa;
begin
m:
=qm[closed].y;
n:
=qm[closed].x;
comp('a');
end;
procedureopb;
begin
m:
=(qm[closed].xmod10)*100+(qm[closed].xdiv10);
n:
=(qm[closed].ymod10)*100+(qm[closed].ydiv10);
comp('b');
end;
procedureopc;
var
a,b,c,d:
integer;
begin
a:
=qm[closed].xdiv100;
b:
=qm[closed].xmod100div10;
c:
=qm[closed].ydiv100;
d:
=qm[closed].ymod100div10;
m:
=c*100+a*10+(qm[closed].xmod10);
n:
=d*100+b*10+(qm[closed].ymod10);
comp('c');
end;
begin
write('InputSourceM(1,2,3):
');readln(qm[1].x);
write('InputSourceN(6,5,4):
');readln(qm[1].y);
writeln;
write('InputObjectK(1,2,3):
');readln(k);
write('InputObjectL(6,5,4):
');readln(l);
writeln;
qm[1].pnt:
=0;closed:
=0;open:
=1;
whileclosedclosed:
=closed+1;
opa;
opb;
opc;
end;
end.
例1-2-4八数码问题:
在3*3的棋盘上,摆有八个棋子,每个棋子上标有1至8的某一个数字,棋盘上留有一个空格,空格周围的棋子可以移到空格中。
要求给出一种初始状态和一种目标状态,找出一种最少步骤的移动方法,实现从初始状态到目标状态的转变。
例如:
初始状态目标状态
283123
16484
75765
Type
a33=array[1..3,1..3]ofbyte;
a4=array[1..4]of-1..1;
node=record
ch:
a33;
y,x:
byte;
pnt:
word;
end;
Const
start:
a33=((2,8,3),(1,6,4),(7,0,5));
goal:
a33=((1,2,3),(8,0,4),(7,6,5));
x1=2;y1=3;
max=4800;
dx:
a4=(0,-1,0,1);
dy:
a4=(-1,0,1,0);
Var
data:
array[1..max]ofnode;
temp:
node;
r,tx,ty:
byte;
closed,open:
word;
Functioncheck(k:
byte):
boolean;
begin
ty:
=temp.y+dy[k];
tx:
=temp.x+dx[k];
if(tyin[1..3])and(txin[1..3])
thencheck:
=true
elsecheck:
=false;
end;
Functiondupe:
boolean;
Var
i:
word;
j,k:
integer;
b:
boolean;
begin
i:
=0;
repeat
inc(i);
b:
=true;
forj:
=1to3do
fork:
=1to3do
ifdata[i].ch[j,k]<>data[open].ch[j,k]thenb:
=false;
untilbor(i>=open-1);
dupe:
=b;
end;
Functiongoals:
boolean;
Vari,j:
byte;
begin
goals:
=false;
fori:
=1to3do
forj:
=1to3do
ifdata[open].ch[i,j]<>goal[i,j]thenexit;
goals:
=true;
end;
Proceduretrace;
Vari,j:
byte;
begin
writeln('closed=',closed,'open=',open,'oprater=',r);
fori:
=1to3dobegin
forj:
=1to3do
ifdata[open].ch[i,j]=0thenwrite('')
elsewrite(data[open].ch[i,j]);
writeln
end;
readln;
end;
Procedureprint;
Var
b:
array[1..20]ofword;
i,j:
word;
k,n:
byte;
begin
n:
=0;
i:
=open;
whilei>0dobegin
inc(n);
b[n]:
=i;
i:
=data[i].pnt;
end;
writeln('Staps:
',n-1:
5);
fork:
=ndownto1dobegin
fori:
=1to3dobegin
forj:
=1to3do
ifdata[b[k]].ch[i,j]=0
thenwrite('')
elsewrite(data[b[k]].ch[i,j]);
writeln;
end;
readln;
end;
halt;
end;
begin
closed:
=0;open:
=1;
withdata[1]dobegin
ch:
=start;
y:
=y1;x:
=x1;
pnt:
=0;
end;
repeat
inc(closed);
temp:
=data[closed];
forr:
=1to4do
ifcheck(r)thenbegin
inc(open);
data[open]:
=temp;
withdata[open]dobegin
ch[y,x]:
=ch[ty,tx];
ch[ty,tx]:
=0;
y:
=ty;x:
=tx;
pnt:
=closed;
end;
{trace;}
ifdupethendec(open)
elseifgoalsthenprint;
end;
until(closed>=open)or(open>max-3);
writeln('Nosolution!
');
readln;
end.
二、队列与广度优先搜索
队列是不同于栈的另一种线性表。
在日常生活中,无论是购物、订票或候车都有可能要排队。
排队所遵循的原则是“先来先服务”,后来者总是加到队尾,排头者总是先离开队伍。
队列就是从日常生活中的排队现象抽象出来的。
所谓队列,就是允许在一端进行插入,在另一端进行删除的线性表。
允许插入的一端称为队尾,通常用一个队尾指针open指向队尾元素,即open总是指向最后被插入的元素;允许删除的一端称为队首,通常也用一个队首指针closed指向排头元素的前面。
初始时closed=open=0(如图)。
显然,在队列这种数据结构中,最先插入在元素将是最先被删除;反之最后插入的元素将最后被删除,因此队列又称为“先进先出”(FIFO—firstinfirstout)的线性表。
与栈相似,队列的顺序存储空间可以用一维数组q[1‥m]模拟:
Q:
1m
我们按照如下方式定义队列:
Const
M=队列元素的上限;
Type
Equeue=array[1..m]ofqtype;{队列的类型定义}
Var
qm:
equeue;{队列}
open,closed:
integer;{队尾指针和队首指针}
队列的运算主要有两种
1.过程ADD(qm,x,open)—在队列qm的尾端插入元素x
procedureADD(varqm:
equeue;x:
qtype;varopen:
integer);
begin
ifopen=m
thenwriteln(‘Overflow’){上溢}
elsebegin{后移队尾指针并插入元素x}
open:
=open+1;qm[open]:
=x;
end;
end;
2.过程DEL(qm,y,closed,open)—取出qm队列的队首元素y
procedureDEL(varqm:
equeue;vary:
qtype;varclosed,open:
integer);
begin
ifclosed=open
thenwriteln(‘underflow’){下溢}
elsebegin{后移队首指针并取出队首元素}
closed:
=closed+1;y:
=qm[closed];
end;
end;
由于队列只能在一端插入,在另一端删除,因此随着入队及出队运算的不断进行,就会出现一种有别于栈的情形:
队列在数组中不断地向队尾方向移动,而在队首的前面产生一片不能利用的空闲存储区,最后会导致当尾指针指向数组最后一个位置(即open=m)而不能再加入元素时,存储空间的前部却有一片存储区无端浪费,这种现象称为“假溢出”。
下图给出了一个“假溢出”的示例:
m
m
m
Open→m
Am
“假溢出”
A4
open→3
A3
closed→3
Closed→3
2
A2
2
2
1
1
A1
1
1
Closed,open→Closed→
初始时队列空加入三个元素删除三个元素队列空加入m-3个元素队列满
closed=open=0closed=0open=3closed=open=3closed=3open=m
为了解决“假溢出”的问题,我们不妨作这样的设想:
在队列中,当存储空间的最后一个位置已被使用而要进行入队运算时,只要存储空间第一个位置空闲,便可将元素加入到第一个位置,即将存储空间的第一个位置作为队尾。
采用首尾相接的队列结构后,可以有效地解决假溢出的问题,避免数据元素的移动,这就是所谓的循环队列。
下图给出了循环队列的结构。
循环队列将队列存储空间的最后一个位置绕到第一个位置,形成逻辑上的环状空间,供队列循环使用,循环队列的存取方法亦为“先进先出”。
对循环队列操作有以下几种状态:
初始时队列空,队首指针和队尾指针均指向存储空间的最后一个位置,
即closed=open=m。
●入队运算时,尾指针进一,即
open:
=open+1;
ifopen=m+1thenopen:
=1;
这两条语句可用一条语句替代:
open:
=openmodm+1;
●出队运算时,首指针进一,即
closed:
=closed+1;ifclosed=m+1thenclosed:
=1;
这两条语句可用一条语句替代:
closed:
=closedmodm+1;
●队列空时有closed=open。
●队列满时有closed=openmodm+1。
(为了区分队列空和队列满,改用“队尾指针追上队首指针”这一特征作为队列满标志。
这种处理方法的缺点是浪费队列空间的一个存储单元)
循环队列的运算有两种:
1.过程ADD2(qm,x,open)—在循环队列qm中插入一个新元素x
procedureADD2(varqm:
e