计算机学习笔记 二叉搜索树核心操作(含注释代码)

📚 二叉树专题复习笔记:二叉树核心操作

本次复习内容全面覆盖了二叉搜索树(BST)的核心操作。课程从基础的节点定义与手动构建开始,逐步深入到自动插入、查找、两种遍历策略(广度优先与深度优先),最后攻克了最为复杂的删除操作。

第一部分:二叉搜索树的构建与插入

1. 节点定义与手动构建

  • 节点结构:二叉树的节点类(Node)通常包含三个核心部分:
    • value:数据域,用于存储节点的值。
    • left:指针域,存储左子节点的内存地址。
    • right:指针域,存储右子节点的内存地址。
  • 构建过程
    • 手动构建:通过new关键字创建多个独立的节点对象,然后通过赋值操作(如n1.left = n2)将它们的leftright指针连接起来,形成树状结构。
    • 树类封装:设计一个树类(Tree),内部维护一个root变量作为整棵树的入口。所有操作都围绕root展开。

2. 插入操作 (Insert)

  • 核心规则:二叉搜索树遵循“左小右大”的原则,即任意节点的左子树所有值均小于该节点,右子树所有值均大于该节点。
  • 实现逻辑
    1. 空树处理:若root为空,则直接将新节点设为根节点。
    2. 非空树处理:若树不为空,则从root开始,使用一个游标(index)进行遍历比较。
      • 若新节点值 小于 当前节点值,则向子树方向查找。
      • 若新节点值 大于 当前节点值,则向子树方向查找。
    3. 定位插入:重复上述比较过程,直到找到一个空的leftright位置,将新节点插入该位置。
第二部分:二叉树的查找与遍历

1. 查找操作 (Search)

  • 核心思想:充分利用BST“左小右大”的特性,实现高效的二分查找。
  • 实现方式
    1. 定义游标index指向root
    2. 进入循环,只要index不为空,就比较目标值与index.value
    3. 比较逻辑
      • 相等:查找成功,返回该节点。
      • 目标值更小:index移向左子节点(index = index.left)。
      • 目标值更大:index移向右子节点(index = index.right)。
    4. 若循环结束仍未找到,则返回null

2. 遍历操作 (Traversal)
遍历是访问树中所有节点的基础,主要分为广度优先和深度优先两种方式。

  • 广度优先遍历 (BFS) / 层序遍历

    • 核心思想:按层级顺序,从上到下、从左到右逐层访问。
    • 实现方式:借助队列 (Queue) 实现。
      1. root入队。
      2. 当队列不为空时,循环执行:
        • 节点出队并访问。
        • 将其非空的左、右子节点依次入队。
    • 关键点:队列的“先进先出”特性天然保证了节点按层级顺序被处理。
  • 深度优先遍历 (DFS)

    • 核心思想:沿着树的深度,尽可能深地搜索分支。通常使用递归 (Recursion) 实现。
    • 三种遍历方式(区别在于访问根节点的时机):
      • 先序遍历 (Pre-order)根 -> 左 -> 右。先访问当前节点,再递归遍历左右子树。
      • 中序遍历 (In-order)左 -> 根 -> 右。先递归遍历左子树,再访问当前节点,最后递归遍历右子树。
        • 特性:对BST进行中序遍历,结果是一个有序序列。
      • 后序遍历 (Post-order)左 -> 右 -> 根。先递归遍历左右子树,最后访问当前节点。
    • 关键点:理解递归的调用栈和“触底反弹”的过程是掌握DFS的关键。
第三部分:二叉树的删除操作

删除是BST中最复杂的操作,必须在删除后仍保持树的“左小右大”结构。操作前需先定位目标节点及其父节点。

1. 删除叶子节点 (无子树)

  • 情况:目标节点没有左、右子树。
  • 处理
    • 若目标节点是根节点(即整棵树只有一个节点),直接将root置为null
    • 若目标节点有父节点,则判断它是父节点的左孩子还是右孩子,然后将父节点对应的指针(leftright)置为null

2. 删除仅有一棵子树的节点

  • 情况:目标节点只有左子树或只有右子树。
  • 处理
    • 若目标节点是根节点,直接让root指向其唯一的子树根节点。
    • 若目标节点有父节点,则判断它是父节点的左孩子还是右孩子,然后将父节点对应的指针指向目标节点的唯一子树。这相当于让父节点“跳过”目标节点,直接连接其子树。

