二项堆

二项堆是可合并堆的数据结构,应该功能应该类似左偏树。

二项树

二项树Bk是一种递归定义的有序树,如下图所示。

a)二项树Bk的递归定义,三角形表示有根的子树,b)二项树B0至B4,B4中显示出了各节点的深度,c)以另一种方式来看二项树Bk

二项树的性质:

1)共有2k个结点。

2)树的高度为k。

3)在深度i处恰有Cik个结点。

4)根的度数(子女的个数)为k,它大于任何其他结点的度数;如果根的子女从左到右的编号设为k-1, k-2, …, 0,子女i是子树Bi的根。

二项堆

二项堆H是由一组满足下面的二项堆性质的二项树组成:
1)H中的每个二项树遵循最小堆性质:节点的关键字大于或等于其父节点的关键字。
2)对任意非负数k,在H中至多有一颗二项树的根具有度数k。
第一个性质,我们知道在一棵最小堆有序的二项树中,其根包含了树中最小的关键字。
第二个性质,我们知道在包含n个节点的二项堆H中,包含至多floor(lgn)+1棵二项树。

二项堆的表示
每个节点x包含指向其父亲节点的指针parent,指向其最左孩子的指针leftchild,以及指向x的紧右兄弟的指针sibling。每个节点都包含域degree,表示x的子女个数
如图,在一个二项堆中的个二项树的根被组织成一个链表,称为根表。

二项树的ADT:

struct BinHeapNode
{
    int key,degree;//key是键值,degree表示子女个数
    BinHeapNode *parent,*leftChild,*sibling;
    //leftChild是节点x的最左孩子指针,sibling是指向x的紧右兄弟的指针
    //也就是说,每层节点都有指针指向其右边的兄弟节点,如果某个节点是最右边的,那么sibling是null
};

对二项堆的操作

1)寻找最小关键字

由于二项树符合最小堆的性质,那么二项堆的最小关键字就在其根表中
伪代码:Binomial-Heap-Minimum(H)
1  y ← NIL
2  x ← head[H]
3  min ← ∞
4  while x ≠ NIL
5     do if key[x] < min
6           then min ← key[x]
7                y ← x
8         x ← sibling[x]
9  return y
//返回最小关键字的指针
BinHeapNode* BinHeapMin(BinHeapNode *heap)
{
    BinHeapNode *y = NULL,*x = heap;
    int min = 1<<30;
    //由于符合最小堆的性质,所有最小值就在根节点那一行
    while(x != NULL)
    {
        if(x->key < min)
        {
            y = x;
            min = x->key;
        }
        x = x->sibling;//向右边走,到另一个二项树的根
    }
    return y;
}

2)合并两个二项堆

Binomial-Heap-Union是反复连接根节点度数相同的各二项树。下面过程是,将以节点y为根的Bk-1树与以节点z为根的Bk-1树连接起来,即:它使z成为y的父节点,并成为一棵Bk树的根。
BINOMIAL-LINK(y, z)
1  p[y] ← z
2  sibling[y] ← child[z]
3  child[z] ← y
4  degree[z] ← degree[z] + 1
合并操作分为两个阶段,第一阶段是执行Binomial-Heap-Merge,将二项堆H1和H2的根表归并排序成一个链表H,按照度数单调递增排序。
第二阶段是将度数相等的根合并,直到每个度数的根至多为一个为止。
合并阶段分为四种情况:当遍历到根表节点x的时候
Case 1:degree[x] != degree[sibling[x]],那么这时只需要将指针向右移动即可
Case 2:当x为具有相同度数的三个根中的第一个的时候,即degree[x] == degree[sibling[x]] == degree[sibling[sibling[x]]],这时候处理的情况同Case 1相同,当下次迭代的时候将执行Case 3或Case 4,把三个相等度数节点的后两个合并起来
Case 3 Case4 都是degree[x] == degree[sibling[x]] != degree[sibling[sibling[x]]],这时候按照关键字的大小进行合并
Case 3:key[x] <= key[sibling[x]]那么就将sibling[x]连接到x上,x作为父节点
Case 4:key[x] > key[sibling[x]]那么就将x连接到sibling[x]上,sibling[x]作为父节点
时间复杂度是O(logn),四种Case如下

一个完整的合并操作示意图


