堆排序与优先队列

堆排序与优先队列说到堆就必须要说二叉树,二叉树指每个节点最多只能包含两个子节点的树。二叉树常用的实现为二叉搜索树(BinarySearchTree)和二叉堆(BinaryHeap) 这里不再对树的概念进行赘述,有需求的自行google,二叉堆其实对应着一棵完全二叉树,最后一层除外。因此使得一个…

说到堆就必须要说二叉树,二叉树指每个节点最多只能包含两个子节点的树。二叉树常用的实现为二叉搜索树(BinarySearchTree)和二叉堆(BinaryHeap)

这里不再对树的概念进行赘述,有需求的自行google,二叉堆其实对应着一棵完全二叉树,最后一层除外。因此使得一个堆可以利用数组来存储,二叉堆又分为大根堆和小根堆,下图展示了一个大根堆与数组的对应。

image

因此可以很简单的得到堆节点所对应的数组。 :banana:

  • 父节点parent = index / 2
  • 左节点left = index * 2
  • 右节点right = index * 2 + 1

堆排序

堆排序就是把最大堆堆顶的最大数取出,将剩余的堆继续调整为最大堆,再次将堆顶的最大数取出,这个过程持续到剩余数只有一个时结束。在堆中定义以下几种操作:

  • 最大堆调整(Max-Heapify):将堆的末端子节点作调整,使得子节点永远小于父节点。
  • 创建最大堆(Build-Max-Heap):将堆所有数据重新排序,使其成为最大堆。
  • 堆排序(Heap-Sort):移除位在第一个数据的根节点,并做最大堆调整的递归运算。

通俗的解释就是: 最大堆调整(Max-Heapify)其实就是一次 <下沉> 的过程,创建最大堆(Build-Max-Heap)就是 <循环> 每个节点进行 最大堆调整,堆排序(Heap-Sort)就是每次 创建完最大堆 之后需要将最大元素 <分离>

一句话概括:循环树的每一个节点进行下沉,然后分离,继续循环。 :tomato:

下图展示了一次下沉的过程:

picture

优先队列

普通的队列满足先进先出,而优先队列满足每次出队列的都是优先级最高的元素。 :strawberry:

常见的优先队列有三种实现方式:

  • 有序数组(add时添加到排序的位置,poll时候移除队尾元素)
  • 无序数组(add时直接添加到队尾,poll时查找优先元素)
  • 二叉堆(add时上浮,poll时下沉)
image

优先队列与堆排序的关系

因为优先队列每次poll只需要最大优先级的元素,所以不需要维持整棵二叉堆的有序,只需要维持根节点满足最大优先级即可。所以只需要对根节点进行一次堆排序的最大堆调整(Max-Heapify)即可。

Java优先队列源码解析

    

    //add方法也就是直接调用offer方法
    public boolean offer(E e) {
        //不允许插入null
        if (e == null)
            throw new NullPointerException();
        modCount++;
        int i = size;
        //容量不够则进行扩容
        if (i >= queue.length)
            grow(i + 1);
        siftUp(i, e);//这里调用了上浮
        size = i + 1;
        return true;
    }
    
    //上浮分为两种情况,判断是否设置了comparator
    private void siftUp(int k, E x) {
        if (comparator != null)
            siftUpUsingComparator(k, x);
        else
            siftUpComparable(k, x);
    }

    //我们只分析这种没有设置comparator的方法,另一种类比
    private void siftUpComparable(int k, E x) {
        Comparable<? super E> key = (Comparable<? super E>) x;
        while (k > 0) {
            //找到父节点
            int parent = (k - 1) >>> 1;
            Object e = queue[parent];
            //如果当前节点大于父节点则上浮结束
            if (key.compareTo((E) e) >= 0)
                break;
            //否则,将父节点下沉
            queue[k] = e;
            k = parent;
        }
        //将节点赋值到正确的上浮位置
        queue[k] = key;
    }
    
    //poll方法
    public E poll() {
        //如果没有元素,则返回null
        if (size == 0)
            return null;
        int s = --size;
        modCount++;
        //返回根节点
        E result = (E) queue[0];
        E x = (E) queue[s];
        //将队尾元素移到根节点,并进行下沉
        queue[s] = null;
        //队列中不止一个元素,则进行下沉
        if (s != 0)
            siftDown(0, x);
        return result;
    }
    
    //下沉方法的实现与上浮类似,就不赘述了。

今天的文章堆排序与优先队列分享到此就结束了,感谢您的阅读。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/14236.html

(0)
编程小号编程小号

相关推荐

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注