堆的定义
两点要求:
- 完全二叉树
- 每一个节点的值都大于等于(或小于等于)其子树中每个节点的值
堆的实现
其实堆的实现关键的就是两个操作:下沉和上浮
- 所谓下沉就是针对某个元素,让其向下交换到一个满足堆定义的位置
- 而上浮与下沉刚好相反,而且上浮比下沉要简单一点,因为一个节点可能有两个孩子,但是只能有一个父节点。
- 所谓的
heapify
,也就是堆的构建,也就是从第一个非叶子节点向上不断调用下沉操作(也可以用别的方式,思考一下如何利用上浮)
下面给出具体代码
/**
* @Classname Heap
* @Description 大顶堆
* @Date 2019/12/17 10:45
* @Created by SunCheng
*/
public class MaxHeap<E extends Comparable<E>> {
private ArrayList<E> data;
public MaxHeap(int capacity){
data = new ArrayList<>(capacity);
}
public MaxHeap(){
data = new ArrayList<>();
}
public MaxHeap(E[] arr){
data = new ArrayList<>();
for (E e :
arr) {
data.add(e);
}
for(int i = parent(arr.length - 1) ; i >= 0 ; i --)
siftDown(i);
}
public int size(){
return data.size();
}
public boolean isEmpty(){
return data.isEmpty();
}
// 返回父节点的索引
// 如果是从0开始,则返回(index-1)/2
// 如果是从1开始,则返回index/2
private int parent(int index){
if(index == 0)
throw new IllegalArgumentException("index-0 doesn't have parent.");
return (index - 1) / 2;
}
// 返回左子节点的索引
// 如果是从0开始, 返回index*2+1
// 如果是从1开始,返回index*2
private int leftChild(int index){
return index * 2 + 1;
}
// 返回右子节点的索引
// 如果是从0开始, 返回index*2+2
// 如果是从1开始,返回index*2+1
private int rightChild(int index){
return index * 2 + 2;
}
public void add(E e){
data.add(e);
siftUp(data.size() - 1);
}
// 上浮操作
private void siftUp(int k){
while(k > 0 && data.get(parent(k)).compareTo(data.get(k)) < 0 ){
Collections.swap(data,k,parent(k));
k = parent(k);
}
}
// 看堆中的最大元素
public E findMax(){
if(data.size() == 0)
throw new IllegalArgumentException("Can not findMax when heap is empty.");
return data.get(0);
}
// 取出堆中最大元素
public E extractMax(){
E ret = findMax();
Collections.swap(data, 0, data.size() - 1);
data.remove(data.size() - 1);
siftDown(0);
return ret;
}
private void siftDown(int k){
while(leftChild(k) < data.size()){
int j = leftChild(k); // 在此轮循环中,data[k]和data[j]交换位置
if( j + 1 < data.size() &&
data.get(j + 1).compareTo(data.get(j)) > 0 )
j ++;
// data[j] 是 leftChild 和 rightChild 中的最大值
if(data.get(k).compareTo(data.get(j)) >= 0 )
break;
Collections.swap(data,k,j);
k = j;
}
}
// 取出堆中的最大元素,并且替换成元素e
public E replace(E e){
E ret = findMax();
data.set(0, e);
siftDown(0);
return ret;
}
}
堆排序VS快排
在业务中,快排优先级一般高于堆排,这是为什么呢?
- 堆排序的附加操作多(heapify等),虽然复杂度相同,但是精确操作数还是要大于快排,快排的交换次数不会大于逆序度
- 堆毕竟还是属于二叉树,很多操作是跳着的,这样对CPU缓存不友好,特别是数据量大的时候,而快排是局部顺序访问的。(在大数据量的时候,快排确实比堆排快不少,小数据量的时候差不多,而且这个过程并不是渐进的,而是有某个阈值,大概是和缓存容量相关)