堆排序
堆排序,就是利用二叉堆的特性来完成排序的工作。
这里假定我们要完成对一个数组进行从小到大排序的工作,那么利用最小堆(见《二叉堆 - 最小堆》)的特性,对于一个已经建好的最小堆,我们每次以DeleteMin函数取出最小的元素,放入一个临时数组中,如此一来,就完成了对该数组的排序。时间复杂度的话,建立堆的过程可以是O(NlogN)或者O(N),看我们是以Insert函数建堆,还是以PercDown函数建堆。取出元素的DeleteMin函数,是O(logN),有N个元素,所以是O(NlogN)。所以时间复杂度为O(NlogN)。但是使用到了额外的临时数组,空间复杂度为O(N)。当待排序数组很大的时候,这一开销还是值得商榷的。
为了避免空间上的额外开销,我们注意到,当我们使用DeleteMin的时候,堆中最小的元素被拿走,最后一个位置的元素向上移动到合适的位置,那么就空出了最后一个位置。所以我们可以把最小的元素放在最后一个位置上。依此类推,在对N个元素进行DeleteMin之后,堆就表现出从大到小排序的数组的样子了。所以我们把堆序改成最大堆,使用DeleteMax,那么最后堆就表现出从小到大的排序数组。
另外,如果我们考虑用PercDown函数建堆,用DeleteMax函数来进行排序数组的输出,观察最大堆的实现(见《二叉堆 - 最大堆》),我们可以稍作调整,合并PercDown函数和DeleteMax函数,并且省略堆结构的初始化和建立,直接以数组的形态进行堆排序。
代码如下:
1 #include <cstdio> 2 #include <cstdlib> 3 4 typedef int Item; 5 6 void 7 Swap(Item* a, Item* b) 8 { if (a != b) { 9 *a ^= *b; 10 *b ^= *a; 11 *a ^= *b; } 12 } 13 14 // 跟前面最大(小)堆的程序不同,这里添加最后一个参数,用来控制边界 15 void 16 PercDown(Item arr[], int i, int len) 17 { 18 int child; 19 Item tmp; 20 21 for (tmp = arr[i]; 2*i+1 < len; i = child) { 22 child = 2 * i + 1; 23 if (child != len-1 && arr[child+1] > arr[child]) { 24 child++; 25 } 26 if (tmp < arr[child]) { 27 arr[i] = arr[child]; 28 } else { 29 break; 30 } 31 } 32 arr[i] = tmp; 33 } 34 35 void 36 HeapSort(Item arr[], int len) 37 { 38 int i; 39 // build maximum heap 40 for (i = len/2; i >=0; i--) { 41 PercDown(arr, i, len); 42 } 43 // delete maximum item 44 for (i = len-1; i > 0 ; i--) { 45 Swap(&arr[0], &arr[i]); 46 PercDown(arr, 0, i); 47 } 48 } 49 50 51 int 52 main(int argc, char** argv) 53 { 54 Item arr[6] = {17, 11, 2, 23, 5, 7}; 55 56 HeapSort(arr, 6); 57 58 for (int i = 0; i < 6; i++) { 59 printf("%d\t", arr[i]); 60 } 61 printf("\n"); 62 63 system("pause"); 64 65 return 0; 66 }
参考:Mark Allen Weiss《数据结构与算法分析——C语言描述》(第二版)