算法学习笔记(23)——树与图的DFS与BFS
树与图的DFS与BFS
首先,树是一种特殊结构的图,所以树与图的存储是相同的,而图又分为有向图与无向图,对无向图我们可以在两个点之间添加两条边。
有向图的存储方式主要有两种
- 稀疏图(点多边少)一般用邻接表存储
- 稠密图(点少边多)一般用邻接矩阵存储。
在邻接表的实现中,用数组h
来记录每个节点向外的边的链表头指针,初始时都是空(即-1)。用idx
来表示链表节点的分配位置。数组e
表示节点的值,即是目标节点的编号。数组ne
表示节点的next
指针,也就是链表节点的下一节点被分配的下标。
注意,当用邻接表去存无向图(或者树)的时候,因为a → b
和b → a
的边都要存,所以放置链表节点信息的e
数组和ne
数组至少要开到边总数目的两倍大。
DFS
#include <iostream>
#include <cstring>
using namespace std;
const int N = 1e5 + 10, M = N * 2;
int n;
int h[N], e[M], ne[M], idx; // 邻接表存储树,无向边所以e与ne数组开两倍大小
bool st[N]; // 标记节点是否被访问过
int ans = N; // 记录答案(各个连通块中点数的最大值)
// 添加一条a到b的有向边
void add(int a, int b)
{
e[idx] = b, ne[idx] = h[a], h[a] = idx ++;
}
int dfs(int u)
{
// 标记当前节点已经被访问
st[u] = true;
// sum存储以当前节点为根的子树大小
// res存储当前节点的最大子连通块
int sum = 1, res = 0;
// 遍历与当前节点相邻的所有节点
for (int i = h[u]; i != -1; i = ne[i]) {
int j = e[i];
// 如果j节点没有被访问过
if (!st[j]) {
int t = dfs(j); // t为以j为根的子树大小
res = max(res, t); // 更新最大子连通块的值
sum += t; // 子树大小加上这一子连通块
}
}
res = max(res, n - sum); // 利用以u号节点为根的子树之外的部分更新最大连通块
ans = min(ans, res); // 更新以不同节点为重心的最大连通块的值
return sum; // 返回以当前节点为根的子树大小
}
int main()
{
memset(h, -1, sizeof h); // 初始化邻接表
cin >> n;
// 注意,此处不能使用while(n --),这会改变n的值
for (int i = 0; i < n - 1; i ++ ) {
int a, b;
cin >> a >> b;
add(a, b), add(b, a);
}
dfs(1); // 从1号节点开始搜索
cout << ans << endl;
return 0;
}
BFS
#include <iostream>
#include <queue>
#include <cstring>
using namespace std;
const int N = 1e5 + 10;
int n, m;
int h[N], e[N], ne[N], idx;
int d[N];
void add(int a, int b)
{
e[idx] = b, ne[idx] = h[a], h[a] = idx ++;
}
// 宽搜套路,返回到1~n的最短距离
int bfs()
{
queue<int> q;
q.push(1);
// 一般情况下距离数组初始化为正无穷,但本题中若不能到达则返回-1,所以用-1代表不可达
memset(d, -1, sizeof d);
d[1] = 0;
while (q.size()) {
int t = q.front();
q.pop();
for (int i = h[t]; i != -1; i = ne[i]) {
int j = e[i];
// 如果当前点还没有访问过
if (d[j] == -1) {
d[j] = d[t] + 1;
q.push(j);
}
}
}
return d[n];
}
int main()
{
memset(h, -1, sizeof h);
cin >> n >> m;
while (m -- ) {
int a, b;
cin >> a >> b;
add(a, b);
}
cout << bfs() << endl;
return 0;
}