您的位置:首页 > 其它

宽度优先搜索

2010-10-11 21:18 225 查看
4.3 宽度优先搜索

宽度优先搜索(又称广度优先搜索)与深度优先搜索同为搜索,但策略完全不同。

深度优先问题就是一般所说的递归、回溯问题,每次沿一个节点展开其下层一个节,然后再下层一个节点,直到找到一个结果为止再返回上层。

宽度优先搜索是每次把当前层每个节点对应的下一层所有节点全部展开,再判断是否有目标状态出现,如果有则这就是最少步数的答案;否则再把展开的新一层的下一层全部展开,直到展开的节点中出现目标状态为止。

宽度优先搜索问题中的每个节点要存放哪些数据是很重要的,一般用record型自定义型数据类型存放。一般至少要存放的数据有:父节点、当前状态数据。父节点是用来存放其父节点,以便在找到结果时打印答案。另外,宽度搜索中要判重(一般通过一个逻辑数组)。

广度优先搜索基本框架:

Procedure Bfs;

Begin

把所有结点标为“未访问”

将起点入队,并标记为“已访问”

repeat

for 检查所有与队首结点相连的结点 do

begin

if 新结点合法 且 新结点没有被访问过 then

begin

将新结点放入队列

计算新结点的最短路径值(=到达它的结点的路径值+1)

如果该新结点就是目标点,则输出最短路径值并退出

end;

end;

队首出队 (inc(head))

until 队列为空 (head>tail)

输出无解信息。(有解的已经在上面直接退出了)

End;

另外,如果题目没有保证初始状态一定不等于目标状态,需要在运行BFS之前特判一下是否发生初始状态直接和目标状态相等的情况。

深搜和广搜的区别:

深搜并不能保证第一次遇到目标点就是最短路径,因此要搜索所有可能的路径,因此要回溯,标记做了之后还要取消掉,因此同一个点可能被访问很多很多次。而广搜由于它的由近及远的结点扩展顺序,结点总是以最短路径被访问。一个结点如果第二次被访问,第二次的路径肯定不会比第一次的短,因此就没有必要再从这个结点向周围扩展――第一次访问这个结点的时候已经扩展过了,第二次再扩展只会得到更差的解。因此做过的标记不必去掉。因此同一个点至多只可能被访问一次。每访问一个结点,与它相连的边就被检查一次。因此最坏情况下,所有边都被检查一次,因此时间复杂度为O(E)。

例9:有两个无刻度标志的水壶,分别可装x升和y升(x,y为整数,x、y<=100)的水。设另有一水缸,可用来向水壶灌水或倒出水,两水壶间,水也可以相互倾灌。已知x升为满壶,y升为空壶。问如何通过倒水或灌水操作用最少步数能在y升壶中量出z(z<=100)升的水来。

输入样例1:

8 5 3

输出样例1:

step 0:8 0

step 1:3 5

step 2:3 0

step 3:0 3

type

atype=record

father,a,b:integer;

end;

btype=array [0..100,0..100] of boolean;

var x,y,z:integer;

data:array [1..10000] of atype;

bool:^btype;

procedure bfs;

var i,j,k,L:integer; open,closed:integer;

function min(a,b:integer):integer;

begin

if a<b then min:=a else min:=b;

end;

procedure out;

var i,j:integer; d:array [1..10000] of integer;

begin

j:=0; i:=closed;

repeat

inc(j); d[j]:=i; i:=data[i].father;

until i=0;

for i:=j downto 1 do

writeln('step ',j-i,':',data[d[i]].a,' ',data[d[i]].b);

close(input);close(output);

halt;

end;

procedure evaluate(i,j:integer);

begin

bool^[i,j]:=false; inc(closed);

data[closed].a:=i; data[closed].b:=j;

data[closed].father:=open;

if j=z then out;

end;

begin

new(bool);

fillchar(bool^,sizeof(bool^),true);

open:=0; closed:=1; bool^[x,0]:=false;

data[1].a:=x; data[1].b:=0; data[1].father:=0;

if (z=x) or (z=0) then out;

repeat

inc(open); i:=data[open].a; j:=data[open].b;

k:=min(i,y-j);

L:=min(j,x-i);

if (i>0) and (j<y) and (bool^[i-k,j+k]) then evaluate(i-k,j+k);

