数据结构——堆

一、堆的定义与基本操作

  堆是一棵完全二叉树,树中每个结点的值都不小于(或不大于)其左右孩子结点的值。其中,如果父亲结点的值大于或等于孩子结点的值,那么称这样的堆为大顶堆,这时候每个结点的值都是以它为根结点的子树的最大值;如果父亲结点的值小于或等于孩子结点的值,那么称这样的堆为小顶堆,这时候每个结点的值都是以它为根结点的子树的最小值。

  对完全二叉树来说,比较简洁的实现方法是使用数组来存储完全二叉树。这样结点按层序存储于数组中,其中第一个结点将存储在数组的 1 号位,并且数组 i 号位表示的结点的左孩子就是 2i 号位,而右孩子则是 (2i+1) 号位。于是定义如下:

1 #define maxn 100
2 // heap 为堆,n 为元素个数 
3 int heap[maxn], n=10; 

  下面介绍向下调整方法:总是将当前结点 V 与它的左右孩子比较(如果有的话),假如孩子中存在权值比结点 V 的权值大的,就将其中权值最大的那个孩子结点与结点 V 交换;交换完毕后继续让结点 V 和孩子比较,直到结点 V 的孩子的权值都比结点 V 的权值小或是结点 V 不存在孩子结点。代码如下:

 1 // 对 heap 数组在 [low,high] 范围进行向下调整
 2 // 其中 low 为欲调整结点的数组下标,high 一般为最后一个元素的数组下标
 3 void downAdjust(int low, int high) {
 4     int i=low, j=2*i;        // i 指向当前交换节点,j指向左孩子 
 5     while(j <= high) {
 6         // 若右孩子存在,且右孩子大于左子树 
 7         if(j+1 <= high && heap[j+1] > heap[j]) {
 8             j=j+1;            // 右孩子更大 
 9         }
10         // 如果最大权值比欲调整结点大 
11         if(heap[j] > heap[i]) {
12             swap(&heap[j], &heap[i]);    // 交换 
13             i = j;                        // 保持 i 指向欲调整结点,j 指向左孩子 
14             j = 2*i;
15         } else {
16             break;                        // 孩子结点比根结点小,调整结束 
17         }
18     }
19 }

  那个建堆的过程也就很容易了。假设序列中元素的个数为 n,数组下标在 $left [ 1,left lfloor frac{n}{2} ight floor ight ]$ 范围内的结点都是非叶子结点。于是可以从 $left lfloor frac{n}{2} ight floor$ 号开始倒着枚举结点,对每个遍历到的结点 i 进行 [i,n] 范围的调整。代码如下:

1 // 建堆
2 void createHeap() {
3     int i;
4     for(i=n/2; i>=1; --i) {                // 倒着枚举 
5         downAdjust(i, n);                // 向下调整 
6     }
7 }

  

  另外,如果要删除堆中的最大元素(也就是删除堆顶元素),并让其仍然保持堆的结构,那么只需要最后一个元素覆盖栈顶元素,然后对根结点进行调整即可。代码如下:

1 // 删除堆顶元素
2 void deleteTop() {
3     heap[1] = heap[n--];                // 用最后一个元素覆盖栈顶元素,并让元素个数-1
4     downAdjust(1, n);                    // 向下调整栈顶元素 
5 } 

  那么,如果想要往堆里添加一个元素,可以把想要添加的元素放在数组最后,然后进行向下调整操作。向上调整总是把欲调整结点与父亲结点比较,如果权值比父亲结点大,那么就交换其与父亲结点,这样反复比较,直到到达堆顶或是父亲结点的权值较大为止。代码如下:

 1 // 对 heap 数组在 [low,high] 范围进行向上调整
 2 // 其中 low 为欲调整结点的数组下标,high 一般为最后一个元素的数组下标
 3 void upAdjust(int low, int high) {
 4     int i=high, j=i/2;                     // i 为欲调整结点,j 为其父亲
 5     while(j >= low) {
 6         // 如果父亲结点小于欲调整结点 
 7         if(heap[j] < heap[i]) {
 8             swap(&heap[j], &heap[i]);    // 交换 
 9             i = j;                        // 保持 i 指向欲调整结点,j 指向父亲结点 
10             j = i/2;
11         } else {
12             break;                        // 孩子结点比根结点小,调整结束 
13         }
14     } 
15 }

  在此基础上就很容易实现添加元素的代码了:

1 // 添加元素 x
2 void insert(int x) {
3     heap[++n] = x;                        // 让元素个数+1,然后将数组末位赋值为 x
4     upAdjust(1, n);                     // 向上调整插入的结点 
5 } 

