二项队列
Posted 读书使人进步
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了二项队列相关的知识,希望对你有一定的参考价值。
虽然左式堆每次操作花费O(logN),这有效的支持了合并,插入和DeleteMin,但还是有改进的余地,因为我们知道,二叉堆以每次操作花费常数平均时间支持插入。二项队列支持所有这种操作,每次操作的最坏情形运行时间为O(logN),而插入操作平均花费常数时间。
1.二项队列结构
二项队列不同于左式堆和二叉堆等优先队列的实现之处在于,一个二项队列不是一棵堆序的树,而是堆序树的集合,即森林。堆序树中的每棵树都是由约束形式的,叫做二项树。每一个高度上至多存在一棵二项树。高度为0的二项树是一颗单节点树,高度为k的二项树Bk通过将一棵二项树Bk-1附接到另一颗二 项树Bk-1的根上而构成。图1显示二项树B0,B1,B2,B3以及B4。
图1:高度为0,1,2,3,4的五棵二项树
从图中可以看出,二项树Bk由一个带有儿子B0,B1,B2,……Bk-1 的根组成。高度为k的二项树恰好有2^k个节点。如果我们把堆序施加到二项树上并允许任意高度上最多有一棵二项树,那么我们便能够用二项树的集合惟一的表示任意大小的优先队列。例如,大小为13的优先队列可以用森林B3,B2,B0表示,可以把这种表示写成1101,它不仅以二进制表示了13,而且说明了 B3,B2,B0出现,而B1没出现。
2.二项队列操作
此时,因为二项队列也满足了堆序性,故最小元可以通过搜索所有的树根来找出,由于最多有logN棵不同的树,因此最小元可以在O(logN)中找到。
合并操作基本上是通过将两个队列加到一起来完成的。令H1,H2是两个二项队列,H3是新的二项队列。由于H1没有高度为0的二项树而H2有, 因此就用H2中高度为0的二项树作为H3的一部分。因为H1,H2都有高度为1的二项树,将它们合并,让大的根成为小的根的子树,从而建立高度为2的二项树,见图3所示。这样,H3将没有高度为1的二项树。先存在3棵高度为2的二项树,即H1,H2原有的两棵树及由上一步合并得到的一棵二项树。我们将一棵 高度为2的二项树放入到H3中,并合并其他两个二项树,得到一棵高度为3的二项树。由于H1,H2都没有高度为3的二项树,因此该二项树就成为H3的一部 分,合并结束,见图4所示:
图2:两个二项队列H1,H2
图3:H1和H2中两棵B1树合并
图4:最终结果
插入实际上就是合并的特殊情形,我们只要创建一棵单节点的树并执行一次合并,这种操作的最坏情形运行时间也是O(logN)。更准确的说,如果元素将要插入的那个优先队列不存在的最小的二项树是Bi,那么运行时间与i+1成正比。图4和图5演示通过依序插入1到7来构成一个二项队列。
DeleteMin可以通过首先找出一棵具有最小根的二项树来完成,令该树为Bk,并令原有的优先队列为H,我们从H的树的森林中除去二项树Bk,形成新的二项队列H1。再出去Bk的根,得到一些二项树B0,B1,B2,….Bk-1,它们共同形成了优先队列H2,合并H1,H2,操作结束。
3.二项队列实现
DeleteMin操作需要快速找出根的所有子树的能力,因此,需要一般树的标准表示方法:每个节点的儿子都在一个链表中,每个节点都有一个指 向它的第一个儿子的指针。该操作还要求,诸儿子按照他们的子树大小排序。当两棵树被合并时,其中的一棵树最为儿子被加到另一棵树上。由于这棵新树将是最大 的子树,因此,以大小递减的方式保持这些子树很有意义。
总之,二项树的每一个节点将包括:数据,指向第一个儿子的指针以及右兄弟。二项树中的诸儿子以递减次序排序。
图10解释如何表示H3中的二项队列。
图10 二项队列H3的表示方式
为了合并两个二项队列,我们需要一个例程来合并两个大小相同的二项树。图11指出两个大小相同的二项树合并时指针是如何变化的:
图11: 合并两颗二项树
4.详细代码:
public class BinomialQueue{ public BinomialQueue( ){ theTrees = new BinomialNode[ MAX_TREES ]; makeEmpty( ); } /** * Merge rhs into the priority queue. * rhs becomes empty. rhs must be different from this. * @param rhs the other binomial queue. * @exception Overflow if result exceeds capacity. */ public void merge( BinomialQueue rhs ) throws Overflow{ if( this == rhs ) // Avoid aliasing problems return; if( currentSize + rhs.currentSize > capacity( ) ) throw new Overflow( ); currentSize += rhs.currentSize; BinomialNode carry = null; for( int i = 0, j = 1; j <= currentSize; i++, j *= 2 ) { BinomialNode t1 = theTrees[ i ]; BinomialNode t2 = rhs.theTrees[ i ]; int whichCase = t1 == null ? 0 : 1; whichCase += t2 == null ? 0 : 2; whichCase += carry == null ? 0 : 4; switch( whichCase ) { case 0: /* No trees */ case 1: /* Only this */ break; case 2: /* Only rhs */ theTrees[ i ] = t2; rhs.theTrees[ i ] = null; break; case 4: /* Only carry */ theTrees[ i ] = carry; carry = null; break; case 3: /* this and rhs */ carry = combineTrees( t1, t2 ); theTrees[ i ] = rhs.theTrees[ i ] = null; break; case 5: /* this and carry */ carry = combineTrees( t1, carry ); theTrees[ i ] = null; break; case 6: /* rhs and carry */ carry = combineTrees( t2, carry ); rhs.theTrees[ i ] = null; break; case 7: /* All three */ theTrees[ i ] = carry; carry = combineTrees( t1, t2 ); rhs.theTrees[ i ] = null; break; } } for( int k = 0; k < rhs.theTrees.length; k++ ) rhs.theTrees[ k ] = null; rhs.currentSize = 0; } /** * Return the result of merging equal-sized t1 and t2. */ private static BinomialNode combineTrees( BinomialNode t1, BinomialNode t2 ){ if( t1.element.compareTo( t2.element ) > 0 ) return combineTrees( t2, t1 ); t2.nextSibling = t1.leftChild; t1.leftChild = t2; return t1; } /** * Insert into the priority queue, maintaining heap order. * This implementation is not optimized for O(1) performance. * @param x the item to insert. * @exception Overflow if capacity exceeded. */ public void insert( Comparable x ) throws Overflow{ BinomialQueue oneItem = new BinomialQueue( ); oneItem.currentSize = 1; oneItem.theTrees[ 0 ] = new BinomialNode( x ); merge( oneItem ); } /** * Find the smallest item in the priority queue. * @return the smallest item, or null, if empty. */ public Comparable findMin( ) { if( isEmpty( ) ) return null; return theTrees[ findMinIndex( ) ].element; } /** * Find index of tree containing the smallest item in the priority queue. * The priority queue must not be empty. * @return the index of tree containing the smallest item. */ private int findMinIndex( ){ int i; int minIndex; for( i = 0; theTrees[ i ] == null; i++ ) ; for( minIndex = i; i < theTrees.length; i++ ) if( theTrees[ i ] != null && theTrees[ i ].element.compareTo( theTrees[ minIndex ].element ) < 0 ) minIndex = i; return minIndex; } /** * Remove the smallest item from the priority queue. * @return the smallest item, or null, if empty. */ public Comparable deleteMin( ){ if( isEmpty( ) ) return null; int minIndex = findMinIndex( ); Comparable minItem = theTrees[ minIndex ].element; BinomialNode deletedTree = theTrees[ minIndex ].leftChild; BinomialQueue deletedQueue = new BinomialQueue( ); deletedQueue.currentSize = ( 1 << minIndex ) - 1; for( int j = minIndex - 1; j >= 0; j-- ){ deletedQueue.theTrees[ j ] = deletedTree; deletedTree = deletedTree.nextSibling; deletedQueue.theTrees[ j ].nextSibling = null; } theTrees[ minIndex ] = null; currentSize -= deletedQueue.currentSize + 1; try { merge( deletedQueue ); } catch( Overflow e ) { } return minItem; } /** * Test if the priority queue is logically empty. * @return true if empty, false otherwise. */ public boolean isEmpty( ) { return currentSize == 0; } /** * Test if the priority queue is logically full. * @return true if full, false otherwise. */ public boolean isFull( ){ return currentSize == capacity( ); } /** * Make the priority queue logically empty. */ public void makeEmpty( ){ currentSize = 0; for( int i = 0; i < theTrees.length; i++ ) theTrees[ i ] = null; } private static final int MAX_TREES = 14; private int currentSize; // # items in priority queue private BinomialNode [ ] theTrees; // An array of tree roots /** * Return the capacity. */ private int capacity( ){ return ( 1 << theTrees.length ) - 1; } }
以上是关于二项队列的主要内容,如果未能解决你的问题,请参考以下文章