1. 程式人生 > >B樹、B+樹資料結構及操作

B樹、B+樹資料結構及操作

B樹的定義:

B樹(B-tree)是一種樹狀資料結構,它能夠儲存資料、對其進行排序並允許以O(log n)的時間複雜度執行進行查詢、順序讀取、插入和刪除的資料結構。

B樹結構如下:


其中,m是B樹的階,m>=3,par是指向父節點指標。

K1,K2...Kn是從小到大順序排列的關鍵字。n是變化的:

(1),對於非樹根節點:m/2-1 <= n <=  m-1

(2),對於根節點:1<= n <= m-1

P0,P1,P2...Pn 為n+1個指標,用於指向n+1顆子樹.其中,P0指向的關鍵字都小於K1,Pn指向的關鍵字都大於Kn,Pi(1<= i <=n-1 )指向的關鍵字均大於Ki小於K[i+1]

其他特性:

(1),根節點最少有兩顆子樹,最多有m顆子樹

(2),非根節點最少有m/2顆子樹,最多有m顆子樹.

資料結構:

#define KeyType int
#define Pointer int
const int m=10;   //樹的階

struct MBNode{
	
    int keynum;              //關鍵字個數
    MBNode *parent;      //指向父節點
    KeyType key[m+1];   //關鍵字個數
    MBNode *ptr[m+1];    //指向子節點
    Pointer recptr[m+1];     //關鍵字對應的儲存位置
    
}
//key和recptr都是從1開始,故而0位置未用

1, 查詢:
//從樹根MT的b樹上查詢關鍵字為K的記錄位置
int SearchMBTree(MBNode *MT,KeyType k)
{
    int i ;
    MBNode *p=MT;

	
    while(p != NULL)   //從樹根處向下查詢
    {
        i=1;                          //i表示關鍵字序號初始值1
        while(k > p->key[i])//順序比較
        	i++;
        if(k == p->key[i])         //如果找到
	     return p->recptr[i];
        else
	     p= p->ptr[i-1];        //查詢下一個節點
    }
    return -1;
}

查詢比較簡單,還有一些查詢時間複雜度的計算就不多做介紹.

2,插入:

新結點通過搜尋找到對應的結點進行插入,該節點插入一個新節點後該節點的關鍵字個數為n分為下面幾種情況:
(1)如果該結點的關鍵字個數 <= m-1個,那麼直接插入即可;
(2)如果該結點的關鍵字個數 > m-1個,那麼根據B樹的性質顯然無法滿足,需要將其進行分裂。
分裂的規則是該結點分成兩半,將中間的關鍵字進行提升,加入到父親結點中,但是這又可能存在父親結點也滿員的情況,則不得不向上進行回溯,甚至是要對根結點進行分裂,那麼整棵樹都加了一層.

具體過程:

(1),生成一個新節點a'

(2),將a節點中的原有資訊:m,P0,(K1,P1),(K2,P2),...,(Km,Pm),除K[m/2]之外分為前後兩個部分,分別存於a和a'節點:

     a 節點:

                 [m/2]-1,P0,(K1,P1),(K2,P2),...,(K[m/2-1],P[m/2-1])

     a' 節點:

                 m-[m/2],P[m/2],(K[m/2+1],P[m/2+1]),...,(Km,Pm)

     其中a節點中含有[m/2]-1個索引,a' 中含有m-[m/2]個索引項,每個索引項包含一個關鍵字Ki,該關鍵字所對應的記錄儲存位置Ri和一個子樹指標Pi.

(3),將關鍵字K[m/2]和指向a' 節點的指標(假定用P表示)作為新節點a' 的索引項(K[m/2],P)插入到a節點在前驅節點(即父親節點)中的索引項是的後面(特別的.若a節點是由前驅節點中的P0指標指向的,則插入到K1和P1的位置上).

當a的前驅節點被插入一個索引項後,其關鍵字個數又有可能超過m-1,若超過又使得該節點分裂成兩個節點,分裂過程如上.如下:

插入程式碼:

