1. 程式人生 > >B樹C語言實現-建立、插入、刪除

B樹C語言實現-建立、插入、刪除

1. 課程設計題目標題:  B樹的基本操作演算法(建立、插入、刪除)

問題描述:

 在電腦科學中,B樹在查詢、訪問、插入、刪除操作上時間複雜度為O(log2~n),與自平衡二叉查詢樹不同的是B樹對大塊資料讀寫的操作有更優的效能,其通常在資料庫和檔案系統中被使用。  

對於一棵B樹,對其進行建立、插入、刪除的基本操作。

2. 演算法描述

(1) 總體思路(最初的演算法)

一棵m階的B樹,或為空樹,或為滿足下列特徵的m叉樹:

 ①、樹中每個結點至多有m棵子樹;    

 ②、若根結點不是終端結點,則至少有2棵子樹;

 ③、除根之外,所有非終端結點至少有棵子樹;    

 ④、所有的非終端結點中包含下列資訊資料: [n, C0, K0, C1, K1, C2, K2, ...., Kn-1, Cn]         其中:Ki[i=0,1,...,n-1]為關鍵字,且Ki<Ki+1[i=0, 1, ..., n-2];Ci[i=0,1,...,n]為至上子樹根結點的指標,且指標Ci所指子樹中所有結點的關鍵字均小於Ki[i=0,1,...,n-1],但都大於Ki-1[i=1,...,n-1];

資料結構:

(2) 第二版本演算法

基本操作之插入的步驟:

B樹是從空樹起,逐個插入關鍵字而建立起來的,由於B樹結點中的關鍵字個數num必須>=,因此,每次插入一個關鍵字不是在樹中新增一個終端結點,而是首先在最底層的某個非終端結點中插入一個關鍵字,若該結點的關鍵字個數不超過m-1,則插入完成,否則要進行結點的“分裂”。

  假設結點node的關鍵字個數num>max,則需進行分裂處理,其大體處理流程如下:

  1) 結點node以sidx關鍵字為分割點,索引(0 ~ sidx-1)關鍵字繼續留在結點node中,索引(sidx+1 ~ num-1)關鍵字放入新結點node2中

  2) 而索引sidx關鍵字則插入node->parent中,再將新結點node2作為父結點新插入關鍵字的右孩子結點

  3) 判斷插入node的sidx關鍵字後,node->parent的關鍵字個數num是否超過max,如果超過,則以parent為操作物件進行1)的處理;否則,處理結束。

當結點關鍵字個數num達到max時,則需要進行“分裂”處理,分割序號為num/2。分裂過程如下:

    ->1) 以序列號idx=num/2為分割點,原結點分裂為2個結點A和B;

    ->2) 原結點無父結點,則新建一個結點P,並將關鍵字插入到新結點P中;若有父節點就直接把AB作為父節點的子結點

    ->3) 將結點A和B作為結點P的子結點,並遵循B樹特徵④;

    ->4) 因結點P的結點數未超過max,則分裂結束。

(3) 第三版本演算法

基本操作之刪除的步驟:

假設現有一棵m階的B樹,則單個結點的關鍵字最大個數max=m-1,關鍵字最小個數min=。假設被刪關鍵字key為結點node中的第idx個關鍵字,由B樹的特徵可知,在刪除操作之前,結點node的關鍵字個數num需滿足min <= num <= max)。  

情況1:被刪關鍵字KEY所在結點node為非最底層結點時    

Step1:找到被刪關鍵字KEY在結點node中的位置idx —— 即:node->key[idx]為將被刪除的關鍵字    

Step2:找到以子結點child = node->child[idx]為根節點的子樹    

Step3:再找到該子樹中的最大關鍵字KEY2,並將之拿去填充被刪關鍵字KEY的位置,即:node->key[idx] = KEY2。 —— 子樹最大關鍵字MaxKey被拿走後,相當於子樹最大關鍵字的原位置被空缺了出來,也可在一定意義上理解為最終刪除的子樹中的最大關鍵字。    

經過思考後可發現:以子結點child = node->child[idx]為根節點的子樹中最大關鍵字一定是在最底層某個結點中,不管要求被刪的關鍵字KEY在哪個結點,均可視為最終被刪的關鍵字都是在最底層結點中,而最底層結點的處理請參考2)的處理流程。

 情況2:被刪關鍵字KEY所在結點node為最底層結點時    

