`
dengbaoleng
  • 浏览: 1162523 次
文章分类
社区版块
存档分类
最新评论

PriorityQueue

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

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics