深入理解二叉搜索树:定义、操作及平衡二叉树

devtools/2024/9/22 8:37:41/

引言

二叉搜索树(Binary Search Tree,BST)是一种特殊的二叉树结构,每个节点的左子树节点值小于根节点值,而右子树节点值大于根节点值。二叉搜索树在计算机科学中有着广泛的应用,尤其在动态查找表和优先队列的实现中。本文将详细介绍二叉搜索树的定义、基本操作及平衡二叉树(AVL树和红黑树)。

二叉搜索树的定义和操作

定义

二叉搜索树是一种节点值满足特定排序的二叉树,定义如下:

  • 每个节点都有一个值。
  • 左子树中所有节点的值都小于根节点的值。
  • 右子树中所有节点的值都大于根节点的值。
  • 左右子树也分别是二叉搜索树。

基本操作

插入操作

插入操作是向二叉搜索树中添加一个新节点。根据新节点的值与当前节点的值比较,决定插入左子树或右子树。以下是插入操作的代码示例:

java">class TreeNode {int value;TreeNode left, right;// 构造函数TreeNode(int item) {value = item;left = right = null;}
}class BinarySearchTree {TreeNode root;BinarySearchTree() {root = null;}// 插入值到二叉搜索树void insert(int value) {root = insertRec(root, value);}// 递归方式插入值TreeNode insertRec(TreeNode root, int value) {// 如果树为空,则创建新节点if (root == null) {root = new TreeNode(value);return root;}// 如果值小于根节点,则插入左子树if (value < root.value)root.left = insertRec(root.left, value);// 如果值大于根节点,则插入右子树else if (value > root.value)root.right = insertRec(root.right, value);// 返回(未变更的)节点指针return root;}// 中序遍历打印树void inorder() {inorderRec(root);}// 递归方式中序遍历树void inorderRec(TreeNode root) {if (root != null) {inorderRec(root.left);System.out.print(root.value + " ");inorderRec(root.right);}}// 测试插入操作public static void main(String[] args) {BinarySearchTree tree = new BinarySearchTree();tree.insert(50);tree.insert(30);tree.insert(20);tree.insert(40);tree.insert(70);tree.insert(60);tree.insert(80);// 打印插入后的树System.out.print("中序遍历结果: ");tree.inorder();  // 输出:20 30 40 50 60 70 80 }
}
插入操作图解
  1. 初始化一个空树。
  2. 插入第一个值 50,成为根节点。
  3. 插入值 30,比 50 小,插入到 50 的左子树。
  4. 插入值 20,比 30 小,插入到 30 的左子树。
  5. 插入值 40,比 30 大,插入到 30 的右子树。
  6. 插入值 70,比 50 大,插入到 50 的右子树。
  7. 插入值 60,比 70 小,插入到 70 的左子树。
  8. 插入值 80,比 70 大,插入到 70 的右子树。
    50/  \30  70/ \  / \
20 40 60 80
20
30
40
50
60
70
80
删除操作

删除操作是从二叉搜索树中移除一个节点。根据要删除节点的位置及其子节点情况进行相应处理。以下是删除操作的代码示例:

java">class BinarySearchTree {TreeNode root;BinarySearchTree() {root = null;}// 删除指定值的节点void deleteKey(int value) {root = deleteRec(root, value);}// 递归方式删除值TreeNode deleteRec(TreeNode root, int value) {// 基本情况:树为空if (root == null) return root;// 递归查找要删除的节点if (value < root.value)root.left = deleteRec(root.left, value);else if (value > root.value)root.right = deleteRec(root.right, value);else {// 节点只有一个子节点或没有子节点if (root.left == null)return root.right;else if (root.right == null)return root.left;// 节点有两个子节点,获取右子树中最小值root.value = minValue(root.right);// 递归删除右子树中的最小值节点root.right = deleteRec(root.right, root.value);}return root;}// 查找最小值int minValue(TreeNode root) {int minValue = root.value;while (root.left != null) {minValue = root.left.value;root = root.left;}return minValue;}// 中序遍历打印树void inorder() {inorderRec(root);}// 递归方式中序遍历树void inorderRec(TreeNode root) {if (root != null) {inorderRec(root.left);System.out.print(root.value + " ");inorderRec(root.right);}}// 测试删除操作public static void main(String[] args) {BinarySearchTree tree = new BinarySearchTree();tree.insert(50);tree.insert(30);tree.insert(20);tree.insert(40);tree.insert(70);tree.insert(60);tree.insert(80);System.out.println("删除前的树:");tree.inorder();  // 输出:20 30 40 50 60 70 80 tree.deleteKey(20);System.out.println("\n删除20后的树:");tree.inorder();  // 输出:30 40 50 60 70 80 tree.deleteKey(30);System.out.println("\n删除30后的树:");tree.inorder();  // 输出:40 50 60 70 80 tree.deleteKey(50);System.out.println("\n删除50后的树:");tree.inorder();  // 输出:40 60 70 80 }
}
删除操作图解

