前言
本篇作者花的时间比较久,特别想层序遍历二项堆中的树的时候,遇到了很大的麻烦。头脑不清晰,debug
的时候浪费了很多很多时间。
1. 定义
- Binomial queues support all three operations in worst-case time per operation, but insertions take constant time on average.
- There is at most one binomial tree of every height. A binomial tree of height 0 is a one-node tree; a binomial tree, , of height is formed by attaching a binomial tree, , to the root of another binomial tree, .
- It is probably obvious from the diagram that a binomial tree, consists of a root with children , …, . Binomial trees of height have exactly nodes.
- 假设一个二项式堆总共有n个节点,那么最多该堆有 个二项树。
如上图是一个二项式堆,总结下上面的内容,二项树,构成了二项堆,二项堆中的每个高度的树只有一个,且该树的节点总数为
个。每个树都可由它上一个树合并而得到。这种合并是直接将高度相同的两个树中,root较大的树连接至root较小树的子代得到。
2. 时间复杂度分析
- Since merging two binomial trees takes constant time with almost any reasonable implementation, and there are binomial trees, the merge takes time in the worst case.
- Insertion is just a special case of merging, since we merely create a one-node tree and perform a merge. The worst-case time of this operation is likewise .
- Furthermore, an easy analysis will show that performing n inserts on an initially empty binomial queue will take worst-case time.
- For the analysis, note first that the delete_min operation breaks the original binomial queue into two. It takes time to find the tree containing the minimum element and to create the queues and . Merging these two queues takes time, so the entire delete_min operation takes time.
笔者简单的分析下这几点,第一点合并操作的最坏时间复杂度为
:假设有n个节点,那么一个二项堆的二项树的个数,由等比公式得:
,当n足够大时,
。在最坏的情况下,二项堆中的每个高度的树都要进行一次合并,所以总共进行
次,故合并操作的最坏时间复杂度为
。
插入操作,就是特殊的合并操作,所以时间复杂度不变。但是,通过连续数次的插入建立的二项式堆的时间复杂度为
。这点姑且当结论记住吧,因为会想当然的认为是
。
删除操作上面也是基于合并操作的分析,上面英文很清楚,不赘述。所以对于二项堆来说,插入,删除,合并的时间复杂度都为
。
3. 实现
本篇的实现,参考DSAA的实现。有些地方的处理不同,通过代码逻辑理解实现也是不错的选择。笔者已经详尽的注释了关键的地方,另外没有直接使用别人的代码的话,肯定都会有很多debug
的地方的。
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <err.h>
#define tree_hegiht(size) (log(size)/log(2))
#define MAX_SIZE (int)(pow(2,Maxtrees)-1)
#define Maxtrees 50
#define TYPE NODE
#define MAXQUEUESIZE Maxtrees
typedef struct node * NODE;
typedef struct forest * BINQUEUE;
//这种形状的树,不同于其他常见的。
struct node {
int key;
NODE firstchild;
NODE nextsibling;
};
struct forest {
int size;
NODE trees[Maxtrees];
};
typedef struct queue{
int size;
int front;
int rear;
TYPE * arrary;
} QUEUE;
void destroy_queue(QUEUE * queue);
TYPE dequeue(QUEUE * queue);
int enqueue(TYPE value,QUEUE * queue);
QUEUE * creat_queue();
TYPE first_queue(QUEUE * queue);
void print_layer(NODE tree);
NODE equal_merge( NODE N1,NODE N2 );
BINQUEUE merge(BINQUEUE H1,BINQUEUE H2);
int delete_min(BINQUEUE H);
BINQUEUE insert(BINQUEUE H, int key);
BINQUEUE bulid_binqueue(BINQUEUE H,int * array,int size);
void print_binqueue(BINQUEUE H);
void print_layer(NODE tree);
QUEUE * ptr;
int main (void){
BINQUEUE binqueue=NULL;
int data[10];
int i;
ptr=creat_queue();
printf("please input your own data:\n");
for(i=0;i<10;i++)
scanf("%d",&data[i]);
binqueue=bulid_binqueue(binqueue,data,10);
print_binqueue(binqueue);
printf("the min_key :%d\n",delete_min(binqueue));
print_binqueue(binqueue);
}
NODE equal_merge( NODE N1,NODE N2 ){
if(N1->key > N2->key)
return equal_merge(N2,N1);
N2->nextsibling=N1->firstchild;
N1->firstchild=N2;
return N1;
}
BINQUEUE merge(BINQUEUE H1,BINQUEUE H2){
int i,j,sum,current_size;
NODE tree1,tree2,carry=NULL;
if(H1 == NULL )
return H2;
else if(H2 == NULL)
return H1;
//这里需要注意点小细节
H1->size=H1->size+H2->size;
if( H1->size >= MAX_SIZE)
errx(1, "too big to merge\n");
//这里修补下DSAA中的缺陷,每次更新完当前位置i,将2^i累加到sum中
for(i=0,j=1,sum=0;sum<=H1->size;j<<i,i++,sum+=j){
//debug
//printf("i:%d\n",i);
tree1=(H1->trees)[i];
tree2=(H2->trees)[i];
//这里估计一般都会这么处理,DSAA那样我觉得徒手编程的话
//很难想到那种优化方式,一般直接考虑分清楚情况。tree1 tree2 carry
//1. 000 和 001
if(tree1 == NULL && tree2 == NULL){
if(carry == NULL)
continue;
else{
(H1->trees)[i]=carry;
carry=NULL;
}
}
//2. 010 011
else if(tree1 == NULL && tree2 != NULL){
if(carry != NULL){
carry=equal_merge(tree2,carry);
(H1->trees)[i]=NULL;
}
else
(H1->trees)[i]=tree2;
}
//3. 100 101
else if(tree1 != NULL && tree2 == NULL){
if(carry != NULL){
carry=equal_merge(tree1,carry);
(H1->trees)[i]=NULL;
}
else
(H1->trees)[i]=tree1;
}
//4. 110 111
else if(tree1 != NULL && tree2 != NULL){
if(carry != NULL){
//取谁,留谁,可以参考加法:先加,然后留下进位。
//笔者认为这里都可以的,留谁都可以。
(H1->trees)[i]= carry;
carry=equal_merge(tree1,tree2);
}
else{
carry=equal_merge(tree1,tree2);
(H1->trees)[i]= NULL;
}
}
//debug
//printf("merge in %d\n",i);
//print_binqueue(H1);
}
return H1;
}
int delete_min(BINQUEUE H){
int i,min_key=0xfffffff,i_track;
NODE min_tree,free_tree;
BINQUEUE H_min;
//判断非空
if(H == NULL)
errx(1,"error in delete_min, as input a empty binqueue\n");
//找当前队列最小的树
for(i = 0;i<Maxtrees;i++){
if((H->trees)[i] && (H->trees)[i]->key < min_key ){
min_key=(H->trees)[i]->key;
min_tree=(H->trees)[i];
i_track=i;
}
}
printf("min_key : %d, i_track : %d\n",min_key,i_track);
//从最小的树中提取出森林,也就是二项堆
//1. 给新的二项堆分配空间
if((H_min =calloc(1, sizeof(struct forest))) == NULL)
errx(1,"erro in calloc\n");
//2. 初始化二项堆
free_tree=min_tree;
// 特别的,i_track此时代表被删除的树的高度,也等于数组的index
for(i=i_track-1,min_tree=min_tree->firstchild;i>=0;i--){
(H_min->trees)[i]=min_tree;
min_tree=min_tree->nextsibling;
(H_min->trees)[i]->nextsibling=NULL;
}
//debug
//printf("H_min:\n");
//print_binqueue(H_min);
//3. 更新新堆的size信息
H_min->size=(1<<i_track)-1;
(H->trees)[i_track]=NULL;
//debug
//printf("H:\n");
//print_binqueue(H);
H->size-=H_min->size+1;
//4. 融合新旧堆
merge(H,H_min);
//debug
//print_binqueue(H);
//5. 回收空间,并返回最小值。
free(free_tree);
free(H_min);
return min_key;
}
BINQUEUE insert(BINQUEUE H, int key){
BINQUEUE H_tmp;
NODE tmp;
//1. 给新的二项堆分配空间,给新的二项树分配空间
if((H_tmp =calloc(1, sizeof(struct forest))) == NULL)
errx(1,"erro in calloc\n");
if((tmp =calloc(1, sizeof(NODE))) == NULL)
errx(1,"erro in calloc\n");
//2. 初始化二项堆
tmp->key=key;
(H_tmp->trees)[0]=tmp;
//3. 更新新堆的size信息
H_tmp->size=1;
//4. 合并
H=merge(H,H_tmp);
//5. 回收空间,注意不能回收了NODE
free(H_tmp);
return H;
}
BINQUEUE bulid_binqueue(BINQUEUE H,int * array,int size){
int i;
for(i=0;i<size;i++){
//debug
//printf("insert key %d\n",array[i]);
H=insert(H,array[i]);
//debug
//print_binqueue(H);
}
return H;
}
QUEUE * creat_queue(){
QUEUE * ptr;
if((ptr=malloc(sizeof(QUEUE))) == NULL)
errx(1,"error in malloc\n");
ptr->front=0;
ptr->rear=0;
ptr->size=0;
if((ptr->arrary=malloc(MAXQUEUESIZE*sizeof(TYPE))) == NULL)
errx(1,"error in malloc\n");
return ptr;
}
int enqueue(TYPE value,QUEUE * queue){
if(queue->size == MAXQUEUESIZE )
return -1;
(queue->arrary)[queue->rear++]=value;
queue->size++;
if(queue->rear == MAXQUEUESIZE)
queue->rear=0;
return 0;
}
TYPE dequeue(QUEUE * queue){
TYPE tmp;
if(queue->size == 0 )
return -1;
tmp = (queue->arrary)[queue->front++];
queue->size--;
if(queue->front == MAXQUEUESIZE)
queue->front=0;
return tmp;
}
void destroy_queue(QUEUE * queue){
if(queue != NULL){
free(queue->arrary);
free(queue);
}
}
void print_layer(NODE tree){
int key=0;
if(tree == NULL)
return ;
//循环终止的条件需要注意
for(;;){
//将当前节点的兄弟辈全部入队列
for(;tree != NULL;){
//debug
//printf("enqueue %d \n",tree->key);
if(enqueue(tree,ptr) == -1)
errx(1,"full queue\n");
tree=tree->nextsibling;
}
//简单的代码,但不代表想起来很简单
if((tree=dequeue(ptr)) != -1){
printf("%d ",tree->key);
tree=tree->firstchild;
}
else
break;
}
printf("\ntree print done \n");
}
TYPE first_queue(QUEUE * queue){
TYPE tmp;
if(queue->size == 0 )
return -1;
tmp = (queue->arrary)[queue->front];
return tmp;
}
void print_binqueue(BINQUEUE H){
NODE child_tree, sibling_tree;
int i,j,k;
int key[Maxtrees][Maxtrees];
for(i=0;i<Maxtrees;i++){
if((H->trees)[i] != NULL){
printf("*********bin_tree:\n");
print_layer((H->trees)[i]);
}
}
}
结果:
[root@bogon ~]# ./6_4
please input your own data:
1 2 3 4 5 6 7 8 9 10
*********bin_tree:
9 10
tree print done
*********bin_tree:
1 5 3 2 7 6 4 8
tree print done
min_key : 1, i_track : 3
the min_key :1
*********bin_tree:
2
tree print done
*********bin_tree:
3 5 9 4 7 6 10 8
tree print done
4. 最后
本来我以为实现比较简单,但是我发现怎么比前面的AVL树还要麻烦,特别是层序遍历的时候,使用队列是很显然的事情,但是如何处理这种遍历的逻辑关系,真是要花点功夫了。
使用指针的过程,很容易出现访问错误。此时不得不在程序的关键地方增加debug
信息,来判断代码逻辑的正确性。大概这篇是我写这么多博文里面,最让我累的一篇,最后好歹完成了所有的功能,但是时间付出和回报不成比例。接下来就要到排序了,一直听别人总结的口诀,终于能亲自看看是啥玩意了。