Java-Collections Framework学习与总结-PriorityQueue

本文深入探讨了Java PriorityQueue的数据结构实现,包括优先队列的底层原理、操作方法及性能分析,帮助开发者掌握如何利用PriorityQueue高效地管理具有优先级的任务队列。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

开发中有时会遇到这样的情况。要求某个调度器去调度一些任务,这些任务放在队列里。任务本身有优先级,调度器要按照优先级去调度队列里的任务,当然也会有新任务不断加入到队列中。
        可以类比操作系统调度程序,操作系统要调度某个进程,简单的说会根据某种优先级进行调度(比如某些情况下,先执行时间较短的任务,保证短任务尽快结束。)
        所以期望有这样一种数据结构,首先它是一个队列,可以从这个队列中不断取出优先级最高的(也可以认为是最大的或者最小的,因为本质是通过比较决定。)元素。也可以不断的将新元素添加到这个队列。
        这种数据结构就叫做优先队列。优先队列的实现方式有很多,但好的实现应该保证关键的操作(如将一个元素放入队列;将优先级最大的元素移出队列等)比较高效。有一种数据结构可以比较高效的实现优先队列,这种数据结构叫二叉堆(binary heap)。
        二叉堆形式上是一棵完全二叉树,它的一个重要性质是堆序性,即对于堆中任一个节点n,节点n上的元素e都要小于或等于其子节点(左右两个儿子节点)上的元素。

        上图中,图(1)和图(2)都是完全二叉树,但只有图(1)是二叉堆,因为图(2)中39大于25和29,不满足堆序性。

        接下来看一下,这样的数据结构怎么支持优先队列的操作。
        先考虑下获取最小元素的操作,因为最小元素就在树的根节点,所以这个操作只要返回树根节点的元素就行了,操作时间为常数时间。
        在看一下添加一个元素的操作,因为二叉堆是一个完全二叉树,所以添加一个元素时首先要将元素添加到完全二叉树的下一个节点上,然后进行一些调整来保证堆序性。

        假设要在图(1)中的二叉堆中添加新元素2,图(2)中标出了新元素2要加入的节点,将元素添加到此节点上,就完成了第一步添加操作,如图(3)。接下来是调整操作,为了保证堆序性。首先将新节点与其父节点进行比较(比较元素大小),如果父节点元素小于新节点元素,那么交换新节点和父节点的元素,如图(4)。然后依次类推,继续向上比较,如图(5)、图(6),一直到当前比较节点大于其父节点,或者没有父节点(到达根节点)为止。
        可以看到,上图中新添元素向上移动过程中比较次数正好是这个完全二叉树的高,而且上图表示是最差情况(需要比较并移动的次数最多)。所以在二叉堆中新添一个元素的操作时间是O(logN)。
        最后看一下移除最小元素的操作(等同于优先级最大元素出队列操作)。因为最小元素就在树根,所以将最小元素移除很简单。但移除之后需要做一系列调整,以保证二叉堆的堆序性。

        当我们移除最小元素时,如图(1)。先将最小元素移除(移除根节点上的元素),并将完全二叉树的最后节点删除,保留其元素(这一步相当于在二叉堆中删除一个元素时,缩短其长度),如图(2)。由于树根没有元素,我们需要把下面的元素向上移动。在移动过程中需要保证堆序性,所以当树根没有元素时,将树根的两个儿子节点中较小的一个中的元素放到树根节点中,如图(3)。现在树根的左子节点元素为空了,我们以同样的方式对其进行处理,直到某个空节点的两个子节点中的元素都大于图(1)中的保留元素27,或者某个空节点没有子节点为止,然后将保留元素27放到这个空节点中。(上图中没有表现出现第一种情况,假设图(4)中空节点下面的两个子节点所带元素为30和34,再想一下上图中的流程)。
        这个过程删掉了最小元素同时保证了堆序性,同时也可以看到这个操作的时间也是O(logN)。
        以上是二叉堆可支持优先队列的主要操作,从中也可以看到,二叉堆只是把最小元素放在树根节点上,其他元素的顺序是无法保证的。(当然也可以把最大元素放到根节点,因为保证堆序性的过程基于比较。所以有最小堆最大堆的说法)

        有了二叉堆的理论基础,就可以看下java.util.PriorityQueue的源码了。
