1. 程式人生 > >演算法導論 之 B樹 B-樹 - 建立 插入 C語言

演算法導論 之 B樹 B-樹 - 建立 插入 C語言

分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow

也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!

               


1 引言

  In computer science, a B-tree is a tree data structure that keeps data sorted and allows searches, sequential access, insertions, and deletions in logarithmic time. The B-tree is a generalization of a binary search tree in that a node can have more than two children (Comer 1979, p. 123). Unlike self-balancing binary search trees, the B-tree is optimized for systems that read and write large blocks of data. It is commonly used in databases and filesystems.


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

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

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

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

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

    ④、所有的非終端結點中包含下列資訊資料:

[n, C

0, 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 編碼實現

2.1 結構定義

  根據m階B樹的性質,B樹的相關結構定義如下:

/* B樹結點結構 */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;

程式碼1 結點結構

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

程式碼2 B樹結構


2.2 建立B樹

  此過程主要是完成btree_t中最大關鍵字個數max、最小關鍵字個數min、分裂索引sidx的設定,並建立一顆空樹,為後續的構造B樹做好準備條件。

/****************************************************************************** **函式名稱: btree_creat **功    能: 建立B樹 **輸入引數:  **     _btree: B樹 **     m: 階 - 取值範圍m>=3 **輸出引數: NONE **返    回: 0:成功 -1:失敗 **實現描述:  **注意事項:  **     注意:引數max的值不能小於2. **作    者: # Qifeng.zou # 2014.03.12 # ******************************************************************************/int btree_creat(btree_t **_btree, int m){    btree_t *btree = NULL;    if(m < 3) {        fprintf(stderr, "[%s][%d] Parameter 'max' must geater than 2.\n", __FILE__, __LINE__);        return -1;    }    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 -1;    }    btree->max= m - 1;    btree->min = m/2;    if(0 != m%2) {        btree->min++;    }    btree->min--;    btree->sidx = m/2;    btree->root = NULL; /* 空樹 */    *_btree = btree;    return 0;}
程式碼3 建立B樹

2.3 插入操作

  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)的處理;否則,處理結束。


  以下將通過構造一棵B樹的方式來講解B樹的插入過程:假設現在需要構建一棵4階B樹(即:階m=4、關鍵字最大個數max=3),其插入操作和處理過程如下描述。   1) 插入關鍵字45     剛開始為空樹,因此插入成功後只有一個結點。
圖1 插入結點   2) 插入關鍵字24和53     在圖1的基礎上,插入關鍵字24和53後,該結點關鍵字個數num仍未超過max,因此不會進行“分裂”處理。插入完成後,該結點關鍵字個數num=3已經達到臨界值max。
圖2 插入結點

  3) 插入關鍵字90

    在圖2基礎上,插入關鍵字90後,該結點關鍵字個數num=4超過max值,需要進行“分裂”處理。


圖3 分裂處理

    當結點關鍵字個數num達到max時,則需要進行“分裂”處理,分割序號為num/2。圖3中的[4| 24, 45, 53, 90]的分割序號為num/2 = 4/2 = 2,序號從0開始計數,因此關鍵字53為分割點,分裂過程如下:

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

    ->2) 原結點無父結點,則新建一個結點P,並將關鍵字插入到新結點P中;

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

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

  4) 插入關鍵字46和47

    在圖3右圖的基礎上,插入關鍵字46和47後,得到圖4左圖,此時結點[4| 24, 45, 46, 47]已經達到分裂條件。


圖4 分裂處理

    連續插入關鍵字46、47後,該結點[2| 24, 45]變為[4| 24, 45, 46, 47],因此其達到了“分裂”的條件,其分裂流程如下:

    ->1) 以序列號idx=num/2為分割點,結點[2| 24, 45, 46, 47]分裂為兩個結點A[2| 24, 45]和B[1| 47];

    ->2) 分割點關鍵字46被插入到父結點P中,得到結點P[2| 46, 53]

    ->3) 新結點B[1| 47]加入到結點P[2| 46, 53]的子結點序列中 - 遵循特徵④

    ->4) 因結點P[2| 46, 53]的關鍵字個數num為超過max,因為分裂結束。

  5) 插入關鍵字15和18

    在圖4右圖的基礎上,插入關鍵字15和18後,得到圖5左圖,此時結點[4| 15, 18, 24, 45]已經達到分裂條件。其處理過程同4),在此不再贅述。


圖5 分裂處理

  6) 插入關鍵字48、49、50

    在圖5右圖的基礎上插入48、49、50,可得到圖6左圖,此時結點[1| 47, 48, 49, 50]已達到分裂條件。


圖6 分裂處理

    完成第一步分裂處理之後,父結點P[4| 24, 46, 49, 53]此時也達到了分裂條件。


圖7 進一步分裂

  通過對1) ~ 6)的插入操作過程的理解和分析,可使用如下程式碼實現:

