茶艺师学算法打卡15:深度、广度优先搜索
学习笔记
深度优先搜索,广度优先搜索,他们的名字起的十分直白,一个一直往下搜索,一个一层层搜索。
深度优先搜索适合使用递归简化写法。
广度优先搜索需要队列辅助计算过程。
这里直接用两道算法题来巩固一下对他们的影响吧。
看题目前,得先知道的
以下题目中的图都使用邻接表,而且是用数组模拟的单链表版本邻接表来存,它的相关示例代码如下:
const N int = 10000 // 图顶点个数
var (
h [N]int // 邻接表链表的头
e [N]int // 存储元素
ne [N]int // 存储列表的next值
idx int // 链表指针
)
// 邻接表初始化
// 将 h 数组全赋值 -1
h[0] = -1; for i := 1; i < N; i *= 2 {copy(h[i:], h[:i])}
// 顶点 a 至 顶点 b 加边
func add(a, b int) {
e[idx] = b
ne[idx] = h[a]
h[a] = idx
idx++
}
// 如果是有向图,a 至 b 点加边
add(a, b)
// 如果是无向图,a 至 b 点加边
add(a, b)
add(b, a)
// 遍历邻接表
for i := h[u]; i != -1; i = ne[i] {
j := e[i]
// 具体场景的操作
}
深度优先搜索 AcWing 846. 树的重心
题目说明
给定一颗树,树中包含 n
个结点(编号 1∼n
)和 n−1
条无向边。
请你找到树的重心,并输出将重心删除后,剩余各个连通块中点数的最大值。
重心定义:重心是指树中的一个结点,如果将这个点删除后,剩余各个连通块中点数的最大值最小,那么这个节点被称为树的重心。
输入格式
第一行包含整数 n
,表示树的结点数。
接下来 n−1
行,每行包含两个整数 a
和 b
,表示点 a
和点 b
之间存在一条边。
输出格式
输出一个整数 m
,表示将重心删除后,剩余各个连通块中点数的最大值。
数据范围
输入样例
9
1 2
1 7
1 4
2 8
2 5
4 3
3 9
4 6
输出样例
4
题目分析
就拿输入样例来看,它是这样的一副图:
那么只要把每个节点为重心的剩余最大连通块节点数算出来,挑出里面的最小值,答案就出来了。
正好 dfs 就能干这事。
先准备对应的图的 dfs 模板 :
var st [N]bool // 标记哪个点访问过了
func dfs(u int) {
st[u] = true
for i := h[u]; i != -1; i = ne[i] {
j := e[i]
if !st[j] {
dfs(j)
}
}
}
参考代码
package main
import (
"fmt"
"os"
"bufio"
)
const (
N = 100010
M = 2 * N
)
var (
out = bufio.NewWriter(os.Stdout)
in = bufio.NewReader(os.Stdin)
n, idx int
h [N]int
e, ne[M]int
ans = N
st [N]bool
)
func add(a, b int) {
e[idx] = b; ne[idx] = h[a]; h[a] = idx; idx++
}
func min(i, j int) int {if i < j {return i}; return j}
func max(i, j int) int {if i > j {return i}; return j}
// 返回以u为根的子树中节点的个数,包括u节点
func dfs(u int) int {
st[u] = true
size, sum := 0, 0
for i := h[u]; i != -1; i = ne[i] {
j := e[i]
if st[j] {continue}
s := dfs(j)
size = max(size, s)
sum += s
}
size = max(size, n - sum - 1) // 选择u节点为重心,最大的连通子图节点数
ans = min(ans, size) // 更新答案 ans
return sum + 1
}
func main() {
fmt.Fscan(in, &n)
h[0] = -1; for i := 1; i < N; i *= 2 {copy(h[i:], h[:i])}
for i := 0; i < n - 1; i++ {
a, b := 0, 0
fmt.Fscan(in, &a, &b)
add(a, b)
add(b, a)
}
dfs(1)
fmt.Fprintln(out, ans)
out.Flush()
}
广度优先搜索 AcWing 847. 图中点的层次
题目说明
给定一个 n
个点 m
条边的有向图,图中可能存在重边和自环。
所有边的长度都是 1
,点的编号为 1∼n
。
请你求出 1
号点到 n
号点的最短距离,如果从 1
号点无法走到 n
号点,输出 −1
。
输入格式
第一行包含两个整数 n
和 m
。
接下来 m
行,每行包含两个整数 a
和 b
,表示存在一条从 a
走到 b
的长度为 1
的边。
输出格式
输出一个整数,表示 1
号点到 n
号点的最短距离。
数据范围
输入样例
4 5
1 2
2 3
3 4
1 3
1 4
输出样例
1
题目分析
根据输入样例,我们能得到这样的图:
很直白得看出,答案就是1。
不用说,直接用图的 bfs 来做吧。
参考代码
package main
import (
"fmt"
"os"
"bufio"
)
const N = 100010
var (
out = bufio.NewWriter(os.Stdout)
in = bufio.NewReader(os.Stdin)
idx int
h, e, ne [N]int
n, m int
d [N]int // 存从起点到终点的距离
)
func add(a, b int) {
e[idx] = b; ne[idx] = h[a]; h[a] = idx; idx++
}
func bfs() int {
for i := range d {d[i] = -1} // 初始化距离表,全赋值-1
q := []int{1} // 1号点入队
d[1] = 0
for len(q) > 0 {
top := q[0]; q = q[1:]
for i := h[top]; i != -1; i = ne[i] {
j := e[i]
if d[j] == -1 { // 当新的点未被更新
d[j] = d[top] + 1 // 更新距离
q = append(q, j) // 该点入队
}
}
}
return d[n]
}
func main() {
fmt.Fscan(in, &n, &m)
for i := range h {h[i] = -1}
for i := 0; i < m; i++ {
a, b := 0, 0
fmt.Fscan(in, &a, &b)
add(a, b)
}
fmt.Fprintln(out, bfs())
out.Flush()
}