求割点 和 点联通分量

今天又开始搞图论,今天的这个算法是用来求割点的算法。以及将这个点去掉后可以形成的强联通分量数。
先来解释一下原理:
先来思考一下最朴素的算法,就是将每个点进行标记,意为将其去掉,然后进行深搜遍历,统计联通分量的个数。
这个时间复杂度为 n (这个是n个被去掉的点)* n (判断n个点)* n(搜索)???

这个宝宝也不是很清楚的拉 略略略~

先来说一下Tarjan(塔尔杨) 算法的原理,
假定从任意一点开始深搜,记录深搜的顺序,这样的话就会形成一颗深度优先搜索树,这时 观察边‘
就会发现边存在两种情况
一种是在生成树中的,另一种是不在生成树中的。
不在生成树中的边叫做回边,至于为什么叫它回边一会说。

在观察生成树树中的边,有这样的发现。

如果点u是割点,分两种情况,
一种是 u 是树根,这时就要求u至少有两个孩子,这样的话将u去掉的话,u有多少孩子,就会出现多少联通分支。

另一种情况就是 u 不是树根,这是 它至少有一个孩子w, 从w出发不可能通过w,及w的子孙和 回边到达u的祖先,

这时来解释一下 “回边” 这个词,为什么叫回,首先考虑为什么上面的情况说不能到达u
的祖先。

如果可以到达u的祖先的话这样当去掉u时,u的孩子w 可以通过 w的子孙 加上回边 连接到u 的祖先,当去掉u的时候 u 下面的部分还是可以通过回边连接在u 所在的联通分支上的,就相当于回去了又。

反之的话,u 的孩子w通过回边回到的点还是在u的子孙节点上的话,去掉u,这样u的子树就可以断开了。

以上的话就要知道 每个点的遍历顺序,和其能回到的点的最小值。不妨用low[] 来存储这个最小值。用dfn[]来存储每个点的点的遍历顺序
又可以发现low[u]有来自以下三个值

low[u] = MIN
{
    dfn[u];
    min(low[w],|w 是 u 的子女)
    min(dfn[v],|(u,v)是一条回边);
}

就是取这三个值的最小值。
总结一下的话,就是如果这个 是树根那样的话,必须有两个以上的孩子节点
如果u不是孩子节点,那样 low[u] >= dfn[u] 说明就算走回边 的话,也只能回到u的下面或者u本身。

来水个题 poj1523 这个题的背景也是联通分量应用的场景之一。

