zoukankan      html  css  js  c++  java
  • 最近公共祖先

    LCA算法

    朴素算法

    也就是我们所说的暴力算法,大致的思路是从树根开始,往下迭代,如果当前结点比两个结点都小,那么说明要从树的右子树中找;相反则从左子树中查找;直到找到一个结点在当前结点的左边,一个在右边,说明当前结点为最近公共祖先,如果一个结点是另外一个结点的祖先,那么返回前面结点的父亲结点即可。

    class Node:
        val = 0
        left = right = None
        def __init__(self, val=0):
            self.val = val
    def getLCA(current, p, q):
        left_val, right_val = p.val, q.val
        parent = Node()
        # 保证左结点的值小于右结点
        if left_val > right_val:
            left_val, right_val = right_val, left_val
        while True:
            if current.val > right_val:
                parent = current
                current = current.left
            elif current.val < left_val:
                parent = current
                current = current.right
            elif current.val == left_val or current.val == right_val:
                return parent.val
            else:
                return current.val
    

    如果这不是一颗BST,那么同样的可以使用递归来计算,其原理是找到两个相对应的结点,如果不存在则返回空,然后向上递归,如果当前结点的左子结点和右子结点同时存在,那么说明这是最近公共祖先。

    def getLCA2(root, node1, node2):
        if root == None:
            return None
        if root == node1 or root == node2:
            return root
        left = getLCA2(root.left, node1, node2)
        right = getLCA2(root.right, node1, node2)
        if left != None and right != None:
            return root
        elif left != None:
            return left
        elif right != None:
            return right
        else:
            return None
    

    这种做法的缺点在于,每一次查询都需要重新计算,显然不适用于批量查询。

    Tarjan算法

    这是离线算法,离线和在线的区别在于是否提前知道它们的数据。

    可以把树看作是一个有向图,在有向图G中,如果两个顶点间至少存在一条路径,称两个顶点强连通(strongly connected)。如果有向图G的每两个顶点都强连通,称G是一个强连通图。非强连通图有向图的极大强连通子图,称为强连通分量(strongly connected components)。那么这个问题就转化成了如何在有向图中查找强连通,而Tarjan算法正好是解决这个的算法。

    比如在这个图中,1,2,4,5,6,7,8构成一个强连通分量,而由于39都无法达到强联通分量的相互联通的要求,因此各自单独构成一个强连通分量。

    如何理解

    Tarjan算法的基本框架:

    1. 遍历一个点,指定一个唯一时间戳DFN[i],指定该点向前追溯可追溯到的最老时间戳LOW[i];
    2. 枚举当前点所有边,若DFN[j]=0表明未被搜索过,递归搜索之;
    3. 若DFN[j]不为0,则j被搜索过,这时判断j是否在栈中,且j的时间戳DFN[j]小于当前时间戳DFN[i],可判定成环.将LOW[i]设定为DFN[j];
    4. 若这个点LOW[i]和DFN[i]相等,说明这个节点是所有强连通分量的元素中在栈中最早的节点,也就是我们要找的跟;
    5. 弹栈,将这个强连通分量全部弹出,缩成点。

    Tarjan算法在DFS的过程中维护了一些信息:DFN、LOW和一个栈。

    • DFN[i]:表示结点 i 在DFS中是第几个被访问到的结点,称为时间戳;
    • LOW[i]:表示结点 i 出发,通过有向边可以到达的所有结点中最小的index;
    • 栈:存储当前已经访问过,但是没有被归类为任何的一个强连通分量的结点;

    算法的关键在于如何判定某结点是否是强连通分量的根,在这里根结点指深度优先搜索时强连通分量中首个被访问的结点。需要注意的是,栈中的结点不是在以它为根的子树搜索完成后出栈,而是在整个强连通分量被找到时。

    void tarjan(int i)//Tarjan 
    {
        int j;
        DFN[i]=LOW[i]=++Dindex;//Index 时间戳 
        instack[i]=true;//标记入栈 
        Stap[++Stop]=i;//入栈 
        for (edge *e=V[i];e;e=e->next)//枚举所有相连边 
        {
            j=e->t;//临时变量 
            if (!DFN[j])//j没有被搜索过 
            {
                tarjan(j);//递归搜索j 
                if (LOW[j]<LOW[i])//回溯中发现j找到了更老的时间戳 
                    LOW[i]=LOW[j];//更新能达到老时间戳 
            }
            else if (instack[j] && DFN[j]<LOW[i])//如果已经印有时间戳 且 时间戳比较小,则有环 
                LOW[i]=DFN[j];//当前记录可追溯时间戳更新 
        }
        if (DFN[i]==LOW[i])//可追溯最小的index是自己,表明自己是当前强连通分量的跟
        {
            Bcnt++;//强连通分量数增加 
            //在当前结点之后入栈并且还不属于其它的强连通分量的结点构成以当前结点为跟的强连通分量
            do
            {
                j=Stap[Stop--];//出栈顶元素 
                instack[j]=false;//标记出栈 
                Belong[j]=Bcnt;//记录j所在的强连通分量编号
            }
            while (j!=i);//如果是当前元素,弹栈完成 
        }
    }
    void solve()
    {
        int i;
        Stop=Bcnt=Dindex=0;
        memset(DFN,0,sizeof(DFN));//标记为为搜索过 
        for (i=1;i<=N;i++) // 确保所有结点都被访问到
            if (!DFN[i])
                tarjan(i);
    }
    

    同样的,根据程序对之前的那种图进行分析,得到最后的结果,其中每个结点上面的值代表的是该结点在有向边中能够到达的最小的index。

    其它

    除了上面提到的算法,还有RMQ算法,倍增算法,后面遇到再更新了。

    参考

    参考1参考文章2

  • 相关阅读:
    java初学者之java语言主要知识点三
    C++类的对象和类的指针的区别
    win32多线程: 线程创建与结束等待
    多线程学习:win32多线程编程基本概念(转)
    C++常用数据类型和Windows常见数据类型
    VC++2017关于项目出现"const char *" 类型的实参与 "char *" 类型的形参不兼容错误的解决方法
    Linux环境下vi/vim编辑器常用命令
    c++学习笔记之类模板
    c++学习笔记之函数重载和模板理解
    c++学习笔记之多态和虚函数
  • 原文地址:https://www.cnblogs.com/George1994/p/7111274.html
Copyright © 2011-2022 走看看