算法竞赛进阶指南0x21 树与图的遍历

树与图的深度优先遍历,树的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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值