if (j>0) and (i<x) and (bool^[i+L,j-L]) then evaluate(i+L,j-L);

if (i>0) and (bool^[0,j]) then evaluate(0,j);

if (j>0) and (bool^[i,0]) then evaluate(i,0);

if (i<x) and (bool^[x,j]) then evaluate(x,j);

if (j<y) and (bool^[i,y]) then evaluate(i,y);

until open>=closed;

writeln('No answer!');

close(input); close(output);

end;

Begin

assign(input,'input.txt'); reset(input);

assign(output,'output.txt'); rewrite(output);

readln(x,y,z); bfs;

end.

修改后程序:

program aa;

type atype=record

father,a,b:integer;

end;

var x,y,z,q:integer;

data:array[-2..10000] of atype;

bool:array[-2..100,0..100] of boolean;

procedure bfs;

var i,j,k,l:integer;

f,r:integer;

function min(a,b:integer):integer;

begin

if a<b then min:=a

else min:=b;

end;

procedure out;

var i,j:integer;

d:array[1..10000] of integer;

begin

j:=0; i:=r;

repeat

j:=j+1;

d[j]:=i;

i:=data[i].father;

until i=0;

for i:=j downto 1 do

writeln('step',j-i:3,':',data[d[i]].a:5,data[d[i]].b:5);

// 编号

halt;

end;

procedure fuzhi(i,j:integer);

begin

bool[i,j]:=true; inc(r);

data[r].a:=i; data[r].b:=j;

data[r].father:=f;

if j=z then out;

end;

begin

if z>y then exit;

fillchar(bool,sizeof(bool),false);

f:=0; r:=1; bool[x,0]:=true;

data[1].a:=x; data[1].b:=0; data[1].father:=0;

if (z=x)or(z=0) then out;

repeat

inc(f); i:=data[f].a; j:=data[f].b;

k:=min(i,y-j); l:=min(j,x-i);

if (i>0)and(j<y)and not(bool[i-k,j+k]) then fuzhi(i-k,j+k);

if (j>0)and(i<x)and not(bool[i+l,j-l]) then fuzhi(i+l,j-l);

if (j<y)and not(bool[i,y]) then fuzhi(i,y);

if (i<x)and not(bool[x,j]) then fuzhi(x,j);

if (i>0)and not(bool[0,j]) then fuzhi(0,j);

if (j>0)and not(bool[i,0]) then fuzhi(i,0);

until f>=r;

end;

begin

readln(x,y,z);

bfs;

writeln('NO');

end.

例10:砝码称重。给定n种砝码(每种个数不限)和一个整数M,求至少需要几个砝码才可以称出刚好M克。(n<=100, M<=1000)

输入:n, m,n个整数Wi表示砝码的质量。

输出:最少需要的砝码数或Impossible表示无法满足。

const maxn=100; maxm=1000;

var n,m:integer; w:array [1..maxn] of integer;

dist:array [0..maxm] of integer;

queue:array [1..maxm+1] of integer;

procedure init;

var i:integer;

begin

read(n,m);

for i:=1 to n do read(w[i]);

end;

procedure solve;

var cl,op,k,i:integer;

begin

cl:=0; op:=1;

fillchar(dist,sizeof(dist),255); -1

dist[0]:=0;

queue[1]:=0;

while cl<op do

begin

inc(cl); k:=queue[cl];

for i:=1 to n do

if (k+w[i]<=m) and (dist[k+w[i]]= -1) then

begin

dist[k+w[i]]:=dist[k]+1;

inc(op);

queue[op]:=k+w[i];

end;

end;

if dist[m]= -1 then writeln('Impossible') else writeln(dist[m]);

end;

Begin

init;

solve;

End.

修改后程序:

program aa;

var w:array[1..100] of integer;

q,dis:array[0..1000] of integer;

n,m:integer;

procedure init;

var i:integer;

begin

readln(n,m);

for i:=1 to n do read(w[i]);

end;

procedure guang;

var f,r,k,i:integer;

begin

fillchar(dis,sizeof(dis),0);

f:=0; r:=1;

q[1]:=0;

while f<r do

begin

inc(f); k:=q[f];

for i:=1 to n do

if (k+w[i]<=m)and(dis[k+w[i]]=0) then //注意条件:能加入k, 且未加入过

begin

dis[k+w[i]]:=dis[k]+1; //当前所需个数为 上一个的 加一

