一、堆
堆是一种数据结构,通常通常所说的堆即二叉堆。二叉堆是一个数组,可以被看成一个完全二叉树,如下图所示:

他在数组中的表现形式为:

通过数组很容易得到每个父节点和其子节点的关系,假设数组的起始下标为 0,那么有:
|
1 2 3 |
PARENT(i) = (i - 1) / 2 --> 如下标 1 和 2 的数组元素,其父节点是下标 0 的元素。 LEFT_CHILD(i) = (i * 2) + 1 RIGHT_CHILD(i) = (i * 2) + 2 --> 如下标为 0 的数组元素,其左右子节点的下标分别是 1 和 2 。 |
因此可以直接在程序中定义:
|
1 2 3 |
#define PARENT(i) ((i - 1) >> 1) #define LEFT_CHILD(i) (((i) << 1) + 1) #define RIGHT_CHILD(i) (((i) << 1 ) + 2) |
二、最大堆和最小堆
堆有最大堆和最小堆之分,在最大堆中,除了根节点以外的所有节点 i 都要满足:A[PARENT[i]] >= A[i],所有的子节点的值不会超过其父节点的值。因此,在最大堆中,最大的元素存放在根节点中。
而最小堆和最大堆相反,除了根节点以外的所有节点 i 都要满足:A[PARENT[i]] <= A[i], 所有子节点的值都大于等于其根节点的值。因此,最小堆中根节点的元素是最小的。
在堆排序算法中,使用的是最大堆,最小堆通常用于构造优先级队列。
以最大堆为例,其包含的操作为:
MAX_HEAPIFY:用来维护一个最大堆。BUILD_MAX_HEAP:从一堆无序的数组中构造出一个最大堆。HEAPSORT:执行一次堆排序过程。
三、最大堆
3.1 维护最大堆
把一个堆构造成最大堆的流程:
- 从 A[i], A[LEFT[i]], A[RIGHT[i]] 中选出最小的,保存其下标 largest 。
- 如果 largest 不等于 i,交换 A[i] 和 A[largest] 。
以下图为例,当前堆中 4 处于一个非正确的位置:

首先把 4 和其儿子中最大的 14 交换,得到以下堆:

交换后 4 依旧不是最小的元素,继续交换 4 和 8:

4 当前已经是叶子节点了,此时对 4 的最大堆化操作就完成了。并且此时的堆也就是一个最大堆。
不难看出:对一个高度为 h 的树来说,这个操作的时间复杂度为 O(h)。
对应的 c 代码:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
static void _max_heapify(int *data, const uint len, const uint i) { unsigned int lchild, rchild; unsigned int largest; lchild = LEFT_CHILD(i); rchild = RIGHT_CHILD(i); // 得到父子节点中最小的元素 if (lchild < len && data[i] < data[lchild]) largest = lchild; else largest = i; if (rchild < len && data[largest] < data[rchild]) largest = rchild; // 交换最小的元素 if (i != largest) { swap_int(&data[i], &data[largest]); _max_heapify(data, len, largest); } } |
3.2 建堆
建堆的过程实际上是执行多次 MAX_HEAPIFY,我们只需对 2/n 以内的元素都执行 MAX_HEAPIFY 操作即可完成一个最大堆的构建。
因为 [n/2, n) 之间的元素都是叶子节点,所以无需对它们进行转换操作。
要注意的是建堆必须从下往上,否则可能出现堆只是局部有效,对全局而言并非有效:

以上图为例,如果从上到下建堆,在调整完 4 的位置之后,14 被放在了树根。而实际上被放在树根的应该是 16,因为接下来就不会调整 14 了,它的位置就永远不对,这个堆也就不是一个符合要求的堆。
代码:
|
1 2 3 4 5 6 |
static void _build_max_heap(int *data, const uint len) { int i; for (i = (len / 2) - 1; i >= 0; i--) { _max_heapify(data, len, i); } } |
四、堆排序
参考:排序算法之堆排序

![[leetcode]199-二叉树的右视图](https://www.dyxmq.cn/wp-content/themes/begin/prune.php?src=https://www.dyxmq.cn/wp-content/uploads/2020/04/f24a8-image.png&w=280&h=210&a=&zc=1)
![【每日打卡】[leetcode]72-编辑距离](https://www.dyxmq.cn/wp-content/themes/begin/prune.php?src=https://www.dyxmq.cn/wp-content/uploads/2020/04/88c60-imageff84a6c5047db6ed.png&w=280&h=210&a=&zc=1)
![【每日打卡】[leetcode]460-LFU缓存](https://www.dyxmq.cn/wp-content/themes/begin/prune.php?src=https://www.dyxmq.cn/wp-content/uploads/2020/04/5b3f2-image.png&w=280&h=210&a=&zc=1)
![[leetcode]125-验证回文串](https://www.dyxmq.cn/wp-content/themes/begin/prune.php?src=https://www.dyxmq.cn/wp-content/uploads/2020/03/5bfa5-image.png&w=280&h=210&a=&zc=1)
![【每日打卡】[程序员面试宝典]17.16-按摩师](https://www.dyxmq.cn/wp-content/themes/begin/prune.php?src=https://www.dyxmq.cn/wp-content/uploads/2020/03/9ecc9-image.png&w=280&h=210&a=&zc=1)
![【每日打卡】[leetcode]876-链表的中间节点](https://www.dyxmq.cn/wp-content/themes/begin/prune.php?src=https://www.dyxmq.cn/wp-content/uploads/2020/03/64d0d-imagece778ab033c9d90d.png&w=280&h=210&a=&zc=1)
![【每日打卡】[剑指offer]面试题40-最小的k个数](https://www.dyxmq.cn/wp-content/themes/begin/prune.php?src=https://www.dyxmq.cn/wp-content/uploads/2020/03/c83c9-imagec745c425c670e18c.png&w=280&h=210&a=&zc=1)
![【每日打卡】[leetcode]-409最长回文子串](https://www.dyxmq.cn/wp-content/themes/begin/prune.php?src=https://www.dyxmq.cn/wp-content/uploads/2020/03/e5589-image788d511d7c1e839c.png&w=280&h=210&a=&zc=1)
![【每日打卡】[leetcode]面试题1.6-字符串压缩](https://www.dyxmq.cn/wp-content/themes/begin/prune.php?src=https://www.dyxmq.cn/wp-content/uploads/2020/03/776bc-image.png&w=280&h=210&a=&zc=1)
评论