| |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
-> 数据结构与算法 -> 【数据结构】堆的实现 -> 正文阅读 |
|
[数据结构与算法]【数据结构】堆的实现 |
【数据结构】堆——完全二叉树顺序存储实现
??🎶🎶 Hello!上期我们初步认识了树,今天我们来一起学习一下完全二叉树的顺序实现——堆。 所谓顺序实现即我们借助数组来实现。 一上来我们有一些问题: 1.为什么是完全二叉树?普通的二叉树可以吗?
2.堆与完全二叉树有什么关系
3.为什么要学堆,堆有什么用处
注意:在堆这部分,我们逻辑上是借助完全二叉树来思考,但实际上操作的是数组。这十分重要 1.堆的概念与结构定义:将集合 S = { k 1 , k 2 , k 3 , k 4 ? ? ? ? k n } S=\{ k_1,k_2,k_3,k_4····k_n\} S={k1?,k2?,k3?,k4?????kn?} 的所有元素按照完全二叉树的顺序存储形式存储在数组中且满足如下性质 k i ≥ k 2 i + 1 且 k i ≥ k 2 i + 2 ( 大 堆 ) 或 者 k i ≤ k 2 i + 1 且 k i ≤ k 2 i + 2 ( 小 堆 ) , 其 中 2 i + 2 ≤ n k_i \ge k_{2i+1} 且 k_i \ge k_{2i+2}(大堆) 或者k_i \le k_{2i+1} 且 k_i \le k_{2i+2}(小堆),其中{2i+2 } \le n ki?≥k2i+1?且ki?≥k2i+2?(大堆)或者ki?≤k2i+1?且ki?≤k2i+2?(小堆),其中2i+2≤n 由定义出发我们可以得到如下性质:
堆的结构:
2.堆的创建2.1 插入建堆正如我们学习链表时,我们通过一个数据一个数据插入来创建一个链表,我们也可以通过插入数据来建立一个堆。在示例中,我们会创建一个小堆。 在此之前我们需要介绍一下:向上调整算法 2.1.1 向上调整算法如图:我们初始时有一个小堆,即橘色曲线圈出的部分。我们要在这个堆后面插入一个数据为11的结点。通过向上调整算法,要把这个结点向上调整,维持堆的特性。 调整过程: 从图中我们可以发现,向上调整的路径:孩子结点—>父节点—>祖父结点—>曾祖父······ 形象一点来说整个向上调整的过程就是一个后辈孩子寻祖宗的过程,我且称为”寻根“(虽然不是特别贴切hhh) 不扯了,我们总结一下向上调整算法的思路(维持小堆):
perception:
代码实现:
2.1.2 插入建堆相关API
2.1.3 HeapPush的实现思路:
2.1.4 HeapCreate实现完成了前面的函数,我们手上就有了所有的强有力的工具。插入建堆,手到擒来! 注: 此处的
2.1.5 复杂度分析为了简化我们分析问题的复杂程度,我们考虑最多最坏的情况(满二叉树) 第 h 层 需 要 调 整 的 总 次 数 : ( h ? 1 ) ? 2 h ? 1 第h层需要调整的总次数:(h-1)*2^{h-1} 第h层需要调整的总次数:(h?1)?2h?1
h
层
满
二
叉
树
需
要
调
整
的
次
数
:
h层满二叉树需要调整的次数:
h层满二叉树需要调整的次数: 插入建堆时间复杂度为: O ( N l o g N ) O(NlogN) O(NlogN) 2.向下调整建堆提前剧透一下,这个建堆方法就厉害了嗷! 同样我们先来介绍一下向下调整算法 2.2.1 向下调整算法在这个状态下虽然不满足堆的条件但是,根结点左子树是一个小堆,根结点的右子树是一个小堆,这个时候我们便采取向下调整算法来将根结点向下调整,使其原结构成为堆 调整过程: 向下调整的思路:
perception:
代码实现: 在选出左右孩子中小的那个的逻辑的实现是下面代码实现的十分优雅简洁,值得学习 回顾:下标为 i i i 的结点的左孩子下标 2 i + 1 2i+1 2i+1,右孩子下标 2 i + 2 2i+2 2i+2
2.2.2 向下调整建堆相关API
2.2.3 HeapCreate实现分析: 此时面对任意一个随意的数组,要按照一定的顺序进行向下调整来将其改造成堆 而向下调整需要左右子树均为堆,我们选择从倒数第一个非叶子结点开始调整,调整完根结点终止。 因为倒数第一个非叶子结点之后的所有结点都是叶子结点,叶子结点左右孩子都为空,可以看成是一个堆(不违反堆的定义),那么倒数第一个叶子的左子树和右子树(存在的情况下)都为堆,此时便可以对倒数第一个非叶子结点运用向下调整。 因为是从后往前调整,后面的局部不断变为堆,那么对于前面的结点,左右子树都为堆,便也可以运用向下调整。(像是一个滚动的过程) 我们可以证明:经过这样的调整,原数组被改造成了一个堆 因为:经历了上述过程后,对于树的每一个局部,都是一个堆,那么整体便也是堆。 实现思路:
代码实现:
2.2.4 复杂度分析同样为了简化问题,我们考虑最坏的情况:
对
于
高
度
为
h
的
树
,
总
调
整
次
数
S
h
为
:
对于高度为h的树,总调整次数S_h为:
对于高度为h的树,总调整次数Sh?为:
用
n
替
换
(
3
)
式
中
的
h
得
到
需
要
调
整
的
总
次
数
N
:
用n替换(3)式中的h得到需要调整的总次数N:
用n替换(3)式中的h得到需要调整的总次数N: 向下调整建堆的时间复杂度为: O ( N ) O(N) O(N) 也就是说我们建堆的时间复杂度是线性的,较于插入建堆,向下调整建堆的效率更加高。 3.堆其他操作的API
3.1 HeapPop实现3.1.1 规定我们规定,在堆中删除一个元素时,删除的是堆顶元素。 这很好理解,因为堆顶元素有一个特性,即为集合中元素的最大/最小值,删除这个值相较于其他值,会更加“有用” 3.1.2 误区既然我们操作的数组,我们知道,在数组中要去删除一个元素只需要移动覆盖即可。是不是删除堆顶的元素只需要移动覆盖掉第一个元素就可以了呢? 回答是否定的! 在删除堆顶元素时,我们希望删除后仍能维持堆的特性。 仅仅粗暴地把首元素覆盖掉,会把原来堆的结构完全破坏掉,此后我们如果需要再维持堆的特性得重新经历向下调整建堆的过程,由上面的分析可知,复杂度为 O ( N ) O(N) O(N) 这样的损耗还是太大了,我们有更加优雅的实现。 3.1.3 思路
分析: 因为我们操作的是数组,删除最后一个元素只需 向下调整根结点的复杂度: O ( log ? N ) O(\log{N}) O(logN) notes:当数据量足够大时,比如: N = 2 20 N=2^{20} N=220,大约是1千万时, O ( N ) O(N) O(N)要调约千万次,而 O ( log ? N ) O(\log{N}) O(logN)只需调20多次即可 3.1.4 代码实现
3.2 HeapInit实现
3.3 HeapTop实现
3.4 HeapSize实现
3.5 HeapEmpty实现
3.6HeapDestroy 实现
4.一句话总结:堆这最核心的部分就是要去理解并灵活运用向上调整算法与向下调整算法
|
|
|
上一篇文章 下一篇文章 查看所有文章 |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 | -2024/11/26 5:43:32- |
|
网站联系: qq:121756557 email:121756557@qq.com IT数码 |