内容简介:本文要描述的堆是二叉堆。二叉堆是一种数组对象,可以被视为一棵完全二叉树,树中每个结点和数组中存放该结点值的那个元素对应。树的每一层都是填满的,最后一层除外。二叉堆可以用于实现堆排序,优先级队列等。本文代码地址在使用数组来实现二叉堆,二叉堆两个属性,其中二叉堆对应的树的根为
本文要描述的堆是二叉堆。二叉堆是一种数组对象,可以被视为一棵完全二叉树,树中每个结点和数组中存放该结点值的那个元素对应。树的每一层都是填满的,最后一层除外。二叉堆可以用于实现堆排序,优先级队列等。本文代码地址在 这里 。
1 二叉堆定义
使用数组来实现二叉堆,二叉堆两个属性,其中 LENGTH(A) 表示数组 A 的长度,而 HEAP_SIZE(A) 则表示存放在A中的堆的元素个数,其中 LENGTH(A) <= HEAP_SIZE(A) ,也就是说虽然 A[0,1,...N-1] 都可以包含有效值,但是 A[HEAP_SIZE(A)-1] 之后的元素不属于相应的堆。
二叉堆对应的树的根为 A[0] ,给定某个结点的下标 i ,可以很容易计算它的父亲结点和儿子结点。 注意在后面的示例图中我们标注元素是从1开始计数的,而实现代码中是从0开始计数。
#define PARENT(i) ( i > 0 ? (i-1)/2 : 0) #define LEFT(i) (2 * i + 1) #define RIGHT(i) (2 * i + 2) 复制代码
注:堆对应的树每一层都是满的,所以一个高度为 h 的堆中,元素数目最多为 1+2+2^2+...2^h = 2^(h+1) - 1 (满二叉树),元素数目最少为 1+2+...+2^(h-1) + 1 = 2^h 。 由于元素数目 2^h <= n <= 2^(h+1) -1 ,所以 h <= lgn < h+1 ,因此 h = lgn 。即一个包含n个元素的二叉堆高度为 lgn 。
2 保持堆的性质
本文主要建立一个最大堆,最小堆原理类似。为了保持堆的性质, maxHeapify(int A[], int i) 函数让堆数组 A 在最大堆中下降,使得以 i 为根的子树成为最大堆。
void maxHeapify(int A[], int i, int heapSize)
{
int l = LEFT(i);
int r = RIGHT(i);
int largest = i;
if (l <= heapSize-1 && A[l] > A[i]) {
largest = l;
}
if (r <= heapSize-1 && A[r] > A[largest]) {
largest = r;
}
if (largest != i) { // 最大值不是i,则需要交换i和largest的元素,并递归调用maxHeapify。
swapInt(A, i, largest);
maxHeapify(A, largest, heapSize);
}
}
复制代码
-
在算法每一步里,从元素
A[i]和A[left]以及A[right]中选出最大的,将其下标存在largest中。如果A[i]最大,则以i为根的子树已经是最大堆,程序结束。 -
否则,
i的某个子结点有最大元素,将A[i]与A[largest]交换,从而使i及其子女满足最大堆性质。此外,下标为largest的结点在交换后值变为A[i],以该结点为根的子树又有可能违反最大堆的性质,所以要对该子树递归调用maxHeapify()函数。
当 maxHeapify() 函数作用在一棵以 i 为根结点的、大小为 n 的子树上时,运行时间为调整 A[i] 、 A[left] 、 A[right] 的时间 O(1) ,加上对以 i 为某个子结点为根的子树递归调用 maxHeapify 的时间。 i 结点为根的子树大小最多为 2n/3 (最底层刚好半满的时候),所以可以推得 T(N) <= T(2N/3) + O(1) ,所以 T(N)=O(lgN) 。
下图是一个运行 maxHeapify(heap, 2) 的例子。 A[] = {16, 4, 10, 14, 7, 9, 3, 2, 8, 1} ,堆大小为 10 。
3 建立最大堆
我们可以知道,数组 A[0, 1, ..., N-1] 中, A[N/2, ..., N-1] 的元素都是树的叶结点。如上面图中的 6-10 的结点都是叶结点。每个叶子结点可以看作是只含一个元素的最大堆,因此我们只需要对其他的结点调用 maxHeapify() 函数即可。
void buildMaxHeap(int A[], int n)
{
int i;
for (i = n/2-1; i >= 0; i--) {
maxHeapify(A, i, n);
}
}
复制代码
之所以这个函数是正确的,我们需要来证明一下,可以使用循环不变式来证明。
循环不变式:在for循环开始前,结点 i+1、i+2...N-1 都是一个最大堆的根。
初始化:for循环开始迭代前, i = N/2-1 , 结点 N/2, N/2+1, ..., N-1 都是叶结点,也都是最大堆的根。
保持:因为结点 i 的子结点标号都比 i 大,根据循环不变式的定义,这些子结点都是最大堆的根,所以调用 maxHeapify() 后, i 成为了最大堆的根,而 i+1, i+2, ..., N-1 仍然保持最大堆的性质。
终止:过程终止时,i=0,因此结点 0, 1, 2, ..., N-1 都是最大堆的根,特别的,结点0就是一个最大堆的根。
虽然每次调用 maxHeapify() 时间为 O(lgN) ,共有 O(N) 次调用,但是说运行时间是 O(NlgN) 是不确切的,准确的来说,运行时间为 O(N) ,这里就不证明了,具体证明过程参见《算法导论》。
4 堆排序
开始用 buildMaxHeap() 函数创建一个最大堆,因为数组最大元素在 A[0] ,通过直接将它与 A[N-1] 互换来达到最终正确位置。去掉 A[N-1] ,堆的大小 heapSize 减1,调用 maxHeapify(heap, 0, --heapSize) 保持最大堆的性质,直到堆的大小由N减到1。
void heapSort(int A[], int n)
{
buildMaxHeap(A, n);
int heapSize = n;
int i;
for (i = n-1; i >= 1; i--) {
swapInt(A, 0, i);
maxHeapify(A, 0, --heapSize);
}
}
复制代码
5 优先级队列
最后实现一个最大优先级队列,主要有四种操作,分别如下所示:
insert(PQ, key) maximum(PQ) extractMax(PQ) increaseKey(PQ, i, key)
这里定义一个结构体 PriorityQueue 便于操作。
typedef struct PriorityQueue {
int capacity;
int size;
int elems[];
} PQ;
复制代码
最终优先级队列的操作实现代码如下:
/**
* 从数组创建优先级队列
*/
PQ *newPQ(int A[], int n)
{
PQ *pq = (PQ *)malloc(sizeof(PQ) + sizeof(int) * n);
pq->size = 0;
pq->capacity = n;
int i;
for (i = 0; i < pq->capacity; i++) {
pq->elems[i] = A[i];
pq->size++;
}
buildMaxHeap(pq->elems, pq->size);
return pq;
}
int maximum(PQ *pq)
{
return pq->elems[0];
}
int extractMax(PQ *pq)
{
int max = pq->elems[0];
pq->elems[0] = pq->elems[--pq->size];
maxHeapify(pq->elems, 0, pq->size);
return max;
}
PQ *insert(PQ *pq, int key)
{
int newSize = ++pq->size;
if (newSize > pq->capacity) {
pq->capacity = newSize * 2;
pq = (PQ *)realloc(pq, sizeof(PQ) + sizeof(int) * pq->capacity);
}
pq->elems[newSize-1] = INT_MIN;
increaseKey(pq, newSize-1, key);
return pq;
}
void increaseKey(PQ *pq, int i, int key)
{
int *elems = pq->elems;
elems[i] = key;
while (i > 0 && elems[PARENT(i)] < elems[i]) {
swapInt(elems, PARENT(i), i);
i = PARENT(i);
}
}
复制代码
以上所述就是小编给大家介绍的《数据结构和算法面试题系列-二叉堆》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- 算法与数据结构之递归算法
- Python 数据结构与算法 —— 初识算法
- js实现数据结构及算法之排序算法
- 数据结构和算法面试题系列—递归算法总结
- 数据结构和算法面试题系列—随机算法总结
- 数据结构与算法——常用排序算法及其Java实现
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Developer's Guide to Social Programming
Mark D. Hawker / Addison-Wesley Professional / 2010-8-25 / USD 39.99
In The Developer's Guide to Social Programming, Mark Hawker shows developers how to build applications that integrate with the major social networking sites. Unlike competitive books that focus on a s......一起来看看 《Developer's Guide to Social Programming》 这本书的介绍吧!