可并堆的实现

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

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("\n\n第二个二叉堆H2:");
    PrintBinHeap(H2);
 
    BinHeap H3 = NULL;
    H3 = BinHeapUnion(H1, H2);
    puts("\n\n合并H1,H2后,得到H3:");
    PrintBinHeap(H3);
 
    BinHeap H4 = NULL;
    H4 = MakeBinHeapWithArray(kp4, 22);
    puts("\n\n用于测试提取和删除的二叉堆H4:");
    PrintBinHeap(H4);
 
    BinHeap extractNode = BinHeapExtractMin(H4);
    if (extractNode != NULL) {
        printf("\n\n抽取最小的值%d后:\n", extractNode->key);
        PrintBinHeap(H4);
    }
 
    extractNode = BinHeapExtractMin(H4);
    if (extractNode != NULL) {
        printf("\n\n抽取最小的值%d后:\n", extractNode->key);
        PrintBinHeap(H4);
    }
 
    extractNode = BinHeapExtractMin(H4);
    if (extractNode != NULL) {
        printf("\n\n抽取最小的值%d后:\n", extractNode->key);
        PrintBinHeap(H4);
    }
 
    BinHeapDelete(H4, 12);
    puts("\n\n删除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短路.时间复杂度$V\log{V}+E+K\log{k}$.

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

2.配对堆

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

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

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

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

 

 

 

转载于:https://www.cnblogs.com/tmzbot/p/4470349.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值