ACM | BFS 广度优先算法
Slide 1
ACM程序设计
Slide 2
预备知识
队列 特点: 1、先进先出(FIFO) 2、从队头删除元素 3、在队尾加入元素
常见操作: 判断队列是否为空 查询队列大小 访问队首元素 访问队尾元素 加入元素 删除元素
Slide 3
STL中队列的基本用法
创建队列对象:queue<元素类型> 队列名
;
队列添加元素:队列名.push(元素名)
;
去掉队首元素:队列名.pop()
;
访问队首元素:队列名.front()
;
访问队尾元素:队列名.back()
;
判断是否为空:队列名.empty()
;
返回队列大小:队列名.size()
;
Slide 4
STL实例介绍
int a,b,c,d;
queue<int> q;
q.push(1); q.push(3); q.push(4); q.pop();
a=q.front();
b=q.back();
c=q.size();
d=q.empty();
cout<<a<<' '<<b<<' '<<c<<' '<<d<<endl;
Slide 5
第七讲
宽度优先搜索 (BFS)
Slide 6
例1
输入一棵二叉树,如何对该二叉树进行层次遍历? 所谓“层次遍历”就是从上到下,从左到右的顺序进行遍历。 例如,右边的树层次遍历的结果是?
5 1 7 2 4 6 3
Slide 7
算法思想:维护一个队列,用于存放节点的信息。当访问到一个节点的时候,先访问该节点,然后将该节点的左右儿子分别入对列。
ccbl(int root)
queue<int> Q ;创建一个队列
Q.push(root) ;将根节点入队列
while(队列不为空){
获得队首元素 //这一行和下一行能交换吗?
将队首元素出队
输出当前节点的值
如果该节点的左儿子不为空,将左儿子加入到队列中
如果该节点的右儿子不为空,将右儿子加入到队列中
}
树的层次遍历
Slide 8
图的BFS示意图
Slide 9
例2
有一个奇怪的电梯,他可以停在任何一层,并且在每个楼层有一个Ki(0 <= Ki <= N)。电梯只有两个按钮:上、下。当你在第i层,如果你按下“UP”按钮,你将上升Ki层,也就是说,你将会到达第i+Ki层,如果你按下“DOWN”按钮,你会下降 Ki层,即您将前往第i-Ki层。当然,电梯不能高于N,也不能低于1。 例如,有5层的建筑物,并且k1=3,k2=3,k3=1,k4=2,k5=5。从1楼开始,你可以按下“UP”按钮,你会到4楼,但如果你按下“DOWN”按钮,电梯不做处理,因为它不能下到-2楼。 问:当你在A楼而想去B楼时,至少须按下“UP”或“DOWN”按钮多少次? 其中,1 <= N,A,B <= 200
Slide 10
例2
能看出这是一个搜索题吗? 能否根据样例画出对应的状态转移图? 5 1 5 3 3 1 2 5 通过这个题目能否总结出:BFS最适合哪一类的求解?
Slide 11
例2 #include<bits/stdc++.h>
using namespace std;
int N,Start,End;
int a[202];
int vis[202];
struct pos
{ int level; //含义?
int steps; //含义?
};
void bfs();
int main()
{ while(scanf("%d",&N)==1)
{ if(N == 0) break;
scanf("%d%d",&Start,&End);
for(int i = 1; i <= N; i++)
{ scanf("%d",&a[i]);
vis[i] = 0;
}
bfs();
}
return 0;
}
Slide 12
void bfs()
{ pos cur,nex;
cur.level = Start;
cur.steps = 0;
queue<pos>qu;
qu.push(cur);
vis[Start] = 1;
while(!qu.empty())
{ cur = qu.front();
qu.pop();
if(cur.level == End)
{ printf("%d\n",cur.steps);
return;
}
nex.level=cur.level + a[cur.level];
nex.steps = cur.steps + 1;
if(nex.level <= N)
{ if(vis[nex.level] == 0)
{ vis[nex.level] = 1;
qu.push(nex); }
}
nex.level= cur.level - a[cur.level];
nex.steps = cur.steps + 1;
if(nex.level >= 1)
{ if(vis[nex.level] == 0)
{ vis[nex.level] = 1;
qu.push(nex); }
}
}
printf("-1\n");
return;
}
Slide 13
例3
题目描述: 每当刘一丁买了可乐,刘二丁就要求和他一起分享,而且一定要喝的和刘一丁一样多。但刘一丁的手中只有两个杯子,它们的容量分别是N和M毫升,可乐的体积为S(S<101)毫升(正好装满一瓶) ,它们三个之间可以相互倒可乐 (都是没有刻度的,且 S==N+M,101>S>0,N>0,M>0) 。
如果能平分,请输出倒可乐的最少次数,如果不能,请输出"NO"。
Input 7 4 3
4 1 3
0 0 0
NO
3
Slide 14
啊,这连图都没有,也能搜索?!! 如何定义节点信息(状态)? 三杯水量 + 当前状态最少倒水次数 有了状态如何转移呢? 有了状态、有了转移(边),是否就能进行搜索了?! 我们称之为——“隐式图” 如何优化(剪枝)? 已经访问的节点不再访问(做标记)
例3
Slide 15
状态转移规则(倒水规则): 如果i水杯内水的容量大于j水杯内倒满所需的容量x,则——i水杯倒水后的容量为:i-x,j水杯倒水后容量为:j+x 如果i水杯内水的容量小于j水杯内倒满所需的容量x,则——i水杯倒水后的容量为:0,j水杯倒水后容量为:j+x 每操作一次,最少倒水次数+1
例3
Slide 16
例4
题目大意:
给定起始位置a和目标位置b,请计算从a到b路上骑士移动的最少次数。
注:骑士即象棋中的马,走日字。 Sample Input a1 h8
To get from a1 to h8 takes 6 knight moves.
Slide 17
跳马规则
在2×3的矩形里
Slide 18
例如:从a1到e4
当目标出现在所扩展出的结点里,结果就找到了。
To get from a1 to e4 takes 3 knight moves.
例4
Slide 19
从初始状态S开始,利用规则,生成所有可能的状态,构成树的下一层节点。 检查是否出现目标状态G,若未出现,就对该层所有状态节点,分别依次利用规则,生成再下一层的所有状态节点。 对新一层的所有状态节点继续检查是否出现G,若未出现,继续按上面思想生成再下一层的所有状态节点,这样一层一层往下展开,直到出现目标状态为止。
BFS基本思想:
Slide 20
BFS算法(伪代码) Node bfs(node source , node target){
memset(visit , 0 , sizeof(visit)) ;
queue<node> Q ;
Q.push(source) ;
visit[source] = 1 ;
while(!Q.empty()){
Node a = Q.front();
Q.pop() ;
if(a==target) { return a ; }
for(对于a所有的后继节点 b)
if(visit[b]){ continue ; }
Q.push(b) ;
visit[b] = 1 ; //剪枝,保证节点只进队列一次
}
return NULL ;
}
Slide 21
预备知识-优先队列(priority_queue)
特点: 1、在队尾加入元素 2、从队头删除元素 3、每次取出的是具有最高优先权的元素 (不一定先进先出)
常见操作: 判断队列是否为空 查询队列大小 返回优先权最高的元素 加入元素 删除元素
Slide 22
STL中优先队列的基本用法
创建队列对象:
priority_queue<元素类型> 队列名
; 队列添加元素:队列名.push(元素名)
; 去掉第一个元素:队列名.pop()
; 判断是否为空:队列名.empty()
; 返回队列大小:队列名.size()
; 访问最优元素:队列名.top()
;
Slide 23
优先队列实例1:
#include<bits/stdc++.h>
using namespace std;
int main()
{ priority_queue<int> q;
int t, a=2, b=5, c=3;
q.push(a); q.push(b); q.push(c);
while (!q.empty())
{ t = q.top();
q.pop();
cout << t << endl;
}
return 0;
}
运行结果: 5
3
2
Slide 24
优先队列实例2:
struct T
{ int x, y, z;
friend bool operator < (const T &t1, const T &t2)
{ if (t1.z!= t2.z) return t1.z > t2.z;
else if(t1.y!=t2.y) return t1.y<t2.y;
else return t1.x>t2.x;
}
}t;
int main()
{ priority_queue<T> q;
T a={4,4,3}, b={2,2,5}, c={1,2,5}, d={2,1,5};
q.push(a); q.push(b); q.push(c); q.push(d);
while (!q.empty())
{ t = q.top(); q.pop();
cout << t.x << " " << t.y << " " << t.z << endl; }
return 0;
}
Slide 25
例5
题目大意: 丁爸被火星人抓走,关在一个\(N * M\)矩形的监狱(监狱里有墙壁、道路和警卫队)。 丁爸的学生想拯救他(到达丁爸停留的位置即视为成功)。拯救过程中若遇到警卫,则必须干掉。假设每次向上,向下,向右,向左移动需要1个单位时间,杀死一个守卫额外需要1个单位时间。 请计算:拯救丁爸需要的最短时间。 (每次只能上,下,左,右移动到边界内的邻居网格。)
Sample Input 7 8
#.#####.
#.a#..r.
#..#x...
..#..#.#
#...##..
.#......
........
13
Slide 26
如果使用传统的BFS,局限性在哪里? 出队元素所记忆的时间并不是层次递增的... 希望的效果? 让因为遇到士兵而多花时间的结点在队列中向后推迟一层出队 实现方法: 优先队列——根据时间进行优先性选择,每次出队当前队列中记录时间最少的元素 思考:前来拯救的学生有多个,如何方便处理?
要点分析: