无向图的连通性(割点/边,双连通分量)

本文详细介绍了图论中的割点和割边概念,通过深度优先搜索(DFS)实现割点判断模板,并应用于解决实际问题。文章提供了POJ1144、POJ1523、POJ3352等题目解题思路,包括求割点数量、计算每个割点割出的连通分量以及将无向图转换为边双连通图的方法。代码示例展示了如何使用C++实现这些算法。
摘要由CSDN通过智能技术生成

割点 : 连通分量中一些关键结点,一旦删去,就不连通

割边 : 连通分量中一些关键边,一旦删去,就不连通

关于割点的判断 : 利用深搜优先生成树的思想dfs

1. 若u是根结点,且子树数量大于等于2,则是割点

2, 若u是非根结点,且存在子节点v,v和v的所有子节点都没有退回边连会u之前的祖先结点,则为割点。

(割边的判断只要把条件改为low[v] > num[u]那么(v, u)就为割边了)

#include<bits/stdc++.h>
int low[1010]; // low代表v和v的后代能连到的祖先的num,
int num[1010]; // num代表递推深度(即第几个背访问到)
int cnt[1010];
void dfs(int u, int fa){
    low[u] = num[u] = ++dfn;
    int child = 0;
    lop(i, 0, Edge[u].size()){
        int v = Edge[u][i];
        if(!num[v]){
            dfs(v, u);
            low[u] = min(low[u], low[v]);
            if(low[v] >= num[u]){//
                cnt[u] = 1;
            }
            child++;
        }
        else if(fa != v && num[v] < num[u])//处理回退边
            low[u] = min(low[u], num[v]);
    }
    if(fa == -1){
        if(child >= 2)
            ans.push_back(u);
    }
    else if(cnt[u] >= 1){
        ans.push_back(u);
    }
}

POJ 1144                        http://poj.org/problem?id=1144

割点模板题tarjan搜就完事了,要注意的是这题的读入有坑,反正我卡了好久,所有样例都能过,但是就是wa了,结果全部改完发现没问题,打算放弃了,一改输入就对了。。。。别掉坑了各位

#include<iostream>
#include<vector>
#include<algorithm>
#include<cstring>
#include<cstdio>
using namespace std;

#define el '\n'
#define rep(i, a, b) for(int i = (a); i <= (b); i++)
#define lop(i, a, b) for(int i = (a); i <  (b); i++)
#define dwn(i, a, b) for(int i = (a); i >= (b); i--)
#define ms(a, b) memset(a, b, sizeof(a))

const int MAXN = 110;
int n;
bool iscut[MAXN];
int cnt = 0;
int low[MAXN], num[MAXN];
int dfn = 0;
vector<int> Edge[MAXN];

void init(){
    memset(iscut, false, sizeof(iscut));
    memset(low, 0, sizeof(low));
    memset(num, 0, sizeof(num));
    dfn = 0;
    cnt = 0;
    rep(i, 1, 110)
        Edge[i].clear();
}
void dfs(int u, int fa){
    low[u] = num[u] = ++dfn;
    int child = 0;
    lop(i, 0, Edge[u].size()){
        int v = Edge[u][i];
        if(!num[v]){
            dfs(v, u);
            low[u] = min(low[v], low[u]);
            if(low[v] >= num[u] && u != 1){
                iscut[u] = true;
            }
            child++;
        }
        else if(v != fa && num[v] < num[u])
            low[u] = min(low[u], num[v]);
    }
    if(u == 1 && child >= 2)
        iscut[u] = true;
}
int main(){
    while(cin >> n && n){
        getchar();
        //cout << n << el;
        init();
        int u, v;
        while(cin >> u && u){//这题的读入有坑!一定要注意
            while(1){
                char ch = getchar();
                if(ch == '\n')
                    break;
                cin >> v;
                Edge[u].push_back(v);
                Edge[v].push_back(u);
            }
        }
        dfs(1, -1);//本题题意思已经说明图是连通的,所以搜一个点就行了
        rep(i, 1, n){
            if(iscut[i])
                cnt++;
        }
        cout << cnt << el;
    }
    return 0;
}

POJ 1523                        http://poj.org/problem?id=1523

题意

        很简单就是求出所有割点以及计算出每个割点割出的连通分量(或者说是包含这个割点的点双连通分量有几个)分别为几个

思路

        用tarjan算法思想,在找割点的同时记录割出的点数

#include<iostream>
#include<cstdio>
#include<vector>
#include<algorithm>
using namespace std;

#define el '\n'
#define dwn(i, a, b) for(int i = (a); i >= (b); i--)
#define rep(i, a, b) for(int i = (a); i <= (b); i++)
#define lop(i, a, b) for(int i = (a); i < (b); i++)

