1. 程式人生 > >B-樹的詳解

B-樹的詳解

前言

部落格編寫人:Willam
部落格編寫時間:2017/3/27
博主郵箱:2930526477@qq.com(有志同道合之人,可以加qq交流交流程式設計心得)

1、背景知識

下面這段摘抄自部落格:(從B 樹、B+ 樹、B* 樹談到R 樹
動態查詢樹主要有:二叉查詢樹(Binary Search Tree)平衡二叉查詢樹(Balanced Binary Search Tree),紅黑樹(Red-Black Tree ),B-tree/B+-tree/ B*-tree (B~Tree)。前三者是典型的二叉查詢樹結構,其查詢的時間複雜度O(log2N)與樹的深度相關,那麼降低樹的深度自然會提高查詢效率。

但是咱們有面對這樣一個實際問題:就是大規模資料儲存中,實現索引查詢這樣一個實際背景下,樹節點儲存的元素數量是有限的(如果元素數量非常多的話,查詢就退化成節點內部的線性查找了),這樣導致二叉查詢樹結構由於樹的深度過大而造成磁碟I/O讀寫過於頻繁,進而導致查詢效率低下(為什麼會出現這種情況,待會在外部儲存器-磁碟中有所解釋),那麼如何減少樹的深度(當然是不能減少查詢的資料量),一個基本的想法就是:採用多叉樹結構(由於樹節點元素數量是有限的,自然該節點的子樹數量也就是有限的)。

也就是說,因為磁碟的操作費時費資源,如果過於頻繁的多次查詢勢必效率低下。那麼如何提高效率,即如何避免磁碟過於頻繁的多次查詢呢?根據磁碟查詢存取的次數往往由樹的高度所決定,所以,只要我們通過某種較好的樹結構減少樹的結構儘量減少樹的高度,那麼是不是便能有效減少磁碟查詢存取的次數呢?那這種有效的樹結構是一種怎樣的樹呢?

這樣我們就提出了一個新的查詢樹結構——多路查詢樹。根據平衡二叉樹的啟發,自然就想到平衡多路查詢樹結構,也就是這篇文章所要闡述的第一個主題B~tree,即B樹結構(後面,我們將看到,B樹的各種操作能使B樹保持較低的高度,從而達到有效避免磁碟過於頻繁的查詢存取操作,從而有效提高查詢效率)。

2、B-樹的介紹

B-樹其實就是我們平時所說的B樹,除了B-樹外,還有另外一種叫B+樹,我們這裡先介紹什麼是B-樹:
B-樹是一種平衡的多路查詢樹,它在檔案系統中很有用(原因之前已經介紹了)。B-樹的結構有如下的特點:
**一棵度為m的B-樹稱為m階B-樹。一個結點有k個孩子時,必有k-1個關鍵字才能將子樹中所有關鍵字劃分
為k個子集。B-樹中所有結點的孩子結點最大值稱為B-樹的階,通常用m表示。從查詢效率考慮,一般要求
m≥3。一棵m階的B-樹或者是一棵空樹,或者是滿足下列要求的m叉樹:**

  • 樹中的每個結點至多有m顆子樹。
  • 若根結點不是葉子結點,則至少有兩顆子樹。
  • 除根結點外,所有非終端結點至少有[ m/2 ] ( 向上取整 )顆子樹。
  • 所有的非終端結點中包括如下資訊的資料

(n,A0,K1,A1,K2,A2,….,Kn,An)
其中:Ki(i=1,2,…,n)為關鍵碼,且Ki < K(i+1),

Ai 為指向子樹根結點的指標(i=0,1,…,n),且指標A(i-1) 所指子樹中所有結點的關鍵碼均小於Ki (i=1,2,…,n),An 所指子樹中所有結點的關鍵碼均大於Kn.
這裡寫圖片描述
n 為關鍵碼的個數。

  • 所有的葉子結點都出現在同一層次上,並且不帶資訊(可以看作是外部結點或查詢失敗的結點,實際上這些結點不存在,指向這些結點的指標為空)。

3、B-樹的基本操作–查詢介紹

我們先給出如下的一個4階的B-樹結構。
這裡寫圖片描述

如上圖所示,這是我們的一個4階的B-樹,現在假設我們需要查詢45這個數是否在B-樹中。

  1. 從根節點出發,發現根節點a有1個關鍵字為35,其中45>35,往右子樹走,進入節點c
  2. 發現結點c有2個關鍵字,其中其中43<45<78,所以進入結點g。
  3. 發現結點g有3個關鍵字,其中3<45<47,所以繼續往下走,發現進入了結束符結點:F,所以45不在B-樹中

OK,我們從上述的查詢的過程可以得出,在B-樹的查詢過程為:

  1. 在B- 樹中查詢結點
  2. 在結點中查詢關鍵字。

由於B- 樹通常儲存在磁碟上, 則前一查詢操作是在磁碟上進行的, 而後一查詢操作是在記憶體中進行的, 即
在磁碟上找到指標p 所指結點後, 先將結點中的資訊讀入記憶體, 然後再利用順序查詢或折半查詢查詢等於K
的關鍵字。顯然, 在磁碟上進行一次查詢比在記憶體中進行一次查詢的時間消耗多得多.
因此, 在磁碟上進行查詢的次數、即待查詢關鍵字所在結點在B- 樹上的層次樹, 是決定B樹查詢效率的首要
因素,對於有n個關鍵字的m階B-樹,從根結點到關鍵字所在結點的路徑上路過的結點數不超過:
這裡寫圖片描述

4、B-樹的插入

其實B-樹的插入是很簡單的,它主要是分為如下的兩個步驟:

 1. 使用之前介紹的查詢演算法查找出關鍵字的插入位置,如果我們在B-樹中查詢到了關鍵字,則直接返回。否則它一定會失敗在某個最底層的終端結點上。
 2.然後,我就需要判斷那個終端結點上的關鍵字數量是否滿足:n<=m-1,如果滿足的話,就直接在該終端結點上新增一個關鍵字,否則我們就需要產生結點的“分裂”。
     分裂的方法是:生成一新結點。把原結點上的關鍵字和k(需要插入的值)按升序排序後,從中間位置把關鍵字(不包括中間位置的關鍵字)分成兩部分。左部分所含關鍵字放在舊結點中,右部分所含關鍵字放在新結點中,中間位置的關鍵字連同新結點的儲存位置插入到父結點中。如果父結點的關鍵字個數也超過(m-1),則要再分裂,再往上插。直至這個過程傳到根結點為止。

下面我們來舉例說明,首先假設這個B-樹的階為:3。樹的初始化時如下:
這裡寫圖片描述

首先,我需要插入一個關鍵字:30,可以得到如下的結果:
這裡寫圖片描述

再插入26,得到如下的結果:

這裡寫圖片描述

OK,此時如圖所示,在插入的那個終端結點中,它的關鍵字數已經超過了m-1=2,所以我們需要對結點進分裂,所以我們先對關鍵字排序,得到:26 30 37 ,所以它的左部分為(不包括中間值):26,中間值為:30,右部為:37,左部放在原來的結點,右部放入新的結點,而中間值則插入到父結點,並且父結點會產生一個新的指標,指向新的結點的位置,如下圖所示:
這裡寫圖片描述

OK,然後我們繼續插入新的關鍵字:85,得到如下圖結果:
這裡寫圖片描述

正如圖所示,我需要對剛才插入的那個結點進行“分裂”操作,操作方式和之前的一樣,得到的結果如下:
這裡寫圖片描述

哦,當我們分裂完後,突然發現之前的那個結點的父親結點的度為4了,說明它的關鍵字數超過了m-1,所以需要對其父結點進行“分裂”操作,得到如下的結果:
這裡寫圖片描述

好,我們繼續插入一個新的關鍵字:7,得到如下結果:
這裡寫圖片描述

同樣,需要對新的結點進行分裂操作,得到如下的結果:
這裡寫圖片描述
到了這裡,我就需要繼續對我們的父親結點進行分裂操作,因為它的關鍵字數超過了:m-1.
這裡寫圖片描述

哦,終於遇到這種情況了,我們的根結點出現了關鍵子數量超過m-1的情況了,這個時候我們需要對父親結點進行分列操作,但是根結點沒父親啊,所以我們需要重新建立根結點了。
這裡寫圖片描述

好了,到了這裡我們也知道怎麼進行B-樹的插入操作。

5、B-樹的刪除操作

B-樹的刪除操作同樣是分為兩個步驟:

  1. 利用前述的B-樹的查詢演算法找出該關鍵字所在的結點。然後根據 k(需要刪除的關鍵字)所在結點是否為葉子結點有不同的處理方法。如果沒有找到,則直接返回。
  2. 若該結點為非葉結點,且被刪關鍵字為該結點中第i個關鍵字key[i],則可從指標son[i]所指的子樹中找出最小關鍵字Y,代替key[i]的位置,然後在葉結點中刪去Y。

如果是葉子結點的話,需要分為下面三種情況進行刪除。

  • 如果被刪關鍵字所在結點的原關鍵字個數n>=[m/2] ( 上取整),說明刪去該關鍵字後該結點仍滿足B-樹的定義。這種情況最為簡單,只需刪除對應的關鍵字:k和指標:A 即可。
  • 如果被刪關鍵字所在結點的關鍵字個數n等於( 上取整)[ m/2 ]-1,說明刪去該關鍵字後該結點將不滿足B-樹的定義,需要調整。

調整過程為:如果其左右兄弟結點中有“多餘”的關鍵字,即與該結點相鄰的右兄弟(或左兄弟)結點中的關鍵字數目大於( 上取整)[m/2]-1。則可將右兄弟(或左兄弟)結點中最小關鍵字(或最大的關鍵字)上移至雙親結點。而將雙親結點中小(大)於該上移關鍵字的關鍵字下移至被刪關鍵字所在結點中。

  • 被刪關鍵字所在結點和其相鄰的兄弟結點中的關鍵字數目均等於(上取整)[m/2]-1。假設該結點有右兄弟,且其右兄弟結點地址由雙親結點中的指標Ai所指,則在刪去關鍵字之後,它所在結點中剩餘的關鍵字和指標,加上雙親結點中的關鍵字Ki一起,合併到 Ai所指兄弟結點中(若沒有右兄弟,則合併至左兄弟結點中)。

下面,我們給出刪除葉子結點的三種情況:
第一種:關鍵字的數不小於(上取整)[m/2],如下圖刪除關鍵字:12
這裡寫圖片描述
刪除12後的結果如下,只是簡單的刪除關鍵字12和其對應的指標。
這裡寫圖片描述

第二種:關鍵字個數n等於( 上取整)[ m/2 ]-1,而且該結點相鄰的右兄弟(或左兄弟)結點中的關鍵字數目大於( 上取整)[m/2]-1。
這裡寫圖片描述

如上圖,所示,我們需要刪除50這個關鍵字,所以我們需要把50的右兄弟中最小的關鍵字:61上移到其父結點,然後替換小於61的關鍵字53的位置,53則放至50的結點中。然後,我們可以得到如下的結果:
這裡寫圖片描述

第三種:關鍵字個數n等於( 上取整)[ m/2 ]-1,而且被刪關鍵字所在結點和其相鄰的兄弟結點中的關鍵字數目均等於(上取整)[m/2]-1

這裡寫圖片描述

如上圖所示,我們需要刪除53,那麼我們就要把53所在的結點其他關鍵字(這裡沒有其他關鍵字了)和父親結點的61這個關鍵字一起合併到70這個關鍵字所佔的結點。得到如下所示的結果:
這裡寫圖片描述

Ok,我已經分別對上述的四種刪除的情況都做了舉例,大家如果還有什麼不清楚的,可以看看程式碼,估計就可以明白了

6、B-樹的基本操作的程式碼實現

  • BTree.h檔案的程式碼

/************************************************************/
/*                程式作者:Willam                          */
/*                程式完成時間:2017/3/28                   */
/*                有任何問題請聯絡:[email protected]       */
/************************************************************/
//@儘量寫出完美的程式

#ifndef BMT_H_
#define BMT_H_


#include<iostream>
#include<cstdlib>
using namespace std;
#define m 3


typedef int KeyType;

typedef struct BMTNode {
    int keynum;
    BMTNode * parent;

    KeyType   key[m + 1];
    BMTNode * ptr[m + 1];

    BMTNode() : keynum(0), parent(NULL) {
        for (int i = 0; i <= m; ++i) {
            key[i] = 0;
            ptr[i] = NULL;
        }//endfor
    }//endctor
}*BMT;
typedef struct Node {
    int keynum;  //關鍵字的數量
    Node * parent;  //父親結點
    KeyType key[m + 1]; //記錄關鍵字,但是0號單元不用
    Node *  ptr[m + 1]; //記錄孩子結點的指標
    Node() :keynum(0), parent(NULL) {
        for (int i = 0; i <= m; i++)
        {
            key[i] = 0;
            ptr[i] = NULL;
        }//endfor
    }//endcontruct
};

class BTree {
private:
    Node * head;
    int search(Node *& T, KeyType K); //查詢關鍵字
    void insert(Node * & T, int i, KeyType K, Node * rhs); //插入關鍵字的位置
    bool split(Node *& T, int s, Node * & rhs, KeyType & midK); //結點分裂
    bool newroot(Node * & T, Node * & lhs, KeyType midK, Node * & rhs);
    void RotateLeft(Node * parent, int idx, Node * cur, Node * rsilb);
    void RotateRight(Node * parent, int idx, Node * cur, Node * lsilb);
    void Merge(Node * parent, int idx, Node * lsilb, Node * cur);
    void DeleteBalance(Node * curNode);
    void Delete(Node * curNode, int curIdx);
public:
    BTree();
    Node * gethead();
    bool searchKey_BTree(KeyType K, Node * & recNode, int & recIdx);
    bool insert_BTree(KeyType k);
    bool Delete_BTree(KeyType K);
    void Destroy(Node * & T);
    void WalkThrough(Node * & T);

};


#endif /* BMT_H_ */
  • BTree.cpp檔案的程式碼


#include"BTree.h"
BTree::BTree() {
    this->head = NULL;
}
//結點中,查詢關鍵字序列,是否存在k,私有方法
int BTree::search(Node * & t,KeyType k) {
    int i = 0;
    for (int j = 1; j <= t->keynum; ++j) {
        if (t->key[j] <= k) {
            i = j;
        }
    }
    return i;
}

//遍歷整個樹,查詢對應的關鍵字,公有方法,
bool BTree::searchKey_BTree(KeyType k, Node * & recNode, int & recIdx) {
    if (!head) {
        //cerr << "樹為空" << endl;
        return false;
    }
    Node * p = head;
    Node * q = NULL;
    bool found = false;
    int i=0;
    while (p && !found) {
        i = this->search(p, k); //記住i返回兩種情況:第一種是找到對應的關鍵字
        //第二次是找到了最後一個小於k的關鍵字下標(主要作用與插入時)
        if (i > 0 && p->key[i] == k) {
            //找到了記錄結點和結點中關鍵字的下標
            recIdx = i;
            recNode = p;
            return true;
        }//endif
        else {
            recNode = p;         // 記錄p的值,方便返回
            recIdx = i;
            p = p->ptr[recIdx]; // 查詢下一個結點,
        }//endelse

    }//endw
    return false;
}
//這是在結點的關鍵字序列中,插入一個而關鍵字,私有方法
void BTree::insert(Node * & t, int i, KeyType k, Node * rhs) {
    //我們需要把關鍵字序列往後移動,然後插入新的關鍵字
    for (int j = t->keynum; j >= i + 1; --j) {
        t->key[j + 1] = t->key[j];
        t->ptr[j + 1] = t->ptr[j];
    }
    //插入新的關鍵字
    t->key[i + 1] = k;
    t->ptr[i + 1] = rhs;
    ++t->keynum;
}

//對對應的結點進行分裂處理,對t結點進行分裂處理,私有方法
bool BTree::split(Node * & t, int s, Node * & rhs, KeyType & midk) {
    rhs = new Node;
    //rhs為新建的結點,用於儲存右半部分的。
    if (!rhs) {
        overflow_error;
        return false;
    }
    //我們們把t分裂的,所以rhs是t的兄弟結點,有相同的父母
    rhs->parent = t->parent;

    //其中關鍵字序列的中間值為
    midk = t->key[s];
    t->key[s] = 0;
    //這個通過畫圖,就可以知道rhs的0號孩子的指標,就是t的s號結點指標
    rhs->ptr[0] = t->ptr[s];

    //如果原來的t的s號孩子指標,現在的rhs的0號孩子指標不為空,則需要改變孩子的的父親結點
    if (rhs->ptr[0]) {
        rhs->ptr[0]->parent = rhs;
    }//endif
    t->ptr[s] = NULL;
    for (int i = 1; i <= m - s; ++i) {
        //現在是把右半部分全部複製到到rhs中
        rhs->key[i] = t->key[s + i]; t->key[s + i] = 0;
        rhs->ptr[i] = t->ptr[s + i]; t->ptr[s + i] = NULL;
        //理由和剛才的理由一樣
        if (rhs->ptr[i]) {
            rhs->ptr[i]->parent = rhs;
        }//endif
    }//endfor
    rhs->keynum = m - s;
    t->keynum = s - 1;
    return true;
}
//新建一個新的結點,私有方法
bool BTree::newroot(Node * & t, Node * & lhs, KeyType midk, Node * & rhs) {
    Node * temp = new Node;
    if (!temp) {
        overflow_error;
        return false;
    }
    temp->keynum = 1;
    temp->key[1] = midk;

    temp->ptr[0] = lhs;
    //左孩子不為空
    if (temp->ptr[0]) {
        temp->ptr[0]->parent = temp;
    }
    temp->ptr[1] = rhs;
    //右孩子不為空
    if (temp->ptr[1]) {
        temp->ptr[1]->parent = temp;
    }
    t = temp;
    return true;

}
//插入一個k(public方法)
bool BTree::insert_BTree(KeyType k) {
    Node * curNode = NULL;
    int preIdx = 0;
    if (this->searchKey_BTree(k, curNode, preIdx)) {
        cout << "關鍵已經存在" << endl;
        return false;
    }
    else {
        //沒有找到關鍵字
        KeyType curk = k;
        Node * rhs = NULL;
        bool finished = false;
        while (!finished && curNode) {
            //不管是否合法,直接先插入剛才找到的那個關鍵字序列中
            this->insert(curNode, preIdx, curk, rhs);
            if (curNode->keynum < m) {//滿足條件,直接退出
                finished = true;
            }
            else {
                int s = (m + 1) / 2;  //s為中間值的下標
                if (!this->split(curNode, s, rhs, curk)) {
                    //分裂失敗,直接返回
                    return false;
                }
                if (curNode->parent == NULL) {
                    //如果curNode已經是根節點了,則可以直接退出了
                    break;
                }
                else {
                    //如果有那個父親結點的話,此時curk指向的是原來這個結點中間值
                    //所以需要和父親結點融合
                    curNode = curNode->parent;
                    preIdx = this->search(curNode, curk);
                }
            }
        }
        //如果head為空樹,或者根結點已經分裂為結點curNode和rhs了,此時是肯定到了
        //根結點了
        if (!finished && !this->newroot(head, curNode, curk, rhs)) {
            cerr << "failed to create new root" << endl;
            exit(EXIT_FAILURE);
        }
    }
}

//刪除結點k,找到合適的結點(public方法)
bool BTree::Delete_BTree(KeyType k) {
    Node * curNode = NULL;
    int curIdx = 0;
    if (this->searchKey_BTree(k, curNode, curIdx)) {
        this->Delete(curNode, curIdx);
        return true;
    }
    else {
        return false;
    }
}

//刪除對應的進入結點,去刪除關鍵字
void BTree::Delete(Node * curNode, int curIdx) {
    //curIdx不合法法時,直接返回
    if (curIdx<0 || curIdx>curNode->keynum) {
        return;
    }
    while (true) {//這裡的步驟不是很清楚,等下來討論
        //此時說明我們是處於非葉子結點
        if (curNode->ptr[curIdx - 1] && curNode->ptr[curIdx]) {

            //使用右子樹中最小的關鍵字替換對應當前的關鍵的,然後刪除那個最小的關鍵字
            Node * p1 = curNode->ptr[curIdx];
            while (p1->ptr[0]) {
                p1 = p1->ptr[0];
            }
            int res = p1->key[1];
            this->Delete_BTree(p1->key[1]);

            curNode->key[curIdx] = res;

            break;
        }
        else if (!curNode->ptr[curIdx - 1] && !curNode->ptr[curIdx])
        {   // is leaf
            for (int i = curIdx; i <= curNode->keynum; ++i) {
                curNode->key[i] = curNode->key[i + 1];
                // all ptr are NULL , no need to move.
            }//end for.
            --curNode->keynum;
            this->DeleteBalance(curNode);
            break;
        }
        else { //debug
            cerr << "Error" << endl;
        }
    }//endw
}
//刪除對應關鍵字後,我們需要對刪除後的樹進行調整
void BTree::DeleteBalance(Node * curNode) {
    int lb = (int)m / 2;  
    Node * parent = curNode->parent;
    while (parent && curNode->keynum < lb) {//說明刪除了關鍵字後,原來的那個結點已經不
        //符合B-樹的最小結點要求,這個不懂可以回去看看條件
        int idx = 0;
        //找到curNode在其父親節點中的位置
        for (int i = 0; i <= parent->keynum; ++i) {
            if (parent->ptr[i] == curNode) {
                idx = i;
                break;
            }
        }
        Node * lsilb = NULL; Node * rsilb = NULL;
        if (idx - 1 >= 0) {//如果當前結點有左兄弟
            lsilb = parent->ptr[idx - 1];
        }
        if (idx + 1 <= parent->keynum) {//說明當前結點有右兄弟
            rsilb = parent->ptr[idx + 1];
        }
        //只要右兄弟存在,而且滿足rsilb->keynum > lb,即是刪除的調整的情況2
        if (rsilb && rsilb->keynum > lb) {
            this->RotateLeft(parent, idx, curNode, rsilb);
            break;
        }//如果右兄弟不滿足,而左兄弟滿足,同樣可以
        else if (lsilb && lsilb->keynum > lb) {
            this->RotateRight(parent, idx, curNode, lsilb);
            break;
        }//如果左右兄弟都不滿足,那就是情況3了,
        else {
            //合併到左兄弟,
            if (lsilb)
                this->Merge(parent, idx, lsilb, curNode);
            else//沒有左兄弟,合併到右兄弟
                this->Merge(parent, idx + 1, curNode, rsilb);
            // potentially causing deficiency of parent.
            curNode = parent;
            parent = curNode->parent;
        }
    }
    if (curNode->keynum == 0) {
        // root is empty,此時樹為空
        head = curNode->ptr[0];
        delete curNode;
    }//endif
}
void BTree::RotateLeft(Node * parent, int idx, Node * cur, Node * rsilb) {
    //這個是在右兄弟存在的情況下,而且滿足rsilb->keynum > lb,則我們需要從把
    //右兄弟結點中的最小關鍵字移動到父親結點,而父親結點中小於該右兄弟的關鍵字的關鍵字
    //就要下移到剛剛刪除的那個結點中。

    //父親結點中某個結點下移
    cur->key[cur->keynum + 1] = parent->key[idx + 1]; 
    cur->ptr[cur->keynum + 1] = rsilb->ptr[0];  //
    if (cur->ptr[cur->keynum + 1]) {
        cur->ptr[cur->keynum + 1]->parent = cur;    
    }
    rsilb->ptr[0] = NULL;

    ++cur->keynum;

    parent->key[idx + 1] = rsilb->key[1];
    rsilb->key[idx] = 0;
    //右兄弟上移一個結點到父親結點,
    for (int i = 0; i <= rsilb->keynum; ++i) {//刪除最靠右的那個結點
        rsilb->key[i] = rsilb->key[i + 1];
        rsilb->ptr[i] = rsilb->ptr[i + 1];
    }
    rsilb->key[0] = 0;
    --rsilb->keynum;
}
void BTree::RotateRight(Node * parent, int idx, Node * cur, Node * lsilb) {

    //這個是在左兄弟存在的情況下,而且滿足lsilb->keynum > lb,則我們需要從把
    //左兄弟結點中的最大關鍵字移動到父親結點,而父親結點中大於該左兄弟的關鍵字的關鍵字
    //就要下移到剛剛刪除的那個結點中。

    //因為是在左邊插入
    for (int i = cur->keynum; i >= 0; --i) {//因為左邊的都比右邊小,所以要插入第一個位置
        cur->key[i + 1] = cur->key[i];
        cur->ptr[i + 1] = cur->ptr[i];
    }
    //在第一個位置插入父親結點下移下來的結點
    cur->key[1] = parent->key[idx];
    cur->ptr[0] = lsilb->ptr[lsilb->keynum];

    if (cur->ptr[0])
        cur->ptr[0]->parent = cur;
    lsilb->ptr[lsilb->keynum] = NULL;
    ++cur->keynum;

    // from lsilb to parent.
    parent->key[idx] = lsilb->key[lsilb->keynum];
    lsilb->key[lsilb->keynum] = 0;
    --lsilb->keynum;

}
void BTree::Merge(Node * parent, int idx, Node * lsilb, Node * cur) {

    //函式實現都是往lsilb上合併,首先是先把cur中的剩餘部分,全部合到左兄弟中個,
    for (int i = 0; i <= cur->keynum; ++i) {
        lsilb->key[lsilb->keynum + 1 + i] = cur->key[i];
        lsilb->ptr[lsilb->keynum + 1 + i] = cur->ptr[i];
        if (lsilb->ptr[lsilb->keynum + 1 + i])
            lsilb->ptr[lsilb->keynum + 1 + i] = lsilb;
   }
    //然後再把父親結點中的idx對應的內容新增到左兄弟
    lsilb->key[lsilb->keynum + 1] = parent->key[idx];
    lsilb->keynum = lsilb->keynum + cur->keynum + 1;
    delete cur;
    //然後更新我們的父親結點內容
    for (int i = idx; i <= parent->keynum; ++i) {
        parent->key[i] = parent->key[i + 1];
        parent->ptr[i] = parent->ptr[i + 1];
    }//end for.
    --parent->keynum;
}
void BTree::Destroy(Node * & T) { //是否空間
    if (!T) { return; }
    for (int i = 0; i <= T->keynum; ++i)
        Destroy(T->ptr[i]);
    delete T;
    T = NULL;
    return;
}
void BTree::WalkThrough(Node * &T) {
    if (!T) return;
    static int depth = 0;
    ++depth;
    int index = 0;

    bool running = true;

    while (running) {
        int ans = 0;
        if (index == 0) {
            ans = 2;
        }
        else {
            cout << "Cur depth: " << depth << endl;
            cout << "Cur Pos: " << (void*)T << ";  "
                << "Keynum: " << T->keynum << "; " << endl;
            cout << "Index: " << index << ";  Key: " << T->key[index] << endl;

            do {
                cout << "1.Prev Key; 2.Next Key; 3.Deepen Left; 4.Deepen Right; 5.Backup  << endl;
                cin >> ans;
                if (1 <= ans && ans <= 5)
                    break;
            } while (true);
        }


        switch (ans) {
        case 1:
            if (index == 1)
                cout << "Failed." << endl;
            else
                --index;
            break;
        case 2:
            if (index == T->keynum)
                cout << "Failed" << endl;
            else
                ++index;
            break;
        case 4:
            if (index > 0 && T->ptr[index])
                WalkThrough(T->ptr[index]);
            else
                cout << "Failed" << endl;
            break;
        case 3:
            if (index > 0 && T->ptr[index - 1])
                WalkThrough(T->ptr[index - 1]);
            else
                cout << "Failed" << endl;
            break;
        case 5:
            running = false;
            break;
        }//endsw
    }//endw

    --depth;
}
Node * BTree::gethead() {
    return this->head;
}

main.cpp檔案的程式碼


#include"BTree.h"

#define BMT_TEST
#ifdef BMT_TEST
//BMT: 10 45 24 53 90 3 37 50 61 70 100
int main(void)
{
    BTree t;

    int n;
    cout << "輸入數的個數:" << endl;
    cin >> n;
    cout << "輸入各個數的值:" << endl;
    for (int i = 0; i < n; i++) {
        int temp;
        cin >> temp;
        t.insert_BTree(temp);
    }
    Node * head = t.gethead();
    t.WalkThrough(head);

    int key;
    cout << "輸入需要刪除的值:" << endl;
    cin >> key;
    t.Delete_BTree(key);
    head = t.gethead();
    t.WalkThrough(head);

    return 0;
}
#endif

輸入:

11
10 45 24 53 90 3 37 50 61 70 100

輸出:
這裡寫圖片描述

7、總結