BFS就是广度优先算法,BFS相对DFS来说不太直观。BFS中,我们会搜索r的“孙子节点”之前先访问结点r的所有相邻结点。一般用队列+迭代方案实现。
为了复习下BFS的思想,看下BFS最基本的场景=。=
无向图的BFS来举栗子
BFS是“地毯式”层层递进的搜索策略,先查找离起始顶点最近的,依次往外搜索。下图表示了搜索的过程,盗一张大佬的图。
其实思想不复杂,实现起来还是要琢磨一下的。
前面提到BFS要用队列+迭代方案实现,那么我们需要三个辅助变量visited,queue,prev
visited:记录以及访问过的顶点,避免顶点被重复访问
queue:关键的是这里,实现的是记录功能,因为是层层递进,需要暂存访问过的顶点,方便进行下一层搜索
prev:记录搜索过的路径,不过是反向存储的,prev[w]存储的是从哪个前驱顶点遍历过来的。关注下print函数的实现。
talk is cheap,show you the code:
public class BFS {
public int v;//顶点个数
public LinkedList adj[];// 邻接表
public BFS(int v) {
this.v = v;
adj = new LinkedList[v];
for (int i = 0; i < v; ++i) {
adj[i] = new LinkedList<>();
}
}
// 添加连接关系
public void addEdge(int s, int t) {
adj[s].add(t);
adj[t].add(s);
}
// bfs搜索,s到t的路径
public void bfs(int s, int t) {
// s,t是同一节点,返回
if (s == t) {
return;
}
//三大辅助变量---visited
boolean[] visited = new boolean[v];
// 三大辅助变量---queue
Queue queue = new LinkedList<>();
// 加入s
queue.add(s);
// 三大辅助变量-- prev
int[] prev = new int[v];
// 初始化为-1
for (int i = 0; i < v; ++i) {
prev[i] = -1;
}
while (queue.size() != 0) {
// 堆顶出队
int w = queue.poll();
// 遍历w的临接节点
for (int i = 0; i < adj[w].size(); ++i) {
//和w相邻的一个节点
int q = adj[w].get(i);
// 如果q没有访问过
if (!visited[q]) {
// 记录prev,q的上前驱节点是w
prev[q] = w;
// 到终点了打印路径
if (q == t) {
print(prev, s, t);
return;
}
// q访问过了,标记
visited[q] = true;
// 将q加入队列
queue.add(q);
}
}
}
}
// 递归打印从终点t到起点的路径
private void print(int[] prev, int s, int t) {
if (prev[t] != -1 && t != s) {
print(prev, s, prev[t]);
}
System.out.print(t + " ");
}
}
其实呢,要记住模板~编码5分钟,调试两小时,再放一个方便记忆的模板,也方便手写
void search(Node root) {
Queue queue = new Queue();
root.visited = true;
visit(root);
queue.add(root); // 加至队列尾部
while(!queue.isEmpty()) {
Node r = queue.poll(); //从队列头部移除
foreach(Node n in r.adjacent) {
if(n.visited == false) {
visit(n);
n.visited = true;
queue.add(n);
}
}
}
}
下面实操一下,看看LeetCode真题
最短路径的题,“地毯式搜索”,广度优先返回的就是最短路线
- visited数组,记录访问过的数字,是从0到n
- queue 这里存放的挑选后剩余的总和n,以及已选数字的个数
- 满足条件时,返回
- 用java写感觉有点啰嗦,=。=
public int numSquares(int n) {
if (n == 0) {
return 0;
}
// 暂存队列,每一种元素有剩余总数和已选元素个数组成
Deque> queue = new LinkedList>();
ArrayList list = new ArrayList(2);
list.add(n);
list.add(0);
queue.add(list);
boolean[] visited = new boolean[n + 1];
visited[n] = true;
while (!queue.isEmpty()) {
ArrayList temp = queue.removeFirst();
int remain = temp.get(0);
int step = temp.get(1);
if(remain == 0) {
return step;
}
for (int i = 1; remain - i * i >= 0; i++) {
int a = remain - i * i;
if (!visited[a]) {
if (a == 0) {
return step+1;
}
ArrayList tempList = new ArrayList(2);
tempList.add(a);
tempList.add(step + 1);
queue.addLast(tempList);
visited[a] = true;
}
}
}
return 0;
}
再看一题:
根据题目的意思,就是要广搜来遍历对应id的员工所有impance的值
- 使用了java 8的stream来筛选出对应id的employee的信息
public int getImportance(List employees, int id) {
int result = 0;
Employee rootEmployee = new Employee();
Queue queue = new LinkedList<>();
rootEmployee = getEmployeeById(employees, id);
queue.offer(rootEmployee);
while (!queue.isEmpty()) {
Employee employee = queue.poll();
result += employee.importance;
for (Integer subordinate : employee.subordinates) {
queue.offer(getEmployeeById(employees, subordinate));
}
}
return result;
}
private Employee getEmployeeById(List employees, int id) {
return employees.stream().filter(employee -> employee.id == id).collect(Collectors.toList()).get(0);
}