//将以H1为根的树和以H2为根的树连接,使H2变成H1的父节点
void BinLink(BinHeapNode *&H1,BinHeapNode *&H2)
{
    H1->parent = H2;
    H1->sibling = H2->leftChild;//向右连接H2的最左孩子
    H2->leftChild = H1;//更新H2的最左孩子
    H2->degree++;//H2的degree++
}
BINOMIAL-HEAP-MERGE ,将H1和H2的根表合并成一个按度数的单调递增次序排列的链表
//将二项堆H1和H2的根表合并成一个按度数(degree)单调递增次序的链表,合并到H3然后给heap
//类似于归并排序过程
BinHeapNode* BinHeapMerge(BinHeapNode *&H1,BinHeapNode *&H2)
{
		BinHeapNode *heap = NULL, *fHeap=NULL,*sHeap=NULL,*pre_H3=NULL,*H3=NULL;
        fHeap = H1;
        sHeap = H2;
        while(fHeap != NULL && sHeap != NULL)
        {
            if(fHeap->degree <= sHeap->degree)
            {
                H3 = fHeap;
                fHeap=fHeap->sibling;//向右走
            }
            else
            {
                H3 = sHeap;
                sHeap = sHeap->sibling;//向右走
            }
            if(pre_H3 == NULL)//第一次
            {
                pre_H3 = H3;//把首节点给pre_H3
                heap = H3;//给heap
            }
            else
            {
                pre_H3->sibling = H3;//pre_H3->sibling = H3
                pre_H3 = H3;
            }
        }//while
        if(fHeap != NULL) H3->sibling = fHeap;
        else H3->sibling = sHeap;
		return heap;
}
Binomil-Heap-Union伪代码:
1  H ← MAKE-BINOMIAL-HEAP()
2  head[H] ← BINOMIAL-HEAP-MERGE(H1, H2)
3  free the objects H1 and H2 but not the lists they point to
4  if head[H] = NIL
5     then return H
6  prev-x ← NIL
7  x ← head[H]
8  next-x ← sibling[x]
9  while next-x ≠ NIL
10      do if (degree[x] ≠ degree[next-x]) or
                (sibling[next-x] ≠ NIL and degree[sibling[next-x]] = degree[x])
11            then prev-x ← x                                ▹ Cases 1 and 2
12                 x ← next-x                                ▹ Cases 1 and 2
13            else if key[x] ≤ key[next-x]
14                    then sibling[x] ← sibling[next-x]          ▹ Case 3
15                         BINOMIAL-LINK(next-x, x)               ▹ Case 3
16                    else if prev-x = NIL                        ▹ Case 4
17                            then head[H] ← next-x              ▹ Case 4
18                            else sibling[prev-x] ← next-x       ▹ Case 4
19                         BINOMIAL-LINK(x, next-x)               ▹ Case 4
20                         x ← next-x                            ▹ Case 4
21         next-x ← sibling[x]
22  return H
BinHeapNode* BinHeapUnion(BinHeapNode *&H1,BinHeapNode *&H2)
{
    BinHeapNode *heap = NULL,*pre_x = NULL,*x=NULL,*next_x=NULL;
    heap = BinHeapMerge(H1,H2);
    if(heap == NULL) return heap;

    x = heap;
    next_x = x->sibling;
    while(next_x != NULL)
    {
        //case1: degree[x] != degree[sibling[x]]
        if((x->degree!=next_x->degree)
           //case2:degree[x]=degree[next_x]=degree[sibling[next_x]]
                ||(next_x->sibling!=NULL && x->degree == next_x->sibling->degree))
        {//只需要向右移
            pre_x = x;
            x = next_x;
        }
        //case3:degree[x]=degree[next_x]!=degree[sibling[next_x]]&x->key <= next_x->key
        //将sibling[x]连接到x上
        else if(x->key <= next_x->key)
        {
            x->sibling = next_x->sibling;
            BinLink(next_x,x);
        }
        else
        {//将x连接到sibling[x]上
            if(pre_x == NULL) heap = next_x;
            else pre_x->sibling = next_x;
            BinLink(x,next_x);
            x = next_x;
        }
        next_x = x->sibling;
    }
    return heap;
}

3)插入一个节点

插入节点的操作就是先构造一个只包含一个节点的二项堆H1,再在O(logn)时间内,将其与包含n个节点的二项堆H合并
BINOMIAL-HEAP-INSERT(H, x)
1  H′ ← MAKE-BINOMIAL-HEAP()
2  p[x] ← NIL
3  child[x] ← NIL
4  sibling[x] ← NIL
5  degree[x] ← 0
6  head[H′] ← x
7  H ← BINOMIAL-HEAP-UNION(H, H′)

