深入理解二叉搜索树:定义、操作及平衡二叉树
引言
二叉搜索树(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 }
}
插入操作图解
- 初始化一个空树。
- 插入第一个值
50
,成为根节点。 - 插入值
30
,比50
小,插入到50
的左子树。 - 插入值
20
,比30
小,插入到30
的左子树。 - 插入值
40
,比30
大,插入到30
的右子树。 - 插入值
70
,比50
大,插入到50
的右子树。 - 插入值
60
,比70
小,插入到70
的左子树。 - 插入值
80
,比70
大,插入到70
的右子树。
50/ \30 70/ \ / \
20 40 60 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 }
}
删除操作图解
假设我们要从上面的树中删除值 50
、30
和 20
:
- 删除
20
:节点20
没有子节点,直接删除。 - 删除
30
:节点30
有一个子节点40
,用40
替换30
。 - 删除
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
查找操作
查找操作是搜索二叉搜索树中是否存在某个特定值。根据目标值与当前节点值比较,决定在左子树或右子树中继续查找。以下是查找操作的代码示例:
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}
}
查找操作图解
假设我们在上面的树中查找值 50
和 90
:
- 查找
50
:从根节点开始,50
正是根节点值,查找成功。 - 查找
90
:从根节点50
开始,90
大于50
,查找右子树。然后90
大于70
,查找右子树。接着90
大于80
,发现右子树为空,查找失败。
平衡二叉树
平衡二叉树是一类特殊的二叉搜索树,通过自动调整树的结构,使树的高度保持在较低水平,从而提高查找、插入和删除操作的效率。常见的平衡二叉树有 AVL 树和红黑树。
AVL 树
AVL 树是一种自平衡二叉搜索树,它的每个节点都需要满足以下平衡条件:任意节点的左右子树高度差不超过 1。AVL 树通过旋转操作来保持平衡。
AVL 树的旋转操作
- 右旋(Right Rotation)
- 左旋(Left Rotation)
- 左右旋(Left-Right Rotation)
- 右左旋(Right-Left Rotation)
红黑树
红黑树是一种自平衡二叉搜索树,每个节点都有颜色属性(红色或黑色),并且需要满足以下性质:
- 节点是红色或黑色。
- 根节点是黑色。
- 每个叶节点(NIL 节点)是黑色。
- 如果一个节点是红色,则它的两个子节点都是黑色。
- 从一个节点到该节点的所有后代叶节点的所有路径上,包含相同数目的黑色节点。
红黑树的旋转和颜色翻转
- 左旋(Left Rotation)
- 右旋(Right Rotation)
- 颜色翻转(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,以下是每一步的插入和相应的颜色标注和旋转操作:
- 插入
55
,成为根节点,颜色变为黑色。 - 插入
40
,为55
的左子节点,颜色为红色。 - 插入
65
,为55
的右子节点,颜色为红色。 - 插入
60
,为65
的左子节点,颜色为红色,需要左旋65
。 - 插入
75
,为65
的右子节点,颜色为红色。 - 插入
57
,为60
的左子节点,颜色为红色,需要右旋60
,然后左旋55
。
结论
通过上述讲解和实例代码,我们详细展示了二叉搜索树的定义、基本操作及平衡二叉树(AVL 树和红黑树)。二叉搜索树在计算机科学中有着广泛的应用,平衡二叉树通过保持树的高度在较低水平,从而提高查找、插入和删除操作的效率。希望这篇博客对您有所帮助!记得关注、点赞和收藏哦,以便随时查阅更多优质内容!
如果您觉得这篇文章对您有帮助,请关注我的CSDN博客,点赞并收藏这篇文章,您的支持是我持续创作的动力!
关键内容总结:
- 二叉搜索树的定义和基本操作
- 二叉搜索树的插入、删除和查找操作
- 平衡二叉树:AVL 树和红黑树
- AVL 树的旋转操作
- 红黑树的性质和操作
推荐阅读:深入探索设计模式专栏,详细讲解各种设计模式的应用和优化。点击查看:深入探索设计模式。
特别推荐:设计模式实战专栏,深入解析设计模式的实际应用,提升您的编程技巧。点击查看:设计模式实战。
如有任何疑问或建议,欢迎在评论区留言讨论。谢谢阅读!
相关文章:

深入理解二叉搜索树:定义、操作及平衡二叉树
引言 二叉搜索树(Binary Search Tree,BST)是一种特殊的二叉树结构,每个节点的左子树节点值小于根节点值,而右子树节点值大于根节点值。二叉搜索树在计算机科学中有着广泛的应用,尤其在动态查找表和优先队列…...

vue3组件通信(二)
组件通信 一.$attrs(祖>孙间接)二、$refs()父>子, $parent()子>父三.provide,inject(祖>孙直接)四.pinia五.slot1.默认插槽2.具名插槽3.作用域插槽 一.$attrs(祖>孙间接) $attrs用于实现当前组件的父组…...

关键词查找【Boyer-Moore 算法】
1、【Boyer-Moore 算法】 【算法】哪种算法有分数复杂度?- BoyerMoore字符串匹配_哔哩哔哩_bilibili BM算法的精华就在于BM(text, pattern),也就是BM算法当不匹配的时候一次性可以跳过不止一个字符。即它不需要对被搜索的字符串中的字符进行逐一比较,而…...

【前端手写代码】手写Object.create
思路:将传入的对象作为原型 // 思路:将传入的对象作为原型 function create(obj) {function F() { }F.prototype objreturn new F() }...

速通JS模块化规范
目录 1模块化概述 1.1什么是模块化? 1.2为什么需要模块化? 2有哪些模块化规范? 3导入与导出的概念 4CommonJS 规范 4.1初步体验 4.2导出数据 4.3导入数据 4.4扩展理解 4.5浏览器端运行 5ES6 模块化规范 5.1初步体验 5.2Node 中运…...

HamonyOS性能优化工具和方法
性能优化,如何做到更快的启动、更流畅的使用,概括图如下 ArkTS高性能编程: 1. ArkTS规则:有利于方舟编译运行时进行编译优化 2. 使用AOT(Ahead Of Time)模式对应用进行编译优化:方舟编译运行时通过采用PGO(Profile-Gui…...

前端实现边下载文件边上传
问题记录原因: 因为需要实现网络文件的上传,结果是由前端实现,方式是一边下载,一遍上传文件,小文件直接上传,大文件进行切片,切片大小和下载大小有关,特此记录。 1.实现方案 fetc…...

滑线变阻器的优缺点是什么?
滑线变阻器是常见的电子元件,主要用于调节电路中的电阻值,从而达到改变电流、电压的目的。它的主要优点是结构简单、操作方便、成本低,因此在各种电子设备中都有广泛的应用。然而,滑线变阻器也存在一些缺点,主要表现在…...

K8s大模型算力调度策略的深度解析
随着大数据和人工智能技术的飞速发展,Kubernetes(简称K8s)作为容器编排的领军者,在支撑大规模模型训练和推理方面扮演着越来越重要的角色。在大模型算力的调度过程中,如何高效、合理地分配和管理资源成为了一个亟待解决…...

Unity Transform组件实现动画:基础与进阶技巧
在Unity中,Transform组件是控制游戏对象(GameObject)位置、旋转和缩放的核心组件。通过编程控制Transform组件,开发者可以创建各种动画效果。本文将介绍如何使用Transform组件实现动画,从基础的运动到更高级的动画技巧…...

基于深度学习的图像与文本结合
基于深度学习的图像与文本结合的研究领域,是近年来多模态学习(Multimodal Learning)中非常活跃的方向。该领域涉及到如何将图像和文本两种不同类型的数据进行融合和处理,从而实现更智能的任务和应用。以下是对这一领域的详细介绍&…...

windows安全加固
一、补丁管理 及时安装补丁:定期检查和安装Windows系统及其应用程序的更新和补丁,以修复已知的安全漏洞。可以使用Windows Update功能或第三方补丁管理工具来实现。补丁管理策略:对于无法直接访问互联网的服务器,可以建立内部补丁…...

网络安全是什么?怎么入门网络安全?
一、网络安全的定义 网络安全,简单来说,就是保护网络系统中的硬件、软件以及其中的数据不因偶然或恶意的原因而遭到破坏、更改、泄露,保障系统连续可靠正常地运行,网络服务不中断。 随着信息技术的飞速发展,网络安全的…...

语义分割介绍
1. 定义 语义指具有人们可用语言探讨的意义,分割指图像分割。 语义分割(semantic segmentation)能够将整张图的每个部分分割开,使每个部分都有一定类别意义(语义),让计算机可以理解图像。 语义分割是以描边的形式&…...

Unity Editor免登录启动 无需UnityHub
Unity Editor免登录启动项目无需UnityHub,命令行启动项目。需要开发Unity项目,就必须使用 Unity Hub来管理你的项目,还必须要申请一个免费许可,确实有点麻烦,官方已经提供了相关命令行,来直接使用Unity Edi…...

Redis实战篇(黑马点评)笔记总结
一、配置前后端项目的初始环境 前端: 对前端项目在cmd中进行start nginx.exe,端口号为8080 后端: 配置mysql数据库的url 和 redis 的url 和 导入数据库数据 二、登录校验 基于Session的实现登录(不推荐) …...

vulntarget-b
实际部署之后centos7 的ip有所变动分别是 :192.168.127.130以及10.0.20.30 Centos7 老规矩还是先用fscan扫一下服务和端口,找漏洞打 直接爆出来一个SSH弱口令…,上来就不用打了,什么意思??? 直接xshell…...

Axure Web端元件库:构建高效互动网页的基石
在快速迭代的互联网时代,Web设计与开发不仅追求视觉上的美感,更注重用户体验的流畅与功能的强大。Axure RP,作为一款专业的原型设计工具,凭借其强大的交互设计能力和丰富的元件库,成为了众多UI/UX设计师、产品经理及前…...

mac OS matplotlib missing from font(s) DejaVu Sans
如果能搜索到这篇文章,我猜你遇到了和我一样的问题:matplotlib绘图中文乱码。如下: 出现这个问题的原因是:matplotlib使用的字体列表中默认没有中文字体。 这里说一种解决方案:我们可以在文件中手动指定matplotlib使用…...

在 .NET 中使用 Elasticsearch:从安装到实现搜索功能的完整指南
在 .NET 中使用 Elasticsearch Elasticsearch 是一个强大的搜索和分析引擎,广泛应用于处理大规模数据和实时搜索需求。本文将介绍如何在 .NET 环境下使用 Elasticsearch,帮助开发者快速上手并实现基本的搜索功能。 1. 环境准备 首先,我们需…...

Ecovadis认证的步骤需要怎么做?
Ecovadis是一家提供企业可持续发展评估和认证服务的机构。如果您想获得Ecovadis的认证辅导,可以按照以下步骤进行: 了解Ecovadis认证要求:在开始准备之前,先仔细研究Ecovadis的认证要求和标准。您可以访问Ecovadis的官方网站&…...

git sendemail使用
教程参考: git-send-email - 以电子邮件形式发送补丁集 1、安装git-email 2、配置 SMTP 服务器 git config --global sendemail.smtpserver smtp.163.com git config --global sendemail.smtpserverport 465 git config --global sendemail.smtpuser xxxxxx163.c…...

【React】package.json 文件详解
文章目录 一、package.json 文件的基本结构二、package.json 文件的关键字段1. name 和 version2. description3. main4. scripts5. dependencies 和 devDependencies6. repository7. keywords8. author 和 license9. bugs 和 homepage 三、package.json 文件的高级配置1. 配置…...

【嵌入式开发】Keil下载安装
目录 前言 一、Keil的安装 Keil官网 微控制器开发套件版本说明 前言 作为最常见的单片机程序编辑工具,keil有绝对的占有率。Keil提供了包括C编译器、宏汇编、链接器、库管理和一个功能强大的仿真调试器等在内的完整开发方案,通过一个集成开发环境&am…...

【vluhub】elasticsearch漏洞
Elasticsearch介绍 是Apache旗下的一个开源的、分布式、RESTful的搜索和分析引擎,适用于java语言项目 默认端口9200 kali中搭建ElasticHD, 即可未授权绕过ES可视化界面 直通车 https://github.com/360EntSecGroup-Skylar/ElasticHD/releases/download/1.4/elas…...

七言-绝美崇州
题记 今天,2024年07月30日,在看到《今日崇州》 发布的航拍风光照片之后,这才方知笔者虽已寄居崇州“西川第一天”街子古镇养老逾五年,竟然不知崇州拥有如此之多的青山绿水,集生态、宜居、智慧、文化、旅游丰富资源于一…...

C++11新增特性及右值引用
1. 统一的列表初始化 1.1 {}初始化 在C98中,标准允许使用花括号{}对数组或者结构体元素进行统一的列表初始值设定。C11扩大了用大括号括起的列表(初始化列表)的使用范围,使其可用于所有的内置类型和用户自 定义的类型࿰…...

MySQL --- 表的操作
在对表进行操作时,需要先选定操作的表所在的数据库,即先执行 use 数据库名; 一、创建表 create table 表名( field1 datatype, field2 datatype, field3 datatype ) character set 字符集 collate 校验规则 engine 存储引擎 ; 说明:…...

MongoDB 基础知识
一、为什么学习MongoDB MongoDB解决Mysql 的“三高”问题: 1.对数据库高并发写入需求 2.对海量数据高效率存储访问需求 3.对数据库高扩展和高可用的需求 MongoDB 实际应用: 1.社交场景,比如朋友圈,附近的人的地点的存储 2.…...

HDFS原理
HDFS(Hadoop Distributed File System) HDFS——hadoop的分布式文件存储系统 HDFS原理19:49...