2.1) 刪除操作前,結點node的關鍵字個數num>時,則進行刪除操作後,結點node關鍵字個數num仍然處在min <= num <= max的範圍之中,此時刪除操作處理完成;    

2.2) 刪除操作前,結點node的關鍵字個數num=時,則進行刪除操作後,結點node的關鍵字個數num<,顯然已經不符合B樹的特徵,為了維護B樹的特徵,此時需要進行的處理有2種情況:    

->2.2.1) 如果結點node的兄弟結點brother的結點個數num>時,則結點node可以向brother借用一個結點,但是需要以父結點的關鍵字為跳板,此時刪除操作處理完成;    

->2.2.2) 如果結點node的兄弟結點brother的節點個數num=時,則將node和brother進行合併為一個結點new,同時需要將父結點parent中夾在node和brother之間的關鍵字插入到新結點new中。如果父結點parent中的一個關鍵字被插入到了新結點後,父結點parent的關鍵字個數num>=,則刪除操作處理完成; 如果父結點parent的關鍵字個數num<,則父結點parent此時已經不滿足B樹特徵,則需以父結點為操作物件進行2.2)中的情況判斷,並依次類推直至根結點。

3. 資料結構

typedef struct _btree_node_t  
{  
    int num;                      /*關鍵字個數*/ 
    int *key;                     /* 關鍵字:所佔空間為(max+1) - 多出來的1個空間用於交換空間使用 */  
    struct _btree_node_t **child;  /* 子結點:所佔空間為(max+2)- 多出來的1個空間用於交換空間使用 */   
    struct _btree_node_t *parent;  /* 父結點 */  
}btree_node_t;  


typedef struct  
{  
    int max;                /* 單個結點最大關鍵字個數 - 階m=max+1 */        
    int min;                /* 單個結點最小關鍵字個數 min=(m/2)向上取整-1*/         
    int sidx;               /* 分裂索引 = (max+1)/2 */        
    btree_node_t *root;     /* B樹根結點地址 */           
}btree_t;  

4. 程式原始碼

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
typedef struct _btree_node_t  
{  
    int num;                      /*關鍵字個數*/ 
    int *key;                     /* 關鍵字:所佔空間為(max+1) - 多出來的1個空間用於交換空間使用 */  
    struct _btree_node_t **child;  /* 子結點:所佔空間為(max+2)- 多出來的1個空間用於交換空間使用 */   
    struct _btree_node_t *parent;  /* 父結點 */  
}btree_node_t;  

typedef struct  
{  
    int max;                /* 單個結點最大關鍵字個數 - 階m=max+1 */        
    int min;                /* 單個結點最小關鍵字個數 min=(m/2)向上取整-1*/         
    int sidx;               /* 分裂索引 = (max+1)/2 */        
    btree_node_t *root;     /* B樹根結點地址 */           
}btree_t;  

static int btree_merge(btree_t *btree, btree_node_t *node);
static int _btree_merge(btree_t *btree, btree_node_t *left, btree_node_t *right, int mid);
//節點建立
static btree_node_t *btree_creat_node(btree_t *btree)  
{  
    btree_node_t *node = NULL;    
    node = (btree_node_t *)calloc(1, sizeof(btree_node_t));  
    if(NULL == node) {  
        fprintf(stderr, "[%s][%d] errmsg:[%d] %s\n", __FILE__, __LINE__, errno, strerror(errno));  
        return NULL;  
    }  
  
    node->num = 0;  //結點關鍵字個數初始化為0
  
    /* 申請max+1個空間,當關鍵字個數到達max+1時執行分裂操作 */  
    node->key = (int *)calloc(btree->max+1, sizeof(int));  
    if(NULL == node->key) {  
        free(node), node=NULL;  
        fprintf(stderr, "[%s][%d] errmsg:[%d] %s\n", __FILE__, __LINE__, errno, strerror(errno));  
        return NULL;  
    }  
  
    /* 申請max+2個子節點空間,因為子節點每分裂一次,父節點多一個關鍵字,所以當父節點需要分裂時,正好是子節點個數為max+2個 */  
    node->child = (btree_node_t **)calloc(btree->max+2, sizeof(btree_node_t *));  
    if(NULL == node->child) {  
        free(node->key);  
        free(node), node=NULL;  
        fprintf(stderr, "[%s][%d] errmsg:[%d] %s\n", __FILE__, __LINE__, errno, strerror(errno));  
        return NULL;  
    }  
  
    return node;  
}  