BinHeapNode* BinHeapInsert(int key,BinHeapNode *heap)
{
    BinHeapNode *newHeap = NULL;
    newHeap = new BinHeapNode;
    memset(newHeap,0,sizeof(BinHeapNode));//初始化新节点的值,degree,leftChild,sibling等
    newHeap->key = key;
    if(heap == NULL) heap = newHeap;
    else
    {
         heap = BinHeapUnion(heap,newHeap);
         newHeap = NULL;
         delete newHeap;
    }
    return heap;
}

4)抽取具有最小关键字的节点

该过程首先需要找到最小关键字的节点,然后将该节点删除,然后将x的子节点倒序排列,重新组成一个二项堆H1,然后与原来的二项堆H合并组成新的二项堆。


//抽取具有最小关键字的节点
BinHeapNode* BinHeapExtractMin(BinHeapNode *&heap)
{
    BinHeapNode *pre_y=NULL,*y=NULL,*x=heap;
    int min = 1<<30;
    while(x != NULL)
    {
        if(x->key < min)
        {
            min = x->key;
            pre_y = y;
            y = x;
        }
        x = x->sibling;//向根表走
    }//找到最小关键字的节点
    if(y == NULL) return y;//空
    //删除最小关键字节点
    if(pre_y == NULL) heap = heap->sibling;
    else pre_y->sibling = y->sibling;

    //将x节点剩下的子女倒序排列,组成一个新的二项堆
    BinHeapNode *H2 = NULL,*p=NULL;
    x = y->leftChild;
    //这里也就是将x节点的下一层改造成一个新的二项堆
    while(x != NULL)
    {
        p = x;
        x = x->sibling;
        p->sibling = H2;
        H2 = p;
        p->parent = NULL;//由于x节点删除了就没有parent
    }
    //与之前的二项堆合并
    heap = BinHeapUnion(heap,H2);
    return y;
}

5)减小关键字的值

该过程就是更新节点的值,将某一节点的关键字值减小为一个新值K,如果K大于当前节点的关键字值,那么报错。但是注意需要维护最小堆的性质。
BINOMIAL-HEAP-DECREASE-KEY(H, x, k)
1 if k > key[x]
2    then error "new key is greater than current key"
3 key[x] ← k
4 y ← x
5 z ← p[y]
6 while z ≠ NIL and key[y] < key[z]
7     do exchange key[y] ↔ key[z]
8        ▸ If y and z have satellite fields, exchange them, too.
9        y ← z
10        z ← p[y]
//减小关键字的值,将某一节点x的值key减小为一个新值key
void BinHeapDecreaseKey(BinHeapNode *heap,BinHeapNode *x,int key)
{
    if(key > x->key) return;
    x->key = key;
    BinHeapNode *z = NULL,*y=NULL;
    //需要维护最小堆的结构
    y = x;
    z = x->parent;
    while(z != NULL && z->key > y->key)
    {
        swap(z->key,y->key);
        y = z;
        z = y->parent;//这里只需要向父节点比较
    }
}

6)删除一个关键字

BINOMIAL-HEAP-DELETE(H, x)
1  BINOMIAL-HEAP-DECREASE-KEY(H, x, -∞)
2  BINOMIAL-HEAP-EXTRACT-MIN(H)
可以看出,删除的原理非常简单,把关键字减小,让它到达根节点,然后剔除最小值即可。



//找出一个关键字
BinHeapNode* BinHeapFind(BinHeapNode *&heap, int key)
{
    BinHeapNode *p = NULL, *x = NULL;
    p = heap;
    while (p != NULL)
    {
        if (p->key == key)
        {
            return p;
        }
        else
        {
            if((x = BinHeapFind(p->leftChild, key)) != NULL)//一直向最左孩子走
            {
                return x;
            }
            p = p->sibling;
        }
    }
    return NULL;
}

//删除一个关键字
//删除的原理非常简单,把关键字减小,让它到达根节点,然后剔除最小值即可
BinHeapNode* BinHeapDelete(BinHeapNode * &heap, int key)
{
    BinHeapNode * x = NULL;
    x = BinHeapFind(heap, key);//找到该关键字节点
    if (x != NULL)
    {
        BinHeapDecreaseKey(heap, x, -(1<<30));
        return BinHeapExtractMin(heap);
    }
    return x;
}

上面代码拼接起来就可以运行了,就不贴完整的程序的了。
二项堆的代码还是比较难写的,但是理解起来还是挺简单的。




你可能感兴趣的:(数据结构)