• 二项队列


    虽然左式堆每次操作花费O(logN),这有效的支持了合并,插入和DeleteMin,但还是有改进的余地,因为我们知道,二叉堆以每次操作花费常数平均时间支持插入。二项队列支持所有这种操作,每次操作的最坏情形运行时间为O(logN),而插入操作平均花费常数时间。

    二项队列结构

    二项队列不同于左式堆和二叉堆等优先队列的实现之处在于,一个二项队列不是一棵堆序的树,而是堆序树的集合,即森林。堆序树中的每棵树都是由约束形式的,叫做二项树。每一个高度上至多存在一棵二项树。高度为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没出现。

    二项队列操作

    此时,因为二项队列也满足了堆序性,故最小元可以通过搜索所有的树根来找出,由于最多有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树合并

    图3:最终结果

     

    插入实际上就是合并的特殊情形,我们只要创建一棵单节点的树并执行一次合并,这种操作的最坏情形运行时间也是O(logN)。更准确的说,如果元素将要插入的那个优先队列不存在的最小的二项树是Bi,那么运行时间与i+1成正比。图4和图5演示通过依序插入1到7来构成一个二项队列。

                  在1插入之后                        在2插入之后                     在3插入之后                    在4插入之后

                 在5插入之后                                                 在6插入之后                              在7插入之后

     

    DeleteMin可以通过首先找出一棵具有最小根的二项树来完成,令该树为Bk,并令原有的优先队列为H,我们从H的树的森林中除去二项树Bk,形成新的二项队列H1。再出去Bk的根,得到一些二项树B0,B1,B2,….Bk-1,它们共同形成了优先队列H2,合并H1,H2,操作结束。

    图6,图7和图8演示了对H3执行一次DeleteMin。

    图6:二项队列H3,最小元为12

     

                               

                 图7:二项队列H‘,包含除B3外H3中所有的二项树          图8:二项队列H'',除去12之后的B3

    图9:DeleteMin(H3)的结果

     

    二项队列实现

    DeleteMin操作需要快四找出根的所有子树的能力,因此,需要一般树的标准表示方法:每个节点的儿子都在一个链表中,每个节点都有一个指向它的第一个儿子的指针。该操作还要求,诸儿子按照他们的子树大小排序。当两棵树被合并时,其中的一棵树最为儿子被加到另一棵树上。由于这棵新树将是最大的子树,因此,以大小递减的方式保持这些子树很有意义。

    总之,二项树的每一个节点将包括:数据,指向第一个儿子的指针以及右兄弟。二项树中的诸儿子以递减次序排序。

    图10解释如何表示H3中的二项队列。

    图10  二项队列H3的表示方式

     

    为了合并两个二项队列,我们需要一个例程来合并两个大小相同的二项树。图11指出两个大小相同的二项树合并时指针是如何变化的:

    图11: 合并两颗二项树

    详细代码:

    binheap.h

    typedef long ElementType;
    #define Infinity (30000L)
    #ifndef _BinHeap_H
    #define _BinHeap_H
    
    #define MaxTrees (14)  //二项队列中的二项树高度最大为13
    #define Capacity (16383) //高度0,1,2,3,...13的二项树节点数目之和
    
    struct BinNode;
    
    typedef struct BinNode *BinTree;
    struct Collection;
    typedef struct Collection *BinQueue;
    typedef BinTree Position;
    BinQueue Initialize(void);
    void Destroy(BinQueue H);
    BinQueue MakeEmpty(BinQueue H);
    BinQueue Insert(ElementType X,BinQueue H);
    ElementType DeleteMin(BinQueue H);
    BinTree CombineTrees(BinTree T1,BinTree T2);
    BinQueue Merge(BinQueue H1,BinQueue H2);
    ElementType FindMin(BinQueue H);
    int IsEmpty(BinQueue H);
    int IsFull(BinQueue H);
    #endif // _BinHeap_H
    //一个树的节点结构组成
    struct BinNode
    {
        ElementType Element;
        Position LeftChild;
        Position NextSibling;
    };
    
    struct Collection
    {
        int CurrentSize;              //所有树中的节点个数
        Position TheTrees[MaxTrees];  //指向每棵树的指针数组,其中每个元素指向一棵树
    };

    binheap.c

    #include"binheap.h"
    #include"fatal.h"
    #include<stdio.h>
    
    BinQueue Initialize(void)
    {
        BinQueue H;
        int i=0;
        H=malloc(sizeof(struct Collection));
        if(H==NULL)
        {
             FatalError("Out of space!");
        }
        H->CurrentSize=0;
        for(i=0;i<MaxTrees;i++)
            H->TheTrees[i]=NULL;
        return H;
    }
    
    static void DestroyTree(BinTree T)
    {
        if(T!=NULL)
        {
            DestroyTree(T->LeftChild);
            DestroyTree(T->NextSibling);
            free(T);
        }
    }
    
    void Destroy(BinQueue H)
    {
        int i=0;
        for(i=0;i<MaxTrees;i++)
            DestroyTree(H->TheTrees[i]);
    }
    
    BinQueue MakeEmpty(BinQueue H)
    {
        int i=0;
        Destroy(H);
        for(i=0;i<MaxTrees;i++)
        {
            H->TheTrees[i]=NULL;
        }
        H->CurrentSize=0;
        return H;
    }
    //创建一个有单个节点二项树的二项队列,与H的单个节点的二项树树进行合并
    BinQueue Insert(ElementType X,BinQueue H)
    {
        BinTree NewNode;
        BinQueue OneItem;
        NewNode=malloc(sizeof(struct BinNode));
        if(NewNode==NULL)
            FatalError("out of space!");
        NewNode->LeftChild=NewNode->NextSibling=NULL;
        NewNode->Element=X;
        OneItem=Initialize();
        OneItem->CurrentSize=1;
        OneItem->TheTrees[0]=NewNode;
        return Merge(H,OneItem);
    }
    
    ElementType FindMin(BinQueue H)
    {
        int i=0;
        ElementType MinItem=Infinity;
        if(IsEmpty(H))
            Error("empty");
        for(i=0;i<MaxTrees;i++)
        {
            if(H->TheTrees[i]&&H->TheTrees[i]->Element<MinItem)
                MinItem=H->TheTrees[i]->Element;
        }
        return MinItem;
    }
    
    int IsEmpty(BinQueue H)
    {
        return H->CurrentSize==0;
    }
    
    int IsFull(BinQueue H)
    {
        return H->CurrentSize==Capacity;
    }
    
    BinTree CombineTrees(BinTree T1, BinTree T2)
    {
        if(T1->Element>T2->Element)
            return CombineTrees(T2,T1);
        T2->NextSibling=T1->LeftChild;
        T1->LeftChild=T2;
        return T1;
    }
    
    BinQueue Merge(BinQueue H1, BinQueue H2)
    {
        BinTree T1,T2,Carry=NULL;
        int i=0,j=0;
        //首先判断合并是否会超出二项队列限制的大小
        if(H1->CurrentSize+H2->CurrentSize>Capacity)
        {
            FatalError("Out of space!!");
        }
        H1->CurrentSize+=H2->CurrentSize;
    //遍历H1,H2中所有的二项树  
    for(i=0,j=1;j<=H1->CurrentSize;++i,j*=2) { T1=H1->TheTrees[i]; T2=H2->TheTrees[i];
     //若T1为空,!!T1则为0,否则为1  
    switch(!!T1+2*(!!T2)+4*(!!Carry)) { case 0: case 1: break; case 2:
            //只有T2存在,直接将T2放入二项队列H1中对应的位置; H1
    ->TheTrees[i]=T2; H2->TheTrees[i]=NULL; case 3:
             //T1与T2均存在,合并相同大小的二项树   Carry
    =CombineTrees(T1,T2); H1->TheTrees[i]=NULL; H2->TheTrees[i]=NULL; break; case 4:
            //由上一步合并而得的二项树作为二项队列H1的一部分   H1
    ->TheTrees[i]=Carry; Carry=NULL; break; case 5: Carry=CombineTrees(T1,Carry); H1->TheTrees[i]=NULL; break; case 6: Carry=CombineTrees(T2,Carry); H2->TheTrees[i]=NULL; break; case 7: H1->TheTrees[i]=Carry; Carry=CombineTrees(T1,T2); H2->TheTrees[i]=NULL; break; } } return H1; } /* DeleteMin可以通过首先找出一棵具有最小根的二项树来完成。令该树为Bk,并令原来的优先队列为H。我们从H的树的森林中除去二项树Bk,形成新的二项树队列H‘。再除去Bk的 根,得到一些二项树B0,B1,B2...Bk-1,他们共同形成优先队列H''.合并H’和H‘’操作结束。 */ ElementType DeleteMin(BinQueue H) { int i=0,j=0; int MinTree; BinQueue DeletedQueue; Position DeletedTree,OldRoot; ElementType MinItem; if(IsEmpty(H)) { Error("error!"); } MinItem=Infinity; for(i=0;i<MinTree;i++) { if(H->TheTrees[i]&&H->TheTrees[i]->Element<MinItem) { MinItem=H->TheTrees[i]->Element; MinTree=i; } } DeletedTree=H->TheTrees[MinTree]; OldRoot=DeletedTree; DeletedTree=DeletedTree->LeftChild; free(OldRoot);//删除根元素 DeletedQueue=Initialize(); //将1左移MinTree位,即得到高度为MinTree的二项树的大小 //因为高度为k的二项树的大小是2^k,减一是因为删除了根 DeletedQueue->CurrentSize=(1<<MinTree)-1; for(j=MinTree-1;j>0;j--) { DeletedQueue->TheTrees[j]=DeletedTree;//作为单独的一颗二项树,加入删除的二项 队列中 DeletedTree=DeletedTree->NextSibling;//记录下一棵子树 DeletedQueue->TheTrees[j]->NextSibling=NULL;//删除根之后的每个子树形成单独的二项树 } H->TheTrees[MinTree]=NULL; H->CurrentSize-=DeletedQueue->CurrentSize+1;//新二项队列的大小;   Merge(H,DeletedQueue); return MinItem; }
  • 相关阅读:
    【转】C++ ZLib压缩与解压缩
    【转】Log4j 配置最全说明
    【转】每个程序员都必须遵守的编程原则
    【转】MySQL忘记root密码
    【转】OpenGL开发环境配置
    Tomcat不能启动注意事项
    Tomcat不能启动注意事项
    Android通过php插入查询SQL数据库
    Ehcache学习总结(3)--Ehcache 整合Spring 使用页面、对象缓存
    Ehcache学习总结(3)--Ehcache 整合Spring 使用页面、对象缓存
  • 原文地址:https://www.cnblogs.com/wuchanming/p/3812075.html
Copyright © 2020-2023  润新知