双连接图是一种连接的、没有衔接点的图。
内容
- 简介
- 检查图是否是双连接的算法。
- 双连通图的应用。
连通的
如果一个图的每一对顶点之间都有一条路径,那么这个图就被认为是连通的。
下面这个图是连通的,因为一个顶点可以从任何其他顶点访问,而且每对顶点之间至少有一条路径存在。

下面这个图是断开的,因为它有两个独立的部分是断开的,即至少有一对顶点之间没有路径。

衔接点或切割顶点。
衔接点是图中的一个顶点,如果去除它和相应的边会导致图被断开连接。考虑下图中的图形G。
顶点 = { 0,1,2,3,4 }边 = { {0,1}, {1,3}, {2,3}, {2,4}, {3,4}.}
![]()
-
顶点1是一个衔接点,因为移除它将导致图形断开。同样被移除的相关边是{{0,1},{1,3}。}.

-
顶点3也是一个衔接点,因为移除它将导致图形被断开。同样被移除的相关边是{{1,3}, {2,3}, {3,4}}。}.

Biconnected图
下面的图是一个双连接的图。该图是连接的,没有任何衔接点。
![]()
下面这个图不是一个双连接图,因为它有一个衔接点,即顶点 "2"。
![]()
检查图形是否为双连接的算法
Tarjan的算法
-
假设图G是一个无向连接的图。
-
我们使用深度第一生成树找到图G的双连接部分,深度第一生成树是在G的一个起始顶点上使用深度第一搜索找到的。
-
为了保持顶点被访问的顺序或存储发现每个顶点的时间,我们在这里使用一个叫做dfn的向量,它代表深度第一的数字。
-
在深度第一生成树中,u和v是顶点,u是v的祖先,那么dfn(u)< dfn(v)。
-
当且仅当u是v的祖先或反之亦然时,一条非树边{u, v}是一个后边。所有的非树边都是后边。
-
要检查一个顶点是否是一个衔接点。
- 给定顶点是根:深度第一生成树的根是一个衔接点,当且仅当至少有两个子节点。
- 给定顶点不是根。 如果一个顶点v有一个子节点u,并且以u为根的子树没有背缘到v的任何祖先,那么这个顶点可以是一个衔接点:
-
这意味着对于一个非根节点v来说,如果它的后代中至少有一个u存在,使得v的祖先不能通过u或u的后代只用一条背边就能到达。
-
为了存储从一个给定的节点使用至少一条后边可以到达的具有最低发现时间的节点的信息,我们使用一个名为low的向量。
-
对于每条边,我们检查该节点可能的最低*low[]*值
low[node] = min ( low[node], low [child_node]) -
如果该边是后边,那么
low[node] = min ( low[node],discover_time[child_node]) -
现在对于一个给定的节点v,我们需要比较子节点u的low[]值和v的第一深度数。如果子节点u的low[]值大于或等于v的发现时间或第一深度数,那么v就是一个衔接点。
low[u]>= dfn[v]或者low[child_node_of_vertex]>= dfn[vertex],那么v就是一个衔接点。 -
我们需要检查从图中给定顶点到达的所有顶点是否可以从图中的其他顶点到达,以确认连通性。
程序
// Part of iq.opengenus.org
#include<bits/stdc++.h>
using namespace std;
vector<int>G[10];
void dfsMod(int u, vector<int>& dfn, vector<int>& low, vector<int>& parent, vector<int>& cut_vertices, vector<int>& visited, int& time)
{
int children_u = 0;
visited[u] = 1;
dfn[u] = low[u] = time;
time++;
for(int child : G[u])
{
if(dfn[child] == -1)
{
parent[child] = u;
children_u++;
dfsMod(child, dfn, low, parent, cut_vertices, visited, time);
low[u] = min(low[u], low[child]);
if (parent[u] == -1 && children_u > 1)
{
cut_vertices[u] = 1;
}
else if (parent[u] != -1 && low[child] >= dfn[u])
{
cut_vertices[u] = 1;
}
}
else if(parent[u] != child){
low[u] = min(low[u], dfn[child]);
}
}
}
void cut_vertices(int v, int e)
{
vector<int>parent(v + 1, -1);
vector<int>dfn(v + 1, -1);
vector<int>low(v + 1, -1);
vector<int>cut_vertices(v + 1, 0);
vector<int>visited(v+1, 0);
int time = 1;
int ap = 0;
int conn = 1;
for (int i = 0; i <= v; i++)
{
if (dfn[i] == -1)
{
dfsMod(i, dfn, low, parent, cut_vertices, visited, time);
time = 1;
}
}
for(int i =0;i <v;i++)
{
if(visited[i] == 0){
cout << "The graph is not connected and hence not biconnected";
conn = 0;
}
if (cut_vertices[i] == 1)
{
ap++;
}
}
if(ap !=0){
cout << "The graph is not biconnected and has articulation points." <<endl;
cout << "Articulation points :";
for(int i=1;i<=v; i++)
{
if (cut_vertices[i] == 1)
{
cout << i << " ";
}
}
}
if(ap==0 and conn == 1)
{
cout << "The graph is biconnected.";
}
}
int main()
{
int v, e;
v = 5, e = 5;
G[0].push_back(1);
G[1].push_back(0);
G[1].push_back(3);
G[2].push_back(3);
G[1].push_back(4);
G[3].push_back(2);
G[3].push_back(2);
G[3].push_back(4);
G[4].push_back(2);
G[4].push_back(3);
cut_vertices(v, e);
}
输出
该图不是双连接的,有衔接点。
衔接点1 3
检查一个图是否是双连接的时间复杂度是O(V+E),因为在图中使用了深度第一的搜索遍历。这里V是节点的数量,E是图中边的数量。
图G是下图中的图形:
![]()
由DFS遍历形成的生成树将是:

对于每条边,每个节点的低值都被修改为在不访问已经访问过的节点的情况下,从给定节点到达的最低深度第一数
值。
红线代表已经访问过的路径。
对于非后沿:low[节点]=min ( low[节点], low [child_node])
对于后沿:low[节点]=min (low[节点],discover_time[child_node])
![]()
现在我们可以看到,对于节点 "4 "来说,可达到的最低深度第一数是节点 "3",因此low[4]=3。绿色的路径代表通向深度第一号码最低的节点的路径。
![]()
寻找衔接点
现在我们检查每一个顶点v,我们需要比较子节点u的low[]值和v的深度首数,low[u] >= dfn[v] 或 low[child_node_of_vertex] >= dfn[vertex]
![]()
双连接图的应用
- 衔接点可能更加脆弱,衔接点顶点的失效可能导致不仅是顶点,而且是其他顶点之间的功能损失。
- 双连接图表现出冗余的特性,因此在计算机网络中非常有用。
- 它们可用于确保冗余和不希望出现单点故障的情况。