• Java源码阅读PriorityQueue


    1类签名与简介

    public class PriorityQueue<E> extends AbstractQueue<E>
        implements java.io.Serializable

    PriorityQueue是一个基于优先级堆的无限队列,无限的意思是说队列的容量可以自动扩容,最大容量为整形最大值。扩容规定如下当容量小于64时扩大为原来的2倍,否则扩大为原来的1.5倍(也就是容量增大50%)。

    优先级队列不允许null元素,也不允许插入不可比较的对象。不可比较是指待插入对象的类没有实现Comparable接口,或者构造PriorityQueue对象是没有提供一个待插入对象的comparator比较器。强行插入不可比较对象会报ClassCastException异常。举个例子,现有一个Student类,只有以下两种情况才能将Student的对象放入PriorityQueue中:

    (1)Student实现Comparable接口

    (2)new PriorityQueue时提供一个Student的比较器:Queue<Student> students = new PriorityQueue<>(new MyComparator())

    该队列的头部是相对于指定顺序的最小元素。 如果多个元素被绑定到最小值,那么头就是这些元素之一。

    优先级堆(小顶堆)是用数组实现的,对于小顶堆的性质请执行查阅相关数据结构。队出队的方法时间复杂度都是O(logn)。

    请注意,此实现不同步。若想要在多线程并发,请使用线程安全的PriorityBlockingQueue类。

    2 入队

    public boolean add(E e) {
            return offer(e);
        }
    
    public boolean offer(E e) {
            if (e == null)
                throw new NullPointerException();
            modCount++;
            int i = size;
            if (i >= queue.length)
                grow(i + 1);
            size = i + 1;
            if (i == 0)
                queue[0] = e;
            else
                siftUp(i, e);
            return true;
        }

    add和offer都是入队实现,add其实就是调用offer方法实现的,所以两者本质上没有区别。

    offer首先会判断插入元素是否为null,优先队列不允许插入null的元素。接下来判断队列是否满了,满了就扩容。如果插入的是队列第一个元素就直接放在数组0号位。否则就进行上移调整siftUp。上移调整是从堆的最后一个节点(也就是队尾)开始不断通过比较parent节点找到插入的位置。

    private void siftUp(int k, E x) {
            if (comparator != null)
                siftUpUsingComparator(k, x);
            else
                siftUpComparable(k, x);
        }
    
    //实现Comparable接口的情况
    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;
        }
    
    //提供比较器Comparator的情况
    private void siftUpUsingComparator(int k, E x) {
            while (k > 0) {
                int parent = (k - 1) >>> 1;
                Object e = queue[parent];
                if (comparator.compare(x, (E) e) >= 0)
                    break;
                queue[k] = e;
                k = parent;
            }
            queue[k] = x;
        }

    每次插入都进行siftUp保证了始终是一个小顶堆,不会因为新的节点破坏堆的结构。

    siftUpComparable和siftUpUsingComparator过程是一样的,唯一不同的是不同的接口调用的不同比较方法。

    3出队

    出队是移除队列队头的元素,即数组的0号位存储的元素,也是堆结构的堆顶。

    public E poll() {
            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;
        }

    poll原理如下,把最后一个元素放到堆顶(数组0号位),然后从堆顶开始往下调整siftDown,直到满足最小堆为止。siftDown实现过程如下

    private void siftDown(int k, E x) {
            if (comparator != null)
                siftDownUsingComparator(k, x);
            else
                siftDownComparable(k, x);
        }
    
    //内部排序接口调用siftDownComparable
    private void siftDownComparable(int k, E x) {
            Comparable<? super E> key = (Comparable<? super E>)x;
            int half = size >>> 1;        // loop while a non-leaf
            while (k < half) {
                int child = (k << 1) + 1; // assume left child is least
                Object c = queue[child];
                int right = child + 1;
                if (right < size &&
                    ((Comparable<? super E>) c).compareTo((E) queue[right]) > 0)
                    c = queue[child = right];
                if (key.compareTo((E) c) <= 0)
                    break;
                queue[k] = c;
                k = child;
            }
            queue[k] = key;
        }
    
    //有外部比较器调用siftDownUsingComparator
    private void siftDownUsingComparator(int k, E x) {
            int half = size >>> 1;
            while (k < half) {
                int child = (k << 1) + 1;
                Object c = queue[child];
                int right = child + 1;
                if (right < size &&
                    comparator.compare((E) c, (E) queue[right]) > 0)
                    c = queue[child = right];
                if (comparator.compare(x, (E) c) <= 0)
                    break;
                queue[k] = c;
                k = child;
            }
            queue[k] = x;
        }

    4总结

    关于本篇文章,个人感觉描述的不是很清楚。

    因为觉得不适合花费其他篇幅去详细介绍“Comparator与Comparable”以及堆排序等相关知识。如果觉得有问题的话建议先了解一下这些相关的基础知识。

  • 相关阅读:
    java--接口和抽象类
    java-访问权限
    Appium点击掉弹窗的方法小记
    Appium报错及解决小记
    adb获取手机设备蓝牙&热点&wifi状态并操作的笔记
    Linux下安装appium环境搭建杂记
    Appium的使用笔记(自动化测试前的分析)
    Docker学习笔记
    python学习杂记-DDT驱动测试
    python学习杂记-处理CSV文件
  • 原文地址:https://www.cnblogs.com/ouym/p/9042060.html
Copyright © 2020-2023  润新知