关于图的关节点及重连通分量的一些概念,此处不再冗述,不理解的可以阅读相关资料:
http://www.cnblogs.com/bless/archive/2008/09/28/1256875.html
此处着重讲到,在算法DfnLow增加一些语句, 可把连通图的边划分到各重连通分量中。
上文中求重连通分量的代码如下:
void Graph::Biconnected ( ) { //公有函数:从顶点0开始深度优先搜索
int num = 1; //访问计数器num
dfn = new int[NumVertices]; //dfn是深度优先数
low = new int[NumVertices]; //low是最小祖先号
for ( int i = 0; i < NumVertices; i++ ) {
dfn[i] = low[i] = 0;
}
DfnLow ( 0, -1 ); //从顶点 0 开始
delete [ ] dfn;
delete [ ] low;
}
void Graph::Biconnected ( const int u, const int v ) {
//私有函数:计算dfn与low, 根据其重连通分量输出Graph的边。
//在产生的生成树中, v 是 u 的双亲结点, S 是一个初始为空的栈,
//应声明为图的数据成员。
int x, y, w;
dfn[u] = low[u] = num++;
w = GetFirstNeighbor (u); //找顶点u的第一个邻接顶点w
while ( w != - 1 ) {
if ( v != w && dfn[w] < dfn[u] )
S.Push ( (u,w) ); //w不是u的双亲且w先于u被访问, (u,w)进栈
if ( dfn[w] == 0 ) { //未访问过, w是u的孩子
Biconnected (w, u); //从w递归深度优先访问
low[u] = min2 ( low[u], low[w] ); //根据先求出的low[w], 调整low[u]
if ( low[w] >= dfn[u] ) { //无回边, 原来的重连通分量结束
cout << “新重连通分量: ” << endl;
do {
(x, y) = S.Pop ( );
cout << x << "," << y << endl;
} while ( (x, y) 与 (u, w) 不是同一条边 );
} //输出该重连通分量的各边
} else if ( w != v ) //有回边,计算
low[u] = min2 ( low[u], dfn[w] ); //根据回边另一顶点w调整low[u]
w = GetNextNeighbor (u, w); //找顶点u的邻接顶点w的下一个邻接顶点
}
}
文中提到,如果不是根的话,u成为关节点的充要条件就是至少有一个子女w low[w]>=dfn[u],而low的求解又与其所有子女的low相关,所以,此处基本策略是递归,父节点的low的值随着所有子女的low值做调整。
上述有求重连通分量的这么一段话:
if ( low[w] >= dfn[u] ) { //无回边, 原来的重连通分量结束
cout << “新重连通分量: ” << endl;
do {
(x, y) = S.Pop ( );
cout << x << "," << y << endl;
} while ( (x, y) 与 (u, w) 不是同一条边 );
}//输出该重连通分量的各边
这段话表明目前来说,u是关节点了,下面就把所有w下面的边都输出来,直到把(u,w)也输出来,这个构成 一个重连通分量。
这些边是什么时候被压入栈的呢?
if ( v != w && dfn[w] < dfn[u] )
S.Push ( (u,w) ); //w不是u的双亲且w先于u被访问, (u,w)进栈
这句话的判断条件一开始一直没怎么理解,后来才明白,其实这句话把所有的边都保存了,判断条件只是为了防止保存重复的边。
首先来看 v!=w,其实作为u的父亲,v其实也是u的邻居之一,但是(v,u)已经存在了,所以,没有必要把(u,v)也放进去。(对于无向图来说,两者其实一回事)。
其次再来看dfn[w]<dfn[u],
一种情况是w还未被访问过,则dfn[w]肯定等于0,成立。至于注释写到“//w不是u的双亲且w先于u被访问, (u,w)进栈 “,我估计是因为求low值都是先求子女,再求父亲,所以说是w先于u被访问,
这个判断的另外一层意思,很可能w是u的某个子孙q的子女,这样,(w,u)肯定已经作为回边被保存过了,这里dfn[w]>dfn[u],就不再需要保存了。