大家好,这是我在CSDN上的第一篇文章,如果有什么不恰当的地方,请各位大神们指出。
最近都在学可并堆去了,挺有感想,所以给广大朋友们分享一下。
今天就讲讲3种吧:左偏树,斜堆,随机堆
声明:
1.转载请声明出处
2.此文章中所有代码是本人亲手编写,如有雷同,纯属巧合
我们先来了解一下堆:
支持在(lg n) 时间内完成以下操作的数据结构:
1.插入一个值
2.取出其中的最小(大)值
这显然就是二叉堆的工作了,那么 可并堆是干啥的?普通的二叉堆不就行了吗?下面这个操作就是二叉堆无法完成的了:
3.将两个堆合并
这时,二叉堆就必须一个一个节点的插入,时间为O(n lg n)
所以,可并堆开始发挥它的神奇作用了!下面为大家一一讲解:
1.斜堆
先来看看它的ADT:
struct SkewHeap{
SkewHeap* lch;
SkewHeap* rch;
int key;
SkewHeap(int n=0){
key=n;
lch=rch=0;
}
};
怎么好像和二叉堆没啥区别?别急,我们慢慢来。
它和二叉堆的区别:
这里用了链表的储存方式,而不是普通的堆方式,因为斜堆需要合并,所以并不是平衡的,而且有可能一边重一边轻,这样,我们就写出了第一个合并的代码(递归进行):
SkewHeap* Merge(SkewHeap*& a,SkewHeap*& b){
if(a==NULL) return b;
if(b==NULL) return a;
if(a->key>b->key) Swap(a,b);
a->rch=Merge(a->rch,b);
return a;
}
这里的意思就是,如果a或b有一个是空的,就返回另一个
否则在a,b中选一个较小的,让后再把他的右儿子和b合并(递归),直到a的右儿子为空后,将它的右儿子设为b
思路很简单,代码很短,但是效率呢?
我们很清楚地意识到,这样会使得整个堆的右儿子非常大,左儿子却几乎为空,非常不平衡,怎么办?
有一种很好的办法:每次插入完之后就交换左右儿子
SkewHeap* Merge(SkewHeap*& a,SkewHeap*& b){
if(!a) return b;
if(!b) return a;
if(a->key>b->key) Swap(a,b);
a->rch=Merge(a->rch,b);
Swap(a->lch,a->rch);
return a;
}
怎么样,很巧妙吧,第一次与右边边和并,下一次就是左边了,所以这时效率会大大提升(右儿子的高度决定了合并它的效率)
而且一般来说,合并后的右儿子应该是要比左儿子要大的,所以斜堆就把他们就交换了,把较小的放在右边(虽然情况往往不是这样)
那么时间复杂度是多少呢?
最坏情况,整个堆成一条链,时间O(n)
但是别紧张,这种情况在有了那条Swap语句之后根本不会有,其实它的均摊复杂度才为O(lg n) ,最坏也不超过O(4lg n) (具体证明参见《Data Structure and Problem Solving Using Java Second Edition》(Mark Allen Weiss著,电子工业出版社出版)中的784页的Theorem 23.2。
合并讲完了,那么插入和取出最小节点呢?
插入:建立一个新堆n,只包含你要插入的那个元素,让后Merge(root,n)
void Insert(SkewHeap*& root,int k){
SkewHeap* n=new SkewHeap(k);
root=Merge(root,n);
}
取出最小节点:直接将根的左右儿子合并即可,Merge(root.lch,root.rch)
int DeleteMin(SkewHeap*& root){
//if(!root) return 0x80000000;
int k=root->key;
root=Merge(root->lch,root->rch);
return k;
}
贴个完整的代码:
#include
struct SkewHeap{
SkewHeap* lch;
SkewHeap* rch;
int key;
SkewHeap(int n=0){
key=n;
lch=rch=0;
}
};
template
void Swap(_& a,_& b){
_ c=a;a=b;b=c;
}
SkewHeap* Merge(SkewHeap*& a,SkewHeap*& b){
if(!a) return b;
if(!b) return a;
if(a->key>b->key) Swap(a,b);
a->rch=Merge(a->rch,b);
Swap(a->lch,a->rch);
return a;
}
void Insert(SkewHeap*& root,int k){
SkewHeap* n=new SkewHeap(k);
root=Merge(root,n);
}
int Min(SkewHeap* root){
return root->key;
}
int DeleteMin(SkewHeap*& root){
int k=root->key;
root=Merge(root->lch,root->rch);
return k;
}
int main(){
SkewHeap *s;int n;
scanf("%d",&n);
while(n--){getchar();
char c=getchar();
if(c=='A') {
int a;
scanf("%d",&a);
Insert(s,a);
}
else {
printf("%d\n",DeleteMin(s));
}
}
}
2.左偏树
左偏树的ADT:
struct LeftHeap{
LeftHeap* lch;
LeftHeap* rch;
int key,npl;
LeftHeap(int n=0){
key=n;npl=1;
lch=rch=0;
}
};
那么维护了npl之后,我们就不用再乱交换了,而且保证了左儿子大于右儿子的npl,所以效率又会提升。
LeftHeap* Merge(LeftHeap*& a,LeftHeap*& b){
if(!a) return b;
if(!b) return a;
if(a->key>b->key) Swap(a,b);
a->rch=Merge(a->rch,b);
if(!a->lch||a->lch->nplrch->npl)
Swap(a->lch,a->rch);
if(!a->rch) a->npl=0;else
a->npl=a->rch->npl+1;
return a;
}
让我来解释一下:
#include
struct LeftHeap{
LeftHeap* lch;
LeftHeap* rch;
int key,npl;
LeftHeap(int n=0){
key=n;npl=1;
lch=rch=0;
}
}* root;
template
void Swap(_& a,_& b){
_ c=a;a=b;b=c;
}
LeftHeap* Merge(LeftHeap*& a,LeftHeap*& b){
if(!a) return b;
if(!b) return a;
if(a->key>b->key) Swap(a,b);
a->rch=Merge(a->rch,b);
if(!a->lch||a->lch->nplrch->npl)
Swap(a->lch,a->rch);
if(!a->rch) a->npl=0;else
a->npl=a->rch->npl+1;return a;
}
void Insert(LeftHeap*& root,int k){
LeftHeap* n=new LeftHeap(k);
root=Merge(root,n);
}
int Min(LeftHeap*& root){
return root->key;
}
int DeleteMin(LeftHeap*& root){
int k=root->key;
root=Merge(root->lch,root->rch);
return k;
}
int main(){
LeftHeap *s;int n;
scanf("%d",&n);
while(n--){getchar();
char c=getchar();
if(c=='A') {
int a;
scanf("%d%d",&a);
Insert(s,a);
} else {
printf("%d\n",DeleteMin(s[a]));
}
}
}
3.随机堆
struct RandHeap{
RandHeap* lch;
RandHeap* rch;
int key;
RandHeap(int n=0){
key=n;
lch=rch=0;
}
};
然后就是神奇的Merge了:
RandHeap* Merge(RandHeap*& a,RandHeap*& b){
if(!a) return b;
if(!b) return a;
if(a->key>b->key) Swap(a,b);
if(rand()%2)
a->rch=Merge(a->rch,b);
else a->lch=Merge(a->lch,b);
return a;
}
如果觉得长,这里还有一个短的:
RandHeap* Merge(RandHeap*& a,RandHeap*& b){
return !(a&&b)?(a?a:b):
(a->keykey?
(rand()%2?Merge(a->lch,b):Merge(a->rch,b)):
(rand()%2?Merge(b->lch,a):Merge(b->rch,a)));
}
贴个完整的代码:
#include
#include
struct RandHeap{
RandHeap* lch;
RandHeap* rch;
int key;
RandHeap(int n=0){
key=n;
lch=rch=0;
}
}* root;
template
void Swap(_& a,_& b){
_ c=a;a=b;b=c;
}
RandHeap* Merge(RandHeap*& a,RandHeap*& b){
if(!a) return b;
if(!b) return a;
if(a->key>b->key) Swap(a,b);
if(rand()%2)
a->rch=Merge(a->rch,b);
else a->lch=Merge(a->lch,b);
return a;
}
void Insert(RandHeap*& root,int k){
RandHeap* n=new RandHeap(k);
root=Merge(root,n);
}
int Min(RandHeap*& root){
return root->key;
}
int DeleteMin(RandHeap*& root){
if(!root) return 0x80000000;
int k=root->key;
root=Merge(root->lch,root->rch);
return k;
}
int main(){
RandHeap *s;int n;
scanf("%d",&n);
while(n--){getchar();
char c=getchar();
if(c=='A') {
int a;
scanf("%d",&a);
Insert(s,b);
} else {
printf("%d\n",DeleteMin(s));
}
}
}
以上就是3种可并堆的讲解,最后贴个图吧