1、PriorityQueue-底层数据结构
1、PriorityQueue底层使用了数组来存储数据,因为优先级队列:一般使用堆数据结构实现,堆一般使用数组存储。2、成员属性有: // 默认容量 private static final int DEFAULT_INITIAL_CAPACITY = 11; // 队列使用数组保存数据 transient Object[] queue; // 元素个数,即队列的大小 private int size = 0; // 比较器,保证优先级,为空表示不指定,则是自然顺序(用元素自己默认的比较器) private final Comparator<? super E> comparator; // 操作次数 transient int modCount = 0; private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;//容量限制
2、PriorityQueue-构造函数
private static final int DEFAULT_INITIAL_CAPACITY = 11; //默认容量11 public PriorityQueue() { this(DEFAULT_INITIAL_CAPACITY, null); } //指定容量 public PriorityQueue(int initialCapacity) { this(initialCapacity, null); } //默认容量,指定比较器 public PriorityQueue(Comparator<? super E> comparator) { this(DEFAULT_INITIAL_CAPACITY, comparator); } //指定容量,指定比较器,判断传入的容量是否合法,初始化存储元素的数组及比较器 public PriorityQueue(int initialCapacity, Comparator<? super E> comparator) { if (initialCapacity < 1) throw new IllegalArgumentException(); this.queue = new Object[initialCapacity]; this.comparator = comparator; } //根据传入的集合构造队列 public PriorityQueue(Collection<? extends E> c) { if (c instanceof SortedSet<?>) {//SortedSet类型的集合 SortedSet<? extends E> ss = (SortedSet<? extends E>) c; this.comparator = (Comparator<? super E>) ss.comparator();//取c的比较器 initElementsFromCollection(ss);//元素入队 } else if (c instanceof PriorityQueue<?>) {//同上 PriorityQueue<? extends E> pq = (PriorityQueue<? extends E>) c; this.comparator = (Comparator<? super E>) pq.comparator(); initFromPriorityQueue(pq); } else { this.comparator = null;//自然顺序 initFromCollection(c); } } @SuppressWarnings("unchecked") public PriorityQueue(PriorityQueue<? extends E> c) { this.comparator = (Comparator<? super E>) c.comparator(); initFromPriorityQueue(c); } @SuppressWarnings("unchecked") public PriorityQueue(SortedSet<? extends E> c) { this.comparator = (Comparator<? super E>) c.comparator(); initElementsFromCollection(c); } private void initFromPriorityQueue(PriorityQueue<? extends E> c) { if (c.getClass() == PriorityQueue.class) {//说明是相同的类型,直接赋值 this.queue = c.toArray(); this.size = c.size(); } else { initFromCollection(c); } } private void initElementsFromCollection(Collection<? extends E> c) { Object[] a = c.toArray(); if (c.getClass() != ArrayList.class)//底层是数组,进行数组拷贝 a = Arrays.copyOf(a, a.length, Object[].class); int len = a.length; if (len == 1 || this.comparator != null) for (int i = 0; i < len; i++) if (a[i] == null) throw new NullPointerException();//不支持null元素 this.queue = a; this.size = a.length; } private void initFromCollection(Collection<? extends E> c) { initElementsFromCollection(c); heapify();//堆化:从有子节点的最靠后元素开始往前,每次都调用siftDown方法来调整的过程。 }
3、PriorityQueue-扩容
1、PriorityQueue-扩容:扩容函数grow()类似于ArrayList里的grow()函数,就是再申请一个更大的数组,并将原数组的元素复制过去。 1-1、[!!!]旧容量小于64时,容量翻倍,旧容量大于等于64,容量只增加旧容量的一半。2、源码Demo: private void grow(int minCapacity) { // 旧容量 int oldCapacity = queue.length; // 旧容量小于64时,容量翻倍,旧容量大于等于64,容量只增加旧容量的一半 int newCapacity = oldCapacity + ((oldCapacity < 64) ? (oldCapacity + 2) : (oldCapacity >> 1)); // 检查是否溢出 if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); // 创建出一个新容量大小的新数组并把旧数组元素拷贝过去 queue = Arrays.copyOf(queue, newCapacity); } private static int hugeCapacity(int minCapacity) { if (minCapacity < 0) // overflow throw new OutOfMemoryError(); return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE; }
4、PriorityQueue-添加元素
1、添加元素,主要方法有: .add(E e)和.offer(E e) //向优先队列中插入元素,在插入失败时抛出异常 public boolean add(E e) { return offer(e); } //向优先队列中插入元素,在插入失败时返回false public boolean offer(E e) { //不允许放入null元素 if (e == null) throw new NullPointerException(); modCount++; int i = size; if (i >= queue.length) grow(i + 1); //自动扩容 size = i + 1; // 元素个数加1 if (i == 0) //队列原来为空,这是插入的第一个元素 queue[0] = e; else //新加入的元素可能会破坏小顶堆的性质,因此需要进行必要的调整。 siftUp(i, e); //调整堆结构,从下而上堆化 return true; }2、调堆结构: //调整节点顺序/位置,因为这是一个最小堆,最小堆必须要严格按照子节点大于父亲节点的顺序做数组中存放。 //在k位置插入项x private void siftUp(int k, E x) { if (comparator != null) siftUpUsingComparator(k, x); else siftUpComparable(k, x); } //有比较器,调用该方法进行节点位置调整 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; } //没有比较器,调用该方法进行节点位置调整 private void siftUpComparable(int k, E x) { Comparable<? super E> key = (Comparable<? super E>) x; while (k > 0) { int parent = (k - 1) >>> 1; //右移一位,相当于/2,与计算公式:parentNo = (nodeNo-1)/2对应。 Object e = queue[parent]; //调用比较器的比较方法:比较可以是元素的自然顺序,也可以是依靠比较器的顺序。 //从k指定的位置开始,将x逐层与当前点的parent进行比较并交换 if (key.compareTo((E) e) >= 0) break; queue[k] = e; k = parent; } queue[k] = key; }
4-1、PriorityQueue-offer(E e)-堆化过程示意图
5、PriorityQueue-获取元素
1、获取元素,主要方法有: .element(E e)和.peek(E e)。【获取但不删除队首元素,也就是队列中权值最小的那个元素。】2、源码: //当方法失败时返回null public E peek() { if (size == 0) return null; return (E) queue[0]; //根据小顶堆的性质,堆顶那个元素就是全局最小的那个;由于堆用数组表示,根据下标关系,0下标处的那个元素既是堆顶元素。所以直接返回数组0下标处的那个元素即可。 } //当方法失败时抛出异常 public E element() { E x = peek(); if (x != null) return x; else throw new NoSuchElementException(); }
5-1、PriorityQueue-peek()-示意图
6、PriorityQueue-删除元素
1、删除元素,涉及方法:.remove()和.poll()。【获取并删除队首元素】2、源码: //获取并删除队首元素,当方法失败时抛出异常 public boolean remove(Object o) { //找到第一个满足o.equals(queue[i])元素的下标 int i = indexOf(o); if (i == -1) return false; else { removeAt(i); return true; } } //删除元素位置不同,按不同情况处理 private E removeAt(int i) { assert i >= 0 && i < size; modCount++; int s = --size; if (s == i) //1. 删除的是最后一个元素。直接删除即可,不需要调整。 queue[i] = null; else { //2. 删除的不是最后一个元素,从删除点开始以最后一个元素为参照调用一次siftDown()即可。 E moved = (E) queue[s]; queue[s] = null; siftDown(i, moved); if (queue[i] == moved) { siftUp(i, moved); if (queue[i] != moved) return moved; } } return null; } //获取并删除队首元素,当方法失败时返回null public E poll() { if (size == 0) return null; int s = --size; modCount++; E result = (E) queue[0];//0下标处的那个元素就是最小的那个 E x = (E) queue[s]; queue[s] = null; //由于删除操作会改变队列的结构,为维护小顶堆的性质,需要进行必要的调整。 if (s != 0) siftDown(0, x); return result; } //调用siftDown()方法对堆进行调整,最后返回原来0下标处的那个元素(也就是最小的那个元素) private void siftDown(int k, E x) { if (comparator != null) siftDownUsingComparator(k, x); else siftDownComparable(k, x); } //从k指定的位置开始,将x逐层向下与当前点的左右孩子中较小的那个交换,直到x小于或等于左右孩子中的任何一个为止。 private void siftDownComparable(int k, E x) { Comparable<? super E> key = (Comparable<? super E>)x; // 只需要比较一半就行了,因为叶子节点占了一半的元素 int half = size >>> 1; // loop while a non-leaf //首先找到左右孩子中较小的那个,记录到c里,并用child记录其下标。【自上而下堆化】 while (k < half) { //寻找子节点的位置,这里加1是因为元素从0号位置开始 int child = (k << 1) + 1; //leftNo = parentNo*2+1 //左子节点的值 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; } //从k指定的位置开始,将x逐层向下与当前点的左右孩子中较小的那个交换,直到x小于或等于左右孩子中的任何一个为止。 private void siftDownUsingComparator(int k, E x) { int half = size >>> 1; //首先找到左右孩子中较小的那个,记录到c里,并用child记录其下标。【自上而下堆化】 while (k < half) { int child = (k << 1) + 1;//leftNo = parentNo*2+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;//然后用c取代原来的值 k = child; } queue[k] = x; }
6-1、PriorityQueue-删除元素-.poll()-.siftDown()图解过程
6-2、PriorityQueue-删除元素-.remove(Object o)-两种删除情况图解
7、PriorityQueue-建堆过程
1、PriorityQueue的建堆过程和最大堆的建堆过程基本上一样的,从有子节点的最靠后元素开始往前,每次都调用siftDown方法来调整。这个过程也叫做heapify。2、源码: private void heapify() { for (int i = (size >>> 1) - 1; i >= 0; i--) //siftDown的过程是将一个节点和它的子节点进行比较调整,保证它比它所有的子节点都要小。这个调整的顺序是从当前节点向下,一直调整到叶节点。 siftDown(i, (E) queue[i]); }
8、PriorityQueue-应用场景以及测试案例
1、应用场景,队列中元素,需要按照优先队列,也就是队列里面的元素遵从一定的比较规则(比较器比较)有序。2、测试Demo: public class PriorityQueueTest { public static void main(String[] args) { PriorityQueue<Integer> queue = Queues.newPriorityQueue(); test2(queue); while (!queue.isEmpty()){ System.out.println(queue.poll()); } } private static void test1(Queue queue){ queue.add(1); queue.add(8); queue.add(3); queue.add(5); queue.add(2); queue.add(448); queue.add(328); queue.add(18); queue.add(558); queue.add(38); queue.add(98); } private static void test2(Queue queue){ queue.add("a"); queue.add("v"); queue.add("d"); queue.add("s"); queue.add("z"); queue.add("da"); queue.add("dda"); queue.add("tgfa"); queue.add("yha"); queue.add("ihfa"); queue.add("mdtad"); } } 输出结果: 1 2 3 5 8 18 38 98 328 448 558 a d da dda ihfa mdtad s tgfa v yha z