/****************************************************************************** **函式名稱: btree_insert **功    能: 插入關鍵字(對外介面) **輸入引數:  **     btree: B樹 **     key: 被插入的關鍵字 **輸出引數: NONE **返    回: 0:成功 -1:失敗 **實現描述:  **注意事項:  **作    者: # Qifeng.zou # 2014.03.12 # ******************************************************************************/int btree_insert(btree_t *btree, int key){    int idx = 0;    btree_node_t *node = btree->root;    /* 1. 構建第一個結點 */    if(NULL == node) {        node = btree_creat_node(btree);        if(NULL == node) {            fprintf(stderr, "[%s][%d] Create node failed!\n", __FILE__, __LINE__);            return -1;        }        node->num = 1;         node->key[0] = key;        node->parent = NULL;        btree->root = node;        return 0;    }    /* 2. 查詢插入位置:在此當然也可以採用二分查詢演算法,有興趣的可以自己去優化 */    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]) {                break;            }        }        if(NULL != node->child[idx]) {            node = node->child[idx];        }        else {            break;        }    }    /* 3. 執行插入操作 */    return _btree_insert(btree, node, key, idx);}
程式碼4 插入關鍵字(對外介面)
/****************************************************************************** **函式名稱: _btree_insert **功    能: 插入關鍵字到指定結點 **輸入引數:  **     btree: B樹 **     node: 指定結點 **     key: 被插入的關鍵字 **     idx: 指定位置 **輸出引數: NONE **返    回: 0:成功 -1:失敗 **實現描述:  **注意事項:  **作    者: # Qifeng.zou # 2014.03.12 # ******************************************************************************/static int _btree_insert(btree_t *btree, btree_node_t *node, int key, int idx){    int i = 0;    /* 1. 移動關鍵字:首先在最底層的某個非終端結點上插入一個關鍵字,因此該結點無孩子結點,故不涉及孩子指標的移動操作 */    for(i=node->num; i>idx; i--) {        node->key[i] = node->key[i-1];    }    node->key[idx] = key; /* 插入 */    node->num++;    /* 2. 分裂處理 */    if(node->num > btree->max) {        return btree_split(btree, node);    }    return 0;}
程式碼5 插入結點

/****************************************************************************** **函式名稱: btree_split **功    能: 結點分裂處理 **輸入引數:  **     btree: B樹 **     node: 需要被分裂處理的結點 **輸出引數: NONE **返    回: 0:成功 -1:失敗 **實現描述:  **注意事項:  **作    者: # Qifeng.zou # 2014.03.12 # ******************************************************************************/static int btree_split(btree_t *btree, btree_node_t *node){    int idx = 0, total = 0, sidx = btree->sidx;    btree_node_t *parent = NULL, *node2 = NULL;     while(node->num > btree->max) {        /* Split node */         total = node->num;        node2 = btree_creat_node(btree);        if(NULL == node2) {                   fprintf(stderr, "[%s][%d] Create node failed!\n", __FILE__, __LINE__);            return -1;        }        /* Copy data */         memcpy(node2->key, node->key + sidx + 1, (total-sidx-1) * sizeof(int));        memcpy(node2->child, node->child+sidx+1, (total-sidx) * sizeof(btree_node_t *));        node2->num = (total - sidx - 1);        node2->parent  = node->parent;        node->num = sidx;         /* Insert into parent */        parent  = node->parent;        if(NULL == parent)  {                   /* Split root node */             parent = btree_creat_node(btree);            if(NULL == parent) {                       fprintf(stderr, "[%s][%d] Create root failed!", __FILE__, __LINE__);                return -1;            }                   btree->root = parent;             parent->child[0] = node;             node->parent = parent;             node2->parent = parent;             parent->key[0] = node->key[sidx];            parent->child[1] = node2;            parent->num++;        }               else {                   /* Insert into parent node */             for(idx=parent->num; idx>0; idx--) {                       if(node->key[sidx] < parent->key[idx-1]) {                           parent->key[idx] = parent->key[idx-1];                    parent->child[idx+1] = parent->child[idx];                    continue;                }                break;            }                   parent->key[idx] = node->key[sidx];            parent->child[idx+1] = node2;            node2->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<=node2->num; idx++) {            if(NULL != node2->child[idx]) {                       node2->child[idx]->parent = node2;            }               }               node = parent;     }    return 0;}
程式碼6 分裂處理
/****************************************************************************** **函式名稱: btree_creat_node **功    能: 新建結點 **輸入引數:  **     btree: B樹 **輸出引數: NONE **返    回: 節點地址 **實現描述:  **注意事項:  **作    者: # Qifeng.zou # 2014.03.12 # ******************************************************************************/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;    /* More than (max) is for move */    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;    }    /* More than (max+1) is for move */    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;}
程式碼7 新建結點

2.4 結果展示

  只需寫一個簡單的測試函式,呼叫以上的測試介面。隨機插入n個關鍵字,並列印其樹形結構,便可很方便的判斷出插入操作的正確性。
1) 設定B樹階
m=3時
圖8 結果展示
2) 設定B樹階m=10時

圖9 結果展示



           

給我老師的人工智慧教程打call!http://blog.csdn.net/jiangjunshow

這裡寫圖片描述