无向图的双连通分量——AcWing 395. 冗余路径

无向图的双连通分量

定义

在无向图中,一个双连通分量(Biconnected Component, BCC)是指这样的子图:删除其中任意一个顶点都不会使这个子图分离成两个或更多个不相连的子图。换句话说,双连通分量是无割点的极大连通子图。

运用情况

  • 网络可靠性分析:在通信网络中,理解网络的连通性,找出关键的节点和链路,有助于提升网络的稳定性和容错能力。

  • 电路设计:在电路板设计中,识别哪些部分是双连通的可以帮助工程师优化布局,减少因单一故障点导致的整体失效风险。

  • 社交网络分析:在社交网络中,双连通分量可以帮助识别社群内部的关键人物或纽带,这些人物对于社群的凝聚力至关重要。

注意事项

  1. 割点:双连通分量的计算过程中,割点(即删除该点会使得图分裂成多个连通分量的点)的识别非常重要。每个割点都是连接不同双连通分量的桥梁。

  2. 边的冗余:双连通分量中的每条边至少属于一个双连通分量,但可能同时属于多个分量,尤其是那些连接割点的边。

  3. 数据结构:在算法实现时,合理选择数据结构(如邻接表、并查集等)对提高算法效率至关重要。

解题思路

  1. DFS遍历:通过深度优先搜索(DFS)遍历图中的所有顶点,为每个顶点分配发现时间和低值(low value),类似于Tarjan算法中寻找强连通分量的过程。

  2. 识别割点和双连通分量

    • 对于每个顶点u,在其DFS子树中,如果存在一个子节点v,使得v到其所有祖先的路径都经过u,则u是一个割点。
    • 当发现一个顶点u的低值等于其发现时间时,意味着找到了一个双连通分量的边界。
  3. 记录双连通分量:每当找到一个双连通分量的边界,就记录当前DFS栈中的顶点集合作为该双连通分量的一部分。

AcWing 395. 冗余路径

题目描述

395. 冗余路径 - AcWing题库

运行代码

#include <cstring>
#include <iostream>

using namespace std;

const int N = 5010, M = 20010;

int n, m;
int h[N], e[M], ne[M], idx;
int dfn[N], low[N], timestamp;
int stk[N], top;
int id[N], dcc_cnt;
int d[N];
bool is_bridge[N];

void add(int a, int b)
{
    e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}

void tarjan(int u, int from)
{
    dfn[u] = low[u] = ++ timestamp;
    stk[ ++ top] = u;
    
    for(int i = h[u]; ~i; i = ne[i])
    {
        int j = e[i];
        if(!dfn[j])
        {
            tarjan(j, i);
            low[u] = min(low[u], low[j]);
            if(dfn[u] < low[j]) 
                is_bridge[i] = is_bridge[i ^ 1] = true;
        }
        else if(i != (from ^ 1))
            low[u] = min(low[u], dfn[j]);
    }
    
    if(dfn[u] == low[u])
    {
        ++ dcc_cnt;
        int y;
        do
        {
            y = stk[top -- ];
            id[y] = dcc_cnt;
        }while(u != y);
    }
}

int main()
{
    while(cin >> n >> m, n || m)
    {
        memset(h, -1, sizeof h);
        idx = 0;
        
        while(m -- )
        {
            int a, b;
            cin >> a >> b;
            add(a, b);
            add(b, a);
        }
        
        tarjan(1, -1);
        
        for(int i = 0; i < idx; i ++ )
            if(is_bridge[i])
                d[id[e[i]]] ++ ;
        
        int cnt = 0;
        for(int i = 1; i <= dcc_cnt; i ++ )
            if(d[i] == 1) 
                cnt ++ ;
        
        cout << (cnt + 1) / 2 << endl;
        
        return 0;
    }
}

代码思路

  • 首先定义了一些常量和数组来存储图的相关信息,如节点数、边数、邻接表等。
  • add 函数用于向邻接表中添加边。
  • tarjan 函数是核心,通过深度优先搜索来计算每个节点的 dfn(时间戳)和 low(回溯能到达的最早时间戳)值,从而判断边是否为桥,并标记双连通分量。
  • 在 main 函数中,读取输入的节点数和边数,构建图,调用 tarjan 函数进行计算,最后统计双连通分量的相关信息并输出结果。

