前言
数据结构,一门数据处理的艺术,精巧的结构在一个又一个算法下发挥着他们无与伦比的高效和精密之美,在为信息技术打下坚实地基的同时,也令无数开发者和探索者为之着迷。
也因如此,它作为博主大二上学期最重要的必修课出现了。由于大家对于上学期C++系列博文的支持,我打算将这门课的笔记也写作系列博文,既用于整理、消化,也用于同各位交流、展示数据结构的美。
此系列文章,将会分成两条主线,一条“数据结构基础”,一条“数据结构拓展”。“数据结构基础”主要以记录课上内容为主,“拓展”则是以课上内容为基础的更加高深的数据结构或相关应用知识。
欢迎关注博主,一起交流、学习、进步,往期的文章将会放在文末。
二叉堆原理
这一节,我们我们来介绍一个非常有趣且经典的数据结构——二叉堆
二叉堆的主要作用场合在于维护一个集合,使其能够以最快的速度获取集合中的最值,同时支持弹出最值、插入新的值,并以尽可能小的代价维护它。
具体一点说,是希望每次可以以 O ( 1 ) O(1) O(1)的复杂度获取最值。同时删除最值和插入新值后应该在 O ( l o g 2 n ) O(log_2n) O(log2n)的时间内完成对集合的维护。那么根据获取的最值是极大值还是极小值可以将堆分为大根堆和小根堆。
这个数据结构之所以经典,是因为他在很多经典的算法中都有着应用的身影:哈夫曼编码算法,dijkstra算法,堆排序算法等等不胜枚举。这正是因为它解决的问题是最基础,最普遍的一类问题——求集合最值。
那么如何维护一个集合使其能够快速的获得最值呢。其实这个问题可以借鉴生活中的经验:沙堆
诸位来看这张图片
有没有一些灵感迸发呢?
还没有也没关系,我们来接着看这张二叉树的图片
可以发现,如果将集合中的数据填在每个结点上,并且保证层级间的大小关系,那么根据树的递归定义性质就能够轻松的将全部的数据构造成一颗二叉树并且在二叉树的根部取得最值。据说灵感来自于淘汰赛制。
以小根堆为例,对于集合 { 1 , 6 , 8 , 10 , 2 , 5 , 15 } \{1,6,8,10,2,5,15\} {
1,6,8,10,2,5,15},可以构造成如下的二叉树:
对于上图树中的每个结点来说,其值都小于其子节点的值。
有的朋友会说,这样一来,二叉树形态岂不是可以乱来?毕竟满足这个条件的二叉树情况太多了。
我说小伙子你不懂,构建堆要将武德,可不能乱来。如果你乱建堆导致算法出错,那希望你耗子尾汁。
二叉堆存储
二叉堆中结点的位置和顺序有讲究,首先需要它是一棵完全二叉树,其中结点的位置又和存储有很大关系。毕竟:
只讨论逻辑而不考虑存储的数据结构是有灵魂而无肉体的
——沃茨基硕德
如何存储这个二叉树才好呢?我们要先了解一下完全二叉树的概念,下面不妨截取一段来自百度百科的定义
可以看到,完全二叉树中每个位置的结点都是有确定且紧密排列的编号的,这就能够使我们想到前面学到的数组。实际上,用数组完全可以存储完全二叉树,我们也经常要这么做
所以在存储堆的问题上,就采用顺序存储结构来完成对二叉树的存储。
按照上面的定义,给二叉堆的结点按照从上到下,从左到右的顺序编个号,就像下面这样:
将上面的二叉树存储在数组中就是:
于是根据这些规则就会产生一些不得了的性质:
- 根节点的编号为1
- 结点k的父节点为 ⌊ k 2 ⌋ \lfloor \frac k 2 \rfloor ⌊2k⌋
- 结点k的左儿子为 k ∗ 2 k * 2 k∗2
- 结点k的右儿子为 k ∗ 2 + 1 k*2 + 1 k∗2+1
有了这些性质,一个堆便被我们表示成为一个数组了。
二叉堆维护及实现
让我们先用数组封装二叉堆的底层,接着再来一步步的完成二叉堆的各项需求,为了演示方便,我们还是以小根堆为例:
//c
#define N ...
int heap[N];//这个数组用来存放二叉树结点。
int size = 0;//堆中当前结点数
//java
public class Heap {
private int[] heap;
private int size;
private int maxSize;
public Heap(int maxSize) {
this.maxSize = maxSize;
heap = new int[maxSize];
}
}
二叉堆维护
接下来我们要介绍维护二叉堆的两个至关重要的操作:上浮(up)和下沉(down)
他们是二叉堆保持性质的基础
上浮操作意在将一个元素不断地向上和父节点交换,直到该元素比父元素大或成为堆顶
下沉操作意在将一个元素不断地向下和子节点交换,直到该元素比子元素小或无子元素
上浮和下沉操作是堆结构的重中之重,建议读者重点理解。
上浮操作:
对于上浮操作,我们给出如下算法:
- 如果该节点为堆顶,退出循环
- 如果该节点大于等于父节点,退出循环
- 交换父节点与该节点,重复上述过程
举例来说:
代码实现如下,其中的乘2和整除2可以