一个图的点连通度的定义为,最小割点集合中的顶点数。
一个图的边连通度的定义为,最小割边集合中的边数。
如果一个无向连通图的点连通度大于1,则称该图是点双连通的(point
biconnected),简称双连通或重连通。
如果一个无向连通图的边连通度大于1,则称该图是边双连通的(edge
biconnected),简称双连通或重连通。
双连通分支(biconnected
component),或重连通分支,就是图的极大双连通子图。特殊的,点双连通分支又叫做块。
[求割点与桥]
该算法是R.Tarjan发明的。对图深度优先搜索,定义DFS(u)为u在搜索树(以下简称为树)中被遍历到的次序号。定义Low(u)为u或u的子树中能通过非父子边追溯到的最早的节点,即DFS序号最小的节点。根据定义,则有:
Low(u)=Min { DFS(u) DFS(v) (u,v)为后向边(返祖边) 等价于 DFS(v)<DFS(u)且v不为u的父亲节点 Low(v) (u,v)为树枝边(父子边) }
一个顶点u是割点,当且仅当满足(1)或(2)
(1) u为树根,且u有多于一个子树。 (2) u不为树根,且满足存在(u,v)为树枝边(或称父子边,即u为v在搜索树中的父亲),使得DFS(u)<=Low(v)。
一条无向边(u,v)是桥,当且仅当(u,v)为树枝边,且满足DFS(u)<Low(v)。
[求双连通分支]
下面要分开讨论点双连通分支与边双连通分支的求法。
对于点双连通分支,实际上在求割点的过程中就能顺便把每个点双连通分支求出。建立一个栈,存储当前双连通分支,在搜索图时,每找到一条树枝边或后向边(非横叉边),就把这条边加入栈中。如果遇到某时满足DFS(u)<=Low(v),说明u是一个割点,同时把边从栈顶一个个取出,直到遇到了边(u,v),取出的这些边与其关联的点,组成一个点双连通分支。割点可以属于多个点双连通分支,其余点和每条边只属于且属于一个点双连通分支。
对于边双连通分支,求法更为简单。只需在求出所有的桥以后,把桥边删除,原图变成了多个连通块,则每个连通块就是一个边双连通分支。桥不属于任何一个边双连通分支,其余的边和每个顶点都属于且只属于一个边双连通分支。
[构造双连通图]
一个有桥的连通图,如何把它通过加边变成边双连通图?方法为首先求出所有的桥,然后删除这些桥边,剩下的每个连通块都是一个双连通子图。把每个双连通子图收缩为一个顶点,再把桥边加回来,最后的这个图一定是一棵树,边连通度为1。
统计出树中度为1的节点的个数,即为叶节点的个数,记为leaf。则至少在树上添加(leaf+1)/2条边,就能使树达到边二连通,所以至少添加的边数就是(leaf+1)/2。具体方法为,首先把两个最近公共祖先最远的两个叶节点之间连接一条边,这样可以把这两个点到祖先的路径上所有点收缩到一起,因为一个形成的环一定是双连通的。然后再找两个最近公共祖先最远的两个叶节点,这样一对一对找完,恰好是(leaf+1)/2次,把所有点收缩到了一起。
有向图的强连通分量
有向图中若两个顶点i,j间有i到j和j到i的路径 称两个顶点强连通
有向图的极大强连通子图,称为强连通分量SCC
我们希望按SCC图拓扑顺序的逆序进行遍历 这样才能每次DFS只得到一个SCC
Kosaraju算法 Gabow算法 Tarjan算法 O(n+e)
Kosaraju算法
思想:强连通分量一定是某种DFS形成的DFS树森林
步骤:1:先对原图G进行深搜形成森林(树),步骤2.任选一棵树对其进行深搜(注意这次深搜结点A能往子结点B走的要求是边A->B存在于反图GT),能遍历到的顶点就是一个强连通分量。余下部分和原来的森林一起组成一个新的森林,继续步骤2直到没有顶点为止。
实现:(1)在有向图中,从某个顶点出发进行深度优先遍历,并按其所有邻接点的访问都完成(即出栈)的顺序将顶点排列起来。
void dfs(int x)
{
flag[x]=1;
int e=p[x];
while(e>0&&eflag[e]==0)//eflag[e]==0表示e这条边是原图G的边
{
int k = b[e];
if(!flag[k]) dfs(k);//当X的邻接点k没有访问过,就递归搜索k。
e=next[e];//x的下一条边。
}
stack1[++top] = x;//当X的所有邻接点都访问完后,将x存入栈stackl中。
}
(2)在该有向图中,从最后完成访问的顶点出发,沿着以该顶点为头的弧作逆向的深度优先遍历,若此次遍历不能访问到有向图中所有顶点,则从余下的顶点中最后完成访问那个顶点出发,继续作逆向的深度优先遍历,依次类推,直至有向图中所有顶点都被访问到为止。
void re_dfs(int x)
{
flag[x]=1;
attr[x]=ans;//x的所属的连通分量的编号。
int e=pp[x];
while(e>0&&eflag[e]= 1)//eflag[e]= 1表示e这条边是反图GT中的边
{
int k = bb[e];
if(!flag[k])re_dfs(k);
e = next[e];
}
}
(3)每一次逆向深度优先遍历所访问到的顶点集便是该有向图的一个强连通分量的顶点集,若仅作一次逆向深度优先遍历就能访问到图的所有顶点,则该有向图是强连通图
void traver()
{
for(int i = 1; i <= n; i++)//从图中任意一个没有访问过的顶点开始DFS
if(!flag[i]) dfs(i);
}
void re_traver()
{
memset(flag, 0, sizeof(flag));
while(top >= 1)
{
int k = stack1[top--];
if(!flag[k]){
re_dfs(k);
++ans;
}
}
}
计算之后的强分量编号的顺序,刚好是该有向图的一个拓扑排序
Tarjan算法
思想:强连通分量是DFS树中的子树
步骤:任选一结点开始进行DFS,已访问结点不再访问。把当前搜索树中未处理的结点加入一个堆栈, 回溯时可以判断栈顶到栈中的结点是否为一个强连通分量。定义DFN(u)为结点u搜索的次序编号(时间戳),LOW(u)为u或u的子树能够追溯到的最早的栈中结点的次序号。当DFN(u) = LOW(u)时,以u为根的搜索子树上所有结点是一个强连通分量。
void tarjan(int u)
{
++index;
dfn[u] = index;
low[u] = index;
stack[++top] = u;
instack[u] = 1;
flag[u] = 1;
int e = p[u];
while(e > 0)
{
int v = b[e];
if(!flag[v]){
tarjan(v);
low[u] = min(low[u], low[v]);
}
else{
if(instack[v])
low[u] = min(low[u],dfn[v]);
e = next[e];
}
if(low[u] == dfn[u])
{
int j;
do{
j=stack[top--];
instack[j]=0;
cout<<j<<””;
}while(u!=j);
cout<<endl;
}
}
缩点
大幅度降低图的复杂度 缩点后得到的图,必定是DAG(有向无环图)