堆的定义

两点要求:

  1. 完全二叉树
  2. 每一个节点的值都大于等于(或小于等于)其子树中每个节点的值

堆的实现

其实堆的实现关键的就是两个操作:下沉和上浮

  • 所谓下沉就是针对某个元素,让其向下交换到一个满足堆定义的位置
  • 而上浮与下沉刚好相反,而且上浮比下沉要简单一点,因为一个节点可能有两个孩子,但是只能有一个父节点。
  • 所谓的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快排

在业务中,快排优先级一般高于堆排,这是为什么呢?

  1. 堆排序的附加操作多(heapify等),虽然复杂度相同,但是精确操作数还是要大于快排,快排的交换次数不会大于逆序度
  2. 堆毕竟还是属于二叉树,很多操作是跳着的,这样对CPU缓存不友好,特别是数据量大的时候,而快排是局部顺序访问的。(在大数据量的时候,快排确实比堆排快不少,小数据量的时候差不多,而且这个过程并不是渐进的,而是有某个阈值,大概是和缓存容量相关)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值