【java容器的刻意练习】【十七】PriorityQueue的插入源码分析

上一篇我们知道了PriorityDeque的底层结构,是个平衡二叉堆,用“兵阵变队列”的方式储存在数组中。

这一篇我们开始学习,PriorityDeque是如何利用平衡二叉堆实现优先级排序的。

先看添加元素的方法:

    public boolean add(E e) {
        return offer(e);
    }

原来addoffer封装而已,看看offer源码:

    public boolean offer(E e) {
        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;
    }

offer添加元素的逻辑如下:

  • modCount操作次数加1,默认是0
  • size是队列长度,默认是0
  • 如果数组长度不够,则需要调用grow扩容
  • 数组长度足够,调用siftUp进行元素添加
  • 队列长度size加1

grow函数我们比较熟悉了,基本上java里面的自动数组扩容都是这个逻辑:长度在64以下是扩容至原长度2倍+2;大于64长度就是每次扩容50%。当然会充分考虑一些越界问题。

    private void grow(int minCapacity) {
        int oldCapacity = queue.length;
        // Double size if small; else grow by 50%
        int newCapacity = ArraysSupport.newLength(oldCapacity,
                minCapacity - oldCapacity, /* minimum growth */
                oldCapacity < 64 ? oldCapacity + 2 : oldCapacity >> 1
                                           /* preferred growth */);
        queue = Arrays.copyOf(queue, newCapacity);
    }

接下来,我们重点看之前没见过的函数siftUp(int k, E x)

    /**
     * Inserts item x at position k, maintaining heap invariant by
     * promoting x up the tree until it is greater than or equal to
     * its parent, or is the root.
     *
     * To simplify and speed up coercions and comparisons, the
     * Comparable and Comparator versions are separated into different
     * methods that are otherwise identical. (Similarly for siftDown.)
     *
     * @param k the position to fill
     * @param x the item to insert
     */
    private void siftUp(int k, E x) {
        if (comparator != null)
            siftUpUsingComparator(k, x, queue, comparator);
        else
            siftUpComparable(k, x, queue);
    }

siftUp的逻辑如下:

  • 如果我们构造函数时候传入了自定义的comparator比较器,就用siftUpUsingComparator进行优先级判断加入;
  • 如果没有自定义比较器,就用默认的siftUpComparable函数进行排序后再加入。

注释提到,siftUp实现在数组的k位置插入元素x,通过“上浮”x直到它大于或等于其父节点,或者x变成根节点,来保持最小堆的平衡,就是“小顶堆”。为了简化和加速比较,默认比较器和自定义比较器被分成不同的方法,其他实现是相同的。(类似siftDown。)

那么,既然注释siftUpComparablesiftUpUsingComparator就差个自定义比较而已,那么我们先看默认的排序函数siftUpComparable的实现逻辑:

  /**
   * 将元素x插到数组k的位置.
   * 然后按照元素的自然顺序进行堆调整——"上浮",以维持"堆"有序.
   * 最终的结果是一个"小顶堆".
   */
    private static <T> void siftUpComparable(int k, T x, Object[] es) {
        Comparable<? super T> key = (Comparable<? super T>) x;

        // 这个k就是我们传进来的队列长度,默认是0
        while (k > 0) {
            // (k-1)除2, 求出k结点的父结点索引parent
            // 例如,k等于1或者2,那么k的父节点在数组位置0
            // 如果,k等于3,那么k的父节点在数组位置1
            int parent = (k - 1) >>> 1;

            // 取出父节点的元素
            Object e = es[parent];

            // 如果插入的元素值大于等于父结点元素值, 则退出“上浮”循环
            if (key.compareTo((T) e) >= 0)
                break;

            // 如果插入的元素值小于父结点元素值, 则把父节点放到k的位置
            es[k] = e;
            // 继续向上找下一个父节点是否需要上浮调整
            k = parent;
        }

        // 上浮调整结束,找到适合元素key的位置k,保存到数组中
        es[k] = key;
    }

原来,siftUpComparable方法的作用其实就是堆的“上浮调整”,可以把平衡二叉堆想象成一棵完全二叉树,每次插入元素都链接到二叉树的最右下方,然后将插入的元素与其父结点比较,如果父结点大,则交换元素,直到没有父结点比插入的结点大为止。这样就保证了堆顶(二叉树的根结点)一定是最小的元素。(注:以上仅针对“小顶堆”)

再看看siftUpUsingComparator,只是if那句换成if (key.compareTo((T) e) >= 0)而已,所以就不再重复阐述了。

计算机的二叉树就是倒过来的树
上浮过程

如果换成自定义的优先级比较器。可以想象银行的各种金卡黑卡普通卡排队比较。只要金卡来了,就会排比黑卡、普通卡排前面。黑卡来了也可以插普通卡的队。

“有钱大晒啊?”
“抱歉,有钱是真的能为所欲为的。”

抱歉,有钱是真的能为所欲为的

这种操作就是折半法查找,每找一次排除一半的可能,256个数据中查找只要找8次就可以找到目标,2^x =N,所以时间复杂度x=Ο(logN)。

ok,总结下今天的结论:

  • PriorityQueue的默认优先级是数值从小到大排序
  • 排序比较的过程就是堆的上浮处理过程,可以想象银行金卡、黑卡各种插队普通卡
  • 上浮算法的关键是通过 (k - 1) / 2 找到k的父节点,再根据优先级是否调换位置
  • 时间复杂度是Ο(logN)
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,298评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,701评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,078评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,687评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,018评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,410评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,729评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,412评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,124评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,379评论 2 242
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,903评论 1 257
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,268评论 2 251
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,894评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,014评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,770评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,435评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,312评论 2 260