void Tarjan(int x){
// 这里的话我又要说一下了,这个所谓的Tarjan算法,其实是用并查集优化了 打标记的算法;
vis[x] = 1;//这个是在打上标记标记其正在被遍历,但尚未遍历完所有子树;
for(int i = head[x]; i ; i = Next[i]){//先枚举所有出边,
int y = ver[i];
if(vis[y]) continue;//如果这个点已被遍历,或者正在遍历(注:此种情况仅针对双向边,因为会从边的终点开始遍历时会从边的终点再向向起点遍历一次)
// 就continue,也就是不再遍历一次这个点
Tarjan(y);
f[y] = x;//当你遍历完所有子树时,就会进行递归,即向上回溯之类的(其实并不是回溯啦),这时对于这条边的另一个端点的遍历已经完成了,也就是这个
//端点一直到叶节点都已经遍历完成了,这时就将这另一个端点的父亲节点指向其起点;(对于这个端点直到叶节点的所有点都会执行这个操作,
//即他们的父亲节点都将是子树的根节点);
}
for(int i = H[x];i;i = ans[i].Next){//如果这个点及其子树都以遍历完成,那么,对查询进行访问,寻找是否要查询这个点,如果需要查询,
//那么判断,其查询的另一个点是否以遍历完成,如果以便利,那么他们的LCA就是这另一个点的子树根节点;
//这里继续解释一下,首先,你要查询的两个点,肯定会属于同一个子树(这个子树不一定是最小的那个),因为他们拥有一个公共祖先,所以以这个公共祖先为
//根节点,将会得出一个树,其次,在这棵子树中,如果一个要查询的对象尚未遍历,而另一个已经遍历的话,就意味着他们向上的LCA也一定正在被遍历,
//通过上面的分析我们得出了: 这两个点属于同一个子树,并且这个子树的根节点就是Lca,而这时已有一个点已经被遍历完全,那么,这个点的父亲节点已经被更新成了
//这棵子树的根节点(因为一个点已经遍历完成时,他的父亲节点将会指向其上一个点,也就是这条以这个点为终点的边的起点,而这时,这已经遍历完的一个将要查询的点
// 就会更新它到LCA的父亲节点,这是我们先假设,这两个我们要查询的点处在不同的路径上(也就是去其中一个深度较大的点在更新其父亲节点时不会
//经过另一个节点)这时,有一个节点已经便利完全,而又有另一个不在同一路径上的节点未遍历完全,这就是表明了,他们的LCA也处于尚未便利完全的状况,
//这里不妨用反证法证明一下,如果这个LCA已经遍历完全,就意味着,这个点的所有子树都已遍历完全,根本不可能存在有未遍历完全的点,这与我们的初始状态矛盾了)
// 至此,就将这个为什么这另一个点的父亲节点就是LCA解释清楚了;
int y = ans[i].y,id = ans[i].id;
if(vis[y] == 2) {
linlin = get(y);
}
}
vis[x] = 2;//标记这个点以遍历完成;
}
lca(Tarjan)
最新推荐文章于 2022-09-15 20:11:56 发布