宽度优先搜索算法
合集下载
相关主题
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
放
在
23412
右
上
32323
角
的 12321 图
23032
12321
41214
32323
2号骑士
跳步
数 字 放 在 左 下 角
3号骑士 的
3 2 3 2 3 跳步 图
数
23412
字
放
32123
在
右
23230
下
角
32123
发挥你的想象力,把4张图画在透明 胶片上,然后把4张落在一起,这时你可 以看到每一个格子里有4个数据,分别属 于4个骑士的跳步信息
queue[tail].x=x1; //让 x1 入队
queue[tail].y=y1; //让 y1 入队
best[i][x1][y1]=step+1; //记录第i号骑士跳至
//(x1,y1)的跳步信 息
} //if
} //for k
} //for am 从m 到 sq 作为一层已扩展完毕
//之后,将下一段的开始sq+1赋给m,m是待扩 展的段头,段尾是新扩展出的tail
再跳一步到
(4,0)(4,2)(3,3)(3,1)(2,0) (0,2)(1,3)(4,2)(4,0)
见下图,重点看:
标有2的黄色的5个结点是由结点(2,1) 扩展出来的;
标有2的黑色的4个结点是由结点(1,2) 扩展出来的。
0
1
2
3
4y
0 0 -1
2
-1 2
1 -1 -1
1
2 -1
2 2 1 -1
宽度优先搜索
骑士聚会问题
在 N*N 的棋盘上放m个马,问各自 跳多少步才能在某处聚在一起,希望聚 会时间越早越好,且总步数最少。
任务:处在(0,0)位置上的象棋马跳到任 何一个位置所需步数 012 3 4 y 0032 3 2 1341 2 3 2214 3 2 3323 2 3 4232 3 4
-1 2
3 -1 2
-1
2 -1
4 2 -1 2 x
-1 -1
0 (0,0)
1(2,1)
1(1,2)
2 2 22 22 2 2 2 (4,0) (4,2) (3,3) (1,3) (0,2) (2,0) (3,1) (2,4) (0,4)
怎样来描述与实现这种扩展过程呢? 需要引入队列数据结构
队列的基本概念 队列是限定在一端进行插入,在另一端进 行删除的特殊的线性表。 像食堂排队买饭,后来的人排在队尾(插 入),队头上的人买完饭后离队(删除)。所 有需要进队的数据项只能从队尾进入;所有需 要出队的数据项只能从队头离开。 先入队 的元素先出队,这种表又称为先进 先出表(F I FO 表)。
sq k /23 x 021 y 012 step 0 1 1 tail 1 2 3
m
12 3 4 5 44 3 1 0 02 3 3 2 22 2 2 2 45 6 7 8
01 3 4 23 2 0 01 4 4 22 2 2 9 10 11 12
m=2, sq=3
0 (0,0)
1(2,1)
1(1,2)
m=sq+1;
//扩展下一层时,是从step加1 的步数开始 的
step=step+1;
} //while } //BFS()
//可跳步的判别函数 bool okjump(int i,int x,int y)
{
return ( x>=1 && x<=4 && y>=1 && y<=4 && best[i][x][y]==-1 );
下面会看到,格子中的数为-1时才 允许存入跳步信息。
马从(0,0)跳一步,有两个可行位置 (2,1)--------k=2 (1,2)--------k=3 称(0,0)为结点,(2,1)和
(1,2)为由(0,0)扩展出的结点 0 0,0)
1 (2,1) 1(1,2)
马由(0,0)跳一步到 (2,1)和(1,2)
向
{ //for k //从(x,y)点沿k方向跳至(x1,y1)
int x1=queue[am].x+dx[k];
if( okjump(i,x1,y1)) //判第i号骑士能否跳至(x1,y1),
//如能,让该点入队并记跳步信 息
{ //if
tail=tail+1;
//队尾加1(这是新扩展出的)
//定义队尾tail,并初始化为1
queue[head].x=rec[i].x; //让骑士的坐标点x 入队
queue[head].y=rec[i].y; //让骑士的坐标点y 入队
int step=0; //定义马的跳步数step,初始化 为0
int m=head; //定义中间变量m,记住队头
while( m<=tail ) //从队头m 到队尾tail 进行扩展,直
在5*5的棋盘上讨论马的跳步信息的 基础上,我们来研究骑士聚会问题。
任务:在8×8的棋盘上,输入n个 骑士的出发点,假定骑士每天只能跳一 步,计算n个人的最早聚会地点和走多少 天。要求尽早聚会,且n个人走的总步数 最少。骑士的跳步按中国象棋的马来跳。
解题思路:
1 从小到大,从简到繁,先讨论5*5的棋 盘
} //如果马跳至棋盘界内且该处尚未有马跳 到过,则返回true,否则返回false
void display() //显示棋局(调试过程用到)
{ //display
for( x=0;x<5;x++) //从0,1,...,4枚举x
{ //for x
cout<<endl;
wk.baidu.com
//换行
for( y=0;y<5;y++) //从0,1,...,4枚举y
m=4
sq=12
对 m 到 sq 区间内的结点进行扩展
2 (2,1)
(0,0) 1
3 (1,2)
4
12
(4,0) (4,2) (3,3) (1,3) (0,2) (2,0) (3,1) (2,4) (0,4)
(3,2) (3,4) (2,3) (3,0) (4,1) (1,4) (0,1) (1,0) (4,3) (0,3)
13
22
园圈中的数字为该结点在队列中的序号
2 (2,1)
(0,0) 1 跳1步
3 (1,2)
4
跳2步
12
(4,0) (4,2) (3,3) (1,3) (0,2) (2,0) (3,1) (2,4)(0,4)
13
跳3步
22
(3,2) (3,4) (2,3) (3,0) (4,1) (1,4) (0,1) (1,0) (4,3) (0,3)
m=head=1 ,sq=tail=1
对 m 到 sq 区间内的结点进行扩展
k / 2312345 x 02144310 y 01202332 w 01122222 tail 1 2 3 4 5 6 7 8
0 1 34 2 3 20 0 4 44 2222 9 10 11 12
m sq m=2, sq=3 对 m到 sq 区间内的结点进行扩展
4 怎样标记一层的左右位置是编程的关键
用结构数组来表示队列
struct qtype 构
//定义有两个成员的结
{ int x,y;
//成员x 和成员y
}queue[T*T+20],rec[T*T+20]; //定义数组queue和数组rec是结构类型的 //queue是结构数组,作队列用 ,T=5 //rec是结构数组,用来存储骑士们在棋盘
tail=tail+1; queue[tail].x=x1; queue[tail].y=y1; queue[tail].k=k; queue[tail].step=step;
k/ 2 3 x0 2 1 y01 2 step 0 1 1 tail 1 2 3
1个待扩结点 0 (0,0)
11 (2,1) (1,2) 扩展出2个结点
x1=x+dx[k];
012 34
y1=y+dy[k];
00
如马的初始位置在(0,0)则 1
1
x1=0+dx[k]
21
y1=0+dy[k] ,
3
k=0,1,…7
4
k01234567
x1 1 2 2 1 -1 -2 -2 -1
y1 -2 -1 1 2 2 1 -1 -2
定义二维数组 int w[5][5];
2个待扩结点
2 2 22 22 2 2 2 (4,0) (4,2) (3,3) (1,3) (0,2) (2,0) (3,1) (2,4) (0,4)
扩展出9个结点
k / 2 3 1 2 3 4 5 01 3 4 x 0 2 1 4 4 3 1 0 23 2 0 y 0 1 2 0 23 3 2 01 4 4 w0 1 1 2 2 2 2 22 2 2 2 tail 1 2 3 4 5 6 7 8 9 10 11 12
队列可用数组表示,在队列运算中要设两个指针: 队头指针--------head 队尾指针--------t a i l
q 12 3 4 5 6 7
head
tail
temp=q[tail]; tail=tail+1;q[tail]=temp+1;
q
12 3 4 5 6 7 8
head
tail
将马的跳步信息放入队列中,用到 以下5条语句:
用来存储每个格子中马的跳步信息 对数组w进行初始化,目的是让每
个格子只记录一次,避免重复记录。
for(int i=0;i<=4;i++) for(int j=0;j<=4;j++) w[i][j]=-1;
经初始化后的5x5格子中的数字均为-1 01234
0 -1 -1 -1 -1 -1 1 -1 -1 -1 -1 -1 2 -1 -1 -1 -1 -1 3 -1 -1 -1 -1 -1 4 -1 -1 -1 -1 -1
2 从个别到一般 3 理思路 4 假定有4个骑士,初始位置分别在
(0,0),(1,4),(1,2),(3,4) 5 对每一个骑士做扩展,得到如下4张跳
步信息图
03232 士的
图 34123 数
字 21432 放
在 32323
0号骑 跳步
3 2 1 2 3 1号骑士 的
跳步 图
23230
数
字
32123
跳4步
23
25
(4,4) (1,1) (2,2)
马的初始位置(队头)结点1 由结点1 扩展
第1步跳到结点2,结点3 由结点2,3 扩展
第2步跳到结点4,结点5,…结点12 由结点4,5…12 扩展
第3步跳到结点13,结点14,…结点22 由结点13,14,…22 扩展
第4步跳到结点23,结点24,结点25
上的起始位置
void init() //初始化函数 { //init
memset(best,-1,sizeof(best)); //初始化数组元素的值均为-1,
int i=0;
rec[i].x=0; //第0号骑士的 x 坐标点 rec[i].y=0; //第0号骑士的 y 坐标点 best[i][rec[i].x][rec[i].y]=0;
根据上图可构造如下队列 以序号来表示结点
queue 1 2 3 4 5 …12 13 14… 22 23 24 25
从图看出
1 队列中有25个结点,每个结点对应棋盘 中的 25个格子;
2 队列的序号,表示结点扩展过程中的先 后顺序;
3 扩展是分层的,处于同一层的结点从左 至右依次扩展,称之为“宽度优先”;
x
根据马的跳步规则研究8个方向的跳步 增量dx[k],dy[k], k=0,1,…7 6
-2 -1 0 1 2
-2
5
-1
70
4
1
2
k=0
12 3
k 0 1 23 4 5 6 7 dx 1 2 2 1 -1 -2 -2 -1 dy -2 -1 1 2 2 1 -1 -2
dx----马跳一步在x方向上的增量 dy----马跳一步在y方向上的增量 k----- 方向号 从(x,y)马跳一步到(x1,y1)
到
//队空为止. 扩展过程tail会不断增 加
{ //while
int sq=tail;
tail
//让sq记住原来的队尾
for( int am=m;am<=sq;am++)
扩展
//从 m 到 sq 作为一层
{ //for am
for( int k=0;k<8;k=k+1 ) //枚举 k ,k为跳步方
{cout<<best[i][x][y]<<" ";}
//输出第i个骑士跳至座标点(x,y)上的步数
} //for x
cout<<endl;
//换行
} //display
int main() //主函数 {
init(); //初始化 BFS(); //宽度优先扩展结点 display();//显示棋盘中的跳步信息 return 0; }
//将第0号骑士在棋盘上的跳步信息置 为0,表示此处为出发点
} //init
//假定只有一个骑士 i (i=0)
void BFS()
//宽度优先搜索
{ //BFS()
//骑士的坐标点在rec[i].x和rec[i].y
int head=1; 为1
//定义队头head,并初始化
int tail=1;