《Alogrithms》算法学习笔记——第三章:图的分解

由于本章采用手写笔记尝试了一下效果,这里贴手写笔记的图片

图的分解

3.1 图是什么?

IMG_0054 2 2

3.2 无向图上的DFS

IMG_0054 2 3

其中previsit和postvisit在实现中我是没有写进去的,因为用不上。

接下来我们看一下源代码:

  • 数据:

    const int MAXN = 1e6 + 7; // 节点数量的最大值
    int N = 6; // 节点数量
    vector<int> G[MAXN]; // 无向图G
    bool visited[MAXN]; // 访问数组(false为未访问,true为已访问)
    
  • explore函数的实现:

    vector<int> explore(int u) {
        visited[u] = true; // 标记已经访问过的点
    
        vector<int> res = {u};
        for (auto v: G[u])
            if (!visited[v]) {
                vector<int> tmp = explore(v);
                res.insert(res.end(), tmp.begin(), tmp.end());
            }
    
        return res; // 返回探测到的点的集合
    }
    
  • dfs外层函数:

    int dfs() {
        memset(visited, 0, sizeof visited); // visited初始化false
    
        int cnt = 0;
        for(int i = 1; i <= N; i++) {
            if (!visited[i]) {
                vector<int> region = explore(i); // 探索i所在的连通区
    
                cout << "region[" << ++cnt << "]: { ";
                for (auto c: region) cout << c << " ";
                cout << "}" << endl;
            }
        }
    
        return cnt; // 返回连通区数量
    }
    

3.3 有向图上的DFS

IMG_0054 2 4

遍历的方法和上文中无向图的没差别。

3.4 强连通分量

IMG_0054 2

3.4.1 Kosaraju算法——本书采用的算法

IMG_0054 2 5

下面通过C++实现了Kosaraju算法,代码如下:

const int MAXN = 1e6 + 7; // 节点数量的最大值
int N = 8; // 节点数量
vector<int> G[MAXN]; // 有向图G
vector<int> GR[MAXN]; // G的反向图
int t = 0; // 离开节点时间戳
int post[MAXN]; // 离开时间对应的节点编号。例:post[i]=j表示在i时刻离开的节点编号为j
bool visited[MAXN]; // 访问数组(false为未访问,true为已访问)
int kosaraju() {
    int cnt = 0;
    // 先遍历正向图G打上时间戳
    memset(visited, 0, sizeof visited);
    for (int i = 1; i <= N; i++) {
        if (!visited[i]) {
            explore_G(i);
        }
    }

    // 然后按时间序遍历反向图GR返回强连通子图
    memset(visited, 0, sizeof visited);
    for (int i = N; i >= 1; i--) {
        int u = post[i];
        if (!visited[u]) {
            vector<int> region = explore_GR(u);

            cout << "region[" << ++cnt << "]: { ";
            for (auto c: region) cout << c << " ";
            cout << "}" << endl;
        }
    }

    // 返回强连通子图个数
    return cnt;
}
void explore_G(int u) {
    visited[u] = true; // 标记已经访问过的点

    for (auto v: G[u])
        if (!visited[v])
            explore_G(v);

    post[++t] = u;
}
vector<int> explore_GR(int u){
    visited[u] = true; // 标记已经访问过的点

    vector<int> res = {u};
    for (auto v: GR[u])
        if (!visited[v]) {
            vector<int> tmp = explore_GR(v);
            res.insert(res.end(), tmp.begin(), tmp.end());
        }

    return res; // 返回探测到的点的集合
}

3.4.2 Tarjan算法——蓝书拓展算法

Decompositions of graphs

这里采用本节第一张图作为案例,代码如下:

const int MAXN = 1e6 + 7; // 节点数量的最大值
int N = 7; // 节点数量
vector<int> G[MAXN] = { {  }, { 2 }, { 3 }, { 6 }, { 3, 7 }, { 2 }, { 5, 7 }, {  } }; // 有向图G
int t = 0, dfn[MAXN], low[MAXN]; // t为进入的时间,dfn为遍历顺序,low为指向的最小dfn
bool in_stack[MAXN]; // 访问数组(false为未访问,true为已访问)
stack<int> st; // 用于存放节点的栈
int tarjan() {
    int cnt = 0;
    memset(dfn, 0, sizeof dfn);
    memset(low, 0, sizeof low);
    memset(in_stack, 0, sizeof in_stack);
    for (int i = 1; i <= N; i++) {
        if (!dfn[i]) {
            cnt += dfs(i);
        }
    }
    return cnt;
}
int dfs(int u) {
    int cnt = 0;
    dfn[u] = low[u] = ++t; // dfn与low初始化为进入时间
    st.push(u); in_stack[u] = true; // 已访问当前节点
    // 判定递归情况
    for (auto v: G[u]) {
        if (!dfn[v]) {
            cnt += dfs(v);
            low[u] = min(low[u], low[v]);
        } else if (in_stack[v]) {
            low[u] = min(low[u], low[v]);
        }
    }
    // 针对强连通分量入口节点进行栈操作
    if (dfn[u] == low[u]) {
        cnt++; // 确定了一个连通分量

        // 弹出该连通分量所有节点
        cout << "region: { ";
        int top;
        do {
            top = st.top(); st.pop();
            in_stack[top] = false;
            cout << top << " ";
        } while (top != u);
        cout << "}" << endl;
    }
    return cnt;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值