堆排序
堆排序是基于堆这样一种数据结构的排序算法。
下面简单介绍一下堆,在数据结构的范畴里,堆是一种完全二叉树的数据结构,满足任何一个父节点都大于其子节点(大根堆)或者任何一个父节点都小于其子节点(小根堆),所以堆有两个要素:1.堆是一颗完全二叉树,2.其父子的大小关系。
逻辑上的概念比较简单,即以上的两个要素,那么在实际存储堆的时候会有一些小技巧的出现,其实在逻辑概念转化为实际的存储表示时总是存在这样的转化技巧。堆可以用一个抛弃0位不用的数组来表示,这种表示基于完全二叉树的一些性质:
1. parent(i) = i/2
2.left(i) = i*2
3.right(i) = i*2 + 1
以上的三个性质是在C++范畴内做的运算,有了以上的性质,就可以很方便的在一个用数组表示的堆中,追溯父节点和子节点了,下面的一个问题是如何在以数组这样的存储结构上初始化也就是构建一个堆呢?
这个时候利用了完全二叉树的另一个性质,也就是序号从size/2 + 1开始都为叶子节点,那么从size/2 到 1这些都是父节点,那么从size/2 到 1开始循环操作,那么操作的内容就是接收两个子堆和一个父节点为输入,将父节点加入两个子堆,进行调整形成一个大的堆,如此循环,到最后就可以产生一个以1号位元素为根的堆,即完成堆的构建。
下面参见测试代码:
#include <cstdio> #define MAX 100 int h[MAX]; int PARENT(int i){ return i/2; } int LEFT(int i){ return 2*i; } int RIGHT(int i){ return 2*i + 1; } /** * in heap a, a[i] is the inserted key, * and Left son tree of a[i] and Right son tree are all max-heap. * now adjust it to a whole max-heap */ void MaxHeapify(int a[], int i, int size){ int l = LEFT(i); int r = RIGHT(i); int largest; if(l<size && a[l] > a[i]) largest = l; else largest = i; if(r<size && a[r] > a[largest]) largest = r; if(largest != i){ int tmp = a[largest]; a[largest] = a[i]; a[i] = tmp; MaxHeapify(a, largest, size); } } /** * @a[] where the heap stores * @length represents size of the heap */ void BuildMaxHeap(int a[], int length){ int i; for(i=length/2;i>=0;i--){ MaxHeapify(a, i, length); } } /** * here, a[] is a heap, and heapsize is size of the heap * now we want to sort it. */ void HeapSort(int a[], int heapsize){ BuildMaxHeap(a, heapsize); // exchange a[0] with a[heapsize-1] and MaxHeapify(a, 0, heapsize-1) int i; for(i=heapsize-1;i>=1;i--){ int tmp = a[i]; a[i] = a[0]; a[0] = tmp; heapsize--; MaxHeapify(a, 0, heapsize); } } int main(){ int a[] = {3, 4, 1, 9, 8, 7, 5, 6, 2}; // BuildMaxHeap(a, 9); HeapSort(a, 9); int i; for(i=0;i<9;++i) printf("%d ", a[i]); printf("\n"); return 0; }
可以看出HeapSort的核心在于两个部分一个部分是BuildMaxHeap(),另一个核心是MaxHeapify(),因为BuildMaxHeap的核心也是MaxHeapify,
所以整个堆排序的核心就是MaxHeapify,这个函数接受一个数组,一个下标(待插入的父节点),以及堆的大小,当然这里的输入还有一个前提是:
必须保证以该待插入父节点为父节点的两个子树必须已经是堆。
这样在MaxHeapify里,算法找到父节点,子节点当中最大的,如果最大的即为待插入父节点,则什么也不做了,
如果是其中一个子节点,则将最大的节点和父节点交换,并且递归解决可能造成子树非堆的问题。
对于和树结合在一起的相关算法,很多时候都会是一个递归的算法。
好了,已经建好堆之后,我们交换1和size这两个元素,那么1上的根元素为最大已经放到了下标最大的位置上了,接着以1为根,size-1为堆大小的堆可能已经不是
一个堆了,这样由于问题的模式相同,可以调用MaxHeapify来维护这个堆,这样循环操作,直到堆的大小为1,即完成了堆排序。
以上有不妥之处,欢迎指正:)