3. 删除有两棵子树的节点

  • 情况:目标节点同时拥有左、右子树。这是最复杂的情况。
  • 处理:采用“值替换法”。
    1. 寻找替代值:在目标节点的左子树中找到最大值节点,或在其右子树中找到最小值节点。
      • 右子树的最小值:从目标节点的右子节点开始,一路向左,直到左指针为空的节点。
      • 左子树的最大值:从目标节点的左子节点开始,一路向右,直到右指针为空的节点。
    2. 替换值:将找到的替代值复制到目标节点上,覆盖其原有值。
    3. 删除替代节点:在子树中删除那个被取走值的节点。
  • 关键点:被选中的替代节点(右子树最小值或左子树最大值)本身最多只有一个子树(或没有),因此删除它的操作会退化为情况1或情况2,从而避免了无限递归。这种方法完美地维持了二叉搜索树的性质。

代码如下


package tree;

public class Test {
	public static void main(String[] args) {
		YouxvTree tree = new YouxvTree();
		tree.insert(10);
		tree.insert(5);
		tree.insert(15);
		tree.insert(1);
		tree.insert(12);
		tree.insert(30);
		System.out.println(tree.root);
		tree.search(10);
		System.out.println(tree.search(30).value);
		tree.levelOrder();
		tree.beforeOrder(tree.root);
		tree.inOrder(tree.root);
		tree.afterOrder(tree.root);
		System.out.println(tree.searchParent(12).value);
		tree.delete(1);
		System.out.println(tree);
	}
}
package tree;

import java.util.LinkedList;
import java.util.Queue;

/**
 * 二叉搜索树(Binary Search Tree)实现类
 * 特点:左子树所有节点值小于根节点,右子树所有节点值大于根节点
 */
public class YouxvTree {
    Node root = null; // 树的根节点

    /**
     * 插入节点
     * @param value 要插入的整数值
     */
    public void insert(int value) {
        Node node = new Node(value); // 创建新节点
        
        // 如果树为空,新节点作为根节点
        if(root == null) {
            root = node;
            return;
        }
        
        Node index = root; // 从根节点开始遍历
        while(index != null) {
            // 如果当前节点值小于新节点值,向右子树移动
            if(index.value < node.value) {
                if(index.right == null) { // 右子树为空,直接插入
                    index.right = node;
                    return;
                } else {
                    index = index.right; // 继续向右遍历
                }
            }
            // 如果当前节点值大于新节点值,向左子树移动
            if(index.value > node.value) {
                if(index.left == null) { // 左子树为空,直接插入
                    index.left = node;
                    return;
                } else {
                    index = index.left; // 继续向左遍历
                }
            }
        }
    }

    /**
     * 查找指定值的节点
     * @param nums 要查找的值
     * @return 找到的节点,如果未找到返回null
     */
    public Node search(int nums) {
        Node index = root;
        while(index != null) {
            if(index.value == nums) { // 找到目标节点
                System.out.println("Found");
                return index;
            } else if(index.value < nums) { // 目标值较大,向右查找
                index = index.right;
            } else { // 目标值较小,向左查找
                index = index.left;
            }
        }
        System.out.println("NotFound");
        return null;
    }

    /**
     * 查找指定值节点的父节点
     * @param nums 要查找的值
     * @return 父节点,如果该节点是根节点或未找到则返回null
     */
    public Node searchParent(int nums) {
        // 如果树为空或查找的是根节点,没有父节点
        if (root == null || root.value == nums) {
            return null;
        }
        
        Node current = root;
        while (current != null) {
            // 检查当前节点的左右子节点是否为目标节点
            if ((current.left != null && current.left.value == nums) ||
                (current.right != null && current.right.value == nums)) {
                return current; // 找到父节点
            }
            
            // 根据值的大小决定遍历方向
            if (nums < current.value) {
                current = current.left;
            } else {
                current = current.right;
            }
        }
        return null; // 未找到父节点
    }