//向樹根MT的樹種插入(k,num,NULL)
bool InsertMBTree(MBTree *MT,KeyType k,int num)
{

    //當樹為空時
    if(MT == NULL)
    {
    	  MT= new MBNode;
         MT->keynum =1;
	  MT->parent = NULL;
	  MT->key[1]=k;
	  MT->key[2]=MaxKey;
	  MT->recptr[1]=num;
	  MT->ptr[0]=MT->ptr[1]=NULL;
	  return true;
	  
    }

    //從樹中查到到插入的位置
    int i;
    MBNode *xp=MT,*p=NULL;
    while(xp != NULL)
    {
         i=1;
	  while(k > xp->key[i])
  	      i++;
	  if(k == xp->key[i])
	  {
	      return false;   //key 已經存在返回異常
	  }
	  else
	  {
	      p=xp;
	      xp=xp->ptr[i];   //找下一層
	  }
    }

    //準備向p中插入索引項
    //此時k < p->key[i]
    MBNode *ap=NULL;
    while(1)
    {
        int j,c;
	 for (j=p->keynum;j >= i;j--)
	 {
	     p->key[j+1]=p->key[j];
	     p->recptr[j+1]=p->recptr[j];
	     p->ptr[j+1]=p->ptr[j];
	 }
	 //把一個插入索引項(k,num,ap)放入p節點的i下標位置
	 p->key[i]=k;
	 p->recptr[i]=num;
	 p->ptr[i]=ap;
	 p->keynum++;
	 //若插入後節點中關鍵字個數不超過所允許的最大值,則完成插入
	 if(p->keynum <= m-1)
	 {
	     p->key[p->keynum +1]=MaxKey;
	     return true;
	 }

	 //以下為節點分裂的情況
	 //計算出m/2向上取整值
	 c=(m%2 ? (m+1)/2;m/2);
	 //建立新節點該節點含有m-c個索引值
	 ap=new MBNode;
	 ap->keynum=m-c;
	 ap->parent=p->parent;

        //複製關鍵字和記錄
	 for(j=1;j<= ap->keynum; j++)
	 {
	     ap->key[j]=p->key[j+c];
	     ap->recptr[j]=p->recptr[j+c];
	 }

	  //複製指標
	 for(j=0;j<= ap->keynum; j++)
	 {
	     ap->ptr[j]=p->ptr[j+c];
	     if(ap->ptr[j] != NULL)
	     {
	         ap->ptr[j]->parent=ap;
	     }
	 }
	 //最大值放入所有關鍵字之後
	 ap->key[m-c+1]=MaxKey;

	 //修改p節點中的關鍵字個數
	 p->keynum=c-1;

	 //建立新的待向雙親節點插入的索引項(k,num,ap)
	 k=p->key[c];
	 num=p->recptr[c];

	 //p節點所有關鍵字後放入最大鍵值
	 p->key[c]=MaxKey;

	 //如果p的父節點是根節點,則建立新的樹根節點
	 if(p->parent == NULL)
	 {
	     MT=new MBNode;
	     MT->keynum=1;
	     MT->parent=NULL;
            MT->key[1]=k;
	     MT->key[2]=MaxKey;
	     MT->recptr[1]=num;
	     MT->ptr[0]=p;
	     MT->ptr[1]=ap;
	     p->parent=ap->parent=MT;
	     return true;
	 }

	 //如果p的父節點不是根節點
	 p=p->parent;
	 i=1;
	 while(k > p->key[i])
	 {
	     i++;
	 }
    }
}

3,刪除:

首先從根節點查詢,然後再分情況進行刪除。若被刪除的關鍵字在葉子節點中則直接從該葉子結點中刪除之,若被刪除的關鍵字在非葉子結點中,則首先要將被刪除的關鍵字同他的中序前驅關鍵字(即它的左邊指標所指子樹的最右下葉子節點中的最大關鍵字)或中序後繼關鍵字(即他的右邊指標所指子樹的最左下葉子節點中的最小關鍵字)進行對調(連同對應記錄的儲存位置一起對調),然後再從對應的結點中刪除之。

從葉子結點中刪除一個關鍵字後,使得該節點的關鍵字個數減少1,此時分3種情況分析:

(1)、若刪除後該節點的關鍵字個數 n >= m/2-1,則刪除完成。

(2)、若刪除後該節點的關鍵字個數 n < m/2-1,而他的左兄弟(或者右兄弟)節點中的關鍵字個數n > m/2-1,則首先將雙親節點中的指向該節點的左邊(或者右邊)一個關鍵字下移至該節點中,接著將它的左兄弟(或者右兄弟)結點中的最大關鍵字(或者最小關鍵字)上移至他們的雙親結點中剛空出來的位置,然後再將左兄弟(或者右兄弟)節點中的Pn指標(或者P0指標)賦值給該節點的P0(或者Pn)指標。