vector<int> Edge[1010], ans;//Edge记录边, ans记录割点
int low[1010], num[1010]; //low代表v和v的后代能连到的祖先的num, num代表递推深度(即第几个背访问到)
int cnt[1010];//记录每个点作为切除点后生成的连通分量
int dfn = 0;//时间戳
int Newnum = 0;//记录网络编号
void init(){//重置初始化
    dfn = 0;
    Newnum++;
    ans.clear();
    rep(i, 1, 1000){
        cnt[i] = 0;
        num[i] = 0;
        Edge[i].clear();
    }
}
void tarjan(int u, int fa){//tarjan算法计算每个割点可以割出的连通分量
    //dfs + 找割边 + tarjan思想
    low[u] = num[u] = ++dfn;
    int child = 0;
    lop(i, 0, Edge[u].size()){
        int v = Edge[u][i];
        if(!num[v]){
            tarjan(v, u);
            low[u] = min(low[u], low[v]);
            if(low[v] >= num[u]){//
                cnt[u]++;
            }
            child++;
        }
        else if(fa != v && num[v] < num[u])//处理回退边
            low[u] = min(low[u], num[v]);
    }
    if(fa == -1){
        if(child >= 2)  //根割点生成的连通分量 == 其子树个数 
            ans.push_back(u);
    }
    else if(cnt[u] >= 1){//非根割点生成连通分量 == 其子树个数 + 1
        ans.push_back(u);
        cnt[u]++;
    }
}
int main(){
    int u, v;
    while(cin >> u && u != 0){
        init();
        while(1){
            cin >> v;
            // cout << u << " " << v << el;
            Edge[u].push_back(v);
            Edge[v].push_back(u);
            cin >> u;
            if(u == 0)
                break;
        }
        rep(i, 1, 1000){//对每一个点都进行一次tarjan
            if(Edge[i].size() && !num[i])
                tarjan(i, -1);
        }
        printf("Network #%d\n", Newnum);
        if(ans.size() == 0)
            printf("  No SPF nodes\n");
        else{
            sort(ans.begin(), ans.end());
            lop(i, 0, ans.size()){
                printf("  SPF node %d leaves %d subnets\n", ans[i], cnt[ans[i]]);
            }
        }
        cout << el;
    }
}

POJ 3352        http://poj.org/submit?problem_id=3352

大致就是求把一个无向图转换为边双连通图

1. 先用dfs把low值全部标记一遍。(方法与求割点数遍历类似)

2. low值相同就是一个边双连通分量,看成一个大结点(缩点法)

3. 缩点后计算大结点的度,并计算出度为1的大结点有ans个

4.答案为(ans + 1) / 2  (其实每2个大结点要连一条边,但是要向上取整)

#include<iostream>
#include<vector>
#include<algorithm>
#include<cstring>
#include<cstdio>
using namespace std;

#define el '\n'
#define rep(i, a, b) for(int i = (a); i <= (b); i++)
#define lop(i, a, b) for(int i = (a); i <  (b); i++)
#define dwn(i, a, b) for(int i = (a); i >= (b); i--)
#define ms(a, b) memset(a, b, sizeof(a))

const int MAXN = 1100;
int n, r;
bool iscut[MAXN];
int cnt = 0;
int low[MAXN], num[MAXN];
int dfn = 0;
vector<int> Edge[MAXN];

void init(){
    memset(iscut, false, sizeof(iscut));
    memset(low, 0, sizeof(low));
    memset(num, 0, sizeof(num));
    dfn = 0;
    cnt = 0;
    rep(i, 1, 110)
        Edge[i].clear();
}
void dfs(int u, int fa){//用割点模板计算vis的值(有多少种vis值就有多少个边双连通分量)
    low[u] = num[u] = ++dfn;
    //int child = 0;
    lop(i, 0, Edge[u].size()){
        int v = Edge[u][i];
        if(!num[v]){
            dfs(v, u);
            low[u] = min(low[v], low[u]);
            // if(low[v] >= num[u] && u != 1){
            //     iscut[u] = true;
            // }
            //child++;
        }
        else if(v != fa && num[v] < num[u])
            low[u] = min(low[u], num[v]);
    }
    // if(u == 1 && child >= 2)
    //     iscut[u] = true;
}
int tarjan(){
    int ans = 0;
    int degree[MAXN] = {0};
    rep(i, 1, n){
        lop(j, 0, Edge[i].size()){
            if(low[i] != low[Edge[i][j]])//缩点法,边连通分量看成一个整体结点
                degree[low[i]]++;//degree[low[Edge[i][j]]]++
                //每条边在实际遍历时会遍历2次,所以度的增加只能一直加其中一个,否则会重复叠加
        }
    }
    rep(i, 1, n){
        if(degree[i] == 1)
            ans++;//记录度为一的大结点
    }
    return (ans + 1) / 2;//所需增边数
}
int main(){
    cin >> n >> r;
    rep(i, 1, r){
        int u, v;
        cin >> u >> v;
        Edge[u].push_back(v);
        Edge[v].push_back(u);
    }
    dfs(1, -1);
    cout << tarjan();
    return 0;
}

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值