#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;
const int MAXN = 1e3 + 10;
int G[1001][1001];
bool tag[1001];
int nodes;
int temp;
int dfn[MAXN];
int low[MAXN];
int son; // 根节点子女节点个数
int subnet[MAXN];
void dfs(int u)
{
    for(int v = 1;v <= nodes;++v)
    {
        if(G[u][v]) // 两点之间可以联通
        {
            if(tag[v]) // 如果被访问过,这个又一次的被访问到说明是 "回边",那样v是u的祖先节点
            {
                low[u] = min(low[u],dfn[v]); // 这时的话就要判断回边可以回到的最小深度
            }
            else // 如果没有被访问过的话,说明这个点是u的儿子节点.
            {
                tag[v] = 1; // 对其标记
                temp++; dfn[v] = low[v] = temp ; // 给其标记深度值
                dfs(v); // 深度搜索

                low[u] = min(low[u],low[v]); //这是回退过程,更新low
                if(low[v] >= dfn[u]) // 如果
                {
                    if(u != 1) subnet[u]++; //如果不是根节点,去掉该点
                    if(u == 1) son++; // 如果是根节点,记录根节点的子女的个数
                }
            }
        }
    }
}
void init()
{
    low[1] = dfn[1] = 1;
    temp = 1;son = 0;
    memset(tag,0,sizeof(tag));
    tag[1] = 1;
    memset(subnet,0,sizeof(subnet));
}
int main()
{
    int i;
    int u,v;
    int flag;
    int num = 1;
    while(1)
    {
        cin >> u;
        if(u == 0) break;
        memset(G,0,sizeof(G));
        nodes = 0;
        cin >> v;
        nodes = max(u,v);
        G[u][v] = G[v][u] = 1;
        while(1)
        {
            cin >> u;
            if(!u) break;
            cin >> v;
            nodes = max(u,v);
            G[u][v] = G[v][u] = 1;
        }
        if(num > 1) cout << endl;
        cout << "Network #" << num++ << endl;
        init();
        dfs(1);
        if(son > 1) subnet[1] = son - 1;
        flag = 0;
        for(int i = 1;i <= nodes;++i)
        {
            if(subnet[i])
            {
                flag = 1;
                cout << "  SPF node " << i << " leaves " << subnet[i] + 1 << " subnets" << endl;
            }
        }
        if(!flag) cout << "  No SPF nodes" << endl;
    }
    return 0;
}
### Tarjan算法 Tarjan算法通过深度优先搜索(DFS)遍历图,利用DFS序回溯边来判断。具体来说,如果一个节u存在至少一个子节v,使得v无法通过其他路径到达u的祖先节(即low[v] >= dfn[u]),则u为。对于根节,需要特殊处理,当它有两个或以上子节时,它才是。 ### Tarjan算法边的判断标准是,对于边(u, v),若low[v] > dfn[u],则(u, v)为边。这是因为v无法通过其他路径到达u及其祖先,所以删除边(u, v)会使得图不连通。 ### Tarjan算法解双连通分量 边双连通分量解可以通过Tarjan算法找到所有的边,然后将这些边从图中移除,剩下的每个连通分量即为一个边双连通分量连通分量解较为复杂,需要在DFS过程中维护一个栈,每当发现一个时,就将栈中的节弹出,直到当前节v被弹出为止,这些节构成了一个连通分量。 ### 实现代码示例 以下是一个使用Tarjan算法边以及边双连通分量的Python代码示例: ```python def tarjan(u, parent): global index_counter dfn[u] = low[u] = index_counter index_counter += 1 child_count = 0 for v in adj[u]: if v == parent: continue if dfn[v] == -1: stack.append((u, v)) child_count += 1 tarjan(v, u) low[u] = min(low[u], low[v]) if low[v] > dfn[u]: # Edge (u, v) is a bridge bridges.append((u, v)) if low[v] >= dfn[u] and parent != -1: # Node u is an articulation point articulation_points.add(u) elif dfn[v] < dfn[u]: stack.append((u, v)) low[u] = min(low[u], dfn[v]) if parent == -1 and child_count > 1: # Node u is an articulation point (root case) articulation_points.add(u) # Initialization n = 5 # Number of nodes in the graph adj = [[] for _ in range(n)] # Adjacency list representation of the graph dfn = [-1] * n # Discovery time of nodes low = [-1] * n # Lowest discovery time reachable index_counter = 0 bridges = [] # List to store bridges articulation_points = set() # Set to store articulation points stack = [] # Stack to keep track of edges for biconnected components # Example graph construction adj[0] = [1, 2] adj[1] = [0, 2] adj[2] = [0, 1, 3, 4] adj[3] = [2, 4] adj[4] = [2, 3] for i in range(n): if dfn[i] == -1: tarjan(i, -1) print("Articulation Points:", articulation_points) print("Bridges:", bridges) ``` ### 边双连通分量的实现 为了找到边双连通分量,可以在找到所有边后,将这些边从图中移除,然后对剩余的边进行连通分量的查找。每个连通分量即为一个边双连通分量。 ### 连通分量的实现 连通分量的实现需要在Tarjan算法中使用栈来跟踪边,每当发现一个时,就将栈中的边弹出,直到当前边(u, v)被弹出为止,这些边构成了一个连通分量。 ### 总结 通过上述方法,可以有效地使用Tarjan算法来解图中的边以及双连通分量问题。这种方法在处理大规模图时非常高效,因为它的时间复杂度是线性的,即O(V + E),其中V是顶点数,E是边数[^1]。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值