详解优先队列

一、队列与优先队列的区别

  1、队列是一种FIFO(First-in-Firse-out)先进先出的数据结构,对应生活中排队场景,排在前面的人总是先通过,依次进行。

   2、优先队列是特殊的队列,优先一词,就可以看出有插队的现象。比如生活中在乘车买票进站时,就会有些比较急的人来插队 ,他们就在前面先通过验票。优先队列至少含有两种操作的数据结构:insert(即将元素插入到优先队列中;以及delete(,他的作用就是删除优先队列中的最小/大的元素。

二、优先队列(堆)的特性

  •    优先队列的实现常选用二叉堆,在数据结构中,优先队列一般也是指堆。
  • 堆的两个性质:

1、结构性:堆是一棵除底层外被完全填满的二叉树,底层的节点从左到右填入,这样的树叫做完全二叉树。

2、堆序性:由于我们想很快找出最小元,则最小元素应在根上,任意节点都小于它的孩子,这就是最小堆;如果是查找最大元素,则最大元素应该在根上,任意节点都要大于它的孩子,这就是最大堆。

å®æäºåæ 

通过观察发现,完全二叉树可以直接使用一个数组表示而不需要其他数据结构。所以我们只要传入一个size就可以构建优先队列的结构(元素之间使用compareTo方法进行比较)。

public class PriorityQueue<T extends Comparable<? super T>> { 
    public PriorityQueue(int capacity) {
        currentSize = 0;
        array = (T[]) new Comparable[capacity + 1];
    }
}

å®å¨äºåæ çæ°ç»å®ç°

对于数组中的任意位置i的元素,其左孩子在位置2i上,则右孩子在2i+1上,父节点在i/2(向下取整)上。通常从数组下标1开始存储,这样的好处很方便找到左右父节点。如果从0开始,左孩子在2i+1,右孩子在2i+2,父节点在(i-1)/2(向下取整)。

三、基本实现

  1、有序队列

       每次加入一个元素的时候,把它移到一个适合的位置上,比如队列中已经有1,3,4,现在要加入2,那么把2移到1和3的中间,这个合适的位置定义为:前一个元素; 这样入队保证有序,那么出队就直接把第一个元素拿出来,就是最小的。显然n个元素的时间复杂度,入队操作O(n),出队操作O(1),缺点:该方法,如果有大量的入队操作比较耗时。

  2、普通队列

        入队的时候什么都不做,那么直接添加到尾部,出队时候选择遍历数组中最小的元素出队,比如队列中1,3,4,加入2,直接把2添加到尾部即可,出队的时候,遍历数组,找到最大或最小的元素,出队就行了,显然,入队操作O(1),出队操作O(n),缺点:如果有大量出队操作比较耗时。

  3、二叉堆

       1、insert(插入)

  由下至上的堆有序化(上浮):为了插入元素X,我们在最后一个可用的位置建立空穴,如果此元素放入不破坏有序性,则插入完成。否则,将父节点下移,此节点上浮。重复操作此过程,直到满足为止。

建ç«ç©ºç©´

å®ææå¥

图中演示插入的过程。这样就实现了元素插入到优先队列(堆)中。

 /**
     * 插入到优先队列,维护堆序性
     *
     * @param x :插入的元素
     */
    public void insert(T x) {
        if (null == x) {
            return;
        }
        //扩容
        if (currentSize == array.length - 1) {
            enlargeArray(array.length * 2 + 1);
        }
        //上滤
        int hole = ++currentSize;
        for (array[0] = x; x.compareTo(array[hole / 2]) < 0; hole /= 2) {
            array[hole] = array[hole / 2];
        }
        array[hole] = x;
    }

    /**
     * 扩容方法
     *
     * @param newSize :扩容后的容量,为原来的2倍+1
     */
    private void enlargeArray(int newSize) {
        T[] old = array;
        array = (T[]) new Comparable[newSize];
        System.arraycopy(old, 0, array, 0, old.length);
    }

如果插入的元素是新的最小元素从而一直上浮到根处,那么这种插入的时间O(logN)。

2、delete

由上至下的堆有序化(下沉):因为我们建立的最小堆,所以删除最小元素,就是将根节点删掉,这样就破坏了有序性。为了满足结构性,堆中最后一个元素X必须移动到合适的位置,如果可以直接放到空穴,则删除完成(一般不可能);否则,将空穴的左右儿子中较小者移到空穴,即空穴下移了一层。继续这样的操作,直到X可以放入到空穴中。这样就可以满足结构性与堆序性。å é¤æå°å

å é¤æå°å

å®æå é¤æå°å

如图所示:在根处建立空穴,将最后一个元素放到空穴,已满足结构性;为满足堆序性,需要将空穴下移到合适的位置。

注意:堆的实现中,经常发生的错误是只有偶数个元素即有一个节点只有一个儿子。所以需要测试右儿子的存在性。

/**
     * 删除最小元
     * 若优先队列为空,抛出UnderflowException
     *
     * @return :返回最小元
     */
    public T deleteMin() {
        if (isEmpty()) {
            throw new UnderflowException();
        }

        T minItem = findMin();
        array[1] = array[currentSize--];
        percolateDown(1);

        return minItem;
    }

     /**
     * 下滤方法
     *
     * @param hole :从数组下标hole1开始下滤
     */
    private void percolateDown(int hole) {
        int child;
        T tmp = array[hole];

        for (; hole * 2 <= currentSize; hole = child) {
            //左儿子
            child = hole * 2;
            //判断右儿子是否存在
            if (child != currentSize &&
                    array[child + 1].compareTo(array[child]) < 0) {
                child++;
            }
            if (array[child].compareTo(tmp) < 0) {
                array[hole] = array[child];
            } else {
                break;
            }
        }
        array[hole] = tmp;
    }

这种操作最坏时间复杂度是O(logN)。平均而言,被放到根处的元素几乎下滤到底层(即来自的那层),所以平均时间复杂度是O(logN)。

四、总结

   优先队列常使用二叉堆实现,本文讲述二叉堆最基本的两个操作:插入以及删除最小元素。相信大家看完后就可以去看java的PriorityQueue源码了。今天就到这吧,毕竟已经不早了哈,注意养生,只说了二叉堆最基本的操作,还有一些额外操作以及分析敬请期待下次分析。

  • 13
    点赞
  • 32
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值