假设我们要从上面的树中删除值 503020

  1. 删除 20:节点 20 没有子节点,直接删除。
  2. 删除 30:节点 30 有一个子节点 40,用 40 替换 30
  3. 删除 50:节点 50 有两个子节点,用其右子树的最小值 60 替换 50,然后删除 60
删除前:50/  \30  70/ \  / \
20 40 60 80删除 20 后:50/  \30  70\  / \40 60 80删除 30 后:50/  \40  70/ \60 80删除 50 后:60/  \40  70\80
删除前
50
30
70
20
40
60
80
删除20后
50
30
70
40
60
80
删除30后
50
40
70
60
80
删除50后
60
40
70
80
查找操作

查找操作是搜索二叉搜索树中是否存在某个特定值。根据目标值与当前节点值比较,决定在左子树或右子树中继续查找。以下是查找操作的代码示例:

java">class BinarySearchTree {TreeNode root;BinarySearchTree() {root = null;}// 查找指定值是否存在boolean search(int value) {return searchRec(root, value);}// 递归方式查找值boolean searchRec(TreeNode root, int value) {// 基本情况:树为空或值为根节点值if (root == null || root.value == value) return root != null;// 值小于根节点值,则在左子树中查找if (value < root.value)return searchRec(root.left, value);// 值大于根节点值,则在右子树中查找return searchRec(root.right, value);}// 插入值到二叉查找树void insert(int value) {root = insertRec(root, value);}// 递归方式插入值TreeNode insertRec(TreeNode root, int value) {// 如果树为空,则创建新节点if (root == null) {root = new TreeNode(value);return root;}// 如果值小于根节点,则插入左子树if (value < root.value)root.left = insertRec(root.left, value);// 如果值大于根节点,则插入右子树else if (value > root.value)root.right = insertRec(root.right, value);// 返回(未变更的)节点指针return root;}// 测试查找操作public static void main(String[] args) {BinarySearchTree tree = new BinarySearchTree();tree.insert(50);tree.insert(30);tree.insert(20);tree.insert(40);tree.insert(70);tree.insert(60);tree.insert(80);// 测试查找操作System.out.println(tree.search(50));  // 输出:trueSystem.out.println(tree.search(90));  // 输出:false}
}
查找操作图解

假设我们在上面的树中查找值 5090

  1. 查找 50:从根节点开始,50 正是根节点值,查找成功。
  2. 查找 90:从根节点 50 开始,90 大于 50,查找右子树。然后 90 大于 70,查找右子树。接着 90 大于 80,发现右子树为空,查找失败。
50
30
70
20
40
60
80
查找50
查找90
null

平衡二叉树

平衡二叉树是一类特殊的二叉搜索树,通过自动调整树的结构,使树的高度保持在较低水平,从而提高查找、插入和删除操作的效率。常见的平衡二叉树有 AVL 树和红黑树。

AVL 树

AVL 树是一种自平衡二叉搜索树,它的每个节点都需要满足以下平衡条件:任意节点的左右子树高度差不超过 1。AVL 树通过旋转操作来保持平衡。

AVL 树的旋转操作
  1. 右旋(Right Rotation)
  2. 左旋(Left Rotation)
  3. 左右旋(Left-Right Rotation)
  4. 右左旋(Right-Left Rotation)
右旋
左旋
左右旋
右左旋

红黑树

红黑树是一种自平衡二叉搜索树,每个节点都有颜色属性(红色或黑色),并且需要满足以下性质:

  1. 节点是红色或黑色。
  2. 根节点是黑色。
  3. 每个叶节点(NIL 节点)是黑色。
  4. 如果一个节点是红色,则它的两个子节点都是黑色。
  5. 从一个节点到该节点的所有后代叶节点的所有路径上,包含相同数目的黑色节点。
红黑树的旋转和颜色翻转
  1. 左旋(Left Rotation)
  2. 右旋(Right Rotation)
  3. 颜色翻转(Color Flip)
左旋
右旋
颜色翻转

红黑树的插入操作

红黑树的插入操作和普通二叉搜索树相同,但插入新节点后需要进行重新平衡操作。以下是红黑树的插入操作代码示例:

