PriorityQueue

本文深入探讨了Java PriorityQueue的内部实现机制,包括如何根据优先级进行排序,使用数组实现动态增加容量,以及其在插入、检索等操作中的时间复杂度。同时,文章通过实例展示了PriorityQueue在实际应用中的行为特性,强调了其非线程安全性和不保证元素遍历顺序的特点。

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

PriorityQueue
<wbr style="line-height:25px">PriorityQueue是个基于优先级堆的极大优先级队列</wbr><wbr style="line-height:25px">。<br style="line-height:25px"><span style="line-height:25px"><wbr style="line-height:25px">此队列按照在构造时所指定的顺序对元素排序,既可以根据元素的自然顺序来指定排序(参阅Comparable),<br style="line-height:25px"> 也可以根据Comparator来指定</wbr></span><wbr style="line-height:25px">,这取决于使用哪种构造方法。优先级队列不允许null元素。<br style="line-height:25px"> 依靠自然排序的优先级队列还不允许插入不可比较的对象(这样做可能导致ClassCastException)。<br style="line-height:25px"> 此队列的头是按指定排序方式的<span style="line-height:25px"><wbr style="line-height:25px">最小元素</wbr></span><wbr style="line-height:25px">。如果多个元素都是最小值,则头是其中一个元素——选择方法是任意的。<br style="line-height:25px"> 队列检索操作poll、remove、peek和element访问处于队列头的元素。<br style="line-height:25px"> 优先级队列是无界的,但是有一个<span style="line-height:25px"><wbr style="line-height:25px">内部容量</wbr></span><wbr style="line-height:25px">,控制着用于存储队列元素的数组的大小。<br style="line-height:25px"> 它总是至少与队列的大小相同。随着不断向优先级队列添加元素,其容量会自动增加。无需指定容量增加策略的细节。<br style="line-height:25px"><span style="line-height:25px"><wbr style="line-height:25px">注意1</wbr></span><wbr style="line-height:25px">:该队列是用<span style="line-height:25px"><wbr style="line-height:25px">数组实现</wbr></span><wbr style="line-height:25px">,但是数组大小可以<span style="line-height:25px"><wbr style="line-height:25px">动态增加,容量无限</wbr></span><wbr style="line-height:25px">。<br style="line-height:25px"><span style="line-height:25px"><wbr style="line-height:25px">注意2</wbr></span><wbr style="line-height:25px">:此实现不是同步的。<span style="line-height:25px"><wbr style="line-height:25px">不是线程安全的</wbr></span><wbr style="line-height:25px">。如果多个线程中的任意线程从结构上修改了列表,则这些线程不应同时访问PriorityQueue实例,这时请使用<span style="line-height:25px"><wbr style="line-height:25px">线程安全的PriorityBlockingQueue类</wbr></span><wbr style="line-height:25px">。<br style="line-height:25px"><span style="line-height:25px"><wbr style="line-height:25px">注意3</wbr></span><wbr style="line-height:25px">:不允许使用<span style="line-height:25px"><wbr style="line-height:25px">null元素</wbr></span><wbr style="line-height:25px">。<br style="line-height:25px"><span style="line-height:25px"><wbr style="line-height:25px">注意4</wbr></span><wbr style="line-height:25px">:此实现为插入方法(offer、poll、remove()和add方法)提供O(log(n))时间;<br style="line-height:25px"> 为remove(Object)和contains(Object)方法提供线性时间;<br style="line-height:25px"> 为检索方法(peek、element和size)提供固定时间。<br style="line-height:25px"><span style="line-height:25px"><wbr style="line-height:25px">注意5</wbr></span><wbr style="line-height:25px">:方法iterator()中提供的迭代器并<span style="line-height:25px"><wbr style="line-height:25px">不保证以有序的方式遍历优先级队列中的元素</wbr></span><wbr style="line-height:25px">。<br style="line-height:25px"> 至于原因可参考下面关于PriorityQueue的内部实现<br style="line-height:25px"> 如果需要按顺序遍历,请考虑使用Arrays.sort(pq.toArray())。<br style="line-height:25px"><span style="line-height:25px"><wbr style="line-height:25px">注意6</wbr></span><wbr style="line-height:25px">:<span style="line-height:25px"><wbr style="line-height:25px">可以在构造函数中指定如何排序</wbr></span><wbr style="line-height:25px">。如:<br style="line-height:25px"> PriorityQueue()<br style="line-height:25px"> 使用默认的初始容量(11)创建一个PriorityQueue,并根据其自然顺序来排序其元素(使用<span style="line-height:25px"><wbr style="line-height:25px">Comparable</wbr></span><wbr style="line-height:25px">)。<br style="line-height:25px"> PriorityQueue(intinitialCapacity)<br style="line-height:25px"> 使用指定的初始容量创建一个PriorityQueue,并根据其自然顺序来排序其元素(使用<span style="line-height:25px"><wbr style="line-height:25px">Comparable</wbr></span><wbr style="line-height:25px">)。<br style="line-height:25px"> PriorityQueue(intinitialCapacity,Comparator&lt;?superE&gt;comparator)<br style="line-height:25px"> 使用指定的初始容量创建一个PriorityQueue,并根据指定的比较器<span style="line-height:25px"><wbr style="line-height:25px">comparator</wbr></span><wbr style="line-height:25px">来排序其元素。<br style="line-height:25px"> 注意7:此类及其迭代器实现了Collection和Iterator接口的所有可选方法。<br style="line-height:25px"><span style="line-height:25px"><wbr style="line-height:25px">PriorityQueue的内部实现<br style="line-height:25px"> PriorityQueue对元素采用的是堆排序,头是按指定排序方式的最小元素。堆排序只能保证根是最大(最小),整个堆并不是有序的。</wbr></span><wbr style="line-height:25px"><br style="line-height:25px"><span style="line-height:25px"><wbr style="line-height:25px">方法iterator()中提供的迭代器可能只是对整个数组的依次遍历。也就只能保证数组的第一个元素是最小的</wbr></span><wbr style="line-height:25px">。<br style="line-height:25px"> 实例1的结果也正好与此相符。<br style="line-height:25px"> 实例1:<br style="line-height:25px"> publicstaticvoidmain(String[]args){<br style="line-height:25px"> PriorityQueue&lt;String&gt;pq=newPriorityQueue&lt;String&gt;();<br style="line-height:25px"> pq.add("dog");<br style="line-height:25px"> pq.add("apple");<br style="line-height:25px"> pq.add("fox");<br style="line-height:25px"> pq.add("easy");<br style="line-height:25px"> pq.add("boy");<br style="line-height:25px"><br style="line-height:25px"> while(!pq.isEmpty()){<br style="line-height:25px"> System.out.print("left:");<br style="line-height:25px"> for(Strings:pq){<br style="line-height:25px"> System.out.print(s+"");<br style="line-height:25px"> }<br style="line-height:25px"> System.out.println();<br style="line-height:25px"> System.out.println("poll():"+pq.poll());<br style="line-height:25px"> }<br style="line-height:25px"> }<br style="line-height:25px"> 输出的结果如下:<br style="line-height:25px"> left:appleboyfoxeasydog<br style="line-height:25px"> poll():apple<br style="line-height:25px"> left:boydogfoxeasy<br style="line-height:25px"> poll():boy<br style="line-height:25px"> left:dogeasyfox<br style="line-height:25px"> poll():dog<br style="line-height:25px"> left:easyfox<br style="line-height:25px"> poll():easy<br style="line-height:25px"> left:fox<br style="line-height:25px"> poll():fox<br style="line-height:25px"> 可以看到,<span style="line-height:25px"><wbr style="line-height:25px">虽然PriorityQueue保持了队列顶部元素总是最小,但内部的其它元素的顺序却随着元素的减少始终处于变化之中</wbr></span><wbr style="line-height:25px">。<br style="line-height:25px"> 察看源代码来一探究竟。从Netbeans中非常方便的连接到PriorityQueue的add函数实现,<br style="line-height:25px"> 最终跟踪到函数privatevoidsiftUpComparable(intk,Ex),定义如下:<br style="line-height:25px"> privatevoidsiftUpComparable(intk,Ex){<br style="line-height:25px"> Comparable&lt;?superE&gt;key=(Comparable&lt;?superE&gt;)x;<br style="line-height:25px"> while(k&gt;0){<br style="line-height:25px"> intparent=(k-1)&gt;&gt;&gt;1;<br style="line-height:25px"> Objecte=queue[parent];<br style="line-height:25px"> if(key.compareTo((E)e)&gt;=0)<br style="line-height:25px"> break;<br style="line-height:25px"> queue[k]=e;<br style="line-height:25px"> k=parent;<br style="line-height:25px"> }<br style="line-height:25px"> queue[k]=key;<br style="line-height:25px"> }<br style="line-height:25px"> 相对于add的操作,该函数的入口参数k是指新加入元素的下标,而x就是新加入的元素。<br style="line-height:25px"> 乍一看,这个函数的实现比较令人费解,尤其是parent的定义。通过进一步分析了解到,<br style="line-height:25px"> PriorityQueue内部成员数组queue其实是实现了一个二叉树的数据结构,这棵二叉树的根节点是queue[0],<br style="line-height:25px"> 左子节点是queue[1],右子节点是queue[2],而queue[3]又是queue[1]的左子节点,依此类推,给定元素queue<wbr style="line-height:25px">,<br style="line-height:25px"> 该节点的父节点是queue[(i-1)/2]。因此parent变量就是对应于下标为k的节点的父节点。<br style="line-height:25px"> 弄清楚了这个用数组表示的二叉树,就可以理解上面的代码中while循环进行的工作是,<br style="line-height:25px"> 当欲加入的元素小于其父节点时,就将两个节点的位置交换。这个算法保证了如果只执行add操作,那么queue这个二叉树是有序的:<br style="line-height:25px"> 该二叉树中的任意一个节点都小于以该节点为根节点的子数中的任意其它节点。这也就保证了queue[0],即队顶元素总是所有元素中最小的。<br style="line-height:25px"> 需要注意的是,这种算法无法保证不同子树上的两个节点之间的大小关系。举例来说,queue[3]一定会小于queue[7],但是未必会小于queue[9],因为queue[9]不在以queue[3]为根节点的子树上。<br style="line-height:25px"> 弄清楚了add的操作,那么当队列中的元素有变化的时候,对应的数组queue又该如何变化呢?察看函数poll(),<br style="line-height:25px"> 最终追中到函数privateEremoveAt(inti),代码如下:<br style="line-height:25px"> privateEremoveAt(inti){<br style="line-height:25px"> asserti&gt;=0&amp;&amp;i&lt;size;<br style="line-height:25px"> modCount++;<br style="line-height:25px"> ints=--size;<br style="line-height:25px"> if(s==i)//removedlastelement<br style="line-height:25px"><span style="line-height:25px; font-style:normal">queue<wbr style="line-height:25px">=null;<br style="line-height:25px"> else{<br style="line-height:25px"> Emoved=(E)queue[s];<br style="line-height:25px"> queue[s]=null;<br style="line-height:25px"> siftDown(i,moved);<br style="line-height:25px"> if(queue</wbr></span><span style="line-height:25px; font-style:normal"><wbr style="line-height:25px">==moved){<br style="line-height:25px"> siftUp(i,moved);<br style="line-height:25px"> if(queue</wbr></span><span style="line-height:25px; font-style:normal"><wbr style="line-height:25px">!=moved)<br style="line-height:25px"> returnmoved;<br style="line-height:25px"> }<br style="line-height:25px"> }<br style="line-height:25px"> returnnull;<br style="line-height:25px"> }</wbr></span><br style="line-height:25px"> 这个函数的实现方法是,将队尾元素取出,插入到位置i,替代被删除的元素,然后做相应的调整,保证二叉树的有序,<br style="line-height:25px"> 即任意节点都是以它为根节点的子树中的最小节点。进一步的代码就留给有兴趣的读者自行分析,<br style="line-height:25px"> 要说明的是,对于queue这样的二叉树结构有一个特性,即如果数组的长度为length,<br style="line-height:25px"> 那么所有下标大于length/2的节点都是叶子节点,其它的节点都有子节点。<br style="line-height:25px"> 总结:<span style="line-height:25px"><wbr style="line-height:25px">可以看到这种算法的实现,充分利用了树结构在搜索操作时的优势,效率又高于维护一个全部有序的队列</wbr></span><wbr style="line-height:25px">。</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值