PTA 6-5 二叉搜索树中的最近公共祖先

文章描述了一个在二叉搜索树(BST)中查找两个节点最近公共祖先的函数LCA,通过递归实现,利用BST的特性简化搜索过程。函数首先检查树是否为空或节点是否存在,然后根据节点关系逐步缩小搜索范围。

在一棵树T中两个结点uv的最近公共祖先(LCA),是树中以uv为其后代的深度最大的那个结点。现给定某二叉搜索树(BST)中任意两个结点,要求你找出它们的最近公共祖先。

函数接口定义:

int LCA( Tree T, int u, int v );

其中Tree的定义如下:

typedef struct TreeNode *Tree;
struct TreeNode {
    int   Key;
    Tree  Left;
    Tree  Right;
};

函数LCA须返回树T中两个结点uv的最近公共祖先结点的键值。若uv不在树中,则应返回ERROR

裁判测试程序样例:

#include <stdio.h>
#include <stdlib.h>

#define ERROR -1
typedef struct TreeNode *Tree;
struct TreeNode {
    int   Key;
    Tree  Left;
    Tree  Right;
};

Tree BuildTree(); /* 细节在此不表 */
int LCA( Tree T,  int u, int v );

int main()
{
    Tree T;
    int u, v, ans;

    T = BuildTree();
    scanf("%d %d", &u, &v);
    ans = LCA(T, u, v);
    if ( ans == ERROR ) printf("Wrong input\n");
    else printf("LCA = %d\n", ans);

    return 0;
}

/* 你的代码将被嵌在这里 */

输入样例1 (对于下图给定的树):

2 7

输出样例1:

LCA = 6

输入样例2 (对于例1中的树):

1 9

输出样例2:

Wrong input

代码长度限制                                                                                                                        16 KB

时间限制                                                                                                                                400 ms

内存限制                                                                                                                                64 MB

一种可能的解答方法:

// 此函数用于寻找二叉搜索树是否含有值为x的节点
int find(Tree T,int x)
{
    // 如果树为空
    if(!T)
        return 0;
    // 找到了节点返回1,表示找到了
    if(T->Key==x)
        return 1;
    // 当前找的值小于目标值,说明目标x在当前节点的右边,递归遍历右子树
    if(T->Key<x)
        return find(T->Right,x);
    // 当前找的值大于目标值,说明目标x在当前节点的左边,递归遍历左子树
    if(T->Key>x)
        return find(T->Left,x);
}
// 寻找最近的公共祖先
int LCA( Tree T, int u, int v ){
    // 如果树为空,直接返回错误
    if(!T)
        return ERROR;
    // 如果树不存在这俩点,直接返回错误
    if((!find(T,u))||(!find(T,v)))
        return ERROR;
    // 当前的节点等于某个后代,说明已经找到最近的公共祖先
    if((u==T->Key)||(v==T->Key))
        return T->Key;
    // 当前节点刚好在u和v之间,直接返回当前节点(因为是二叉搜索树,
    // 从根节点慢慢往下遍历,第一次遇到的正好卡在中间的节点就是公共祖先,
    // 高一级就会大于或小于所有节点,低一级就不可能是公共祖先了,
    // 也遍历不到那里)
    if((u>T->Key&&v<T->Key)||(u<T->Key&&v>T->Key))
        return T->Key;
    // 目标节点大于当前节点,往右子树递归
    if(u>T->Key)
        return LCA(T->Right,u,v);
    // 目标节点小于当前节点,往左子树递归
    if(u<T->Key)
        return LCA(T->Left,u,v);
}

运行结果:

<think> 首先,用户的问题是关于“树的最近公共祖先算法及实现”,他们引用了几个站内引用,我需要参考这些引用,但确保回答真实可靠。 引用内容: - [1]:Java实现LCA算法的示例代码。 - [2]:定义LCA的概念。 - [3]:二叉搜索树中LCA的迭代和递归方法。 - [4]:LCA与树链剖分的学习笔记。 用户的问题焦点是树的最近公共祖先算法及实现。 我的回答需要: - 使用中文。 - 结构清晰,逐步解决问题。 - 行内数学表达式用$...$,独立公式用$$...$$。 - 生成相关问题。 - 在引用段落末尾添加引用标识。 结构回答: 1. **介绍LCA概念**:先定义最近公共祖先是什么,参考引用[2]。 2. **算法概述**:解释常见的LCA算法,如朴素方法、二叉搜索树特定方法、树链剖分等。 3. **实现方法**:提供代码示例,参考引用[1]和[3]。 4. **优化和注意事项**:讨论性能分析或优化。 5. **生成相关问题**:在最后添加相关问题。 关键点: - 引用[2]定义了LCA:最近公共祖先是两个点的最接近的公共祖先节点。 - 引用[1]展示了Java实现。 - 引用[3]讨论二叉搜索树中的LCA。 - 引用[4]提到树链剖分方法。 我需要整合这些信息。 回答结构: 1. **定义和概念**:解释什么是最近公共祖先。 2. **通用算法**:对于普通树或二叉树,使用递归或深度优先搜索。 - 朴素方法:遍历树,存储路径或使用递归。 3. **特定树类型**:二叉搜索树中的LCA(引用[3])。 4. **实现示例**:提供代码,如引用[1]的Java代码。 5. **优化方法**:如引用[4]的树链剖分。 6. **生成相关问题**:在末尾。 确保使用LaTeX正确: - 行内:$some math$ - 独立:$$some math$$ 最后,自然地添加引用标识。 完整回答: - 开头:介绍LCA。 - 主体:算法描述,然后实现。 - 结尾:相关问题。 相关问题示例: 1. LCA算法的性能如何? 2. 如何优化LCA算法? 3. LCA在哪些场景应用? </think> 以下是针对“树的最近公共祖先算法及实现”的完整解答。我将从基本概念出发,逐步介绍算法原理、常见实现方法(包括代码示例),并讨论优化技巧。回答基于您提供的站内引用内容,确保信息真实可靠。树中的最近公共祖先(Lowest Common Ancestor, LCA)定义为:对于树中的两个节点,LCA是距离它们最近公共祖先节点。这有两种情况:LCA可能是其中一个节点自身,也可能是一个独立节点,且LCA必须在从根节点到该节点的路径上[^2]。 ### 1. 最近公共祖先(LCA)的概念 - **定义**:在树结构中,给定两个节点 $p$ 和 $q$,它们的LCA是深度最大(即最接近 $p$ 和 $q$)的公共祖先节点。数学上,如果树的高度为 $h$,则LCA的深度 $d$ 满足 $d \leq h$,且LCA是所有公共祖先中深度最大的节点。 - **关键点**: - LCA可以是 $p$ 或 $q$ 本身(例如,在二叉搜索树中,如果 $p$ 是 $q$ 的祖先,则LCA就是 $p$)[^3]。 - LCA在树结构中有广泛应用,如计算节点距离、解决树查询问题等。 - **树类型**:LCA算法通常适用于二叉树(包括普通二叉树和二叉搜索树),但也可以扩展到多叉树。二叉搜索树(BST)有更高效的算法,因为其有序性简化了搜索过程。 ### 2. LCA算法原理 LCA算法有多种,根据树的结构和需求选择: - **朴素方法(递归或DFS)**:适用于普通二叉树。核心思想是通过深度优先搜索(DFS)找到从根节点到 $p$ 和 $q$ 的路径,然后比较这些路径以找到最后一个公共节点。时间复杂度为 $O(n)$,其中 $n$ 是树中的节点数。 - **二叉搜索树(BST)特定方法**:利用BST的性质(左子树值小于根节点,右子树值大于根节点)。算法递归或迭代地比较节点值: - 如果 $p$ 和 $q$ 的值都小于当前节点值,则LCA在左子树。 - 如果都大于当前节点值,则LCA在右子树。 - 否则,当前节点就是LCA(因为BST的有序性保证了公共祖先的分支点)。时间复杂度为 $O(h)$,其中 $h$ 是树的高度。 - **优化方法(如树链剖分)**:对于大型树或频繁查询,可以使用树链剖分等高级技术。树链剖分将树分解为链,利用DFS序(dfs序)来快速查询LCA。例如,从节点 $x$ 和 $y$ 的DFS序位置 $pos[x]$ 到 $pos[y]$ 的路径中,深度最小的节点就是LCA[^4]。时间复杂度可降至 $O(\log n)$。 #### 算法选择建议: - **普通二叉树**:使用朴素递归方法,易实现且通用。 - **二叉搜索树**:优先使用迭代或递归的BST特定方法,效率更高。 - **多次查询场景**:采用预处理技术如树链剖分或稀疏表(Tarjan's off-line LCA)。 ### 3. 实现方法及代码示例 以下提供两种常见实现:朴素递归方法(通用二叉树)和BST特定方法(迭代和递归)。代码基于Java实现,参考了您提供的引用内容。 #### (1) 朴素递归方法(通用二叉树) 这种方法通过递归遍历树来查找LCA。核心逻辑:如果当前节点是 $p$ 或 $q$,则返回它;否则,递归检查左子树和右子树。如果左右子树都返回非空节点,则当前节点是LCA。 ```java class TreeNode { int val; TreeNode left; TreeNode right; TreeNode(int x) { val = x; } } public class LowestCommonAncestor { public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) { if (root == null || root == p || root == q) { return root; // 如果root为null或等于p/q,直接返回 } TreeNode left = lowestCommonAncestor(root.left, p, q); // 递归左子树 TreeNode right = lowestCommonAncestor(root.right, p, q); // 递归右子树 if (left != null && right != null) { return root; // 如果左右子树都找到p或q,root是LCA } return left != null ? left : right; // 否则返回非空子树的结果 } } // 示例用法(参考引用[1]) public class Main { public static void main(String[] args) { // 创建树结构 TreeNode root = new TreeNode(3); root.left = new TreeNode(5); root.right = new TreeNode(1); // 添加其他节点(如引用[1]示例) root.left.left = new TreeNode(6); root.left.right = new TreeNode(2); root.right.left = new TreeNode(0); root.right.right = new TreeNode(8); root.left.right.left = new TreeNode(7); root.left.right.right = new TreeNode(4); LowestCommonAncestor lca = new LowestCommonAncestor(); // 查找节点5和1的LCA TreeNode result = lca.lowestCommonAncestor(root, root.left, root.right); System.out.println("最近公共祖先的值为:" + result.val); // 输出应为3 } } ``` - **说明**:此方法时间复杂度为 $O(n)$,空间复杂度为 $O(h)$(递归栈深度)。它适用于任何二叉树,但效率较低,不适用于高频查询[^1]。 #### (2) BST特定方法(迭代和递归) 二叉搜索树中,算法利用节点值的比较来简化搜索。引用[3]展示了迭代和递归两种实现。 - **递归实现**: ```java public TreeNode lowestCommonAncestorBSTRecursive(TreeNode root, TreeNode p, TreeNode q) { if (root == null) return null; if (p.val < root.val && q.val < root.val) { return lowestCommonAncestorBSTRecursive(root.left, p, q); // LCA在左子树 } else if (p.val > root.val && q.val > root.val) { return lowestCommonAncestorBSTRecursive(root.right, p, q); // LCA在右子树 } else { return root; // 当前节点是LCA } } ``` - **迭代实现**(更高效,节省栈空间): ```java public TreeNode lowestCommonAncestorBSTIterative(TreeNode root, TreeNode p, TreeNode q) { while (root != null) { if (p.val < root.val && q.val < root.val) { root = root.left; // 移动到左子树 } else if (p.val > root.val && q.val > root.val) { root = root.right; // 移动到右子树 } else { break; // 找到LCA } } return root; } ``` - **示例**:如引用[3]所示,对于节点2和4,在BST中LCA是2,因为2是4的祖先- **性能**:时间复杂度为 $O(h)$,最坏情况 $O(n)$(树不平衡时),但通常高效[^3]。 ### 4. 优化和注意事项 - **性能分析**:朴素递归方法在平均情况下的时间复杂度为 $O(n)$,但树链剖分方法通过预处理DFS序能达到 $O(\log n)$ 的查询时间[^4]。对于BST,方法效率更高。 - **优化技巧**: - **预处理**:使用树链剖分或稀疏表存储节点深度和父节点信息,减少查询时间。 - **平衡树**:确保BST是平衡的(如AVL树),以维持 $O(\log n)$ 时间复杂度。 - **多次查询**:在静态树中,使用离线算法(如Tarjan's LCA),时间复杂度接近 $O(n + q)$,其中 $q$ 是查询数。 - **限制**:LCA算法假设树是连通的,且节点存在。如果树有多个根或动态变化,需要额外处理。 ### 总结 LCA算法是树结构中的基础问题,通过递归、迭代或高级优化(如树链剖分)实现。选择算法时考虑树类型和查询频率:对于普通二叉树,朴素递归可靠;对于BST,迭代方法更优。实现代码应测试边界情况(如节点为null或p/q相同)。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值