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

引言

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

二叉搜索树的定义和操作

定义

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

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

基本操作

插入操作

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

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
删除操作

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

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
查找操作

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

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)
左旋
右旋
颜色翻转

红黑树的插入操作

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

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.mzph.cn/diannao/50397.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Halcon 3D检测平面度,断差,凹坑,凸点

Halcon可以利用深度图做相关检测&#xff0c;也可以直接利用点云数据做检测。但是如果是利用点云数据进行检测&#xff0c;PCL更合适。本文写的是利用深度图检测的方法。 dev_close_window () dev_open_window (0, 0, 512, 512, black, WindowHandle) dev_set_draw (margin) *…

yum换源出现的问题及解决方案

yum 源相关的问题及解决方法 常用替换源的方法 mv /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.backup wget -O /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-7.repo yum clean all yum makecache有时不小心下多了源…

leetcode-79. 单词搜索

题目描述 给定一个 m x n 二维字符网格 board 和一个字符串单词 word 。如果 word 存在于网格中&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 。 单词必须按照字母顺序&#xff0c;通过相邻的单元格内的字母构成&#xff0c;其中“相邻”单元格是那些水平相…

Linux安装TrueNAS(网络附加存储)教程 –第1部分

TrueNAS CORE&#xff08;原名FreeNAS&#xff09;是一款流行的存储系统&#xff0c;可帮助您构建自己的高质量存储设置&#xff0c;而无需支付软件费用。您可以将其安装在计算机硬件或虚拟机 (VM) 上&#xff0c;以获得开源存储的好处。 您可以在家中、办公室或数据中心使用T…

个性化音频生成GPT-SoVits部署使用和API调用

一、训练自己的音色模型步骤 1、准备好要训练的数据&#xff0c;放在Data文件夹中&#xff0c;按照文件模板中的结构进行存放数据 2、双击打开go-webui.bat文件&#xff0c;等待页面跳转 3、页面打开后&#xff0c;开始训练自己的模型 &#xff08;1&#xff09;、人声伴奏分…

RV1126 Linux 系统,接外设,时好时坏(一)应该从哪些方面排查问题

在 Linux 系统中接外设时,遇到“时好时坏”的问题,可能是由多种因素引起的。以下是一些排查问题的建议。 1. 硬件方面的排查 1.1 连接检查 物理连接: 确保外设与主板之间的连接良好,检查插头、插座及线缆是否牢固。引脚配置: 确认设备树中引脚的配置是否正确,尤其是引脚…

shopee虾皮 java后端 一面面经 整体感觉不难

面试总结&#xff1a;总体不难&#xff0c;算法题脑抽了只过了一半&#xff0c;面试官点出了问题说时间到了&#xff0c;反问一点点&#xff0c;感觉五五开&#xff0c;许愿一个二面 1.Java中的锁机制&#xff0c;什么是可重入锁 Java中的机制主要包括 synchronized关键字 Loc…

本地新建项目上传gitee

本地新建项目上传gitee 1、初始化一个新的Git仓库 git init2、添加所有文件到新的Git仓库 git add .3、提交这些更改 git commit -m init4、在Gitee上创建一个新的仓库 登录到您的Gitee帐户&#xff0c;然后点击“新建仓库”。填写仓库名称、描述等信息&#xff0c;然后点击…

Profinet 转 EtherCAT 主站网关

一、功能概述 1.1 设备简介 本产品是 PN(Profinet)和 ECAT(EtherCAT)网关&#xff0c;通过数据映射方式工作。 本产品在 PN 侧作为 PN IO 从站&#xff0c;接西门子 PLC 的 Profinet 口&#xff1b;在 ECAT 侧 做为 ECAT 主站&#xff0c;接 ECAT 从站&#xff0c;如伺服驱…

使用Nacos统一管理Logback日志配置

在项目开发过程,之前日志配置是日志模块单独配置,其他微服务通过引入日志模块依赖来统一日志配置。但是这样结构有一个痛点就是,如果需要修改日志配置,就需要重新打包,然后构建所有依赖了日志模块的微服务,无法实时修改日志配置。为了实时更新日志配置无需重新构建服务,…

