DSAA之 Binomial Queues实现及层序遍历

前言

  本篇作者花的时间比较久,特别想层序遍历二项堆中的树的时候,遇到了很大的麻烦。头脑不清晰,debug的时候浪费了很多很多时间。

1. 定义

  • Binomial queues support all three operations in O ( l o g n ) 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, B k , of height k is formed by attaching a binomial tree, B k 1 , to the root of another binomial tree, B k 1 .
  • It is probably obvious from the diagram that a binomial tree, B k consists of a root with children B 0 , …, B k 1 . Binomial trees of height k have exactly 2 k nodes.
  • 假设一个二项式堆总共有n个节点,那么最多该堆有 l o g n 个二项树。

这里写图片描述
   如上图是一个二项式堆,总结下上面的内容,二项树,构成了二项堆,二项堆中的每个高度的树只有一个,且该树的节点总数为 2 d 个。每个树都可由它上一个树合并而得到。这种合并是直接将高度相同的两个树中,root较大的树连接至root较小树的子代得到。

2. 时间复杂度分析

  • Since merging two binomial trees takes constant time with almost any reasonable implementation, and there are O ( l o g n ) binomial trees, the merge takes O ( l o g n ) 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 O ( l o g n ) .
    • Furthermore, an easy analysis will show that performing n inserts on an initially empty binomial queue will take O ( n ) worst-case time.
  • For the analysis, note first that the delete_min operation breaks the original binomial queue into two. It takes O ( l o g n ) time to find the tree containing the minimum element and to create the queues H and H . Merging these two queues takes O ( l o g n ) time, so the entire delete_min operation takes O ( l o g n ) time.

  笔者简单的分析下这几点,第一点合并操作的最坏时间复杂度为 O ( l o g n ) :假设有n个节点,那么一个二项堆的二项树的个数,由等比公式得: 2 k + 1 1 = n , k = l o g ( n + 1 ) 1 ,当n足够大时, k = l o g n 。在最坏的情况下,二项堆中的每个高度的树都要进行一次合并,所以总共进行 l o g n 次,故合并操作的最坏时间复杂度为 O ( l o g n )
  插入操作,就是特殊的合并操作,所以时间复杂度不变。但是,通过连续数次的插入建立的二项式堆的时间复杂度为 O ( n ) 。这点姑且当结论记住吧,因为会想当然的认为是 O ( n l o g n )
  删除操作上面也是基于合并操作的分析,上面英文很清楚,不赘述。所以对于二项堆来说,插入,删除,合并的时间复杂度都为 O ( l o g 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信息,来判断代码逻辑的正确性。大概这篇是我写这么多博文里面,最让我累的一篇,最后好歹完成了所有的功能,但是时间付出和回报不成比例。接下来就要到排序了,一直听别人总结的口诀,终于能亲自看看是啥玩意了。

猜你喜欢

转载自blog.csdn.net/LoveStackover/article/details/80242256