btree_t* btree_creat(int m)  
{  
    btree_t *btree = NULL;  
  
    if(m < 3) { //階數不能小於3階 
        fprintf(stderr, "[%s][%d] Parameter 'max' must geater than 2.\n", __FILE__, __LINE__);  
        return NULL;  
    }  
  
    btree = (btree_t *)calloc(1, sizeof(btree_t));  
    if(NULL == btree) {  
        fprintf(stderr, "[%s][%d] errmsg:[%d] %s!\n", __FILE__, __LINE__, errno, strerror(errno));  
        return NULL;  
    }  
  
    btree->max= m - 1;  
    btree->min = m/2;  
    if(0 != m%2) {  
        btree->min++;  
    }  
    btree->min--;  //min=(m/2)向上取整-1
    btree->sidx = m/2;  //分裂索引
    btree->root = NULL; 
  
    return btree;  
} 

//分裂結點
static int btree_split(btree_t *btree, btree_node_t *node)  
{  
    int idx = 0, total = 0, sidx = btree->sidx;  
    btree_node_t *parent = NULL, *newNode = NULL;   
  
  
    while(node->num > btree->max) {    
        total = node->num;  
  
        newNode = btree_creat_node(btree);  //建立一個新結點用來放索引sidx+1開始的關鍵字
        if(NULL == newNode) {         
            fprintf(stderr, "[%s][%d] Create node failed!\n", __FILE__, __LINE__);  
            return -1;  
        }  
  
  
        memcpy(newNode->key, node->key + sidx + 1, (total-sidx-1) * sizeof(int));  //把sidx之後的關鍵字放入新結點中
        memcpy(newNode->child, node->child+sidx+1, (total-sidx) * sizeof(btree_node_t *));  //把child[sidx]之後的子結點放到newNode->child中
	

  
        newNode->num = (total - sidx - 1);  //計算新結點的關鍵字個數
        newNode->parent  = node->parent;    //將node和newNode作為原node父節點的子結點
  
        node->num = sidx;   
		
        parent  = node->parent;  
        if(NULL == parent)  {   //如果node沒有父節點      
  
            parent = btree_creat_node(btree);  //新建一個結點作為父節點
            if(NULL == parent) {         
                fprintf(stderr, "[%s][%d] Create root failed!", __FILE__, __LINE__);  
                return -1;  
            }         
  
            btree->root = parent;   //更新根結點
			//把node,newNode作為新建父節點的子結點
            parent->child[0] = node;  
			parent->child[1] = newNode; 	
            node->parent = parent;   
            newNode->parent = parent;   
  
            parent->key[0] = node->key[sidx];  //把分裂索引所在的關鍵字放到該父節點中

            parent->num++;  
        }         
        else {         
			//如果node原本就有父節點,就直接把key[sidx]插入到父節點中
            for(idx=parent->num; idx>0; idx--) {      //找到父節點中比key[sidx]大的最小關鍵字,把key[sidx]插到該關鍵字的前面   
                if(node->key[sidx] < parent->key[idx-1]) {   //在parent->key中每次找到一個比key[sidx]大的就再比較前一個      
                    parent->key[idx] = parent->key[idx-1];  //每找到一個,就把該關鍵字的值,和該關鍵字下方的右孩子結點向後移一位
                    parent->child[idx+1] = parent->child[idx];  
                    continue;  //繼續進行for迴圈
                } 
                break; 
				//直到找到比key[sidx]大的最小parent->key索引,
				//此時的parent->key[idx]表示node->key[sidx]要插入的地方,parent->child[idx+1]是newNode要插入的地方
            }         
  
            parent->key[idx] = node->key[sidx];  //插入分裂關鍵字
            parent->child[idx+1] = newNode;      //插入新結點
            newNode->parent = parent;            
            parent->num++;  
        }         
  
      //  memset(node->key+sidx, 0, (total - sidx) * sizeof(int));  
      //  memset(node->child+sidx+1, 0, (total - sidx) * sizeof(btree_node_t *));   
  
        /* Change node2's child->parent */  
      //  for(idx = 0; idx <= newNode->num; idx++) {  
      //      if(NULL != newNode->child[idx]) {         
      //          newNode->child[idx]->parent = newNode;  
      //      }         
      //  }    
				memset(node->key+sidx, 0, (total - sidx) * sizeof(int));  
        memset(node->child+sidx+1, 0, (total - sidx) * sizeof(btree_node_t *)); 
		//將複製給newNode的子結點中的parent指向newNode
		for(idx = 0; idx <= newNode->num; idx++) {  
            if(NULL != newNode->child[idx]) {         
                newNode->child[idx]->parent = newNode;  
            }         
        } 
       node = parent;   
    }  
  
    return 0;  
}  