改进思路

  • 代码的注释可以更加详细,以提高代码的可理解性。
  • 可以增加一些错误处理,比如输入不合法时的提示。
  • 考虑使用更具可读性的数据结构,如 vector 来替代数组。

改进代码

#include <iostream>
#include <vector>

using namespace std;

// 最大节点数
const int N = 5010; 
// 最大边数
const int M = 20010; 

int n, m;
// 邻接表
vector<int> h(N, -1);
vector<int> e(M);
vector<int> ne(M);
int idx;

// 时间戳
int dfn[N];
// 回溯能到达的最早时间戳
int low[N];
int timestamp;

// 栈
vector<int> stk;
int top;

// 每个节点所属的双连通分量编号
int id[N];
int dcc_cnt;

// 每个双连通分量的度
int d[N];
// 标记边是否为桥
bool is_bridge[M];

// 添加边
void add(int a, int b) {
    e[idx] = b;
    ne[idx] = h[a];
    h[a] = idx++;
}

/**
 * Tarjan 算法核心函数
 * @param u 当前节点
 * @param from 边的编号
 */
void tarjan(int u, int from) {
    dfn[u] = low[u] = ++timestamp;
    stk.push_back(u);

    for (int i = h[u]; i!= -1; i = ne[i]) {
        int j = e[i];
        if (!dfn[j]) {
            tarjan(j, i);
            low[u] = min(low[u], low[j]);
            if (dfn[u] < low[j]) 
                is_bridge[i] = is_bridge[i ^ 1] = true;
        }
        else if (i!= (from ^ 1))
            low[u] = min(low[u], dfn[j]);
    }

    if (dfn[u] == low[u]) {
        ++dcc_cnt;
        int y;
        do {
            y = stk.back();
            stk.pop_back();
            id[y] = dcc_cnt;
        } while (u!= y);
    }
}

/**
 * 主函数
 * 处理输入,计算并输出结果
 */
int main() {
    while (cin >> n >> m, n || m) {
        // 初始化
        fill(h.begin(), h.end(), -1);
        idx = 0;

        while (m--) {
            int a, b;
            cin >> a >> b;
            add(a, b);
            add(b, a);
        }

        tarjan(1, -1);

        for (int i = 0; i < idx; i++)
            if (is_bridge[i])
                d[id[e[i]]]++;

        int cnt = 0;
        for (int i = 1; i <= dcc_cnt; i++)
            if (d[i] == 1) 
                cnt++;

        cout << (cnt + 1) / 2 << endl;

        return 0;
    }
}

  • 5
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
要获取无向图双连通分量,可以使用Tarjan算法。以下是一个Python实现的示例代码: ``` def tarjan_biconnected_components(graph): """ Tarjan算法计算无向图双连通分量 :param graph: 无向图,用邻接列表表示 :return: 双连通分量列表 """ index_counter = [0] stack = [] lowlink = {} index = {} result = [] bridges = [] def strongconnect(node): # 为节点赋予唯一的索引 index[node] = index_counter[0] lowlink[node] = index_counter[0] index_counter[0] += 1 stack.append(node) # 对于每个相邻节点v for v in graph[node]: # 如果v没有被访问过,则递归调用strongconnect if v not in index: strongconnect(v) lowlink[node] = min(lowlink[node], lowlink[v]) # 如果v是一个桥,则将桥添加到bridges列表中 if lowlink[v] == index[v]: bridges.append((node, v)) # 如果v已经在堆栈中,则更新此节点的lowlink elif v in stack: lowlink[node] = min(lowlink[node], index[v]) # 如果节点是一个连接分量的根,则弹出堆栈,并收集连通分量 if lowlink[node] == index[node]: connected_component = [] while True: v = stack.pop() connected_component.append(v) if v == node: break result.append(connected_component) for node in graph: if node not in index: strongconnect(node) return result ``` 使用示例: ``` graph = { 1: {2, 3}, 2: {1, 3, 4}, 3: {1, 2, 4}, 4: {2, 3, 5}, 5: {4} } result = tarjan_biconnected_components(graph) print(result) # 输出:[[1, 2, 3], [4, 5]] ``` 以上代码实现了Tarjan算法,用于计算无向图双连通分量。传入的图以邻接列表表示,返回的结果是双连通分量的列表。对于结果中的每个双连通分量,其包含的节点组成了一个强连通分量,即任意两个节点都有一条路径相连。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

筱姌

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值