JAVA实现二项队列

来源:互联网 发布:打阿里云的投诉电话 编辑:程序博客网 时间:2024/05/22 01:50

二项队列和其他优先队列的区别在于,它是具有一定顺序的树的集合,即森林。

它具有一个数组,每个数组元素存每棵树,通过存每棵树的根节点的引用。

数组中,0索引的那颗树存1个元素,1索引的那颗树存2个元素,以此类推。

二项队列的设计思想完全利用了二进制的原理。而其中merge函数的switch case判断也是利用二进制的原理。而二项队列中的最重要的操作,合并操作,也可以通过将两个二进制数相加来理解。

插入操作就是特殊的合并。

删除最小功能也可以说是特殊的合并:找到最小元素所在的那棵树,去掉根,这样就分解出了一个森林。

二项树从小到大举例:


兄弟孩子表示法举例:


代码之后,还有图解,帮助理解。

代码如下,可运行,应该没有错误,注意写的注释才是最重要的,帮助理解

package six;// BinomialQueue class//// CONSTRUCTION: with no parameters or a single item//// ******************PUBLIC OPERATIONS*********************// void insert( x )       --> Insert x// Comparable deleteMin( )--> Return and remove smallest item// Comparable findMin( )  --> Return smallest item// boolean isEmpty( )     --> Return true if empty; else false// void makeEmpty( )      --> Remove all items// vod merge( rhs )       --> Absord rhs into this heap// ******************ERRORS********************************// Throws UnderflowException as appropriate/** * Implements a binomial queue. * Note that all "matching" is based on the compareTo method. * @author Mark Allen Weiss */class UnderflowException extends Exception { };  public final class BinomialQueue<AnyType extends Comparable<? super AnyType>>{    /**     * Construct the binomial queue.     */    public BinomialQueue( )    {        theTrees = new BinNode[ DEFAULT_TREES ];//生成了一个node数组,这里有警告是java泛型数组无法创建的问题        //即无法通过普通方法创建泛型数组        makeEmpty( );    }    /**     * Construct with a single item.     */    public BinomialQueue( AnyType item )     {        currentSize = 1;        theTrees = new BinNode[ 1 ];//生成了一个node数组        theTrees[ 0 ] = new BinNode<>( item, null, null );    }    private void expandTheTrees( int newNumTrees )    {        BinNode<AnyType> [ ] old = theTrees;//获得自身数组的长度        int oldNumTrees = theTrees.length;        theTrees = new BinNode[ newNumTrees ];//将传进来的参数作为新数组的长度        //以下两个循环,如果新长度比旧长度大,那么两个循环都执行,新数组为旧数组加上null元素        //如果小或等于,那么只执行第一个循环,新数组为旧数组除掉后面几个元素        //但是实际只有两个情况,新长度比旧长度大1,或者新长度和旧长度一样,但情况还是上面的两种情况        for( int i = 0; i < Math.min( oldNumTrees, newNumTrees ); i++ )            theTrees[ i ] = old[ i ];        for( int i = oldNumTrees; i < newNumTrees; i++ )            theTrees[ i ] = null;    }        /**     * Merge rhs into the priority queue.     * rhs becomes empty. rhs must be different from this.     * @param rhs the other binomial queue.     */    public void merge( BinomialQueue<AnyType> rhs )    {        if( this == rhs )    // Avoid aliasing problems            return;        currentSize += rhs.currentSize;//获得size之和                if( currentSize > capacity( ) )//如果size之和大于了从最高位到最低位的树容量之和,即最高位已经不能满足        {//如果进去if,则说明容量不够            int newNumTrees = Math.max( theTrees.length, rhs.theTrees.length ) + 1;            //取二者最高位加1就行,道理同两个四位数加起来最多是个五位数            expandTheTrees( newNumTrees );//扩展树,传的参数是新的森林的树的个数,即最高位是第几位        }        BinNode<AnyType> carry = null;//进位        for( int i = 0, j = 1; j <= currentSize; i++, j *= 2 )//currentSize已经是size之和,如果在j=currentSize结束        //那么就是最高位的那棵树把所有元素都装进去了,别的位都没有树        {            BinNode<AnyType> t1 = theTrees[ i ];//遍历老数组元素,老数组可能被增长            BinNode<AnyType> t2 = i < rhs.theTrees.length ? rhs.theTrees[ i ] : null;//遍历新数组,如果到头,就为null            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;        //对于以上三行代码,不如直接调用rhs的makeEmpty函数来代替    }            /**     * Return the result of merging equal-sized t1 and t2.     */    private BinNode<AnyType> combineTrees( BinNode<AnyType> t1, BinNode<AnyType> 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.     */    public void insert( AnyType x )    {        merge( new BinomialQueue<>( x ) );//调用只有一个元素的构造参数,插入就是特殊的合并    }    /**     * Find the smallest item in the priority queue.     * @return the smallest item, or throw UnderflowException if empty.     */    public AnyType findMin( ) throws UnderflowException    {        if( isEmpty( ) )            throw new UnderflowException( );//throw到了最终的那个函数,必须有throws        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++ )//这里意思是找到第一个有树的位置,它有可能是最小位置            ;//如果第一位含有树,那么不符合循环条件,i直接=0        for( minIndex = i; i < theTrees.length; i++ )//循环比较,如果更小就更新minIndex            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 throw UnderflowException if empty.     */    public AnyType deleteMin( ) throws UnderflowException    {        if( isEmpty( ) )            throw new UnderflowException( );        int minIndex = findMinIndex( );        AnyType minItem = theTrees[ minIndex ].element;        BinNode<AnyType> deletedTree = theTrees[ minIndex ].leftChild;//除了没有根的所有链表        // Construct H''        BinomialQueue<AnyType> deletedQueue = new BinomialQueue<>( );//构造一个新的二项队列        deletedQueue.expandTheTrees( minIndex );//如果是第4位,数组索引是3,扩展到只有三颗树,刚好装下        //因为第4位的那棵树装8个元素,去掉根还有7个,前三棵树的大小=1+2+4=7,刚好装下,不过每个数组元素都是null                deletedQueue.currentSize = ( 1 << minIndex ) - 1;//道理之前讲过        for( int j = minIndex - 1; j >= 0; j-- )//如果是第4位,数组索引是3。从数据结构上看除去根还有三棵树        //三棵树构成数组,数组最大索引为3-1=2,所以minIndex - 1        {            deletedQueue.theTrees[ j ] = deletedTree;//第一次循环肯定是具有儿子最多的那颗子树,所以赋值给最大索引            deletedTree = deletedTree.nextSibling;//把下一个兄弟赋值给deletedTree,为下一次循环做准备            deletedQueue.theTrees[ j ].nextSibling = null;//赋值给数组元素后,每个根就应该没有兄弟链了        }        // Construct H'        theTrees[ minIndex ] = null;//最小根所在的那棵树,已经被分解完毕,则需要赋值为null        currentSize -= deletedQueue.currentSize + 1;//因为deletedQueue少了根,所以再加1就好了        merge( deletedQueue );//合并                return minItem;    }    /**     * Test if the priority queue is logically empty.     * @return true if empty, false otherwise.     */    public boolean isEmpty( )    {        return currentSize == 0;    }    /**     * Make the priority queue logically empty.     */    public void makeEmpty( )    {        currentSize = 0;        for( int i = 0; i < theTrees.length; i++ )            theTrees[ i ] = null;    }    private static class BinNode<AnyType>    {            // Constructors        BinNode( AnyType theElement )        {            this( theElement, null, null );        }        BinNode( AnyType theElement, BinNode<AnyType> lt, BinNode<AnyType> nt )        {            element     = theElement;            leftChild   = lt;//高度最高的儿子节点,正常表示时每个节点有几个儿子,但实际存储时只有一个儿子            nextSibling = nt;//高度略小的兄弟        }        AnyType          element;     // The data in the node        BinNode<AnyType> leftChild;   // Left child        BinNode<AnyType> nextSibling; // Right child    }    private static final int DEFAULT_TREES = 1;    private int currentSize;                // # items in priority queue在堆中有多少个元素    private BinNode<AnyType> [ ] theTrees;  // An array of tree roots森林的根有多少个,即总能知道最高位是多少    /**     * Return the capacity.     */    private int capacity( )    {        return ( 1 << theTrees.length ) - 1;//假设最高位为第n位,那么1 << (n-1)就是最高位的那颗树的容量        //但要求的是前n位即n颗树的容量和,所以(1 << n)-1刚好是容量和,根据二进制数的性质        //比如前五位,16-1=8+4+2+1    }    public static void main( String [ ] args ) throws UnderflowException    {        int numItems = 10000;        BinomialQueue<Integer> h  = new BinomialQueue<>( );        BinomialQueue<Integer> h1 = new BinomialQueue<>( );        int i = 37;        System.out.println( "Starting check." );        for( i = 37; i != 0; i = ( i + 37 ) % numItems )            if( i % 2 == 0 )                h1.insert( i );//h1全是偶数            else                h.insert( i );//h全是奇数        h.merge( h1 );        for( i = 1; i < numItems; i++ )            if( h.deleteMin( ) == i )//你会发现输入从1开始到9999                System.out.println( "Oops! " + i );         System.out.println( "Check done." );    }}


1-8的插入过程


合并两个二项队列的过程:

       



但是最终结果不对,应该说和我代码的过程不一样,因为在代码中,图6-37这颗新树作为进位赋值给carry,在最终结果中,第三个位置即含有4个元素的那棵树应该就是图6-37这颗新树。而最终结果中的那颗包含8个元素的那颗新树应该是由H1和H2中的来构成。不符合代码逻辑

合并两棵树时的链接变化过程如下(t1,t2——t1根更小):

               


删除过程如下:




原创粉丝点击