static int _btree_insert(btree_t *btree, btree_node_t *node, int key, int idx) 
 //傳入要插入關鍵字的結點,idx表示比自己大的結點的位置,也就是key要插入的位置
{  
    int i = 0;  
  
  
    for(i=node->num; i>idx; i--) {  
        node->key[i] = node->key[i-1];  //插入關鍵字
    }  
  
    node->key[idx] = key; 
    node->num++;  
  
    if(node->num > btree->max) {  //當該結點關鍵字個數大於最大關鍵字個數時,執行分裂操作
        return btree_split(btree, node);  
    }  
  
    return 0;  
}  

//插入操作
int btree_insert(btree_t *btree, int key)  
{  
    int idx = 0;  //key索引
    btree_node_t *node = btree->root;  

    if(NULL == node) {  //如果為空樹,就建立第一個結點
        node = btree_creat_node(btree);  
        if(NULL == node) {  
            fprintf(stderr, "[%s][%d] Create node failed!\n", __FILE__, __LINE__);  
            return -1;  
        }  
        //第一個結點建立成果後,插入key
        node->num = 1;   
        node->key[0] = key;  
        node->parent = NULL;  
  
        btree->root = node;  
        return 0;  
    }  
  
  
    while(NULL != node) {  
        for(idx=0; idx < node->num; idx++) {  
            if(key == node->key[idx]) {  //如果要插入的資料已經存在就不需要插入
                fprintf(stderr, "[%s][%d] The node is exist!\n", __FILE__, __LINE__);  
                return 0;  
            }  
             else if(key < node->key[idx]) {  //找到第一個比自己大的關鍵字,沒有的話就繼續idx++
                break;  
            }  
        }  
        //如果該結點存在子結點,此時的idx也表示子結點的索引
		//子結點索引從0開始
		//child[idx]<key[idx]<child[idx+1]
        if(NULL != node->child[idx]) {  //如果這個關鍵字有對應的子節點,就進入這個子節點,在這個子節點中執行上述操作
            node = node->child[idx];  
        }  
        else {  
            break;  //直到找到一個比自己大的且沒有子結點的關鍵字,執行插入操作,插到這個關鍵字的前面
        }  
    }  
  
 
    return _btree_insert(btree, node, key, idx);  //執行插入操作
}  
static int _btree_merge(btree_t *btree, btree_node_t *left, btree_node_t *right, int mid)  
{  
    int m = 0;  
    btree_node_t *parent = left->parent;  
  
    left->key[left->num++] = parent->key[mid];  
  
    memcpy(left->key + left->num, right->key, right->num*sizeof(int));  
    memcpy(left->child + left->num, right->child, (right->num+1)*sizeof(btree_node_t *));  
    for(m=0; m<=right->num; m++) {  
        if(NULL != right->child[m]) {  
            right->child[m]->parent = left;  
        }  
    }  
    left->num += right->num;  
  
    for(m=mid; m<parent->num-1; m++) {  
        parent->key[m] = parent->key[m+1];  
        parent->child[m+1] = parent->child[m+2];  
    }  
  
    parent->key[m] = 0;  
    parent->child[m+1] = NULL;  
    parent->num--;  
    free(right);  
  
    /* Check */  
    if(parent->num < btree->min) {  
        return btree_merge(btree, parent);  
    }  
  
    return 0;  
}  

