先声明:所有的排序我都会总结记录在博客里,最后会有一个大的cpp文件包含全部排序的代码,每个排序算法放在单独的一个namespace里面,清晰明了,并设计对数器给出测试。我会把它放到网盘供需要之人下载,见这里。 ps:所有的排序以升序为基准。测试编译器为Visual C++
堆排序(heap sort)思想:借助于堆这个数据结构进行排序。
堆排序属于比较排序,不稳定。
声明 :堆的操作,包括插入,拔掉堆顶,上浮,下滤等等过程,自己画图辅助理解。
下面贴出百度百科对堆的定义:
堆满足两个性质:
- 结构性质
是一棵完全二叉树。 - 堆序性质
对于树中每个节点(如果它有父亲),那么它的值总是 <= 父亲节点的值(这叫大根堆),总是 >= 父亲节点的值(叫小根堆)。
每次对堆进行的插入,删除操作都必须进行调整保持堆的堆序性质。
堆逻辑上是一棵完全二叉树,但实际上是一个数组表示。
堆排序具体的步骤:
- 建初始堆(buildHeap)
将原始数组变成一棵逻辑上具有堆序性质的完全二叉树。 - 拔掉堆顶 + 重新调整以保持堆序性质。
具体做法:用堆顶与最后一个元素交换,然后堆的逻辑大小减一,然后对现在的堆顶执行下滤过程重新建堆以保持堆序性质(因为现在堆序性质仅仅由交换后的堆顶破坏,所以仅仅对堆顶执行下滤即可)。一直到最后只剩下一个元素完成排序。
因为建的是大根堆,所以最后排序结果是升序的。
下面分析一下建初始堆的过程:(两种做法)
- 第一种
将堆的逻辑大小一开始就设为数组的大小,然后从第一个非叶子节点一直到根节点执行下滤操作,即可将其构造成初始堆。
高为h的满二叉树是高为h的完全二叉树的上界,所以高度为h的满二叉树的节点的高度和是高度为h的完全二叉树的上界,非叶子节点执行下滤过程,所以总的时间复杂度一定不会超过 2h+1 - 1 - (h + 1),即为 O(N) 的时间复杂度。 - 第二种
每次都进行insert操作,一共进行 N -1 次,每次堆的逻辑大小加1,每次执行的操作为上浮过程。每一次插入操作花费 O(1)~O(logN)的时间复杂度,所以总的时间为 O(N) ~O(NlogN),时间复杂度太高,所以下面的demo采用的第一种做法。
demo:
template<typename T>
inline void swap(T* arr, int i, int j) {
T tmp(arr[i]);
arr[i] = arr[j];
arr[j] = tmp;
}
/**
* 返回堆中i位置节点左孩子的索引
*/
inline int leftChild(int i) {
return 2 * i + 1;
}
/**
* 下滤堆中i位置的元素 buildHeap和deleteMax都要使用
* i :要下滤的元素所在的位置
* n :堆的逻辑大小
*/
template<typename T>
void percolateDown(T* arr, int i, int n) { // 辅助记忆,上浮或者下滤的过程有没有类似插入排序的过程?
T tmp = arr[i]; // 如此,相信堆排序手撸的时候忘记的概率会大大降低。
int child = leftChild(i);
for (; child < n; i = child, child = leftChild(i)) {
if (child < n - 1 && arr[child] < arr[child + 1])
child++;
if (tmp < arr[child]) {
arr[i] = arr[child];
}
else {
break;
}
}
arr[i] = tmp;
}
/**
* standard heapsort
* 大根堆
*/
template<typename T>
void heapsort(T* arr, size_t size) {
if (size <= 0)
return;
// buildHeap O(N) Linear Time Complexity, approximately 2N.
for (int i = size / 2 - 1; i >= 0; --i) {
percolateDown(arr, i, size);
}
// deleteMax process
for (int i = size - 1; i > 0; --i) {
swap(arr, 0, i);
percolateDown(arr, 0, i); //从第一个非叶子节点到根节点,执行下滤过程
}
}
结果:
堆是一个很重要的数据结构,优先队列(priority queue)也是使用堆实现的。我写过一篇关于优先队列的文章,见这里。
时间复杂度:
建堆:O(N)
deleteMax+重建堆:O(NlogN)
所以总的时间复杂度:O(NlogN)
空间复杂度:
空间复杂度为O(1)。
纸上得来终觉浅,绝知此事要躬行。