📚 二叉树专题复习笔记:二叉树核心操作
本次复习内容全面覆盖了二叉搜索树(BST)的核心操作。课程从基础的节点定义与手动构建开始,逐步深入到自动插入、查找、两种遍历策略(广度优先与深度优先),最后攻克了最为复杂的删除操作。
第一部分:二叉搜索树的构建与插入
1. 节点定义与手动构建
- 节点结构:二叉树的节点类(Node)通常包含三个核心部分:
value:数据域,用于存储节点的值。left:指针域,存储左子节点的内存地址。right:指针域,存储右子节点的内存地址。
- 构建过程:
- 手动构建:通过
new关键字创建多个独立的节点对象,然后通过赋值操作(如n1.left = n2)将它们的left和right指针连接起来,形成树状结构。 - 树类封装:设计一个树类(Tree),内部维护一个
root变量作为整棵树的入口。所有操作都围绕root展开。
- 手动构建:通过
2. 插入操作 (Insert)
- 核心规则:二叉搜索树遵循“左小右大”的原则,即任意节点的左子树所有值均小于该节点,右子树所有值均大于该节点。
- 实现逻辑:
- 空树处理:若
root为空,则直接将新节点设为根节点。 - 非空树处理:若树不为空,则从
root开始,使用一个游标(index)进行遍历比较。- 若新节点值 小于 当前节点值,则向左子树方向查找。
- 若新节点值 大于 当前节点值,则向右子树方向查找。
- 定位插入:重复上述比较过程,直到找到一个空的
left或right位置,将新节点插入该位置。
- 空树处理:若
第二部分:二叉树的查找与遍历
1. 查找操作 (Search)
- 核心思想:充分利用BST“左小右大”的特性,实现高效的二分查找。
- 实现方式:
- 定义游标
index指向root。 - 进入循环,只要
index不为空,就比较目标值与index.value。 - 比较逻辑:
- 相等:查找成功,返回该节点。
- 目标值更小:
index移向左子节点(index = index.left)。 - 目标值更大:
index移向右子节点(index = index.right)。
- 若循环结束仍未找到,则返回
null。
- 定义游标
2. 遍历操作 (Traversal)
遍历是访问树中所有节点的基础,主要分为广度优先和深度优先两种方式。
-
广度优先遍历 (BFS) / 层序遍历
- 核心思想:按层级顺序,从上到下、从左到右逐层访问。
- 实现方式:借助队列 (Queue) 实现。
- 将
root入队。 - 当队列不为空时,循环执行:
- 节点出队并访问。
- 将其非空的左、右子节点依次入队。
- 将
- 关键点:队列的“先进先出”特性天然保证了节点按层级顺序被处理。
-
深度优先遍历 (DFS)
- 核心思想:沿着树的深度,尽可能深地搜索分支。通常使用递归 (Recursion) 实现。
- 三种遍历方式(区别在于访问根节点的时机):
- 先序遍历 (Pre-order):根 -> 左 -> 右。先访问当前节点,再递归遍历左右子树。
- 中序遍历 (In-order):左 -> 根 -> 右。先递归遍历左子树,再访问当前节点,最后递归遍历右子树。
- 特性:对BST进行中序遍历,结果是一个有序序列。
- 后序遍历 (Post-order):左 -> 右 -> 根。先递归遍历左右子树,最后访问当前节点。
- 关键点:理解递归的调用栈和“触底反弹”的过程是掌握DFS的关键。
第三部分:二叉树的删除操作
删除是BST中最复杂的操作,必须在删除后仍保持树的“左小右大”结构。操作前需先定位目标节点及其父节点。
1. 删除叶子节点 (无子树)
- 情况:目标节点没有左、右子树。
- 处理:
- 若目标节点是根节点(即整棵树只有一个节点),直接将
root置为null。 - 若目标节点有父节点,则判断它是父节点的左孩子还是右孩子,然后将父节点对应的指针(
left或right)置为null。
- 若目标节点是根节点(即整棵树只有一个节点),直接将
2. 删除仅有一棵子树的节点
- 情况:目标节点只有左子树或只有右子树。
- 处理:
- 若目标节点是根节点,直接让
root指向其唯一的子树根节点。 - 若目标节点有父节点,则判断它是父节点的左孩子还是右孩子,然后将父节点对应的指针指向目标节点的唯一子树。这相当于让父节点“跳过”目标节点,直接连接其子树。
- 若目标节点是根节点,直接让
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 + "]";
}
}
&spm=1001.2101.3001.5002&articleId=163310564&d=1&t=3&u=1df296da2aa84647b1ddda67f50bd5ae)
490

被折叠的 条评论
为什么被折叠?



