无向连通图求割点割边tarjan算法

作为21考研党,在做王道图论的练习题的时候发现有一个网易的面试题,就是求无向连通图求割点的题目,按照定义理解就是去暴力每一个点,把它和它相关的边给删除看子图是否还是连通,如果连通那么就是不是割点,否则是割点,割边的定义也是类似。

去网上了学习了一个叫tarjan算法它只需要一遍DFS也是是时间复杂度为O(|V|+|E|),而之前的暴力的方法求割点O(|V|*(|V|+|E|)),求割边O(|E|*(|V|+|E|)),可见性能的提升还是很明显的。

其中有一些值得注意的地方:

1.dfn[v]代表的是顶点V在遍历中的顺序(从1开始),low[v]代表的是以v为树根节点能回溯到的最小遍历顺序。low[v]>=dfn[u]为割点判断条件(u为父节点,v为子节点),其实它就代表具体两种情况 1>low[v]=dfn[u](v最多只能回溯到它的父节点) ,2>low[v]=dfn[u]+1(v只能回溯到它本身low[v]=dfn[v])。

2.void tarjan_dfs(int u,int father) 必须设有father父节点这个参数,我开始就是没设置,你会发现low[v]==dfn[u]的情况,没有low[v]>=dfn[u],也就是说你这样只能求出割点,求不出割边的。

3.DFS中判断割边和割点的代码放在tarjan_dfs(v,u)递归的后面,但要在当前子节点for循环的里面即可。

 

测试数据:

首先录入顶点数和边数,然后再是边的两个顶点

6 6
0 1
0 2
1 3
1 4
3 4
4 5

 测试结果:

6 6
0 1
0 2
1 3
1 4
3 4
4 5
割点如下:
0
1
4
割边如下:
0->1
0->2
4->5

测试代码:

#include <iostream>
#include<stdio.h>
#include<cstring>
#include<vector>
#include<algorithm>
#include<set>
#define MAXSIZE 1000
using namespace std;
struct Node
{
    int u,v;
    Node(int x,int y)
    {
        u=x;
        v=y;
    }
   friend bool operator <(Node node1,Node node2)
   {
        if(node1.u==node2.u)
            return node1.v<node2.v;
        else return node1.u<node2.u;
   }
};
vector<int> V_Edge[MAXSIZE];
int dfn[MAXSIZE];  //dfs的遍历顺序
int low[MAXSIZE];  //对应下表节点能回溯到的最小下标
int visited[MAXSIZE]; //对是否访问过进行标记
int Count=0;        //对遍历进行标号
set<int> Ver;
set<Node> Edge;
void tarjan_dfs(int u,int father)
{
    visited[u]=1;
    dfn[u]=low[u]=++Count;  //最开始的初始化,low=dfn 节点u能回溯的最小节点为它本身
    for(int i=0;i<V_Edge[u].size();i++)
    {
        int v=V_Edge[u][i];
        if(v==father) continue;
        if(visited[v]==0)  //此节点还未访问过树边
        {
            tarjan_dfs(v,u);
            low[u]=min(low[u],low[v]);  //取它本身能回溯和以它子节点为跟的树所能回溯的最小值
            if(low[v]>=dfn[u])  //没有形成环,所以u为割点
                Ver.insert(u);
            if(low[v]>dfn[u])   //求割边
                Edge.insert(Node(min(v,u),max(v,u)));
        }
        else                //此节点已访问过,回边
            low[u]=min(low[u],dfn[v]);  //取它本身能回溯和回边能访问的节点遍历次序的最小值
        // if(low[v]>=dfn[u])和if(low[v]>dfn[u])放在这里其实也是可以的,只要放在tarjan_dfs(v,u)递归的后面,但要在for循环的里面
    }
}
int main()
{
    int n,m,v,u;
    scanf("%d%d",&n,&m);
    for(int i=0;i<m;i++)
    {
        scanf("%d%d",&v,&u);
        V_Edge[v].push_back(u);  //因为是无向图,所以一条边应该加到两个表中
        V_Edge[u].push_back(v);
    }
    tarjan_dfs(0,-1);
    set<int>::iterator it1;
    printf("割点如下:\n");
    for(it1=Ver.begin();it1!=Ver.end();it1++)
        printf("%d\n",*it1);
    printf("割边如下:\n");
    set<Node>::iterator it2;
    for(it2=Edge.begin();it2!=Edge.end();it2++)
        printf("%d->%d\n",it2->u,it2->v);
    return 0;
}

 

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值