树与图的深度优先遍历,树的DFS序、深度和重心
深度优先遍历,就是在每个点 x 上面对多条分支时,任意选择一条边走下去,执行递归,直至回溯到点 x 后,再考虑走向其他的边。我们可以采用下面的代码,调用DFS(1),对一张图进行深度优先遍历。
void DFS(int x) {
v[x] = true;
//记录点x被访问过,v是visit的缩写
for (int i = head[x]; i; i = next[i]) {
int y = ver[i];
if (v[y]) {
continue;
//点y已经被访问过了
}
DFS(y);
}
}
这段代码访问每个点和每条边恰好一次 (如果是无向边,正反向各访问一次),其时间复杂度为 O(N + M),其中 M 为边数。以这段代码为框架,我们可以统计许多关于树与图的基本信息。
时间戳
按照上述深度优先遍历的过程,以每一个节点第一次被访问 (v[x] 被赋值为 1时) 的顺序,一次给予这 N 个节点 1 ~ N 的整数标记,该标记就被称为时间戳,记为 dfn。
树与图的广度优先遍历,拓扑排序
树与图的广度优先遍历需要使用一个队列来实现。起初,队列中仅包含一个起点。在广度优先遍历的过程中,我们不断从队头取出一个节点 x,对于 x 面对的多条分支,把沿着每条分支到达的下一个节点 (如果尚未访问过) 插入队尾。重复执行上述过程,直到队列为空。
我们可以采用下面的代码对一张图进行广度优先搜索。
void BFS() {
std::memset(d, 0, sizeof(d));
std::queue<int> q;
q.push(1);
d[1] = 1;
while (q.size()) {
int x = q.front();
q.pop();
for (int i = head[x]; i; i = next[i]) {
int y = ver[i];
if (d[y]) continue;
d[y] = d[x] + 1;
q.push(y);
}
}
}
拓扑排序
给定一张有向无环图,若一个由图中所有点构成的序列 A 满足:对于图中的每条边 (X,Y),X 在 A 中都出现在 Y 之前,则称 A 是该有向无环图顶点的一个拓扑序。求解序列 A 的过程就称为拓扑排序。
拓扑排序过程的思想非常简单,我们只需要不断选择图中入度为 0 的节点 X,然后把 X 连向的点的入度减1。我们可以结合广度优先遍历的框架来高效地实现这个过程:
1.建立空的拓扑序列 A。
2.预处理出所有点的入度 deg[ i ],起初把所有入度为 0 的点入队。
3.取出队头节点 X,把 X 加入拓扑序列 A 的末尾。
4.对于从 X 出发的每条边 (X,Y),把 deg[Y] 减一。若被减为 0,则把 Y 入队。
5.重复 3 - 4步,直到队列为空,此时 A 即为所求。
拓扑排序可以判定有向图中是否存在环。我们可以对任意有向图执行上述过程,在完成后检查 A 序列的长度。若 A 序列的长度小于图中点的数量,则说明某些节点未被遍历,进而说明图中存在环。
void add(int x, int y) {
//在邻接表中添加一条有向边
ver[++tot] = y, next[tot] = head[x], head[x] = tot;
deg[y]++;
}
void topsort() {
//拓扑排序
std::queue<int> q;
for (int i = 1; i <= n; i++) {
if (deg[i] == 0) {
q.push(i);
}
}
while (q.size()) {
int x = q.front();
q.pop();
a[++cnt] = x;
for (int i = head[x]; i; i = next[i]) {
int y = ver[i];
if (--deg[y] == 0) {
q.push(y);
}
}
}
}
int main() {
n = read(), m = read();
//点数、边数
for (int i = 1; i <= m; i++) {
int x = read(), y = read();
add(x, y);
}
topsort();
for (int i = 1; i <= cnt; i++) {
printf("%d ", a[i]);
}
return 0;
}
例题:可达性统计 CH2101
参考代码:
#include <bits/stdc++.h>
#define i64 long long
inline i64 read() {
bool sym = false; i64 res = 0; char ch = getchar();
while (!isdigit(ch)) sym |= (ch == '-'), ch = getchar();
while (isdigit(ch)) res = (res << 3) + (res << 1) + (ch ^ 48), ch = getchar();
return sym ? -res : res;
}
const int N = 30010;
int head[N], e[N], ne[N], idx;
int n, m;
int d[N], seq[N];
std::bitset<N> f[N];
void add(int x, int y) {
e[idx] = y, ne[idx] = head[x], head[x] = idx++;
}
void topsort() {
std::queue<int> q;
for (int i = 1; i <= n; i++) {
if (d[i] == 0) {
q.push(i);
}
}
int k = 0;
while (q.size()) {
int t = q.front();
q.pop();
seq[k++] = t;
for (int i = head[t]; ~i; i = ne[i]) {
int j = e[i];
if (--d[j] == 0) {
q.push(j);
}
}
}
}
int main() {
n = read(), m = read();
std::memset(head, -1, sizeof(head));
for (int i = 0; i < m; i++) {
int x = read(), y = read();
add(x, y);
d[y]++;
}
topsort();
for (int i = n - 1; ~i; i--) {
int j = seq[i];
f[j][j] = 1;
for (int p = head[j]; ~p; p = ne[p]) {
f[j] |= f[e[p]];
}
}
for (int i = 1; i <= n; i++) {
printf("%d\n", f[i].count());
}
return 0;
}