java">class RedBlackTreeNode {int value;RedBlackTreeNode left, right, parent;boolean color; // true for Red, false for Black// 构造函数RedBlackTreeNode(int item) {value = item;left = right = parent = null;color = true; // new nodes are red by default}
}class RedBlackTree {private RedBlackTreeNode root;private final RedBlackTreeNode TNULL;// 初始化public RedBlackTree() {TNULL = new RedBlackTreeNode(0);TNULL.color = false;TNULL.left = null;TNULL.right = null;root = TNULL;}// 前序遍历private void preOrderHelper(RedBlackTreeNode node) {if (node != TNULL) {System.out.print(node.value + " ");preOrderHelper(node.left);preOrderHelper(node.right);}}// 中序遍历private void inOrderHelper(RedBlackTreeNode node) {if (node != TNULL) {inOrderHelper(node.left);System.out.print(node.value + " ");inOrderHelper(node.right);}}// 后序遍历private void postOrderHelper(RedBlackTreeNode node) {if (node != TNULL) {postOrderHelper(node.left);postOrderHelper(node.right);System.out.print(node.value + " ");}}// 查找树中的节点private RedBlackTreeNode searchTreeHelper(RedBlackTreeNode node, int key) {if (node == TNULL || key == node.value) {return node;}if (key < node.value) {return searchTreeHelper(node.left, key);}return searchTreeHelper(node.right, key);}// 平衡插入操作private void fixInsert(RedBlackTreeNode k) {RedBlackTreeNode u;while (k.parent.color == true) {if (k.parent == k.parent.parent.right) {u = k.parent.parent.left;if (u.color == true) {u.color = false;k.parent.color = false;k.parent.parent.color = true;k = k.parent.parent;} else {if (k == k.parent.left) {k = k.parent;rightRotate(k);}k.parent.color = false;k.parent.parent.color = true;leftRotate(k.parent.parent);}} else {u = k.parent.parent.right;if (u.color == true) {u.color = false;k.parent.color = false;k.parent.parent.color = true;k = k.parent.parent;} else {if (k == k.parent.right) {k = k.parent;leftRotate(k);}k.parent.color = false;k.parent.parent.color = true;rightRotate(k.parent.parent);}}if (k == root) {break;}}root.color = false;}// 左旋private void leftRotate(RedBlackTreeNode x) {RedBlackTreeNode y = x.right;x.right = y.left;if (y.left != TNULL) {y.left.parent = x;}y.parent = x.parent;if (x.parent == null) {this.root = y;} else if (x == x.parent.left) {x.parent.left = y;} else {x.parent.right = y;}y.left = x;x.parent = y;}// 右旋private void rightRotate(RedBlackTreeNode x) {RedBlackTreeNode y = x.left;x.left = y.right;if (y.right != TNULL) {y.right.parent = x;}y.parent = x.parent;if (x.parent == null) {this.root = y;} else if (x == x.parent.right) {x.parent.right = y;} else {x.parent.left = y;}y.right = x;x.parent = y;}// 插入节点public void insert(int key) {RedBlackTreeNode node = new RedBlackTreeNode(key);node.parent = null;node.value = key;node.left = TNULL;node.right = TNULL;node.color = true;RedBlackTreeNode y = null;RedBlackTreeNode x = this.root;while (x != TNULL) {y = x;if (node.value < x.value) {x = x.left;} else {x = x.right;}}node.parent = y;if (y == null) {root = node;} else if (node.value < y.value) {y.left = node;} else {y.right = node;}if (node.parent == null) {node.color = false;return;}if (node.parent.parent == null) {return;}fixInsert(node);}// 查找树中的节点public RedBlackTreeNode searchTree(int k) {return searchTreeHelper(this.root, k);}// 前序遍历public void preorder() {preOrderHelper(this.root);}// 中序遍历public void inorder() {inOrderHelper(this.root);}// 后序遍历public void postorder() {postOrderHelper(this.root);}// 打印树private void printHelper(RedBlackTreeNode root, String indent, boolean last) {if (root != TNULL) {System.out.print(indent);if (last) {System.out.print("R----");indent += "   ";} else {System.out.print("L----");indent += "|  ";}String sColor = root.color ? "RED" : "BLACK";System.out.println(root.value + "(" + sColor + ")");printHelper(root.left, indent, false);printHelper(root.right, indent, true);}}public void printTree() {printHelper(this.root, "", true);}// 测试红黑树public static void main(String[] args) {RedBlackTree bst = new RedBlackTree();bst.insert(55);bst.insert(40);bst.insert(65);bst.insert(60);bst.insert(75);bst.insert(57);bst.printTree();System.out.println("\n前序遍历:");bst.preorder();System.out.println("\n\n中序遍历:");bst.inorder();System.out.println("\n\n后序遍历:");bst.postorder();}
}
红黑树插入操作图解

假设我们在一棵空的红黑树中插入以下节点:55、40、65、60、75、57,以下是每一步的插入和相应的颜色标注和旋转操作:

  1. 插入 55,成为根节点,颜色变为黑色。
  2. 插入 40,为 55 的左子节点,颜色为红色。
  3. 插入 65,为 55 的右子节点,颜色为红色。
  4. 插入 60,为 65 的左子节点,颜色为红色,需要左旋 65
  5. 插入 75,为 65 的右子节点,颜色为红色。
  6. 插入 57,为 60 的左子节点,颜色为红色,需要右旋 60,然后左旋 55
