tarjan求强联通分量详解

本篇博客适用于对tarjan已有一定理解的人,若对该算法尚不熟悉者可自行百度搜索一些比较基础的博文。

另外下面的论述中有大量博主自己的理解,仅供大家参考,但准确性都是可以肯定的,若您在阅读时发现错误或有不能理解的地方,可下方评论。

----------------------------------------------------------------------------------------------------------------

  先上代码

void tarjan(int s) {
	int i, p;
	DFN[s] = LOW[s] = ++index;
	S.push(s);
	instack[s] = true;
	for (i = head[s]; i+1; i = e[i].next) {
		int v = e[i].v;
		if (!DFN[v]) {
			tarjan(v);
			LOW[s] = min(LOW[s], LOW[v]);
		}
		else if (instack[v]) LOW[s] = min(LOW[s], DFN[v]);
	}
	if (DFN[s] == LOW[s]) {
		do {
			p = S.top();
			S.pop();
			instack[p] = false;
			printf("%d ", p);
			
		} while(s != p);
		printf("\n");
	}
	return ;
}
for (i = 1; i <= n; i++)
    if (!DFN[i]) tarjan(i);

首先

DFN[s] = LOW[s] = ++index;
S.push(s);
instack[s] = true;

即初始化当前搜到的点的时间戳,然后把它入栈,并标记,比较好懂,不做过多解释。

for (i = head[s]; i+1; i = e[i].next) {
    int v = e[i].v;
    if (!DFN[v]) {
        tarjan(v);
	LOW[s] = min(LOW[s], LOW[v]);
    }
    else if (instack[v]) LOW[s] = min(LOW[s], DFN[v]);
}

从当前点开始搜索,遍历其所有的边。如果我们找到一个点v还没搜过,那么就去搜索它,类似于深搜,回溯时,要及时更新LOW数组,保证LOW[s]的值为一个时间戳比它小的节点,若没有这样的节点,则不进行任何操作。

我们假设现在是在搜索的过程中,我找到了一个已经搜过的点v,即DFN[v]不为0,又因为v已经搜过,而我们当前节点s还正在搜的过程中,这就保证了v的时间戳要比s的靠前,而这时s却有一条边能够到达v,那么,s和v是强连通的关系吗?(因为v的时间戳靠前也就是说我是从v搜到s的,而这时候又从s搜到了v),答案是可能是,是的情况正如前面所说,那么不是的情况呢?既然v是在s之前搜到的,那么也就不排除v已经搜完的情况,即执行了下面代码的情况

if (DFN[s] == LOW[s]) {
    do {
        p = S.top();
	S.pop();
	instack[p] = false;
	printf("%d ", p);	
    } while(s != p);
    printf("\n");
}

也就是说v点的强连通分量已经确定,从代码中也可以看出,这里使用了栈来解决这个问题。需要特别说的是,一个节点的LOW值会通过什么方式改变呢?从代码中也可以看出,无非是

if (instack[v]) LOW[s] = min(LOW[s], DFN[v]);

还有

LOW[s] = min(LOW[s], LOW[v]);

先看第二种,第二种就是把搜完的子节点的LOW值及时更新到父亲节点,因为既然儿子可以到,那么父亲一定也可以。

第一种相对复杂,我把它分为了两种情况:①我们可以先考虑v点在栈中是因为还没搜完v点,因为这样就不会执行出栈操作,那么这时既然v点还没搜完,现在我们还在搜s点,那么也就是说s点一定是从v那里搜过来的,即s的时间戳比v的大,而这时s又搜到了v,也就说出现强联通关系了,所以更新s的LOW(如果LOW已经有更小的了,那就不必更新了)。这里更新为DFN,当然也可更新为LOW(在本篇论述的内容中,即求强连通分量的时候),其实都是一样的,可以想一下若更新为DFN的话,那么当你回溯到那个点且那个点搜完的时候,若DFN等于LOW,那么就会出栈,不相等,就会继续回溯,可见我们更新的那个点的LOW值是DFN还是LOW完全不影响。②就是那个v点已经搜完,只不过它的LOW值不等于DFN,所以没有退栈。那么这种情况又怎么成环了呢?可以想到,既然v没出站,那也就是说它所在强连通分量的根节点绝对还没搜完,因为若是搜完了,这个强连通分量之中的点就全部确定出栈了。那么既然那个强连通分量根节点没搜完,而我们现在正在搜s这个点,也就是说从根节点能到s,现在又从s搜到了v,从v能间接地到根,所以强连通关系确定,更新为DFN(同样也是不影响)。

为了方便理解,栈中的元素即为搜到但是还没确定强连通分量的点,具体的情况上面已作讨论。

if (DFN[s] == LOW[s]) {
    do {
        p = S.top();
	S.pop();
	instack[p] = false;
	printf("%d ", p);	
    } while(s != p);
    printf("\n");
}

这里就是维护LOW的作用了,那么为什么相等时出栈呢?

因为若DFN[s]!=LOW[s]的话,也就是当前s已经搜完,但LOW[s]还没搜完,又因为LOW[s]和s是强连通的关系,强连通分量的确定需要每一个其中每一个点的确定,也是要搜完,而s和LOW[s]组成的强连通分量中,还有没搜完的,所以不能出栈,出栈意味着当前强连通分量的确定。

for (i = 1; i <= n; i++)
    if (!DFN[i]) tarjan(i);

每次调用tarjan只能解决一个连通图,所以在有多个图的情况下,要执行多次。

----------------------------------------------------------------------------------------------------------------

第一次写博客,内容可能有点晦涩难懂,有不明白的地方可以下方评论。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值