(3)、若刪除後該節點的關鍵字個數n < m/2-1,同時它的左兄弟和右兄弟節點中的關鍵字個數均等於m/2-1在這種情況下就必須進行節點合併了。即將該節點的剩餘關鍵字和指標連同雙親結點中指向該節點指標的左邊(或者右邊)一個關鍵字一起合併到左兄弟(或者右兄弟)結點中,然後回收該節點。

其過程如下:

#define  T 3 
typedef struct B_Tree_Node  //b樹節點定義 
{  
    int n;                         
    int *keys;                      
    bool isLeaf;                      
    struct B_Tree_Node **child ;      
    struct B_Tree_Node *p;           
}B_Tree_Node, *p_B_Tree_Node;  


B_Tree_Node * searchNode(B_Tree_Node *curNode, int k, int &index)  //關鍵字k的查詢,返回查詢資訊 
{  
    int i = 0;  
    while(i<=curNode->n && k >curNode->keys[i])   
        i++;  
	
    if(i<curNode->n && k == curNode->keys[i])  //找到了k  
    {  
        index = i;  
        return curNode;  
    }  
	
    if(curNode->isLeaf) //如果該結點是葉子結點,則k不存在  
        return NULL;  
	
    searchNode(curNode->child[i],k,index);  

}


B_Tree_Node *alloact_Node()       //b樹的初始化 
{  
    B_Tree_Node *newNode = new B_Tree_Node;  
    newNode->n = 0;  
    newNode->isLeaf = true;  
    newNode->keys = new int[2*T-1];  
    newNode->child = new p_B_Tree_Node[2*T];  
    newNode->p = NULL;  
    for(int i=0;i<2*T;i++)  
        newNode->child[i] = NULL;  
    return newNode;  
}  