    /**
     * 广度优先遍历(层序遍历)
     * 使用队列实现,按层从上到下、从左到右输出
     */
    public void levelOrder() {
        Queue<Node> queue = new LinkedList<Node>();
        queue.add(root);
        
        while(queue.isEmpty() == false) {
            Node currentNode = queue.remove(); // 取出队首节点
            System.out.println(currentNode.value);
            
            // 将左右子节点加入队列
            if(currentNode.left != null) {
                queue.add(currentNode.left);
            }
            if(currentNode.right != null) {
                queue.add(currentNode.right);
            }
        }
    }

    /**
     * 深度优先遍历 - 先序遍历(根-左-右)
     * @param currentNode 当前遍历的节点
     */
    public void beforeOrder(Node currentNode) {
        if(currentNode == null) {
            return;
        }
        System.out.println(currentNode.value); // 访问根节点
        beforeOrder(currentNode.left);          // 递归遍历左子树
        beforeOrder(currentNode.right);         // 递归遍历右子树
    }

    /**
     * 深度优先遍历 - 中序遍历(左-根-右)
     * 对于二叉搜索树,中序遍历结果为升序序列
     * @param currentNode 当前遍历的节点
     */
    public void inOrder(Node currentNode) {
        if(currentNode == null) {
            return;
        }
        // 注意:这里应该调用 inOrder 而不是 beforeOrder
        inOrder(currentNode.left);              // 递归遍历左子树
        System.out.println(currentNode.value); // 访问根节点
        inOrder(currentNode.right);             // 递归遍历右子树
    }

    /**
     * 深度优先遍历 - 后序遍历(左-右-根)
     * @param currentNode 当前遍历的节点
     */
    public void afterOrder(Node currentNode) {
        if(currentNode == null) {
            return;
        }
        // 注意:这里应该调用 afterOrder 而不是 beforeOrder
        afterOrder(currentNode.left);           // 递归遍历左子树
        afterOrder(currentNode.right);          // 递归遍历右子树
        System.out.println(currentNode.value); // 访问根节点
    }

    /**
     * 删除指定值的节点
     * 分三种情况处理:
     * 1. 叶子节点(无子节点):直接删除
     * 2. 只有一个子节点:用子节点替换
     * 3. 有两个子节点:用右子树的最小节点替换
     * @param num 要删除的值
     */
    public void delete(int num) {
        Node target = search(num); // 查找要删除的节点
        if(target == null) {
            System.out.println("NotFound");
            return;
        }
        
        Node parent = searchParent(num); // 查找父节点
        
        // 情况1:删除叶子节点(没有子节点)
        if(target.left == null && target.right == null) {
            if(parent == null) { // 如果删除的是根节点
                root = null;
                return;
            }
            // 判断目标节点是父节点的左子节点还是右子节点
            if(parent.left != null && parent.left.value == num) {
                parent.left = null;
            } else {
                parent.right = null;
            }
        }
        // 情况3:删除有两个子节点的节点
        else if(target.left != null && target.right != null) {
            // 找到右子树中的最小节点(即中序后继)
            Node index = target.right;
            while(index.left != null) {
                index = index.left;
            }
            int min = index.value; // 保存最小值
            
            delete(min); // 递归删除这个最小节点
            target.value = min; // 用最小值替换目标节点的值
        }
        // 情况2:删除只有一个子节点的节点
        else {
            if(parent == null) { // 如果删除的是根节点
                if(target.left != null) {
                    root = target.left;
                } else {
                    root = target.right;
                }
                return;
            }
            
            // 判断目标节点是父节点的左子节点还是右子节点
            if(parent.left != null && parent.left.value == num) {
                if(target.left != null) {
                    parent.left = target.left;
                } else {
                    parent.left = target.right;
                }
            } else {
                if(target.left != null) {
                    parent.right = target.left;
                } else {
                    parent.right = target.right;
                }
            }
        }
    }

    /**
     * 重写toString方法,返回树的根节点信息
     */
    @Override
    public String toString() {
        return "YouxvTree [root=" + root + "]";
    }
}
package tree;

public class Node {
	int value;
	Node left;
	Node right;
	public Node(int num) {
		value=num;
	}
	public String toString() {
		return "YouxvTree [value=" + value + ", left=" + left + ", right=" + right + "]";
	}
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

CarIise

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值