zoukankan      html  css  js  c++  java
  • 可并堆的实现

    我要写三种可并堆,然后讲解它们.

    1. 二项堆
    2. 配对堆
    3. RP堆

    这三种堆都有一个共同特点,就是用一棵半满的树表示子堆,用一系列子堆表示整个堆(当然子堆是有限制的).那么我们可以很方便的进行合并两个堆等操作.

    1.二项堆

    这三种堆里最简单的就是二项堆了(Binomial Heap).为什么称为二项堆呢?二项堆这个小婊杂特别有趣,它的每个子堆大小都为2的幂.因此,它每次合并两个大小相等的堆,方法就像下面这张图.

    那么手玩一下就可以得知,合并后两棵树其中的一棵向下偏了一位,层数多了一层.

    那么深度为一时每层节点数是1(就一层)

    深度为二时 1 1

    3) 1 2 1
    4) 1 3 3 1
    5) 1 4 6 4 1

    似曾相识?就是杨辉三角形.杨辉三角形的本质是什么?二项式.那么这个就被称为二项堆了.

    二项堆的合并(merge)

    二项堆的合并不难.我们只需要把所有深度相同的堆合并就可以了(还要用sibling指针串起来).

    二项堆的插入(Insert)

    新建一个二项堆,与原二项堆合并即可.

    二项堆的最小值(Extract_min)

    我们只需要边合并边维护即可.

    二项堆的删除(Delete)

    我们把以min为root的子堆提取出来,在原堆中删去,注意这是一棵只有左子节点的树.我们将左子结点这棵树当成二项堆,其中右子结点路径当成sibling指针(其实完全不用`当成'),合并就行了.

    二项堆实现

    http://dsqiu.iteye.com/blog/1714961 这位大神实现了这三种堆(Bino,Fib,Pairing),但是我不建议看这个.

    他的代码

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<cstdlib>
    #include<climits>
    using namespace std;
     
    typedef struct BinHeapNode BinNode;
    typedef struct BinHeapNode * BinHeap;
    typedef struct BinHeapNode * Position;
     
    //结点ADT
    struct BinHeapNode {
        int key;
        int degree;
        Position parent;
        Position leftChild;
        Position sibling;
    };
     
    //用数组内的值建堆
    BinHeap MakeBinHeapWithArray(int keys[], int n);
     
    //两个堆合并
    BinHeap BinHeapUnion(BinHeap &H1, BinHeap &H2);
     
    //将H1, H2的根表合并成一个按度数的单调递增次序排列的链表
    BinHeap BinHeapMerge(BinHeap &H1, BinHeap &H2);
     
    //使H2成为H1的父节点
    void BinLink(BinHeap &H1, BinHeap &H2);
     
    //返回最小根节点的指针
    BinHeap BinHeapMin(BinHeap heap);
     
    //减少关键字的值
    void BinHeapDecreaseKey(BinHeap heap, BinHeap x, int key);
     
    //删除一个关键字
    BinHeap BinHeapDelete(BinHeap &heap, int key);
     
    //找出一个关键字
    BinHeap BinHeapFind(BinHeap &heap, int key);
     
    //打印输出堆结构
    void PrintBinHeap(BinHeap heap);
     
    //销毁堆
    void DestroyBinHeap(BinHeap &heap);
     
    //用数组内的值建堆
    BinHeap MakeBinHeapWithArray(int keys[], int n) {
        BinHeap heap = NULL, newHeap = NULL;
        for (int i = 0; i < n; i++) {
            newHeap = (BinHeap) malloc(sizeof(BinNode));
            if (newHeap == NULL) {
                puts("Out of the Space");
                exit(1);
            }
            memset(newHeap, 0, sizeof(BinNode));
            newHeap->key = keys[i];
            if (NULL == heap) {
                heap = newHeap;
            } else {
                heap = BinHeapUnion(heap, newHeap);
                newHeap = NULL;
            }
        }
        return heap;
    }
     
    //两个堆合并
    BinHeap BinHeapUnion(BinHeap &H1, BinHeap &H2) {
        Position heap = NULL, pre_x = NULL, x = NULL, next_x = NULL;
        heap = BinHeapMerge(H1, H2);
        if (heap == NULL) {
            return heap;
        }
     
        pre_x = NULL;
        x = heap;
        next_x = x->sibling;
     
        while (next_x != NULL) {
            if ((x->degree != next_x->degree) ||//Cases 1 and 2
                ((next_x->sibling != NULL) && (next_x->degree == next_x->sibling->degree))) {
                    pre_x = x;
                    x = next_x;
            } else if (x->key <= next_x->key) {//Cases 3
                x->sibling = next_x->sibling;
                BinLink(next_x, x);
            } else {//Cases 4
                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;
    }
     
    //将H1, H2的根表合并成一个按度数的单调递增次序排列的链表
    BinHeap BinHeapMerge(BinHeap &H1, BinHeap &H2) {
        //heap->堆的首地址,H3为指向新堆根结点
        BinHeap heap = NULL, firstHeap = NULL, secondHeap = NULL,
            pre_H3 = NULL, H3 = NULL;
     
        if (H1 != NULL && H2 != NULL){
            firstHeap = H1;
            secondHeap = H2;
            //整个while,firstHeap, secondHeap, pre_H3, H3都在往后顺移
            while (firstHeap != NULL && secondHeap != NULL) {
                if (firstHeap->degree <= secondHeap->degree) {
                    H3 = firstHeap;
                    firstHeap = firstHeap->sibling;
                } else {
                    H3 = secondHeap;
                    secondHeap = secondHeap->sibling;
                }
     
                if (pre_H3 == NULL) {
                    pre_H3 = H3;
                    heap = H3;
                } else {
                    pre_H3->sibling = H3;
                    pre_H3 = H3;
                }
                if (firstHeap != NULL) {
                    H3->sibling = firstHeap;
                } else {
                    H3->sibling = secondHeap;
                }
            }//while
        } else if (H1 != NULL) {
            heap = H1;
        } else {
            heap = H2;
        }
        H1 = H2 = NULL;
        return heap;
    }
     
    //使H2成为H1的父节点
    void BinLink(BinHeap &H1, BinHeap &H2) {
        H1->parent = H2;
        H1->sibling = H2->leftChild;
        H2->leftChild = H1;
        H2->degree++;
    }
     
    //返回最小根节点的指针
    BinHeap BinHeapMin(BinHeap heap) {
        Position y = NULL, x = heap;
        int min = INT_MAX;
     
        while (x != NULL) {
            if (x->key < min) {
                min = x->key;
                y = x;
            }
            x = x->sibling;
        }
        return y;
    }
     
    //抽取有最小关键字的结点
    BinHeap BinHeapExtractMin(BinHeap &heap) {
        BinHeap pre_y = NULL, y = NULL, x = heap;
        int min = INT_MAX;
        while (x != NULL) {
            if (x->key < min) {
                min = x->key;
                pre_y = y;
                y = x;
            }
            x = x->sibling;
        }
     
        if (y == NULL) {
            return NULL;
        }
     
        if (pre_y == NULL) {
            heap = heap->sibling;
        } else {
            pre_y->sibling = y->sibling;
        }
     
        //将y的子结点指针reverse
        BinHeap H2 = NULL, p = NULL;
        x = y->leftChild;
        while (x != NULL) {
            p = x;
            x = x->sibling;
            p->sibling = H2;
            H2 = p;
            p->parent = NULL;
        }
     
        heap = BinHeapUnion(heap, H2);
        return y;
    }
     
    //减少关键字的值
    void BinHeapDecreaseKey(BinHeap heap, BinHeap x, int key) {
        if(key > x->key) {
            puts("new key is greaer than current key");
            exit(1); //不为降键
        }
        x->key = key;
     
        BinHeap 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;
        }
    }
     
    //删除一个关键字
    BinHeap BinHeapDelete(BinHeap &heap, int key) {
        BinHeap x = NULL;
        x = BinHeapFind(heap, key);
        if (x != NULL) {
            BinHeapDecreaseKey(heap, x, INT_MIN);
            return BinHeapExtractMin(heap);
        }
        return x;
    }
     
    //找出一个关键字
    BinHeap BinHeapFind(BinHeap &heap, int key) {
        Position 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;
    }
     
    //打印输出堆结构
    void PrintBinHeap(BinHeap heap) {
        if (NULL == heap) {
            return ;
        }
        Position p = heap;
     
        while (p != NULL) {
            printf(" (");
            printf("%d", p->key);
            //显示其孩子
            if(NULL != p->leftChild) {
                PrintBinHeap(p->leftChild);
            }
            printf(") ");
     
            p = p->sibling;
        }
    }       
     
    int kp1[8] = {12,
                   7, 25,
                  15, 28, 33, 41};
     
    int kp2[20] = {18,
                    3, 37,
                    6, 8, 29, 10, 44, 30, 23, 2, 48, 31, 17, 45, 32, 24, 50, 55};
     
    int kp4[23] = {37, 41,
                   10, 28, 13, 77,
                   1, 6, 16, 12, 25, 8, 14, 29, 26, 23, 18, 11, 17, 38, 42, 27};
    int main() {
        BinHeap H1 = NULL;
        H1 = MakeBinHeapWithArray(kp1, 7);
        puts("第一个二叉堆H1:");
        PrintBinHeap(H1);
     
        BinHeap H2 = NULL;
        H2 = MakeBinHeapWithArray(kp2, 19);
        puts("
    
    第二个二叉堆H2:");
        PrintBinHeap(H2);
     
        BinHeap H3 = NULL;
        H3 = BinHeapUnion(H1, H2);
        puts("
    
    合并H1,H2后,得到H3:");
        PrintBinHeap(H3);
     
        BinHeap H4 = NULL;
        H4 = MakeBinHeapWithArray(kp4, 22);
        puts("
    
    用于测试提取和删除的二叉堆H4:");
        PrintBinHeap(H4);
     
        BinHeap extractNode = BinHeapExtractMin(H4);
        if (extractNode != NULL) {
            printf("
    
    抽取最小的值%d后:
    ", extractNode->key);
            PrintBinHeap(H4);
        }
     
        extractNode = BinHeapExtractMin(H4);
        if (extractNode != NULL) {
            printf("
    
    抽取最小的值%d后:
    ", extractNode->key);
            PrintBinHeap(H4);
        }
     
        extractNode = BinHeapExtractMin(H4);
        if (extractNode != NULL) {
            printf("
    
    抽取最小的值%d后:
    ", extractNode->key);
            PrintBinHeap(H4);
        }
     
        BinHeapDelete(H4, 12);
        puts("
    
    删除12后:");
        PrintBinHeap(H4);
        return 0;
    }
    

    这份代码很长,而且其逻辑并不清晰.

    我的代码

    其实二项堆很好写的,只要你理解了,它需要考虑的细节甚至比二叉堆还少.

    typedef struct heapNode hpn;
    typedef struct heapNode* hpp;
    typedef struct heapManager_simple hsimple;
    
    struct heapNode{
    	int value,rank;
    	hpp child,sibling,parent;
    };
    
    inline hpp hnalloc(){
    	return malloc(sizeof hpn);
    }
    
    struct heapManager_simple{
    	hpp root,min,rank[22];
    	inline hpp makeHeap(int n){
    		root=min=hnalloc();
    		memset(root,0,sizeof root);
    		root->value=n;
    		rank[0]=root;
    	}
    	inline hpp extract_min(){
    		return min;
    	}
    	inline void assign(hpp rt){//this method requires a O(log n) search for the min root
    		memset(rank,0,sizeof rank);
    		root=min=rt;
    		while(rt){
    			rank[rt->rank]=rt;
    			if(rt->value < min->value) min=y;
    			rt=rt->sibling;
    		}
    	}
    	inline hpp link(hpp a,hpp b){
    		if(b->value < a->value) std::swap(a,b);
    		++a->rank;
    		b->parent=a;
    		b->sibling=a->child;
    		a->child=b;
    		return a;
    	}
    	inline hpp merge(hpp A){
    		hpp b=A;
    		while(A){
    			A=A->sibling;
    			while(rank[b->rank]){
    				b=link(rank[b->rank],b);
    				if(!min || min->value > b->value) min=b;
    				rank[b->rank]=NULL;
    			}
    		}
    		return root;
    	}
    	inline void gen_sibling(){
    		root=NULL;
    		for(int i=21;~i;--i){
    			if(rank[i]){
    				rank[i]->sibling=root;
    				root=rank[i];
    			}
    		}
    	}
    	hpp insert(int n){
    		hpp b=hnalloc();
    		memset(b,0,sizeof b);
    		b->value=n;
    		merge(b);
    		gen_sibling();
    		return b;
    	}
    	hpp delete_top(){
    		rank[min->rank]=NULL;
    		hpp a=min->child;
    		free(min);
    		min=NULL;
    		merge(a);
    		gen_sibling();
    	}
    };

    我的代码用了一种特殊的维护方法.我们知道每个子堆的大小是2rank,那么我们就可以直接用数组存不同rank的子堆,每次操作完再用gen_sibling统一生成sibling指针.用这个技巧可以省掉很多代码,而且可以简化逻辑,处理掉细节.

    二项堆是严格log n的,可以可持久化,可以拿来搞K短路.时间复杂度$Vlog{V}+E+Klog{k}$.

    Decrease_key很好写,就和二叉堆差不多,就不写代码了.(其实是我忘记加了)

    2.配对堆

    配对堆是一种自适应的可并堆.

    配对堆的思想基于对于一个堆来说,树越高则效率越好(假设有堆的限制).如果变成一条链,那就已经排好序了.

    配对堆的合并方法与rank值无关.从这点上来说,配对堆就好像可并堆中的Splay.

    配对堆可以与Dijkstra很好地配合,时间复杂度$O(E+Vlog{V})$.

     

  • 相关阅读:
    [BZOJ2038]小Z的袜子
    [BZOJ5016]一个简单的询问
    [BZOJ1008][HNOI2008]越狱
    [FZU2254]英语考试
    利用Map 的merge方法统计数量
    List 原生态类型
    try-with-resource 关闭 io流
    利用构建器创建对象
    linux 安装 vault
    git 上传文件
  • 原文地址:https://www.cnblogs.com/tmzbot/p/4470349.html
Copyright © 2011-2022 走看看