图论连通性

无向图

  • 割点:删除x和与x相连的边,图不再连通,x为割点
  • 割边:删去该边e,图不再连通,e为割边
  • 点双连通分量:其本身不存在割点,但可以有原图的割点(此时在这个点双中就是普通的点),极大图,一个点双中可以有多个原图的割点
  • 边双连通分量:其本身不存在割边,极大图
  • 点双不具有传递性,边双具有传递性((x,y),(y,z)=>(x,z))

Tarjan

  •  时间戳dfn:访问到的时间
  • 返祖边:搜索树上连向其祖先节点的边
  • 由于搜索树结构,不存在横向边(连向同层节点的边)
  • low[u]定义:u和以u为根的子树通过返祖边(包括树边和非树边)能连到的最小的dfn
  • 若是一条树边:low[u]=min(low[v[,low[u])
  • 若是一条非树边:low[u]=min(low[u],dfn[v])

求割点

  • 割点满足:其儿子的low[v]>=dfn[u],即没有儿子跨过u,儿子与u相连只有树边
  • 对于根要判断儿子个数大于1
  • 可以走反向边
  • 特判为根且搜索树上只有一个儿子,此时注意重边
  • 求a,b间的割点,tarjan(a)判断如下
    if(low[v]>=dfn[x]){
        if(x!=a&&x!=b&&dfn[x]<=low[b])ge[x]=true;
    }

求点双

  •  每次访问到一个点将其入栈
  • 若满足low[v]>=dfn[u],则出栈直到v出,此时出栈的所有点和v是一个点双
  • 每个点双,每个点都在一个环上,若点双有奇环,则所有点都在奇环
  • 判奇环,二分图黑白染色

求割边

  • 割边满足: 一条边只走一次,v没有另一条边到达u(父亲)即low[v]>dfn[u]
  • 可以避免重边

 求边双

  • 一边只走一次,若low[u]==dfn[u](除了树边,没有其他到树上祖先的边),则为边双

点双缩点连图

  • 割点单独为一个点,每个点双除割点,缩成一个点
  • 割点向其所在的点双连边

边双缩点连图

  • 每个边双缩点,用桥相连 

缩点连图

  • 转为无向无环图 

有向图

  • 强连通:图中存在路径u--->v和v--->u
  • 弱连通:图中只存在路径u--->v或v--->u
  • 强连通分量:该子图中对于任意一点对,存在路径u--->v和v--->u,极大图

Tarjan

  • 时间戳dfn
  • low[u]为u和以u为根的子树中能连到的还未出栈的最小dfn
  • 访问到则入栈
  • dfn[u]=low[u],即到该点往上不会有强连通(有向边),
  • 出栈直到u出栈,此时出栈的点为一个强连通分量

 强连通缩点

  • 将有向图,转为有向无环图
  • 之后可以考虑入度出度,topu排序dp
  • 注意会有重边,所以缩点连边时要判断是否已经连上,不要重复计算入度出度
  • 求加入最少的边变成一个强连通,先缩点
    int a=0,b=0;
    for(int i=1;i<=cc;++i){
        if(!in[i])a++;
        if(!out[i])b++;
    }
    if(cc==1)std::cout<<0<<std::endl;
    else std::cout<<max(a,b)<<std::endl;

代码

  • 割点
        auto tarjan=[&](auto &&tarjan,int x)->void{
            dfn[x]=low[x]=++tt;int ch=0;
            for(auto v:G[x]){
                if(!dfn[v]){
                    tarjan(tarjan,v);
                    low[x]=min(low[x],low[v]);
                    if(low[v]>=dfn[x]){
                        ch++;
                        if(x1!=rt||ch>1)
                            ge[x]=true;
                    }
                }else low[x]=min(low[x],dfn[v]);
            }
            if(rt==x&&ch==1)ge[x]=false;
        };

  • 点双,缩点
            std::vector<int> dfn(n+5,0),low(n+5,0);int tt=0;
            std::vector<int> sz(n+5,0),col(n+5,0);int cc=0;
            std::stack<int> st;std::vector<std::vector<int>> hs(n+5);
            std::vector<bool> ge(n+5,false);int rt=0;
            auto tarjan=[&](auto &&tarjan,int x)->void{
                dfn[x]=low[x]=++tt;int ch=0;
                st.push(x);
                for(auto v:G[x]){
                    if(!dfn[v]){
                        tarjan(tarjan,v);
                        low[x]=min(low[x],low[v]);
                        if(low[v]>=dfn[x]){
                            ch++;
                            if(x!=rt||ch>1){
                                ge[x]=true;
                            }
                            int y;cc++;//缩点
                            do{
                                y=st.top();st.pop();
                                col[y]=cc;sz[cc]++;hs[cc].push_back(y);
                            }while(y!=v);
                            hs[cc].push_back(x);sz[cc]++;//割点放入
                        }
                    }else low[x]=min(low[x],dfn[v]);
                }
    
            };
            for(int i=1;i<=n;++i)if(!dfn[i]){
                    rt=i;
                    tarjan(tarjan,i);
                }

  • 割边,边双,缩点
        std::vector<int> dfn(n+5,0),low(n+5,0);int tt=0;
        std::vector<int> sz(n+5,0),col(n+5,0);int cc=0;
        std::stack<int> st;std::vector<std::vector<int>> hs(n+5);
        auto tarjan=[&](auto &&tarjan,int x,int p)->void{
            dfn[x]=low[x]=++tt;
            st.push(x);
            for(auto [v,i]:G[x]){//i为边的编号
                if(i==p)continue;
                if(!dfn[v]){
                    tarjan(tarjan,v,i);
                    low[x]=min(low[x],low[v]);
    //                if(low[v]>dfn[x]){
    //                    std::cout<<i<<std::endl;
    //                }
                }else low[x]=min(low[x],dfn[v]);
            }
            if(low[x]==dfn[x]){
                int y;cc++;
                do{
                    y=st.top();st.pop();
                    col[y]=cc;sz[cc]++;hs[cc].push_back(y);
                }while(y!=x);
            }
        };
        for(int i=1;i<=n;++i)if(!dfn[i])tarjan(tarjan,i,0);

  • 强连通分量
        std::vector<int> dfn(n+5,0),low(n+5);int tt=0;
        std::stack<int> st;std::vector<std::vector<int>> hs(n+5);
        std::vector<int> col(n+5,0),sz(n+5,0);int cc=0;
        auto tarjan=[&](auto &&tarjan,int x)->void{
            dfn[x]=low[x]=++tt;
            st.push(x);
            for(auto v:G[x]){
                if(!dfn[v]){
                    tarjan(tarjan,v);
                    low[x]=min(low[x],low[v]);
                }else if(!col[v])low[x]=min(low[x],dfn[v]);
            }
            if(dfn[x]==low[x]){
                ++cc;
                int y;
                do{
                    y=st.top();st.pop();
                    col[y]=cc;sz[cc]++;hs[cc].push_back(y);
                }while(y!=x);
            }
        };
        for(int i=1;i<=n;++i){
            if(!dfn[i])tarjan(tarjan,i);
        }
     
### 图论连通性的定义 在图论中,连通性是指一个图中任意两个顶点之间是否存在一条路径使得它们能够相互到达。如果在一个无向图中,任何一对顶点都存在至少一条路径相连,则该图为连通图;否则称为非连通图[^1]。 对于有向图而言,强连通的概念更为重要。当有向图中的每对顶点 \(u\) 和 \(v\) 都满足可以从 \(u\) 到达 \(v\) 并且也可以从 \(v\) 返回到 \(u\) 时,这样的子图被称为强连通分量。 ### 连通性检测的算法及其实现 #### Kosaraju 算法 Kosaraju 算法是一种用于寻找有向图中所有强连通分量的有效方法。它利用两次深度优先搜索(DFS),第一次是在原始图上执行 DFS 得到逆序排列的顶点序列,第二次则是在反转后的图上按照上述顺序再次进行 DFS 来划分不同的强连通分量。 以下是简单的 Python 实现: ```python def kosaraju(graph): visited = set() stack = [] def first_dfs(node): if node not in visited: visited.add(node) for neighbor in graph[node]: first_dfs(neighbor) stack.append(node) reversed_graph = {} for key, values in graph.items(): if key not in reversed_graph: reversed_graph[key] = [] for value in values: if value not in reversed_graph: reversed_graph[value] = [] reversed_graph[value].append(key) sccs = [] def second_dfs(node, component): if node not in visited: visited.add(node) component.append(node) for neighbor in reversed_graph.get(node, []): if neighbor not in visited: second_dfs(neighbor, component) for vertex in list(graph.keys()): if vertex not in visited: first_dfs(vertex) visited.clear() while stack: v = stack.pop() if v not in visited: current_scc = [] second_dfs(v, current_scc) if current_scc: sccs.append(current_scc) return sccs ``` #### Union-Find 算法 Union-Find 是一种解决动态连通性问题的数据结构技术,广泛应用于最小生成树等问题之中。它的核心在于维护一组元素之间的等价关系,并支持两种主要的操作:“查找”某元素所属集合以及“合并”两个不同集合][^[^24]。 下面是一个基于路径压缩优化版本的 Quick-Union 实现示例: ```python class UnionFind: def __init__(self, n): self.id = list(range(n)) self.size = [1]*n def root(self, i): while i != self.id[i]: self.id[i] = self.id[self.id[i]] # Path compression i = self.id[i] return i def connected(self, p, q): return self.root(p) == self.root(q) def union(self, p, q): rp = self.root(p) rq = self.root(q) if rp == rq: return if self.size[rp] < self.size[rq]: self.id[rp] = rq self.size[rq] += self.size[rp] else: self.id[rq] = rp self.size[rp] += self.size[rq] ``` #### 基于 DFS 的连通分量检测 这种方法适用于无向图的情况,通过递归访问每一个未被标记过的节点来探索整个连通区域[^3]。 Python 示例代码如下所示: ```python def dfs_connected_components(graph): visited = set() components = [] def explore(node, comp): visited.add(node) comp.append(node) for neighbor in graph[node]: if neighbor not in visited: explore(neighbor, comp) for vertex in graph: if vertex not in visited: component = [] explore(vertex, component) components.append(component) return components ```
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值