void BTree_delete_key(B_Tree_Node *subNode, int k)  //刪除關鍵字k,涉及合併 
{  
    int index = 0;  
    B_Tree_Node *deleteNode = NULL;  
	
    if((deleteNode = searchNode(subNode,k,index)) == NULL)  
        return;  
	
    int keyIndex = -1;  
    for(int i=0;i<subNode->n;i++)  
    {  
        if(k == subNode->keys[i])  
        {  
            keyIndex = i;  
            break;  
        }  
    }  
    //如果在當前結點,且當前結點為葉子結點,則直接刪除
    if(keyIndex != -1 && subNode->isLeaf)   
    {  
        for(int i=keyIndex;i<subNode->n-1;i++)  
        {  
            subNode->keys[i] = subNode->keys[i+1];  
        }  
        (subNode->n)--;  
    }  
    else if(keyIndex != -1 && subNode->isLeaf!= true)   //如果在當前結點中,且當前結點不為葉子結點  
    {  
        B_Tree_Node *processorNode = subNode->child[keyIndex];  
        B_Tree_Node *succssorNode = subNode->child[keyIndex+1];  

        if(processorNode->n >= T)   //如果小於k的孩子結點關鍵字數大於T  
        {  
            int k1 = processorNode->keys[processorNode->n-1];  
            subNode->keys[keyIndex] = k1;  
            BTree_delete_key(processorNode,k1);  
        }  

        else if(succssorNode->n >=T)   //如果大於k的孩子結點關鍵字數大於T 
        {  
            int k1 = succssorNode->keys[0];  
            subNode->keys[keyIndex] = k1;  
            BTree_delete_key(succssorNode,k1);  
        }  

        else   //如果兩個孩子結點關鍵字數均不大於T,則將k與右孩子結點的關鍵字歸併到左孩子中  
        {  
            for(int j=0;j<T-1;j++)  
            {  
                processorNode->keys[processorNode->n] = k;  
                processorNode->keys[processorNode->n+1+j] = succssorNode->keys[j];  
            }  

            processorNode->n = 2*T -1 ;    
            if(!processorNode->isLeaf)  
            {  
                for(int j=0;j<T;j++)  
                {  
                    processorNode->child[T+j] = succssorNode->child[j];  
                }  
            }  

            for(int j = keyIndex;j<subNode->n-1;j++)   //修改subNode中的key值  
            {  
                subNode->keys[j] = subNode->keys[j+1];  
            }  
            subNode->n = subNode->n - 1;  
            delete succssorNode;  
            BTree_delete_key(processorNode,k);  


        }  

    }  
    else if(keyIndex == -1) //不在當前結點中  
    {  
        int childIndex = 0;  
        B_Tree_Node *deleteNode = NULL;  
        for(int j = 0;j<subNode->n;j++)     //尋找合適的子孩子,以該子孩子為根的樹包含k  
        {  
            if(k<subNode->keys[j])  
            {  
                childIndex = j;  
                deleteNode = subNode->child[j];  
                break;  
            }  
        }  
        if(deleteNode->n <= T-1)   //如果該子孩子的關鍵字數小於T,考慮那兩種情況  
        {  

            B_Tree_Node *LeftNode = subNode->child[childIndex-1];   //deleteNode的左兄弟結點  

            B_Tree_Node *RightNode = subNode->child[childIndex+1];   //deleteNode的右兄弟結點 
//如果左兄弟結點關鍵字數大於T,將父結點中的第childIndex-1個元素送給deleteNode,將Left中的最大元素送給父結點, 
            if(childIndex>=1 && LeftNode->n >= T)    
            {  
                for(int i = deleteNode->n;i>0;i--)  
                {  
                    deleteNode->keys[i] = deleteNode->keys[i-1];  
                }  
                deleteNode->keys[0] = subNode->keys[childIndex];  
                subNode->keys[childIndex] = LeftNode->keys[LeftNode->n - 1];  
                (LeftNode->n)--;  
                (deleteNode->n)++;  
                BTree_delete_key(deleteNode,k);  
            } 
//如果右兄弟關鍵字大於T,將父結點中的第childIndex個元素送給deleteNode,將Right中的最小元素送給父結點, 
            else if(childIndex<subNode->n && RightNode->n >= T)               
            {  
                deleteNode->keys[deleteNode->n] = subNode->keys[childIndex];  
                subNode->keys[childIndex] = RightNode->keys[0];  
                for(int i=0;i<RightNode->n-1;i++)  
                    RightNode[i] = RightNode[i+1];  
                (RightNode->n)--;  
                (deleteNode->n)++;  
                BTree_delete_key(deleteNode,k);  
            }  
 //如果左兄弟和右兄弟的關鍵字數均不在於T,則將左兄弟或右兄弟與其合併  
            else   
            {  
                if(childIndex>=1)//左兄弟存在,合併  
                {  
                    //將keys合併  
                    for(int i=0;i<deleteNode->n;i++)  
                    {  
                        LeftNode->keys[LeftNode->n+i] = deleteNode->keys[i];  
                    }  
                    //如果非葉子結點,則將葉子也合併  
                    if(!deleteNode->isLeaf)  
                    {  
                        for(int i=0;i<deleteNode->n+1;i++)  
                        {  
                            LeftNode->child[LeftNode->n+1+i] = deleteNode->child[i];  
                        }  

                    }  
                    LeftNode->n = LeftNode->n + deleteNode->n;  

                    //調整subNode的子節點  
                    for(int i = childIndex;i<subNode->n;i++)  
                    {  
                        subNode->child[i] = subNode->child[i+1];  
                    }  
                    BTree_delete_key(LeftNode,k);  
                }  
                else //合併它和右兄弟  
                {  
                    //將keys合併  
                    for(int i=0;i<RightNode->n;i++)  
                    {  
                        deleteNode->keys[i+deleteNode->n] = RightNode->keys[i];  
                    }  
                    //如果非葉子結點,則將葉子合併  
                    if(!deleteNode->isLeaf)  
                    {  
                        for(int i = 0;i<RightNode->n+1;i++)  
                        {  
                            deleteNode->child[deleteNode->n + 1 + i] = RightNode->child[i];  
                        }  
                    }  
                    deleteNode->n = deleteNode->n + RightNode->n;  

                    //調整subNode的子節點  
                    for(int i = childIndex+1;i<subNode->n;i++)  
                    {  
                        subNode->child[i] = subNode->child[i+1];  
                    }  
                    BTree_delete_key(deleteNode,k);  
                }  
            }  

        }  
        BTree_delete_key(deleteNode,k);  
    }  
}  

B+

       B+樹是B樹的變體,也是一種多路搜尋樹:

       1.其定義基本與B-樹同,除了:

       2.非葉子結點的子樹指標與關鍵字個數相同;

       3.非葉子結點的子樹指標P[i],指向關鍵字值屬於[K[i], K[i+1])的子樹(B樹是開區間);

       5.為所有葉子結點增加一個鏈指標;

       6.所有關鍵字都在葉子結點出現;

B+的特性:

       1.所有關鍵字都出現在葉子結點的連結串列中(稠密索引),且連結串列中的關鍵字恰好

是有序的;

       2.不可能在非葉子結點命中;

       3.非葉子結點相當於是葉子結點的索引(稀疏索引),葉子結點相當於是儲存

(關鍵字)資料的資料層;

       4.更適合檔案索引系統;