二项树及二项队列
二项队列是由二项树按照一定规则组成的森林,故先介绍二项树:
1 二项树
二项树是这样的一种树:设高度为k的树Bk有
单节点树 when k=0
高度为k的树是由上一棵高度k-1的树作为孩子附接到另一棵高度k-1的树而构成 when k~=0
例如:
分析二项树特点可知,当高度k=0时树中节点数为1
当k~=0时,设此时节点数目N(k)=2*N(k-1)=2^2N(k-2)=...=2^k*N(0)=2^k
且第d层的节点树满足二项式系数Cd/k,故命名这种树为二项树。
2 二项队列(森林)
将上述二项树多棵组成森林按照以下原则:
a.符合堆序(根值最大或最小)
b.每一高度k上最多只能有一棵树,例如有高度k=2二项树多棵时需要合并。
可以知道二项队列有如下特点:
a.它是由二项树构成的森林;
b.每种高度最多只有一棵二项树,故最多含有Log(N)棵树;
c.二项队列中每棵树按照树高度可记为B0 B1 B2。。。;
d.任意正整数可由二项队列表示如上图中表示的7=二进制111(B2B1B0)。
3 二项队列操作
搜索最小元:
可以通过搜索所有树的根来找出。由于最多有有log(N)棵树故搜索最小元耗时O(log N)
合并二项队列:
合并的核心操作是:
a 两个队列若只存在一个高度k的二项树,保留
b 两个队列若存在两个高度k相同的二项树H1中的t1和H2中的t2,要让大的根成为小的根的子树从而产生高度为k+1的类进位树记为carry
c 两个队列若只存在三个高度k的二项树,选择一个保留,剩下两个相合并
设H1 H2是两个二项队列,H3是合并之后二项队列,基本操作如下(0为不存在,1为存在):
carry t2 t1 | 表示含义 |
0 0 0 | 该高度没有树 |
0 0 1 | 该高度只有t1树 |
0 1 0 | 该高度只有t2树 |
1 0 0 | 该高度只有carry树 |
0 1 1 | 该高度t2和carry树 |
1 0 1 | 该高度t1和carry树 |
1 1 0 | 该高度t1,t2树 |
1 1 1 | 该高度t1,t2和carry树 |
合并两棵树只需常数时间,而最多log N棵树,故最多花费时间O(log N)
另外插入操作也可以归为合并,只不过是单节点的合并。以空队列插入1-7为例:
删除最小元:
主要操作:
a 找出一棵具有最小根的二项树并记为Bk,该二项队列为H
b 从H中的Bk删去根,剩下的为若干棵二项树B0,B1,B2。。。,Bk-1共同组成二项队列H11
c 从H中删去Bk,剩下的构成二项队列H22
d 合并H11 H22
合并:
查找最小和创建队列耗时O(log N),最终合并也是耗时O(log N)故最终耗时O(log N)。
4 二项队列C++实现
右上面分析可知,删除最小值需要快速找出根的所有子树,故每一个二项树表示方法采用左孩子右兄弟方法,且由一个指针数组存储每棵二项树的根节点
注意:
指针数组的大小一般至少是二项队列中二项树数目×2+1;
数组第 i 号索引处,存储的是高度为 i 的二项树。如,第0号索引,存储高度为0的二项树,该二项树只有一个结点,结点权值为13;
每棵二项树由左孩子右兄弟的链表保存;
诸孩子按照它们子树的大小排序。
.h 文件
#ifndef _binary_tree_
#define _binary_tree_
class binary_tree;
class bin_node{
private:
int key;
bin_node*leftchild;
bin_node*brother;
public:
bin_node():key(-10000),leftchild(NULL),brother(NULL){}
friend binary_tree;
};
class binary_tree{
private:
int size;
bin_node *Tree[M];
public:
binary_tree(){int i;for(i=0;i<M;i++)Tree[i]=new bin_node();
size=0;}
~binary_tree(){int i;for(i=0;i<M;i++)delete Tree[i];}//not delete []
bin_node*combinetree(bin_node*t1,bin_node*t2);
binary_tree mergequeue(binary_tree h1,binary_tree h2);
int deletemin(binary_tree h);
};
#endif
需要注意的是析构函数对指针数组的处理。指针数组中每一个都是一个数组指针,每一个都是delete[] 数组名,故加一个for循环,而不能简单的delete[]指针数组名。
.cpp文件
//**************************binary_tree**********************//
bin_node*binary_tree::combinetree(bin_node*t1,bin_node*t2){
if(t1->key>t2->key)
return combinetree(t2,t1);
t2->brother=t1->leftchild;
t1->leftchild=t2;
return t1;
}//相同高度
binary_tree binary_tree::mergequeue(binary_tree h1,binary_tree h2){
int i,j;
bin_node*carry=NULL;//上一步得来的树类似加法里的进位
bin_node*t1=NULL,*t2=NULL;
h1.size+=h2.size;
for(i=0,j=1;j<h1.size;i++,j*=2)
{t1=h1.Tree[i];t2=h2.Tree[i];
switch(4*!!carry+2*!!t2+!!t1)// c t2 t1
// 0 0 0
{case 0:break;//no any trees
case 1:break;//only t1
case 2:h1.Tree[i]=t2;h2.Tree[i]=NULL;break;//only t2
case 4:h1.Tree[i]=carry;carry=NULL;break;//only carry
case 3:carry=combinetree(t1,t2);
h1.Tree[i]=h2.Tree[i]=NULL;break;//t1,t2,carry
case 5:carry=combinetree(t1,carry);
h1.Tree[i]=NULL;break;//t1 carry
case 6:carry=combinetree(t2,carry);
h2.Tree[i]=NULL;break;//t2 carry
case 7:h1.Tree[i]=carry;
carry=combinetree(t1,t2);h2.Tree[i]=NULL;break;// t1 t2 carry
}
}//一共log2(size)棵时故执行log2(size)次
return h1;
}
int binary_tree::deletemin(binary_tree h){
int i,j;
int min_index=0,min_value=h.Tree[min_index]->key;
//先找到最小值所在的二项树
for(i=0,j=1;j<h.size;i++,j*=2)
{ if(h.Tree[i]&&h.Tree[i]->key<min_value)
{min_index=i;min_value=h.Tree[i]->key;}
}
bin_node*root=h.Tree[min_index];
//删除最小值并重新构成优先队列H22
bin_node*delete_tree=root->leftchild;
free(root);
binary_tree delete_queue;
delete_queue.size=(1<<min_index)-1;//原始大小为2^(min_index)
for(j=min_index-1;j>=0;j--)
{delete_queue.Tree[j]=delete_tree;
delete_tree=delete_tree->brother;
delete_queue.Tree[j]->brother=NULL;
}
//去掉含最小值的二项树构成H11
h.Tree[min_index]=NULL;
h.size-=delete_queue.size+1;
mergequeue(h,delete_queue);
return min_value;
}