40 红
55 黑
57 红
60 红
65 红
75 红

结论

通过上述讲解和实例代码,我们详细展示了二叉搜索树的定义、基本操作及平衡二叉树(AVL 树和红黑树)。二叉搜索树在计算机科学中有着广泛的应用,平衡二叉树通过保持树的高度在较低水平,从而提高查找、插入和删除操作的效率。希望这篇博客对您有所帮助!记得关注、点赞和收藏哦,以便随时查阅更多优质内容!


如果您觉得这篇文章对您有帮助,请关注我的CSDN博客,点赞并收藏这篇文章,您的支持是我持续创作的动力!


关键内容总结

  • 二叉搜索树的定义和基本操作
  • 二叉搜索树的插入、删除和查找操作
  • 平衡二叉树:AVL 树和红黑树
  • AVL 树的旋转操作
  • 红黑树的性质和操作

推荐阅读:深入探索设计模式专栏,详细讲解各种设计模式的应用和优化。点击查看:深入探索设计模式。


特别推荐:设计模式实战专栏,深入解析设计模式的实际应用,提升您的编程技巧。点击查看:设计模式实战。

如有任何疑问或建议,欢迎在评论区留言讨论。谢谢阅读!


http://www.ppmy.cn/devtools/84852.html

相关文章

关于iphone不能下载三方软件

iPhone 不能下载第三方软件的原因主要是因为苹果公司严格控制其应用生态系统&#xff0c;确保所有应用都通过其官方的 App Store 分发。这有几个主要原因&#xff1a; 安全性&#xff1a;苹果公司希望通过这种方式减少恶意软件的传播&#xff0c;保护用户的隐私和数据安全。所…

使用集成线性 LED 驱动器替代分立 LED 电路设计

在转向灯、刹车灯和尾灯等汽车照明中&#xff0c;LED 电路设计通常采用分立元件&#xff0c;如双极结晶体管 (BJT)。分立元件之所以突出有几个常见原因&#xff1a;它们简单、可靠且便宜。然而&#xff0c;随着 LED 数量和项目要求的增加&#xff0c;重新考虑离散设计可能是值得…

vue 一个数组 获取最大值与最小值

<template><div>最小值: {{ minValue }}最大值: {{ maxValue }}</div> </template><script> export default {data() {return {numbers: [10, 2, 33, 4, 55, 6]};},computed: {minValue() {return Math.min(...this.numbers);},maxValue() {retu…

前端面试 vue 按钮级的权限控制

方案一 按钮权限也可以用v-if判断 但是如果页面过多&#xff0c;每个页面页面都要获取用户权限role和路由表里的meta.btnPermissions&#xff0c;然后再做判断 这种方式就不展开举例了 方案二 使用自定义指令实现 按钮级的权限控制 思维导图 心就是自定义指令的书写 首先…

LiRouter V3.0无人机自主精细化巡检 LiStation V3.0输电线路巡检数障处理分系统 软件下载License使用

PeacePower LiRouter 输电线路无人机自主巡检航线规划系统&#xff08;本文档中简称 “LiRouter”&#xff09;&#xff0c;是基于高精度三维点云数据&#xff0c;在少量人工干预下为输电线路无人 机自主精细化巡检自动生成并输出航线的专业软件工具。 凭借在输电线路无人机智能…

系统架构师(每日一练7)

每日一练 1.关于网络延迟正确的是()。答案与解析 A.在对等网络中&#xff0c;网络的延迟大小与网络中的终端数量无关 B.使用路由器进行数据转发所带来的延迟小于交换机, C.使用internet服务器可最大程度地减小网络延迟 D.服务器延迟的主要影响因素是队列延迟和磁盘10延迟 2.以…

Linux:传输层(1) -- UDP协议

1. 端口号 同一台主机的不同端口号(Port)标记了主机上不同的进程&#xff0c;如下图所示&#xff1a; 在 TCP/IP 协议中 , 用 " 源IP", "源端口号", "目的IP", "目的端口号", "协议号" 这样一个五元组来标识一个通信 ( 可…

加速决策过程:企业级爬虫平台的实时数据分析

摘要 在当今数据驱动的商业环境中&#xff0c;企业如何才能在海量信息中迅速做出精准决策&#xff1f;本文将探讨企业级爬虫平台如何通过实时数据分析加速决策过程&#xff0c;实现数据到决策的无缝衔接。我们聚焦于技术如何赋能企业&#xff0c;提升数据处理效率&#xff0c;…