这篇的主题主要是Heapsort(堆排序),下一篇ADT数据结构随笔再谈谈 - 优先队列(堆)。
首先,我们先来了解一点与堆相关的东西。堆可以实现优先队列(Priority Queue),看到队列,我们马上就想到了队列的一个特点 - 先进先出(FIFO - first in first out),但优先队列有些不同的地方,优先队列是一种具有优先级先出的数据结构。
堆的结构:
typedef int ElemType; typedef struct { ElemType * arr; int size; }Heap;
和数组栈的结构类似。
初始化堆:
Heap * InitHeap(ElemType * nums, int ArraySize) { Heap * heap; heap = (Heap *)malloc(sizeof(Heap)); heap -> arr = (ElemType *)malloc(sizeof(ElemType)); for(heap -> size = 0; heap -> size < ArraySize; heap -> size ++) heap -> arr[heap -> size] = nums[heap -> size]; return heap; }
找节点的关系:
/* * 节点i的双亲 */ static int HeapParent(int i) { return i/2; } /* * 节点i的左孩子 */ static int HeapLeft(int i) { return 2*i + 1; } /* * 节点i的右孩子 */ static int HeapRight(int i) { return 2*(i + 1); }
这里要保持的是一个大根堆:
/* * 维护的最大堆(或最小堆)的性质 * 参数说明: * 1.接收一个已存在的堆 * 2.节点位置 * 无返回值 */ void Max_Heapify(Heap * heap, int i) { int _L = HeapLeft(i); int _R = HeapRight(i); int largest; int temp; if(_L < heap -> size && heap -> arr[_L] > heap -> arr[i]) largest = _L; else largest = i; if(_R < heap -> size && heap -> arr[_R] > heap -> arr[largest]) largest = _R; if(largest != i) { temp = heap -> arr[i]; heap -> arr[i] = heap -> arr[largest]; heap -> arr[largest] = temp; Max_Heapify(heap, largest); } }
建大根堆:
static void Build_Max_Heap(Heap * heap) { int i; for(i = heap -> size/2; i >= 0; i--) Max_Heapify(heap, i); }
排序:
void HeapSort(Heap * heap) { int i; int temp; //维护堆,把初始建立的混乱的堆变为大根堆,以便进行排序 Build_Max_Heap(heap); for(i = heap -> size - 1; i >= 0; i--) { temp = heap -> arr[0]; heap -> arr[0] = heap -> arr[i]; heap -> arr[i] = temp; -- heap -> size; //每次把具有最小值的节点交换到根节点上,则把需要维护的堆的大小减小 Max_Heapify(heap, 0); //交换根节点后的堆的性质变了,需要不断维护 } }
至此,用二叉堆对一个数组的排序就完成了。
然后,测试一下:
int main() { Heap * heap; ElemType nums[] = {5, 13, 2, 25, 7, 17, 20, 8, 4}, * arr = nums; int arrSize = sizeof(nums)/sizeof(int); int i; heap = InitHeap(arr, arrSize); HeapSort(heap); for(i = 0; i < arrSize; i++) printf("%d ", heap -> arr[i]); printf(" "); return 0; }
运行结果:
2 4 5 7 8 13 17 20 25
完整的堆排序代码:
/** * 堆排序(Heap Sort) */ #include <stdio.h> #include <stdlib.h> typedef int ElemType; typedef struct { ElemType * arr; int size; }Heap; Heap * InitHeap(); static int HeapParent(); static int HeapLeft(); static int HeapRight(); void Max_Heapify(); static void Build_Max_Heap(); void HeapSort(); /* * 初始化堆 * * 参数说明: * 1.传入数组 * 2.数组大小 * * 返回值:堆 */ Heap * InitHeap(ElemType * nums, int ArraySize) { Heap * heap; heap = (Heap *)malloc(sizeof(Heap)); heap -> arr = (ElemType *)malloc(sizeof(ElemType)); for(heap -> size = 0; heap -> size < ArraySize; heap -> size ++) heap -> arr[heap -> size] = nums[heap -> size]; return heap; } /* * 某节点i的双亲 */ static int HeapParent(int i) { return i/2; } /* * 某节点i的左孩子 */ static int HeapLeft(int i) { return 2*i + 1; } /* * 某节点i的右孩子 */ static int HeapRight(int i) { return 2*(i + 1); } /* * 维护堆 */ void Max_Heapify(Heap * heap, int i) { int _L = HeapLeft(i); int _R = HeapRight(i); int largest; int temp; if(_L < heap -> size && heap -> arr[_L] > heap -> arr[i]) largest = _L; else largest = i; if(_R < heap -> size && heap -> arr[_R] > heap -> arr[largest]) largest = _R; if(largest != i) { temp = heap -> arr[i]; heap -> arr[i] = heap -> arr[largest]; heap -> arr[largest] = temp; Max_Heapify(heap, largest); } } /* * 建堆 */ static void Build_Max_Heap(Heap * heap) { int i; for(i = heap -> size/2; i >= 0; i--) Max_Heapify(heap, i); } /* * 排序 */ void HeapSort(Heap * heap) { int i; int temp; Build_Max_Heap(heap); //维护堆,把初始建立的混乱的堆变为大根堆,以便进行排序 for(i = heap -> size - 1; i >= 0; i--) { temp = heap -> arr[0]; heap -> arr[0] = heap -> arr[i]; heap -> arr[i] = temp; -- heap -> size; //每次把具有最小值的节点交换到根节点上,则把需要维护的堆的大小减小 Max_Heapify(heap, 0); //交换根节点后的堆的性质变了,需要不断维护 } } int main(void) { Heap * heap; ElemType nums[] = {5, 13, 2, 25, 7, 17, 20, 8, 4}, * arr = nums; int arrSize = sizeof(nums)/sizeof(int); int i; heap = InitHeap(arr, arrSize); HeapSort(heap); for(i = 0; i < arrSize; i++) printf("%d ", heap -> arr[i]); printf(" "); return 0; }
这里的堆显然是一个二叉堆,因此可以把它想象成二叉树的样子来理解,关于整个堆排序的过程,它的时间复杂度为O(nlgn),因为调用Build_Max_Heap()的代价为O(n),而n - 1次调用Max_Heapify()的代价为O(lgn)。
但至于为什么Build_Max_Heap()函数的时间复杂的只有O(n)...看起来应该是O(nlgn),我也还没完全理解这里的时间复杂度的计算...留个坑慢慢理解...懂了之后再补充。
前面提到了堆的结构型和数组栈相似,实际上,堆与队列和栈有很大的关系,我们知道队列是FIFO,栈是FILO,从上面堆的维护操作等就可以看出,堆还有着具有优先级的先出的特性(注意与栈并不同),优先队列是基于最大/小堆实现的,虽然名字中有个队列,但区别还是很大的。