二、堆排序

  堆排序是指使用堆结构对一个序列进行排序。此处讨论递增排序的情况。

  可以倒着遍历数组,假设当前遍历到 i 号位,那么堆顶元素与 i 号位的元素交换,接着在 [1,i-1] 范围内对栈顶元素进行一次向下调整即可。代码如下:

1 // 堆排序
2 void heapSort() {
3     int i;
4     for(i=n; i>1; --i) {                // 倒着枚举 
5         swap(&heap[i], &heap[1]);        // 交换 heap[i] 与堆顶
6         downAdjust(1, i-1);                // 调整堆顶 
7     }
8 }

  完整测试代码如下:

  1 /*
  2   3 */
  4 
  5 #include <stdio.h>
  6 #include <string.h>
  7 #include <math.h>
  8 #include <stdlib.h>
  9 #include <time.h>
 10 #include <stdbool.h>
 11 
 12 #define maxn 100
 13 // heap 为堆,n 为元素个数 
 14 int heap[maxn], n=10; 
 15 
 16 // 交换 a,b
 17 void swap(int* a, int* b) {
 18     int temp = *a;
 19     *a = *b;
 20     *b = temp;
 21 } 
 22 
 23 // 对 heap 数组在 [low,high] 范围进行向下调整
 24 // 其中 low 为欲调整结点的数组下标,high 一般为最后一个元素的数组下标
 25 void downAdjust(int low, int high) {
 26     int i=low, j=2*i;        // i 指向当前交换节点,j指向左孩子 
 27     while(j <= high) {
 28         // 若右孩子存在,且右孩子大于左子树 
 29         if(j+1 <= high && heap[j+1] > heap[j]) {
 30             j=j+1;            // 右孩子更大 
 31         }
 32         // 如果最大权值比欲调整结点大 
 33         if(heap[j] > heap[i]) {
 34             swap(&heap[j], &heap[i]);    // 交换 
 35             i = j;                        // 保持 i 指向欲调整结点,j 指向左孩子 
 36             j = 2*i;
 37         } else {
 38             break;                        // 孩子结点比根结点小,调整结束 
 39         }
 40     }
 41 } 
 42 
 43 // 建堆
 44 void createHeap() {
 45     int i;
 46     for(i=n/2; i>=1; --i) {                // 倒着枚举 
 47         downAdjust(i, n);                // 向下调整 
 48     }
 49 } 
 50 
 51 // 删除堆顶元素
 52 void deleteTop() {
 53     heap[1] = heap[n--];                // 用最后一个元素覆盖栈顶元素,并让元素个数-1
 54     downAdjust(1, n);                    // 向下调整栈顶元素 
 55 } 
 56 
 57 // 对 heap 数组在 [low,high] 范围进行向上调整
 58 // 其中 low 为欲调整结点的数组下标,high 一般为最后一个元素的数组下标
 59 void upAdjust(int low, int high) {
 60     int i=high, j=i/2;                     // i 为欲调整结点,j 为其父亲
 61     while(j >= low) {
 62         // 如果父亲结点小于欲调整结点 
 63         if(heap[j] < heap[i]) {
 64             swap(&heap[j], &heap[i]);    // 交换 
 65             i = j;                        // 保持 i 指向欲调整结点,j 指向父亲结点 
 66             j = i/2;
 67         } else {
 68             break;                        // 孩子结点比根结点小,调整结束 
 69         }
 70     } 
 71 }
 72 
 73 // 添加元素 x
 74 void insert(int x) {
 75     heap[++n] = x;                        // 让元素个数+1,然后将数组末位赋值为 x
 76     upAdjust(1, n);                     // 向上调整插入的结点 
 77 } 
 78 
 79 // 堆排序
 80 void heapSort() {
 81     int i;
 82     for(i=n; i>1; --i) {                // 倒着枚举 
 83         swap(&heap[i], &heap[1]);        // 交换 heap[i] 与堆顶
 84         downAdjust(1, i-1);                // 调整堆顶 
 85     }
 86 }
 87 
 88 // 输出堆 
 89 void print() {
 90     int i; 
 91     for(i=1; i<=n; ++i) {                
 92         printf("%d ", heap[i]);
 93     }
 94     printf("
");    
 95 } 
 96 
 97 int main() {
 98     int i;
 99     for(i=1; i<=n; ++i) {
100         scanf("%d", &heap[i]);
101     } 
102     createHeap();                        // 建堆 
103     print();                            // 输出堆 
104     deleteTop();                        // 删除栈顶元素 
105     print();                            // 输出堆 
106     insert(99);                            // 插入99
107     print();                            // 输出堆 
108     heapSort();                            // 堆排序
109     print();                            // 输出排序结果 
110 
111     return 0;
112 }
原文地址:https://www.cnblogs.com/coderJiebao/p/Algorithmofnotes25.html