static int btree_merge(btree_t *btree, btree_node_t *node)  
{  
    int idx = 0, m = 0, mid = 0;  
    btree_node_t *parent = node->parent, *right = NULL, *left = NULL;  
 
    if(NULL == parent) {  
        if(0 == node->num) {  
            if(NULL != node->child[0]) {  
                btree->root = node->child[0];  
                node->child[0]->parent = NULL;  
            }  
            else {  
                btree->root = NULL;  
            }  
            free(node);  
        }  
        return 0;  
    }  
  
    for(idx=0; idx<=parent->num; idx++) {  
        if(parent->child[idx] == node) {  
            break;  
        }  
    }  
  
    if(idx > parent->num) {  
        fprintf(stderr, "[%s][%d] Didn't find node in parent's children array!\n", __FILE__, __LINE__);  
        return -1;  
    }  

    else if(idx == parent->num) {  
        mid = idx - 1;  
        left = parent->child[mid];  

        if((node->num + left->num + 1) <= btree->max) {  
            return _btree_merge(btree, left, node, mid);  
        }  
  

        for(m=node->num; m>0; m--) {  
            node->key[m] = node->key[m - 1];  
            node->child[m+1] = node->child[m];  
        }  
        node->child[1] = node->child[0];  
  
        node->key[0] = parent->key[mid];  
        node->num++;  
        node->child[0] = left->child[left->num];  
        if(NULL != left->child[left->num]) {  
            left->child[left->num]->parent = node;  
        }  
  
        parent->key[mid] = left->key[left->num - 1];  
        left->key[left->num - 1] = 0;  
        left->child[left->num] = NULL;  
        left->num--;  
        return 0;  
    }  
      

    mid = idx;  
    right = parent->child[mid + 1];  

    if((node->num + right->num + 1) <= btree->max) {  
        return _btree_merge(btree, node, right, mid);  
    }  
  
    node->key[node->num++] = parent->key[mid];  
    node->child[node->num] = right->child[0];  
    if(NULL != right->child[0]) {  
        right->child[0]->parent = node;  
    }  
  
    parent->key[mid] = right->key[0];  
    for(m=0; m<right->num; m++) {  
        right->key[m] = right->key[m+1];  
        right->child[m] = right->child[m+1];  
    }  
    right->child[m] = NULL;  
    right->num--;  
    return 0;  
}  

static int _btree_delete(btree_t *btree, btree_node_t *node, int idx)  
{  
    btree_node_t *orig = node, *child = node->child[idx];  
   
    while(NULL != child) {  
        node = child;  
        child = node->child[child->num];  
    }  
  
    orig->key[idx] = node->key[node->num - 1];  
  

    node->key[--node->num] = 0;  
    if(node->num < btree->min) {  
        return btree_merge(btree, node);  
    }  
  
    return 0;  
}  

int btree_delete(btree_t *btree, int key)  
{  
    int idx = 0;  
    btree_node_t *node = btree->root;  
  
  
    while(NULL != node) {  
        for(idx=0; idx<node->num; idx++) {  
            if(key == node->key[idx]) {  
                return _btree_delete(btree, node, idx);  
            }  
            else if(key < node->key[idx]) {  
                break;  
            }  
        }  
  
        node = node->child[idx];  
    }  
  
    return 0;  
}  

void Inorder(btree_node_t *root,int deep){
    int i,j,k,a=1;
    if(root != NULL)
    {
       if(deep){
        printf("\n");
       }
       for(j = 0;j < deep;j++){
        printf("---");  
       }
       for(i = 0; i <= root->num;i++){
           if(a){
        printf("< %d | ",root->num);
        for( k = 0;k < root->num;k++){
            printf("%d ",root->key[k]);
        }
        a--;
        printf(">");
          }
        Inorder(root->child[i],deep+1);
       }
       printf("\n");
    }
}

int main(){
   btree_t *bt;
   int i;
   int a[21]={3,4,44,12,67,98,32,43,24,100,34,55,33,13,25,8,5,41,77,200};
   bt = btree_creat(4);
   for(i = 0;i < 20;i++){
    printf("insert %d: %d\n",i+1,a[i]);
    btree_insert(bt,a[i]);
    Inorder(bt->root,0);
    printf("\n");
   }

   for(i = 0;i < 10;i++){
    printf("delete %d: %d\n",i+1,a[i]);
    btree_delete(bt,a[i]);
    Inorder(bt->root,0);
   }
   return 0;
}