Java代码  收藏代码
  1. public class PriorityQueue<E> extends AbstractQueue<E>  
  2.     implements java.io.Serializable {  
  3.   
  4.     private static final long serialVersionUID = -7720805057305804111L;  
  5.   
  6.     private static final int DEFAULT_INITIAL_CAPACITY = 11;  
  7.   
  8.     /** 
  9.      * Priority queue represented as a balanced binary heap: the two 
  10.      * children of queue[n] are queue[2*n+1] and queue[2*(n+1)].  The 
  11.      * priority queue is ordered by comparator, or by the elements' 
  12.      * natural ordering, if comparator is null: For each node n in the 
  13.      * heap and each descendant d of n, n <= d.  The element with the 
  14.      * lowest value is in queue[0], assuming the queue is nonempty. 
  15.      */  
  16.     private transient Object[] queue;  
  17.   
  18.     /** 
  19.      * The number of elements in the priority queue. 
  20.      */  
  21.     private int size = 0;  
  22.   
  23.     /** 
  24.      * The comparator, or null if priority queue uses elements' 
  25.      * natural ordering. 
  26.      */  
  27.     private final Comparator<? super E> comparator;  
  28.   
  29.     /** 
  30.      * The number of times this priority queue has been 
  31.      * <i>structurally modified</i>.  See AbstractList for gory details. 
  32.      */  
  33.     private transient int modCount = 0;  

        首先看到PriorityQueue有一个内部数组,用来存储元素。那么一个数组怎么和一个二叉堆(完全二叉树)联系起来呢?我们不妨用上面第二张图中图(1)的二叉堆举个例子。将这个二叉堆中的元素按层级从左到右进行遍历,并放到一个数组里。如下:
Java代码  收藏代码
  1. [5,8,12,11,9,23,17,20,24,32,27]  

        由于完全二叉数的特殊性,这个数组里array[n]的左子节点在array[2n+1]位置,右子节点在array[2n+2]位置。array[0]可以认为是根节点。一个树形结构用简单的数组来表示,是不是感觉很爽,嘎嘎。
        还有一个重要的内部成员是comparator,通过前面知道二叉堆内部通过比较来保证堆序性,所以通过这个比较器可以大致了解到PriorityQueue内部的比较机制了。PriorityQueue内部做比较时,会先通过comparator进行比较;如果没有提供comparator,那么会根据元素的自然顺序进行比较。
        这里注意一下自然顺序。比如我们要在这个优先队列里放基本类型或者String类型,由于在集合里放基本类型必须将集合上的泛型声明为其对应的包装类,当元素加入集合时会自动装箱。那么随便找个包装类看看,就看java.lang.Integer吧:
Java代码  收藏代码
  1. public final class Integer extends Number implements Comparable<Integer> {  

        可以看到这货实现了一个接口java.lang.Comparable。
Java代码  收藏代码
  1. public interface Comparable<T> {  
  2.     public int compareTo(T o);  
  3. }  

        Comparable接口就定义了一个方法,就是和另一个同类型的元素进行比较。如比另一个元素小,就返回负数;相等就返回零;比另一个元素大就返回正数。当然这里的大小不是狭义的数值的大小,也可以认为是两的对象位置先后等等。
        那么来看一下java.lang.Integer的自然顺序是啥?
Java代码  收藏代码
  1. public int compareTo(Integer anotherInteger) {  
  2.     int thisVal = this.value;  
  3.     int anotherVal = anotherInteger.value;  
  4.     return (thisVal<anotherVal ? -1 : (thisVal==anotherVal ? 0 : 1));  
  5. }  

        比较的问题不是重点,就说到这儿。总之能了解PriorityQueue里怎么比较元素即可。
        其他的属性都没什么可说的。接下来是一系列构造方法,也没什么可说的。
Java代码  收藏代码
  1. /** 
  2.  * Creates a {@code PriorityQueue} with the specified initial 
  3.  * capacity that orders its elements according to their 
  4.  * {@linkplain Comparable natural ordering}. 
  5.  * 
  6.  * @param initialCapacity the initial capacity for this priority queue 
  7.  * @throws IllegalArgumentException if {@code initialCapacity} is less 
  8.  *         than 1 
  9.  */  
  10. public PriorityQueue(int initialCapacity) {  
  11.     this(initialCapacity, null);  
  12. }  
  13.   
  14. /** 
  15.  * Creates a {@code PriorityQueue} with the specified initial capacity 
  16.  * that orders its elements according to the specified comparator. 
  17.  * 
  18.  * @param  initialCapacity the initial capacity for this priority queue 
  19.  * @param  comparator the comparator that will be used to order this 
  20.  *         priority queue.  If {@code null}, the {@linkplain Comparable 
  21.  *         natural ordering} of the elements will be used. 
  22.  * @throws IllegalArgumentException if {@code initialCapacity} is 
  23.  *         less than 1 
  24.  */  
  25. public PriorityQueue(int initialCapacity,  
  26.                      Comparator<? super E> comparator) {  
  27.     // Note: This restriction of at least one is not actually needed,  
  28.     // but continues for 1.5 compatibility  
  29.     if (initialCapacity < 1)  
  30.         throw new IllegalArgumentException();  
  31.     this.queue = new Object[initialCapacity];  
  32.     this.comparator = comparator;  
  33. }  
  34.   
  35. /** 
  36.  * Creates a {@code PriorityQueue} containing the elements in the 
  37.  * specified collection.  If the specified collection is an instance of 
  38.  * a {@link SortedSet} or is another {@code PriorityQueue}, this 
  39.  * priority queue will be ordered according to the same ordering. 
  40.  * Otherwise, this priority queue will be ordered according to the 
  41.  * {@linkplain Comparable natural ordering} of its elements. 
  42.  * 
  43.  * @param  c the collection whose elements are to be placed 
  44.  *         into this priority queue 
  45.  * @throws ClassCastException if elements of the specified collection 
  46.  *         cannot be compared to one another according to the priority 
  47.  *         queue's ordering 
  48.  * @throws NullPointerException if the specified collection or any 
  49.  *         of its elements are null 
  50.  */  
  51. public PriorityQueue(Collection<? extends E> c) {  
  52.     initFromCollection(c);  
  53.     if (c instanceof SortedSet)  
  54.         comparator = (Comparator<? super E>)  
  55.             ((SortedSet<? extends E>)c).comparator();  
  56.     else if (c instanceof PriorityQueue)  
  57.         comparator = (Comparator<? super E>)  
  58.             ((PriorityQueue<? extends E>)c).comparator();  
  59.     else {  
  60.         comparator = null;  
  61.         heapify();  
  62.     }  
  63. }  
  64.   
  65. /** 
  66.  * Creates a {@code PriorityQueue} containing the elements in the 
  67.  * specified priority queue.  This priority queue will be 
  68.  * ordered according to the same ordering as the given priority 
  69.  * queue. 
  70.  * 
  71.  * @param  c the priority queue whose elements are to be placed 
  72.  *         into this priority queue 
  73.  * @throws ClassCastException if elements of {@code c} cannot be 
  74.  *         compared to one another according to {@code c}'s 
  75.  *         ordering 
  76.  * @throws NullPointerException if the specified priority queue or any 
  77.  *         of its elements are null 
  78.  */  
  79. public PriorityQueue(PriorityQueue<? extends E> c) {  
  80.     comparator = (Comparator<? super E>)c.comparator();  
  81.     initFromCollection(c);  
  82. }  
  83.   
  84. /** 
  85.  * Creates a {@code PriorityQueue} containing the elements in the 
  86.  * specified sorted set.   This priority queue will be ordered 
  87.  * according to the same ordering as the given sorted set. 
  88.  * 
  89.  * @param  c the sorted set whose elements are to be placed 
  90.  *         into this priority queue 
  91.  * @throws ClassCastException if elements of the specified sorted 
  92.  *         set cannot be compared to one another according to the 
  93.  *         sorted set's ordering 
  94.  * @throws NullPointerException if the specified sorted set or any 
  95.  *         of its elements are null 
  96.  */  
  97. public PriorityQueue(SortedSet<? extends E> c) {  
  98.     comparator = (Comparator<? super E>)c.comparator();  
  99.     initFromCollection(c);  
  100. }  

        下面看一下添加元素的操作。
Java代码  收藏代码
  1. /** 
  2.  * Inserts the specified element into this priority queue. 
  3.  * 
  4.  * @return {@code true} (as specified by {@link Collection#add}) 
  5.  * @throws ClassCastException if the specified element cannot be 
  6.  *         compared with elements currently in this priority queue 
  7.  *         according to the priority queue's ordering 
  8.  * @throws NullPointerException if the specified element is null 
  9.  */  
  10. public boolean add(E e) {  
  11.     return offer(e);  
  12. }  
  13.   
  14. /** 
  15.  * Inserts the specified element into this priority queue. 
  16.  * 
  17.  * @return {@code true} (as specified by {@link Queue#offer}) 
  18.  * @throws ClassCastException if the specified element cannot be 
  19.  *         compared with elements currently in this priority queue 
  20.  *         according to the priority queue's ordering 
  21.  * @throws NullPointerException if the specified element is null 
  22.  */  
  23. public boolean offer(E e) {  
  24.     if (e == null)  
  25.         throw new NullPointerException();  
  26.     modCount++;  
  27.     int i = size;  
  28.     if (i >= queue.length)  
  29.         grow(i + 1);  
  30.     size = i + 1;  
  31.     if (i == 0)  
  32.         queue[0] = e;  
  33.     else  
  34.         siftUp(i, e);  
  35.     return true;  
  36. }  

        add方法中调用了offer,offer方法中首先是一些判断,在必要的时候进行内部数组的扩容。然后将元素e添加到数组的下一个空位(下标为size),当数组中本来没有元素时,说明添加的第一个元素,直接放到queue[0](根节点)上。否则,要调用一个siftUp的方法。看方法名称和我们上面提到的二叉堆添加一个元素后向上移动的动作很像吧,看看实现:
Java代码  收藏代码
  1. /** 
  2.  * Inserts item x at position k, maintaining heap invariant by 
  3.  * promoting x up the tree until it is greater than or equal to 
  4.  * its parent, or is the root. 
  5.  * 
  6.  * To simplify and speed up coercions and comparisons. the 
  7.  * Comparable and Comparator versions are separated into different 
  8.  * methods that are otherwise identical. (Similarly for siftDown.) 
  9.  * 
  10.  * @param k the position to fill 
  11.  * @param x the item to insert 
  12.  */  
  13. private void siftUp(int k, E x) {  
  14.     if (comparator != null)  
  15.         siftUpUsingComparator(k, x);  
  16.     else  
  17.         siftUpComparable(k, x);  
  18. }  
  19.   
  20. private void siftUpComparable(int k, E x) {  
  21.     Comparable<? super E> key = (Comparable<? super E>) x;  
  22.     while (k > 0) {  
  23.         int parent = (k - 1) >>> 1;  
  24.         Object e = queue[parent];  
  25.         if (key.compareTo((E) e) >= 0)  
  26.             break;  
  27.         queue[k] = e;  
  28.         k = parent;  
  29.     }  
  30.     queue[k] = key;  
  31. }  
  32.   
  33. private void siftUpUsingComparator(int k, E x) {  
  34.     while (k > 0) {  
  35.         int parent = (k - 1) >>> 1;  
  36.         Object e = queue[parent];  
  37.         if (comparator.compare(x, (E) e) >= 0)  
  38.             break;  
  39.         queue[k] = e;  
  40.         k = parent;  
  41.     }  
  42.     queue[k] = x;  
  43. }  

        在siftUp里首先检查有没有比较器,没有的话就使用自然顺序的方式比较,所以在siftUpComparable方法里会看到一个Comparable类型的强转。至于siftUpUsingComparator和siftUpComparable方法里的逻辑都是一样的,看一个就行了。
        那就看siftUpUsingComparator吧。首先构造一个while循环,k>0是条件(因为根节点下标是0)。由上面的提到的内部数组的特性可知(queue[k]的左右子节点的位置是2k+1和2k+2),当前位置节点的父节点的位置是(k - 1) >>> 1(相当于k除以2)。然后得到父节点的元素并和当前要添加的元素进行比较。如果当前元素x小于k位置的父元素e,那么把父元素放到k位置,并把k指向父节点位置,继续循环。知道当前元素x大于k位置的父元素,或者k=0(到达根节点),那么将当前元素x放到k位置。基本上实现了上面提到的二叉堆的添加逻辑。

        获取最小(大)元素的方法一目了然。
Java代码  收藏代码
  1. public E peek() {  
  2.     if (size == 0)  
  3.         return null;  
  4.     return (E) queue[0];  
  5. }  


        看一下移除最小(大)元素的方法,也就是出队列的方法。
Java代码  收藏代码
  1. public E poll() {  
  2.     if (size == 0)  
  3.         return null;  
  4.     int s = --size;  
  5.     modCount++;  
  6.     E result = (E) queue[0];  
  7.     E x = (E) queue[s];  
  8.     queue[s] = null;  
  9.     if (s != 0)  
  10.         siftDown(0, x);  
  11.     return result;  
  12. }  

        对比上面提到的二叉堆的移除操作逻辑。E result = (E) queue[0]确定了要返回的是根节点元素;E x = (E) queue[s]拿到了二叉堆最后位置的元素;queue[s] = null移除最后的节点。接下来就是最后的元素该放在哪的问题了,二叉堆里只能一个元素的时候,直接返回;否则调用siftDown方法。
Java代码  收藏代码
  1. /** 
  2.  * Inserts item x at position k, maintaining heap invariant by 
  3.  * demoting x down the tree repeatedly until it is less than or 
  4.  * equal to its children or is a leaf. 
  5.  * 
  6.  * @param k the position to fill 
  7.  * @param x the item to insert 
  8.  */  
  9. private void siftDown(int k, E x) {  
  10.     if (comparator != null)  
  11.         siftDownUsingComparator(k, x);  
  12.     else  
  13.         siftDownComparable(k, x);  
  14. }  
  15.   
  16. private void siftDownComparable(int k, E x) {  
  17.     Comparable<? super E> key = (Comparable<? super E>)x;  
  18.     int half = size >>> 1;        // loop while a non-leaf  
  19.     while (k < half) {  
  20.         int child = (k << 1) + 1// assume left child is least  
  21.         Object c = queue[child];  
  22.         int right = child + 1;  
  23.         if (right < size &&  
  24.             ((Comparable<? super E>) c).compareTo((E) queue[right]) > 0)  
  25.             c = queue[child = right];  
  26.         if (key.compareTo((E) c) <= 0)  
  27.             break;  
  28.         queue[k] = c;  
  29.         k = child;  
  30.     }  
  31.     queue[k] = key;  
  32. }  
  33.   
  34. private void siftDownUsingComparator(int k, E x) {  
  35.     int half = size >>> 1;  
  36.     while (k < half) {  
  37.         int child = (k << 1) + 1;  
  38.         Object c = queue[child];  
  39.         int right = child + 1;  
  40.         if (right < size &&  
  41.             comparator.compare((E) c, (E) queue[right]) > 0)  
  42.             c = queue[child = right];  
  43.         if (comparator.compare(x, (E) c) <= 0)  
  44.             break;  
  45.         queue[k] = c;  
  46.         k = child;  
  47.     }  
  48.     queue[k] = x;  
  49. }  

        siftDown就是向下移动,保证堆序的逻辑了。直接看siftDownUsingComparator的代码吧。先通过size >>> 1得到一个half值,然后在k < half的条件下做循环。因为在内部数组的特殊性下,如果k>=half,那么k位置的节点不存在子节点。然后得到k位置的左子节点位置,并得到其元素c。然后获取其右子节点(如果有的话),并和左子节点进行比较,如果右子节点的元素小于左子结点的元素,那么将child指向右子结点的下标并将c指向右子结点的元素,然后和x比较(也就是用两个子节点中最小的和x比较)。如果c小于当前元素x,那么用c来填补k位置,并将k指向较小的子节点的位置,继续循环。直到c大于等于x,或者k位置没有子节点,那么将x填入k位置。

        再看下另外几个基于siftUp和siftDown的方法。
Java代码  收藏代码
  1. /** 
  2.  * Removes the ith element from queue. 
  3.  * 
  4.  * Normally this method leaves the elements at up to i-1, 
  5.  * inclusive, untouched.  Under these circumstances, it returns 
  6.  * null.  Occasionally, in order to maintain the heap invariant, 
  7.  * it must swap a later element of the list with one earlier than 
  8.  * i.  Under these circumstances, this method returns the element 
  9.  * that was previously at the end of the list and is now at some 
  10.  * position before i. This fact is used by iterator.remove so as to 
  11.  * avoid missing traversing elements. 
  12.  */  
  13. private E removeAt(int i) {  
  14.     assert i >= 0 && i < size;  
  15.     modCount++;  
  16.     int s = --size;  
  17.     if (s == i) // removed last element  
  18.         queue[i] = null;  
  19.     else {  
  20.         E moved = (E) queue[s];  
  21.         queue[s] = null;  
  22.         siftDown(i, moved);  
  23.         if (queue[i] == moved) {  
  24.             siftUp(i, moved);  
  25.             if (queue[i] != moved)  
  26.                 return moved;  
  27.         }  
  28.     }  
  29.     return null;  
  30. }  

        这个方法表示删除某个下标的元素。因为删除后还要做移动操作来保证堆序性,所以在删除的位置上先拿数组中最后一个元素进行siftDown,如果没有任何移动,再进行siftUp。

Java代码  收藏代码
  1. /** 
  2.  * Establishes the heap invariant (described above) in the entire tree, 
  3.  * assuming nothing about the order of the elements prior to the call. 
  4.  */  
  5. private void heapify() {  
  6.     for (int i = (size >>> 1) - 1; i >= 0; i--)  
  7.         siftDown(i, (E) queue[i]);  
  8. }  

        这个操作也比较重要,它将一个普通数组构建成一个二叉堆。有了上面的分析代码很好理解,感兴趣的话可以画图来理解一下过程。这个操作以线性平均时间完成。
        这里有一个有趣的问题,前面提到二叉堆不保证最大元素(或最小元素)之外元素的顺序。但我们想一下,如果用构建二叉堆的方法将一个数组构建成一个二叉堆,然后移除最大(或最小)元素n次(数组长度为n),并将每次移除的元素放到另外一个数组中(或者放到本数组中每次的废弃位置也可以),那么这个数组不就排好序了么!这种方法叫做堆排序,由于构建一个堆要平均O(N)时间,再加上遍历N次,每次O(logN)的操作时间,所以堆排序的时间复杂度是O(NlogN)。
        好了,其他的代码也应该很容易看懂了。java.util.PriorityQueue就总结到这里!
基于C2000 DSP的电力电子、电机驱动和数字滤波器的仿真模型构建及其C代码实现方法。首先,在MATLAB/Simulink环境中创建电力电子系统的仿真模型,如三相逆变器,重点讨论了PWM生成模块中死区时间的设置及其对输出波形的影响。接着,深入探讨了C2000 DSP内部各关键模块(如ADC、DAC、PWM定时器)的具体配置步骤,特别是EPWM模块采用上下计数模式以确保对称波形的生成。此外,还讲解了数字滤波器的设计流程,从MATLAB中的参数设定到最终转换为适用于嵌入式系统的高效C代码。文中强调了硬件在环(HIL)和支持快速原型设计(RCP)的重要性,并分享了一些实际项目中常见的陷阱及解决方案,如PCB布局不当导致的ADC采样异常等问题。最后,针对中断服务程序(ISR)提出了优化建议,避免因ISR执行时间过长而引起的系统不稳定现象。 适合人群:从事电力电子、电机控制系统开发的技术人员,尤其是那些希望深入了解C2000 DSP应用细节的研发工程师。 使用场景及目标:①掌握利用MATLAB/Simulink进行电力电子设备仿真的技巧;②学会正确配置C2000 DSP的各项外设资源;③能够独立完成从理论设计到实际产品落地全过程中的各个环节,包括但不限于数字滤波器设计、PWM信号生成、ADC采样同步等。 其他说明:文中提供了大量实用的代码片段和技术提示,帮助读者更好地理解和实践相关知识点。同时,也提到了一些常见错误案例,有助于开发者规避潜在风险。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值