0
点赞
收藏
分享

微信扫一扫

Tarjan实现的逻辑推导

独兜曲 2022-04-19 阅读 49

//-----------------------------------------------------------------------------------------------

记录学习 如有侵犯请联系我删除文章.

膜拜大佬文章:tarjan算法原理介绍_zakheav的博客-CSDN博客_tarjan算法证明

关于tarjan的正确性证明已经困恼了我好久 今天下定决心 去深究原理。

看到大佬的文章 感觉茅塞顿开特此记录下来

//------------------------------------------------------------------------------------------------

因为个人能力有限 不适合初学者看 仅作为理解tarjan的一份想法;(非教学 不保真)

         //仅仅代表我的看法 不保真 欢迎大家讨论;

         //仅仅代表我的看法 不保真 欢迎大家讨论;

         //仅仅代表我的看法 不保真 欢迎大家讨论;

Tarjan的简单回顾:

Tarjan 是一个O(n+m)求取强连通分量的算法

常用于: 缩点 割点和桥;

实现代码如下

/*
dfn->第一次出现的时间戳;

low->从该节点的根节点到此节点中 出现过的最小时间戳(有点复杂我重点说)

st->储存节点信息的栈

visstack->记录此点是否出在栈中

col->标记点的信息(染色)

collib-> 储存每个强连通分量都有哪些点
*/


void tarjan(int x){
    dfn[x] = low[x] = ++ccnt;
    st.push(x);
    visstack[x] = 1;
    for (int  i = head[x]; i;i=edge[i].nt){
        long long v = edge[i].to;
        if(!dfn[v]){
            tarjan(v);
            low[x] = min(low[x], low[v]);
        }else if (visstack[v]){
            low[x] = min(low[x], dfn[v]);
        }
    }
    if(dfn[x] == low[x]){
        int tot;
        ++colid;
        do{
            tot = st.top();
            st.pop();
            visstack[tot] = 0;
            col[tot] = colid;
            collib[colid].push_back(tot);
        } while (x != tot);
    }
}


问题一:low的问题:

为什么我们强调 一定是要从根节点到该节点(这条路径上) 已经出现过的 low值的最小值?也就是说为什么我们一定要强调 else if(visstack【v】)呢

    if(!dfn[v]){
        tarjan(v);
        low[x] = min(low[x], low[v]);
    }else if (visstack[v]){ //这里就是 精华部分
        low[x] = min(low[x], dfn[v]);
    }

part1:

首先我们先证明 else if(visstack【v】)对于 保证low始终是从根节点到该节点(这条路径上) 已经出现过的 low值的最小值 的有效性

最小值自然不用证明 ,所以我们的证明义务就是 在栈中 和 从根节点到该节点 的关联性

为什么只需要在节点进行tarjan的时候 去判断该点是否位于栈中即可? 或者说 为什么想要拓展的节点不位于栈中就证明 想要拓展的节点 一定不在 根节点到该节点的路径上

既 想要拓展的节点不位于栈中就证明 想要拓展的节点 一定不在 根节点到该节点的任何一条路径上

既 想要拓展的节点一定无法到达我们此时的节点u;

part2:

我们强调 一定是要从根节点到该节点(这条路径上) 已经出现过的 low值的最小值 的目的是什么呢;

那么首先我们就要讨论 维护low值的作用:两个点可以相互到达 <==> 被弹出时两个点的low值相等  

由上一个结果 我们可知 low相等的节点一定位于同一强连通分量

之后我们在讨论 为什么维护 从根节点到该节点(这条路径上) 已经出现过的 low值的最小值而其他路径上的 既已经被弹出了的点的low值无法对该节点更新;

这就有关于我们维护的堆栈弹出临界条件;

我们给出例子:

如图 2->4已经被弹出了 但是 2之后又拓展到了7  7->4 ;

我们的矛盾点就是 现在要不要由4去更新7的low值

我们观察我们的代码 

if(dfn[x] == low[x]){
        int tot;
        ++colid;
        do{
            tot = st.top();
            st.pop();
            visstack[tot] = 0;
            col[tot] = colid;
            collib[colid].push_back(tot);
        } while (x != tot);
    }

我们的弹出阶段是由 发现即将无法在拓展的点的dfn 和 low相等开始 ;之后把他的所有剩余子叶弹干净 和自己弹出 每一个 开始阶段的前置就是 堆栈中不存在其他的 dfn和low相等的点了(因为在其无法拓展进行判断之前 之前的所有节点均进行过无法拓展的判断了) 而如果 我们此时的 7节点被已经弹出的 4 节点更新了low 我们会发先 7 6 无法被正常弹出 划分到了下一个强连通分量内 自然会出错 所以我们需要保证low没有被其他弹出的子块干扰;

问题二:为什么出栈的是完整的强连通分量

这一部分我就不献丑了 我引用的大佬文献已经很清楚了大家可以去上方的链接看 我只说一下我的理解 提供下我的观点; 

为什么出栈的强连通分支是完整的:

假设不完整的话 剩余的部分必居其一
1.之前出栈的部分
2.还没有入栈的部分
3.还没有出栈的部分

2 -->因为弹出的时间限制 定理0可知 没有入栈的一定是当前点无法拓展到的点 自然不会是强连通分量

3 -->因为临界条件是 dfn等于low 所以还没有 的部分的dfn值一定小于等于当前的low 由low的意义可知 无法到达比low还小的时间戳;

1 -->如果之前出栈的部分和此部分能组成强连通分量 站在之前出栈的视角 与结论3相违背(大佬这个想的真的是很巧)

结语:到这里就告一段落啦 可能在证明过程中某些部位由逻辑漏洞 请大家发现了一定要不惜赐教 如果有讨论的也欢迎大家找我讨论 xiexie 支持

举报

相关推荐

0 条评论