
数据结构与算法
文章平均质量分 78
普通网友
这个作者很懒,什么都没留下…
展开
专栏收录文章
- 默认排序
- 最新发布
- 最早发布
- 最多阅读
- 最少阅读
-
12.5 小结
12.5 小结分治是一种常见的算法设计策略,包括分(划分)和治(合并)两个阶段,通常基于递归实现。 判断是否是分治算法问题的依据包括:问题能否分解、子问题是否独立、子问题能否合并。 归并排序是分治策略的典型应用,其递归地将数组划分为等长的两个子数组,直到只剩一个元素时开始逐层合并,从而完成排序。 引入分治策略往往可以提升算法效率。一方面,分治策略减少了操作数量;另一方面,分治后有利于系统的并行优化。 分治既可以解决许多算法问题,也广泛应用于数据结构与算法设计中,处处可见其身影。 相较于暴力原创 2024-06-09 06:30:00 · 222 阅读 · 1 评论 -
12.4 汉诺塔问题
在古印度的一个寺庙里,僧侣们有三根高大的钻石柱子,以及 64 个大小不一的金圆盘。至此,我们可总结出图 12-14 所示的解决汉诺塔问题的分治策略:将原问题 𝑓(𝑛) 划分为两个子问题 𝑓(𝑛−1) 和一个子问题 𝑓(1) ,并按照以下顺序解决这三个子问题。如图 12-15 所示,汉诺塔问题形成一棵高度为 𝑛 的递归树,每个节点代表一个子问题,对应一个开启的。如图 12-11 所示,对于问题 𝑓(1) ,即当只有一个圆盘时,我们将它直接从。如图 12-12 所示,对于问题 𝑓(2) ,即当有两个圆盘时,原创 2024-06-09 05:00:00 · 688 阅读 · 0 评论 -
12.2 分治搜索策略
在之前的章节中,二分查找是基于递推(迭代)实现的。现在我们基于分治(递归)来实现它。从分治角度,我们将搜索区间 [𝑖,𝑗] 对应的子问题记为 𝑓(𝑖,𝑗)。分治能够提升搜索效率,本质上是因为暴力搜索每轮只能排除一个选项,以原问题 𝑓(0,𝑛−1) 为起始点,通过以下步骤进行二分查找。图 12-4 展示了在数组中二分查找元素 6 的分治过程。,其中所有元素都是唯一的,请查找元素。图 12-4 二分查找的分治过程。我们已经学过,搜索算法分为两大类。二分查找的分治策略如下所示。,例如二分查找和树。原创 2024-06-08 05:00:00 · 390 阅读 · 0 评论 -
12.1 分治算法
换句话说,将大问题分解为多个子问题、解决子问题、将子问题的解合并为原问题的解,这几步的效率为什么比直接解决原问题的效率更高?比如在图 12-3 所示的“桶排序”中,我们将海量的数据平均分配到各个桶中,则可将所有桶的排序任务分散到各个计算单元,完成后再合并结果。并行优化在多核或多处理器的环境中尤其有效,因为系统可以同时处理多个子问题,更加充分地利用计算资源,从而显著减少总体的运行时间。请注意,划分后的时间复杂度仍然是平方阶 𝑂(𝑛2) ,只是复杂度中的常数项变小了。我们知道,分治生成的子问题是相互独立的,原创 2024-06-07 05:45:00 · 1663 阅读 · 0 评论 -
第 12 章 分治
Abstract难题被逐层拆解,每一次的拆解都使它变得更为简单。分而治之揭示了一个重要的事实:从简单做起,一切都不再复杂。原创 2024-06-07 05:30:00 · 219 阅读 · 0 评论 -
11.11 小结
假设最差情况,一直为一半长度,那么最终的递归深度就是 log𝑛。在该方法下,输入元素全部相等的数组,仅一轮哨兵划分即可完成排序。回顾原始的快速排序,我们有可能会连续地递归长度较大的数组,最差情况下为 𝑛、𝑛−1、…、2、1 ,递归深度为 𝑛。不行,当我们以最左端元素为基准数时,必须先“从右往左查找”再“从左往右查找”。也就是说,此时最后一步交换操作会把一个比基准数更大的元素交换至数组最左端,导致哨兵划分失败。可以发现,学生 D 和 C 的位置发生了交换,姓名的有序性被破坏了,而这是我们不希望看到的。原创 2024-06-06 05:30:00 · 833 阅读 · 0 评论 -
11.10 基数排序
假设我们需要对 𝑛=106 个学号进行排序,而学号是一个 8 位数字,这意味着数据范围 𝑚=108 非常大,使用计数排序需要分配大量内存空间,而基数排序可以避免这种情况。举例来说,如果第一轮排序结果 𝑎𝑏 ,那么第二轮的结果将取代第一轮的结果。在此基础上,基数排序利用数字各位之间的递进关系,依次对每一位进行排序,从而得到最终的排序结果。以学号数据为例,假设数字的最低位是第 1 位,最高位是第 8 位,基数排序的流程如图 11-18 所示。为什么从最低位开始排序?原创 2024-06-06 05:00:00 · 1244 阅读 · 0 评论 -
11.9 计数排序
这个信息非常关键,因为它告诉我们各个元素应该出现在结果数组的哪个位置。接下来,我们倒序遍历原数组。原创 2024-06-05 05:30:00 · 899 阅读 · 0 评论 -
11.8 桶排序
例如,我们想要将淘宝上的所有商品按价格范围平均分配到 10 个桶中,但商品价格分布不均,低于 100 元的非常多,高于 1000 元的非常少。此时,可以将数据分成 1000 个桶,然后分别对每个桶进行排序,最后将结果合并。它通过设置一些具有大小顺序的桶,每个桶对应一个数据范围,将数据平均分配到各个桶中;如图 11-15 所示,我们假设商品价格服从正态分布,这样就可以合理地设定价格区间,从而将商品平均分配到各个桶中。为实现平均分配,我们可以先设定一条大致的分界线,将数据粗略地分到 3 个桶中。原创 2024-06-05 05:00:00 · 930 阅读 · 0 评论 -
11.7 堆排序
以上方法虽然可行,但需要借助一个额外数组来保存弹出的元素,比较浪费空间。在实际中,我们通常使用一种更加优雅的实现方式。我们可以利用已经学过的“建堆操作”和“元素出堆操作”实现堆排序。值得注意的是,由于堆的长度会随着提取最大元素而减小,因此我们需要给。函数添加一个长度参数 𝑛 ,用于指定堆的当前有效长度。设数组的长度为 𝑛 ,堆排序的流程如图 11-12 所示。在代码实现中,我们使用了与“堆”章节相同的从顶至底堆化。阅读本节前,请确保已学完“堆“章节。步,只是多了一个弹出元素的步骤。原创 2024-06-04 05:30:00 · 392 阅读 · 0 评论 -
11.6 归并排序
合并阶段”从底至顶地将左子数组和右子数组合并为一个有序数组。需要注意的是,从长度为 1 的子数组开始合并,合并阶段中的每个子数组都是有序的。是一种基于分治策略的排序算法,包含图 11-10 所示的“划分”和“合并”阶段。如图 11-11 所示,“划分阶段”从顶至底递归地将数组从中点切分为两个子数组。具体实现细节比较复杂,有兴趣的读者可以查阅相关资料进行学习。观察发现,归并排序与二叉树后序遍历的递归顺序是一致的。对于链表,归并排序相较于其他排序算法具有显著优势,归并排序的实现如以下代码所示。原创 2024-06-04 05:00:00 · 981 阅读 · 0 评论 -
11.5 快速排序
以完全有序的输入数组为例,设递归中的子数组长度为 𝑚 ,每轮哨兵划分操作都将产生长度为 0 的左子数组和长度为 𝑚−1 的右子数组,这意味着每一层递归调用减少的问题规模非常小(只减少一个元素),递归树的高度会达到 𝑛−1 ,此时需要占用 𝑂(𝑛) 大小的栈帧空间。快速排序的核心操作是“哨兵划分”,其目标是:选择数组中的某个元素作为“基准数”,将所有小于基准数的元素移到其左侧,而大于基准数的元素移到其右侧。尽管快速排序的平均时间复杂度与“归并排序”和“堆排序”相同,但通常快速排序的效率更高,主要有以下原因。原创 2024-06-03 05:30:00 · 930 阅读 · 0 评论 -
11.4 插入排序
而在数据量较小时,𝑛2 和 𝑛log𝑛 的数值比较接近,复杂度不占主导地位,每轮中的单元操作数量起到决定性作用。实际上,许多编程语言(例如 Java)的内置排序函数采用了插入排序,大致思路为:对于长数组,采用基于分治策略的排序算法,例如快速排序;插入排序的时间复杂度为 𝑂(𝑛2) ,而我们即将学习的快速排序的时间复杂度为 𝑂(𝑛log𝑛)。尽管插入排序的时间复杂度更高,具体来说,我们在未排序区间选择一个基准元素,将该元素与其左侧已排序区间的元素逐一比较大小,并将该元素插入到正确的位置。原创 2024-06-03 05:00:00 · 818 阅读 · 0 评论 -
11.3 冒泡排序
如图 11-4 所示,冒泡过程可以利用元素交换操作来模拟:从数组最左端开始向右遍历,依次比较相邻元素大小,如果“左元素 > 右元素”就交换二者。遍历完成后,最大的元素会被移动到数组的最右端。经过优化,冒泡排序的最差时间复杂度和平均时间复杂度仍为 𝑂(𝑛2);但当输入数组完全有序时,可达到最佳时间复杂度 𝑂(𝑛)。我们发现,如果某轮“冒泡”中没有执行任何交换操作,说明数组已经完成排序,可直接返回结果。设数组的长度为 𝑛 ,冒泡排序的步骤如图 11-5 所示。来监测这种情况,一旦出现就立即返回。原创 2024-06-02 05:30:00 · 439 阅读 · 0 评论 -
11.2 选择排序
的工作原理非常简单:开启一个循环,每轮从未排序区间选择最小的元素,将其放到已排序区间的末尾。设数组的长度为 𝑛 ,选择排序的算法流程如图 11-2 所示。选择排序(selection sort)原创 2024-06-02 05:00:00 · 460 阅读 · 0 评论 -
11.1 排序算法
我们期望排序算法的时间复杂度尽量低,且总体操作数量较少(时间复杂度中的常数项变小)。通过在原数组上直接操作实现排序,无须借助额外的辅助数组,从而节省内存。通常情况下,原地排序的数据搬运操作较少,运行速度也更快。因此,在选择排序算法时,需要根据具体的数据特点和问题需求来决定。依赖比较运算符()来判断元素的相对顺序,从而排序整个数组,理论最优时间复杂度为 𝑂(𝑛log𝑛)。的时间复杂度会受输入数据的影响,即最佳时间复杂度、最差时间复杂度、平均时间复杂度并不完全相等。原创 2024-06-01 05:30:00 · 410 阅读 · 0 评论 -
第 11 章 排序
排序犹如一把将混乱变为秩序的魔法钥匙,使我们能以更高效的方式理解与处理数据。无论是简单的升序,还是复杂的分类排列,排序都向我们展示了数据的和谐美感。原创 2024-06-01 05:00:00 · 228 阅读 · 0 评论 -
【收录 Hello 算法】10.6 小结
10.6 小结二分查找依赖数据的有序性,通过循环逐步缩减一半搜索区间来进行查找。它要求输入数据有序,且仅适用于数组或基于数组实现的数据结构。 暴力搜索通过遍历数据结构来定位数据。线性搜索适用于数组和链表,广度优先搜索和深度优先搜索适用于图和树。此类算法通用性好,无须对数据进行预处理,但时间复杂度𝑂(𝑛)较高。 哈希查找、树查找和二分查找属于高效搜索方法,可在特定数据结构中快速定位目标元素。此类算法效率高,时间复杂度可达𝑂(log𝑛)甚至𝑂(1),但通常需要借助额外数据结构。 实际原创 2024-05-28 05:00:00 · 232 阅读 · 0 评论 -
【收录 Hello 算法】10.5 重识搜索算法
给定大小为 𝑛 的一组数据,我们可以使用线性搜索、二分查找、树查找、哈希查找等多种方法从中搜索目标元素。不难发现,这些知识点都已在前面的章节中介绍过,因此搜索算法对于我们来说并不陌生。在本节中,我们将从更加系统的视角切入,重新审视搜索算法。例如,二分查找需要预先对数组进行排序,哈希查找和树查找都需要借助额外的数据结构,维护这些数据结构也需要额外的时间和空间开销。自适应搜索利用数据的特有属性(例如有序性)来优化搜索过程,从而更高效地定位目标元素。,其中 𝑛 为元素数量,因此在数据量较大的情况下性能较差。原创 2024-05-27 05:30:00 · 1009 阅读 · 0 评论 -
【收录 Hello 算法】10.4 哈希优化策略
如图 10-9 所示,我们开启一个两层循环,在每轮中判断两个整数的和是否为。考虑借助一个哈希表,键值对分别为数组元素和元素索引。循环遍历数组,每轮执行图 10-10 所示的步骤。此方法的时间复杂度为 𝑂(𝑛2) ,空间复杂度为 𝑂(1) ,在大数据量下非常耗时。此方法通过哈希查找将时间复杂度从 𝑂(𝑛2) 降至 𝑂(𝑛) ,大幅提升运行效率。由于需要维护一个额外的哈希表,因此空间复杂度为 𝑂(𝑛)。的两个元素,并返回它们的数组索引。返回任意一个解即可。,请在数组中搜索“和”为。,若是,则返回它们的索引。原创 2024-05-27 05:00:00 · 526 阅读 · 0 评论 -
【收录 Hello 算法】10.3 二分查找边界
因此,如图 10-8 所示,我们可以构造一个数组中不存在的元素,用于查找左右边界。考虑通过查找插入点的函数实现查找左边界。代码在此省略,有兴趣的读者可以自行实现。当遇到以上两种情况时,直接返回 −1 即可。如图 10-7 所示,查找完成后,指针 𝑖 指向最左一个。回忆二分查找插入点的方法,搜索完成后 𝑖 指向最左一个。,其中可能包含重复元素。若数组中不包含该元素,则返回 −1。图 10-7 将查找右边界转化为查找左边界。最直接的方式是修改代码,替换在。代码在此省略,以下两点值得注意。原创 2024-05-26 06:30:00 · 1814 阅读 · 0 评论 -
【收录 Hello 算法】10.2 二分查找插入点
在不断的循环二分中,指针 𝑖 和 𝑗 都逐渐逼近预先设定的目标。如图 10-6 所示,整体流程保持不变,每轮先计算中点索引 𝑚 ,再判断。总的来看,二分查找无非就是给指针 𝑖 和 𝑗 分别设定搜索目标,目标可能是一个具体的元素(例如。二分查找不仅可用于搜索目标元素,还可用于解决许多变种问题,比如搜索目标元素的插入位置。在上一题的基础上,规定数组可能包含重复元素,其余不变。图 10-6 二分查找重复元素的插入点的步骤。插入到相等元素的左边,这意味着新插入的。时,插入点的索引是否是该元素的索引?原创 2024-05-26 05:00:00 · 1025 阅读 · 0 评论 -
【收录 Hello 算法】10.1 二分查找
如图 10-2 所示,我们先初始化指针 𝑖=0 和 𝑗=𝑛−1 ,分别指向数组首元素和尾元素,代表搜索区间 [0,𝑛−1]。除了上述双闭区间外,常见的区间表示还有“左闭右开”区间,定义为 [0,𝑛) ,即左边界包含自身,右边界不包含自身。在该表示下,区间 [𝑖,𝑗) 在 𝑖=𝑗 时为空。由于“双闭区间”表示中的左右边界都被定义为闭区间,因此通过指针 𝑖 和指针 𝑗 缩小区间的操作也是对称的。如图 10-3 所示,在两种区间表示下,二分查找算法的初始化、循环条件和缩小区间操作皆有所不同。原创 2024-05-25 06:00:00 · 1664 阅读 · 0 评论 -
【收录 Hello 算法】第 10 章 搜索
搜索是一场未知的冒险,我们或许需要走遍神秘空间的每个角落,又或许可以快速锁定目标。在这场寻觅之旅中,每一次探索都可能得到一个未曾料想的答案。原创 2024-05-25 05:00:00 · 354 阅读 · 0 评论 -
【收录 Hello 算法】9.4 小结
维基百科上不同语言版本的定义不一致:英文版是“路径是一个边序列”,而中文版是“路径是一个顶点序列”。但在实际应用中,可能需要按照指定规则来排序,比如按照顶点添加的次序,或者按照顶点值大小的顺序等,这样有助于快速查找“带有某种极值”的顶点。在本文中,路径被视为一个边序列,而不是一个顶点序列。这是因为两个顶点之间可能存在多条边连接,此时每条边都对应一条路径。在非连通图中,从某个顶点出发,至少有一个顶点无法到达。:在邻接表中,“与该顶点相连的所有顶点”的顶点顺序是否有要求?:路径的定义是顶点序列还是边序列?原创 2024-05-24 06:00:00 · 448 阅读 · 0 评论 -
【收录 Hello 算法】9.3 图的遍历
在遍历邻接顶点的过程中,由于是无向图,因此所有边都会被访问 2 次,使用 𝑂(2|𝐸|) 时间;如图 9-11 所示,从左上角顶点出发,访问当前顶点的某个邻接顶点,直到走到尽头时返回,再继续走到尽头并返回,以此类推,直至所有顶点遍历完成。如图 9-9 所示,从左上角顶点出发,首先遍历该顶点的所有邻接顶点,然后遍历下一个顶点的所有邻接顶点,以此类推,直至所有顶点访问完毕。顶点数量最多为 |𝑉| ,递归深度最大为 |𝑉| ,因此使用 𝑂(|𝑉|) 空间。中的顶点数量最多为 |𝑉| ,使用 𝑂(|𝑉|) 空间。原创 2024-05-24 05:00:00 · 1407 阅读 · 0 评论 -
【收录 Hello 算法】9.2 图的基础操作
综合来看,邻接矩阵体现了“以空间换时间”的原则,而邻接表体现了“以时间换空间”的原则。类来表示顶点,这样做的原因是:如果与邻接矩阵一样,用列表索引来区分不同顶点,那么假设要删除索引为 𝑖 的顶点,则需遍历整个邻接表,将所有大于 𝑖 的索引全部减 1 ,效率很低。图的基础操作可分为对“边”的操作和对“顶点”的操作。设图中共有 𝑛 个顶点和 𝑚 条边,表 9-2 对比了邻接矩阵和邻接表的时间效率和空间效率。设无向图的顶点总数为 𝑛、边总数为 𝑚 ,则可根据图 9-8 所示的方法实现各种操作。原创 2024-05-23 06:00:00 · 722 阅读 · 0 评论 -
【收录 Hello 算法】9.1 图
如图 9-5 所示,设邻接矩阵为 𝑀、顶点列表为 𝑉 ,那么矩阵元素 𝑀[𝑖,𝑗]=1 表示顶点 𝑉[𝑖] 到顶点 𝑉[𝑗] 之间存在边,反之 𝑀[𝑖,𝑗]=0 表示两顶点之间无边。第 𝑖 个链表对应顶点 𝑖 ,其中存储了该顶点的所有邻接顶点(与该顶点相连的顶点)。使用邻接矩阵表示图时,我们可以直接访问矩阵元素以获取边,因此增删查改操作的效率很高,时间复杂度均为 𝑂(1)。使用一个 𝑛×𝑛 大小的矩阵来表示图,每一行(列)代表一个顶点,矩阵元素代表边,用 1 或 0 表示两个顶点之间是否存在边。原创 2024-05-23 05:30:00 · 1603 阅读 · 0 评论 -
【收录 Hello 算法】第 9 章 图
在生命旅途中,我们就像是一个个节点,被无数看不见的边相连。每一次的相识与相离,都在这张巨大的网络图中留下独特的印记。原创 2024-05-22 06:00:00 · 313 阅读 · 0 评论 -
【收录 Hello 算法】8.4 小结
两者不是同一个概念,只是碰巧都叫“堆”。计算机系统内存中的堆是动态内存分配的一部分,程序在运行时可以使用它来存储数据。程序可以请求一定量的堆内存,用于存储如对象和数组等复杂结构。当这些数据不再需要时,程序需要释放这些内存,以防止内存泄漏。相较于栈内存,堆内存的管理和使用需要更谨慎,使用不当可能会导致内存泄漏和野指针等问题。:数据结构的“堆”与内存管理的“堆”是同一个概念吗?原创 2024-05-22 05:30:00 · 310 阅读 · 0 评论 -
【收录 Hello 算法】8.3 Top-k 问题
总共执行了 𝑛 轮入堆和出堆,堆的最大长度为 𝑘 ,因此时间复杂度为 𝑂(𝑛log𝑘)。在不断加入数据时,我们可以持续维护堆内的元素,从而实现最大的 𝑘 个元素的动态更新。、𝑘 大的元素,时间复杂度为 𝑂(𝑛𝑘)。此方法只适用于 𝑘≪𝑛 的情况,因为当 𝑘 与 𝑛 比较接近时,其时间复杂度趋向于 𝑂(𝑛2) ,非常耗时。显然,该方法“超额”完成任务了,因为我们只需找出最大的 𝑘 个元素即可,而不需要排序其他元素。进行排序,再返回最右边的 𝑘 个元素,时间复杂度为 𝑂(𝑛log𝑛)。原创 2024-05-21 05:30:00 · 621 阅读 · 0 评论 -
【收录 Hello 算法】8.2 建堆操作
如图 8-5 所示,节点“从顶至底堆化”的最大迭代次数等于该节点到叶节点的距离,而该距离正是“节点高度”。因此,我们可以对各层的“节点数量 × 节点高度”求和,我们首先创建一个空堆,然后遍历列表,依次对每个元素执行“入堆操作”,即先将元素添加至堆的尾部,再对该元素执行“从底至顶”堆化。设元素数量为 𝑛 ,每个元素的入堆操作使用 𝑂(log𝑛) 时间,因此该建堆方法的时间复杂度为 𝑂(𝑛log𝑛)。进一步,高度为 ℎ 的完美二叉树的节点数量为 𝑛=2ℎ+1−1 ,易得复杂度为 𝑂(2ℎ)=𝑂(𝑛)。原创 2024-05-21 05:00:00 · 678 阅读 · 0 评论 -
【收录 Hello 算法】8.1 堆
堆顶元素是二叉树的根节点,即列表首元素。如图 8-2 所示,给定索引 𝑖 ,其左子节点的索引为 2𝑖+1 ,右子节点的索引为 2𝑖+2 ,父节点的索引为 (𝑖−1)/2(向下整除)。如图 8-3 所示,我们比较插入节点与其父节点的值,如果插入节点更大,则将它们交换。然后继续执行此操作,从底至顶修复堆中的各个节点,直至越过根节点或遇到无须交换的节点时结束。,我们将根节点的值与其两个子节点的值进行比较,将最大的子节点与根节点交换。当使用数组表示二叉树时,元素代表节点值,索引代表节点在二叉树中的位置。原创 2024-05-20 06:00:00 · 1503 阅读 · 0 评论 -
【收录 Hello 算法】第 8 章 堆
座座山峰高低错落,而最高的山峰总是最先映入眼帘。堆就像是山岳峰峦,层叠起伏、形态各异。原创 2024-05-20 05:00:00 · 242 阅读 · 0 评论 -
【收录 Hello 算法】7.6 小结
至于根节点的选择,我们通常会将输入数据排序,然后将中点元素作为根节点,再递归地构建左右子树。子树的根节点和其父节点的连接是在该函数返回后完成的,不属于右旋操作的维护范围。是的,例如高度 ℎ=2 的满二叉树,其节点总数 𝑛=7 ,则底层节点数量 4=2ℎ=(𝑛+1)/2。,因此我们只要按照“左 → 根 → 右”的优先级遍历树,就可以获得有序的节点序列。:对于只有一个节点的二叉树,树的高度和根节点的深度都是 0 吗?是没有意义的,它只是插入、删除操作中的一步。是的,构建树的方法已在二叉搜索树代码中的。原创 2024-05-19 06:00:00 · 710 阅读 · 0 评论 -
【收录 Hello 算法】7.5 AVL 树
节点高度”是指从该节点到它的最远叶节点的距离,即所经过的“边”的数量。唯一的区别在于,在 AVL 树中插入节点后,从该节点到根节点的路径上可能会出现一系列失衡节点。如下表所示,我们通过判断失衡节点的平衡因子以及较高一侧子节点的平衡因子的正负号,来确定失衡节点属于图 7-32 中的哪种情况。AVL 树的特点在于“旋转”操作,它能够在不影响二叉树的中序遍历序列的前提下,使失衡节点重新恢复平衡。类似地,在二叉搜索树的删除节点方法的基础上,需要从底至顶执行旋转操作,使所有失衡节点恢复平衡。,执行“右旋”操作。原创 2024-05-19 05:00:00 · 1649 阅读 · 0 评论 -
【收录 Hello 算法】7.4 二叉搜索树
与插入节点类似,我们需要保证在删除操作完成后,二叉搜索树的“左子树 < 根节点 < 右子树”的性质仍然满足。然而,如果我们在二叉搜索树中不断地插入和删除节点,可能导致二叉树退化为图 7-23 所示的链表,这时各种操作的时间复杂度也会退化为 𝑂(𝑛)。如图 7-22 所示,二叉树的中序遍历遵循“左 → 根 → 右”的遍历顺序,而二叉搜索树满足“左子节点 < 根节点 < 右子节点”的大小关系。如图 7-19 所示,当待删除节点的度为 0 时,表示该节点是叶节点,可以直接删除。在代码实现中,需要注意以下两点。原创 2024-05-18 06:00:00 · 1711 阅读 · 0 评论 -
【收录 Hello 算法】7.3 二叉树数组表示
给定一棵完美二叉树,我们将所有节点按照层序遍历的顺序存储在一个数组中,则每个节点都对应唯一的数组索引。如图 7-14 所示,这样处理后,层序遍历序列就可以唯一表示二叉树了。给定数组中的任意一个节点,我们都可以通过映射公式来访问它的左(右)子节点。上一节介绍了链表表示下的二叉树的各项基本操作。如图 7-13 所示,给定一棵非完美二叉树,上述数组表示方法已经失效。以下代码实现了一棵基于数组表示的二叉树,包括以下几种操作。图 7-12 展示了各个节点索引之间的映射关系。二叉树的数组表示主要有以下优点。原创 2024-05-18 05:30:00 · 902 阅读 · 0 评论 -
【收录 Hello 算法】7.2 二叉树遍历
从物理结构的角度来看,树是一种基于链表的数据结构,因此其遍历方式是通过指针逐个访问节点。然而,树是一种非线性数据结构,这使得遍历树比遍历链表更加复杂,需要借助搜索算法来实现。队列遵循“先进先出”的规则,而广度优先遍历则遵循“逐层推进”的规则,两者背后的思想是一致的。图 7-11 展示了前序遍历二叉树的递归过程,其可分为“递”和“归”两个逆向的部分。,在每个节点都会遇到三个位置,分别对应前序遍历、中序遍历和后序遍历。二叉树常见的遍历方式包括层序遍历、前序遍历、中序遍历和后序遍历等。如图 7-9 所示,原创 2024-05-17 07:00:00 · 580 阅读 · 0 评论 -
【收录 Hello 算法】7.1 二叉树
如图 7-1 所示,如果将“节点 2”视为父节点,则其左子节点和右子节点分别是“节点 4”和“节点 5”,左子树是“节点 4 及其以下节点形成的树”,右子树是“节点 5 及其以下节点形成的树”。所有层的节点都被完全填满。与链表类似,二叉树的基本单元是节点,每个节点包含值、左子节点引用和右子节点引用。请注意,我们通常将“高度”和“深度”定义为“经过的边的数量”,但有些题目或教材可能会将其定义为“经过的节点的数量”。当给定一个二叉树的节点时,我们将该节点的左子节点及其以下节点形成的树称为该节点的。原创 2024-05-17 06:00:00 · 590 阅读 · 0 评论