CTF-Web习题:[GXYCTF2019]Ping Ping Ping

题目链接&#xff1a;[GXYCTF2019]Ping Ping Ping 解题思路 访问靶机&#xff0c;得到如下页面&#xff0c;类似于URL参数 尝试用HackBar构造url传输过去看看 发现返回了ping命令的执行结果&#xff0c;可以猜测php脚本命令是ping -c 4 $ip&#xff0c;暂时不知道执行的函数…

IMU用于肌骨相关职业病风险评估

肌肉骨骼疾病&#xff08;WMSDs&#xff09;是职场中常见的健康问题&#xff0c;会导致员工疼痛和工作效率降低。为了更好地评估和管理这些风险&#xff0c;科研人员开发了一种基于惯性测量单元&#xff08;IMU&#xff09;的新型系统。 这个创新系统通过监测员工在工作时的身体…

数据库之运算符

目录 一、算数运算符 二、比较运算符 1.常用比较运算符 2.实现特殊功能的比较运算符 三、逻辑运算符 1.逻辑与运算符&#xff08;&&或者AND&#xff09; 2.逻辑或运算符&#xff08;||或者OR&#xff09; 3.逻辑非运算符&#xff08;&#xff01;或者NOT&#…

软件测试中的压力测试和性能测试区别

压力测试和性能测试是软件测试中两种重要的测试类型&#xff0c;它们都旨在评估软件在不同条件下的表现&#xff0c;但侧重点和目的有所不同。 压力测试&#xff08;Stress Testing&#xff09;定义&#xff1a; 压力测试是一种测试方法&#xff0c;用于确定软件在极端条件下…

BP神经网络及其Python和MATLAB实现预测

## 一、背景 BP&#xff08;Back Propagation&#xff09;神经网络是多层前馈神经网络的一种&#xff0c;广泛应用于模式识别、数据挖掘、机器学习等领域。随着人工智能与机器学习技术的快速发展&#xff0c;BP神经网络作为一种基础的神经网络模型&#xff0c;已经成为研究和应…

安卓开机启动性能优化之-bootchart相关工具使用及查看

背景&#xff1a; 开机启动相关的详细信息&#xff0c;一般都是可以通过logcat中查看boot_progress相关查看&#xff0c;这种方式查看相对不那么方便&#xff0c;毕竟开机过程中涉及的进程较多&#xff0c;要查看也较多&#xff0c;而且还经常需要查看代码才可以对应起来&…

前端系列-8 集中式状态管理工具pinia

集中式状态管理工具—pinia vue3中使用pinia作为集中式状态管理工具&#xff0c;替代vue2中的vuex。 pinia文档可参考: https://pinia.web3doc.top/introduction.html 1.项目集成pinia 安装pinia依赖: npm install pinia在main.ts中引入pinia import { createApp } from vu…

Facebook和Instagram运营中的注意事项和QA

Facebook注意事项 1.Facebook的几种违规行为&#xff1a;加好友过快或者过多&#xff1b;加群过快或者过多&#xff1b;转 发信息到群过快或者过多&#xff1b;创建主页过快或者过多&#xff1b;创建群过快或者过多&#xff1b; 主动给人发信息过多&#xff1b;IP、浏览器更换&…

spring框架实现滑动验证码功能

spring框架实现滑动验证码功能 1. 整体描述2. 具体实现2.1 滑动验证码实体类2.2 滑动验证码登录VO2.3 滑动验证码接口返回类2.4 滑动验证码工具类2.5 滑动验证码Service2.6 滑动验证码Controller 3 工程源码4 总结 1. 整体描述 之前项目需要在验证码模块&#xff0c;增加滑动验…

Android中接入hook框架:lancet-base

me.ele:lancet-base是"饿了么"开发的Android平台的开源hook框架&#xff0c;GitHub地址为&#xff1a;https://github.com/eleme/lancet。 此框架的优点如下&#xff1a; 1.编译速度快, 并且支持增量编译。 2.简洁的 API, 几行 Java 代码完成注入需求。 3.没有任何多…