最近公共祖先(LCA)问题-在线ST算法

LCA算法详解

1. 概述

LCA(Least Common Ancestors),即最近公共祖先,是指这样一个问题:在有根树中,找出某两个结点u和v最近的公共祖先(另一种说法,离树根最远的公共祖先)。对于该问题,最容易想到的解决方案是遍历,复杂度是O(n)。但当数据量非常大且查询很频繁时,该算法也许会存在问题。

2. 在线ST算法

解决此问题存在两种经典的算法,一种是在线ST算法,另外一种是离线的Tarjan算法,
所谓在线算法是指用户每输入一个查询便马上处理一个查询,该算法一般用较长的时间做预处理,待信息充足以后便可以用较少的时间回答每个查询。
所谓离线算法,是指首先读入所有的询问(求一次LCA叫做一次询问),然后重新组织查询处理顺序以便得到更高效的处理方法
在线算法DFS+ST描述:将树看成一个无向图,u和v的公共祖先一定在u与v之间的最短路径上

● DFS:从树的根节点T开始,深度遍历树,并记录下每次到达的顶点。第一个的结点是root(T),每经过一条边都记录它的端点。由于每条边恰好经过2次,因此一共记录了2n-1个结点,用E[1, … , 2n-1]来表示。

● 计算R[]:用R[i]表示E数组中值为i的元素第一次出现的下标,即如果R[u] < R[v]时,DFS访问的顺序是E[R[u], R[u]+1, …, R[v]]。虽然其中包含u的后代,但深度最小的还是u与v的公共祖先。

● RMQ:当R[u] ≥ R[v]时,LCA[T, u, v] = RMQ(L, R[v], R[u]);否则LCA[T, u, v] = RMQ(L, R[u], R[v]),计算RMQ。

在线算法DFS+ST代码如下:
1.数据结构描述:

    // 头结点信息
    private Node heads[];
    // 边信息
    private Edge edges[];
    // 深度遍历过程中每个节点第一次出现的序号
    private int first[];
    // 每个节点出现的深度
    private int depth[];
    // 深度遍历序列
    private int travel[];
    // 保存节点到根节点的距离
    private int dir[];
    // 访问记录矩阵
    private boolean vis[];
    private RMQ mRmq;

    /**
     * 邻接表头结点信息
     */
    class Node {
        private int sno;// 节点编号
        private Edge firstEdge;
    }

    /**
     * 邻接表边信息
     */
    class Edge {
        private int sno;
        private int from;
        private int to;
        private int wight;
        private Edge next;
    }
1.算法步骤描述:
#####     1.根据输入的节点和权重信息建立邻接表
    /**
     * 无向图创建路径
     * 
     * @param from
     * @param to
     * @param wight
     */
    public void createEdge(int from, int to, int wight) {
        addEdge(from, to, wight);
        addEdge(to, from, wight);
    }

    /**
     * 头插法创建邻接表
     * 
     * @param from
     * @param to
     * @param wight
     */
    private void addEdge(int from, int to, int wight) {
        Edge edge = new Edge();
        edge.from = from;
        edge.to = to;
        edge.wight = wight;
        edge.sno = edgeNum;
        edges[edgeNum++] = edge;
        edge.next = heads[from].firstEdge;
        heads[from].sno = from;
        heads[from].firstEdge = edge;
    }

#####     2.深度优先遍历,计算访问序列、深度信息、节点第一次出现的位置信息等

    /**
     * 深度遍历邻接表
     * 
     * @param u
     * @param dep
     */
    public void travelInDepth(int u, int dep) {
        vis[u] = true;
        travel[++index] = u;
        first[u] = index;
        depth[index] = dep;
        for (Edge edge = heads[u].firstEdge; edge != null; edge = edge.next) {
            if (!vis[edge.to]) {
                int v = edge.to;
                dir[v] = dir[u] + edge.wight;
                travelInDepth(v, dep + 1);
                travel[++index] = u;
                depth[index] = dep;
            }
        }
    }

#####     3.根据输入的查询,回答结果
    /**
     * 回答节点的最近公共祖先节点
     * @param u
     * @param v
     * @return
     */
    public int getLCANode(int u, int v) {
        if (mRmq == null) {
            mRmq = new RMQ();
            mRmq.RMQInit(depth);
        }
        u = first[u];
        v = first[v];
        if (u < v) {
            return mRmq.getMax(u, v);
        }
        return mRmq.getMax(v, u);
    }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值