inc(r); q[r]:=k+w[i];

end;

end;

if dis[m]=0 then writeln('Impossible') //未加入

else writeln(dis[m]);

end;

begin

init;

guang;

end.

例11:翻币问题。有N个硬币(6≤N≤8000)正面朝上排成一排,每次将5个硬币翻过来放在原来位置,直到最后全部翻成反面朝上为止。编写程序让计算机找出步数最少的翻法,并把翻币过程及次数打印出来(用O表示正面,*表示反面)。

输入样例:

6

输出样例:

setp0:oooooo

step1:*****o

step2:oooo**

step3:***ooo

step4:oo****

step5:*ooooo

step6:******

var n:integer;

a:array [1..8000,1..3] of integer;

b:array [0..800] of boolean;

procedure calc;

var r,m:integer; open,closed:integer;

procedure out;

var i,j,k,L,h:integer;

d:array [1..5000] of integer;

st:array [1..5000] of char;

begin

write('step 0:');

for i:=1 to n do write('o');

writeln;

for i:=1 to n do st[i]:='o';

j:=0; i:=closed;

repeat

j:=j+1; d[j]:=i; i:=a[i,1];

until i=0;

for i:=j-1 downto 1 do

begin

k:=a[d[i],3]; L:=5-k;

for h:=1 to n do

if st[h]='o' then

begin

if k>0 then begin dec(k); st[h]:='*'; end;

end else

begin

if L>0 then begin dec(L); st[h]:='o'; end;

end;

write('step ',j-i,':');

for h:=1 to n do write(st[h]);

writeln;

end;

close(input); close(output);

halt;

end;

begin

fillchar(b,sizeof(b),true);

b
:=false; open:=0; closed:=1;

a[1,1]:=0; a[1,2]:=n; a[1,3]:=0;

repeat

inc(open);

m:=a[open,2];

for r:=0 to 5 do

if (m>=r) and (n-m>=5-r) and (b[m-r+5-r]) then

begin

inc(closed);

b[m-r+5-r]:=false;

a[closed,1]:=open;

a[closed,2]:=m-r+5-r;

a[closed,3]:=r;

if a[closed,2]=0 then out;

end;

until open>=closed;

writeln('No answer!');

close(input);close(output);

end;

Begin

assign(input,'input.txt'); reset(input);

assign(output,'output.txt'); rewrite(output);

read(n);

calc;

end.

修改后程序:

program aa;

var n:integer;

a:array[1..8000,1..3] of integer; //a[i,1]为前驱, a[i,2]为正面朝上的个数,a[i,3]为反面朝上的个数

b:array[0..800] of boolean; //b[i]为 正面的个数为 i 的

procedure calc;

var k,m:integer;

f,r:integer;

procedure out;

var i,j,k,l,h:integer;

d:array[1..5000] of integer;

st:array[1..5000] of char;

begin

for i:=1 to n do st[i]:='o';

j:=0; i:=r;

repeat //此循环将 结点 逆序存入 d数组

inc(j);

d[j]:=i; i:=a[i,1];

until i=0;

for i:=j downto 1 do //逆序处理

begin

k:=a[d[i],3]; l:=5-k;

for h:=1 to n do

if st[h]='o' then //当前为 正面的,取反

begin

if k>0 then

begin

dec(k); st[h]:='*';

end;

end

else begin //当前为 反面的,取反

if l>0 then

begin

dec(l);st[h]:='o';

end;

end;

write('step',j-i:2,':');

for h:=1 to n do write(st[h]);

writeln;

end;

halt;

end;

begin

fillchar(b,sizeof(b),false);

b
:=true;

f:=0; r:=1;

a[1,1]:=0; a[1,2]:=n; a[1,3]:=0;

repeat

inc(f);

m:=a[f,2]; //取正面朝上的

for k:=1 to 5 do //能翻的次数(即反面的个数)

if (m>=k)and(n-m>=5-k)and not (b[m-k+5-k]) then

begin

inc(r); b[m-k+5-k]:=true;

a[r,1]:=f;

a[r,2]:=m-k+5-k; //正面的个数:原来正-该次反+(该次正)

a[r,3]:=k;

if a[r,2]=0 then out;

end;

until f>=r;

writeln('NO');

end;

begin

readln(n);

calc;

end.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: