数据结构与算法之美
28 | 堆和堆排序:为什么说堆排序没有快速排序快?
王争 2018-11-26
堆排序是一种原地的,时间复杂度为O(nlogn)的排序算法
在美团一面中,问到了堆排序的手写,太久没有看堆排序,所以在这个环节错失良机.
- 堆是一个完全二叉树
- 堆中每一个节点的值都必须大与其子树的值
- 父节点的序号,与左右节点之间存在着关系 .parent *2+1=leftChidren; parent *2+2=rightChidren;
- 根节点大于左右两节点为大顶堆,相反,为小顶堆
往堆中插入元素
往一个堆中添加元素,若直接加入在末尾是不完整的,因此我们需要进行自下向上的调整,这个过程叫做堆化.
#include <iostream>
using namespace std;
//大顶堆
template<typename T>
class heap{
heap(int n):maxN(n){
m_heap=new T[maxN];
if(!m_heap){
throw ("无法申请内存");
}
}
void insert(T &newData){
if(count>maxN){
return ;
}
++count;
m_heap[count]=newData;
int i=count;
while(i/2>0 && m_heap[i]> m_heap[i/2]){
swap(m_heap[i],m_heap[i-2]);
i/=2;
}
}
private:
T * m_heap;// 存储数组
int maxN;
int count=0;
};
2 删除堆顶元素,我们需要把堆顶元素和其他元素进行比较,然后把第二大元素进行交换,然后迭代删除
void pop(){
if(count<0){
return ;
}
m_heap[0]=m_heap[count];
heapify(); //调整堆
}
heapfy 堆化
heapfy是自顶向下堆化,当删除一个节点,把最右下节点转移过来,进行堆化处理.堆化的过程为log2(n),高度为logn 所以插入和删除的复杂度都是O(logn)
堆排序的过程
1 建堆
建堆的两种方式,一种就是全部采用插入的方法,第二种就是从底向上进行堆化. 从count/2 ....0 的过程
void initHeap(){
for (int i=count/2;i>=1;--i) {
heapify(i);
}
}
每个节点的堆化为O(logn),所以n个节点为O(nlogn)
排序
这个过程类似与删除堆顶元素,每次删除后,然后下标n的元素放在堆顶,然后在通过堆化的方法,将剩下的n-1元素重新重新建堆,直到排序完成.
void sort(){
initHeap();
int i=count;
while (i>=1) {
swap(m_heap[i],m_heap[count]);
--i;
heapify(i);
}
}
1 堆排序数据访问的方式没有快速排序优化
堆排序的访问是间隔的,0,1,4.8局部顺序访问,所以CPU缓存是不友好的
2 堆排序的数据交换次数是多于快速排序.