B樹的原理與實現(C++)

B樹的定義

一棵B樹T是具有如下性質的有根樹(根爲root[T]):
1)每個結點x有如下域:
      a)n[x],當前存儲在結點x中的關鍵字個數; 
      b)n[x]個關鍵字本身,以非降序存放,因此key1 [x]≤key2[x]≤…≤keyn[x][x]
      c)leaf[x],是一個布爾值,如果x是葉子結點的話,則它爲TRUE,如果x爲一個內結點,則它爲FALSE。
2)每個內結點x還包含n[x]+1個指向其子女的指針c1[x],c2[x],…,cn[x]+1[x]。葉結點沒有子女,故它們的ci域無定義。
3)各關鍵字Keyi[x]對存儲在各子樹中的關鍵字範圍加以分隔:如果ki爲存儲在以ci[x]爲根的子樹中的關鍵字,則        k1≤key1[x]≤k2≤key2[x]≤…≤keyn[x][x]≤kn[x]+1
4)每個葉結點具有相同的深度,即樹的高度h。
5)每一個結點能包含的關鍵字數有一個上界和下界。這些界可用一個稱作B樹的最小度數(即一個結點中可指向的孩子結點個數)的固定整數t≥2來表示。
       a)每個非根的結點必須至少有t-1個關鍵字,每個非根的內結點必須至少有t個子女。如果樹是非空的,則根結點至少包含一個關鍵字。
       b)每個結點可包含至多2t-1個關鍵字。所以一個內結點至多可有2t個子女。我們說一個結點是滿的,如果它恰好有2t-1個關鍵字。

對B樹的基本操作

搜索B樹

搜索B樹與搜索二叉樹很相似,只是在每個結點所做的不是二叉或者“兩路”分支決定,二是根據該結點的子女數所做的多路分支決定。更準確的說,在每個內結點x處,要做n[x]+1路的分支決定。

向B樹插入關鍵字

向B樹中插入關鍵字,同二叉查找樹中插入一個關鍵字類似,要查找插入新關鍵字的葉子結點位置。因爲不能把關鍵字插入到一個已滿的葉子結點中,故需要將一個已滿的結點按其中間關鍵字分裂成兩個結點,中間關鍵字被提升到該結點的父結點中。但是這種滿結點的分裂動作會沿着樹向上傳播。爲了解決這個問題,可以採取這樣一種策略:當沿着樹根往下查找新關鍵字所屬位置時,就沿途分裂遇到的每個滿結點。因此,每當要分裂一個滿結點時,就能確保它的父結點不是滿的。

從B樹中刪除關鍵字

B樹上的刪除操作與插入操作類似,只是稍微複雜點,因爲一個關鍵字能夠從任意一個結點中刪除,而不只是葉結點。就要我們必須保證一個結點不會因爲插入而變得太大一樣,必須保證一個結點不會因爲刪除而變得太小。下面,大致描述一下刪除關鍵字的各種情況:
1)如果關鍵字k在結點x中而且x是個葉結點,則從x中刪除k。
2)如果關鍵字k在結點x中而且x是個內結點,則作如下操作:
      a)如果結點x中前於k的子結點y包含至少t個關鍵字,則找出k在以y爲根的子樹中的前驅k‘。遞歸的刪除k’,並在x中用k‘取代k。
      b)對稱地,如果結點x中位於k之後的子結點z包含至少t個關鍵字,則找出k在以z爲根的子樹中的後繼k’。遞歸的刪除k‘,並在x中使用k’取代k。
      c)否則,如果y和z都只有t-1個關鍵字,則將k和z中所有關鍵字合併進y,使得x失去k和指向z的指針,這使y包含2t-1個關鍵字。然後,釋放z並將k從y中遞歸刪除。
3)如果關鍵字k不在內結點x中,則確定包含k的正確的子樹的根ci[x]。如果ci[x]只有t-1個關鍵字,執行步驟3a或3b以保證我們降至一個包含至少t個關鍵字的結點。然後,通過對x的某個合適的子結點遞歸而結束。
      a)如果ci[x]只包含t-1個關鍵字,但它的一個相鄰兄弟結點包含至少t個關鍵字,則將x中的某一個關鍵字降至ci[x]中,將ci[x]的相鄰左兄弟或右兄弟中的某一關鍵字升至x,將該兄弟中合適的子結點指針移到ci[x]中,這樣使得ci[x]增加一個額外的關鍵字。
      b)如果ci[x]以及ci[x]的所有相鄰兄弟結點都只包含t-1個關鍵字,則將ci[x]與任意一個兄弟合併,則將x的一個關鍵字移至新合併的結點,使之成爲新結點的中間關鍵字。


B樹的c++實現代碼

#pragma once
template<class T>
class CBTree
{
private:
	static const int M = 3;                  //B樹的最小度數
	static const int KEY_MAX = 2*M-1;        //節點包含關鍵字的最大個數
	static const int KEY_MIN = M-1;          //非根節點包含關鍵字的最小個數
	static const int CHILD_MAX = KEY_MAX+1;  //孩子節點的最大個數
	static const int CHILD_MIN = KEY_MIN+1;  //孩子節點的最小個數
	struct Node
	{
		bool isLeaf;             //是否是葉子節點
		int keyNum;              //節點包含的關鍵字數量
		T keyValue[KEY_MAX];     //關鍵字的值數組
		Node *pChild[CHILD_MAX]; //子樹指針數組

		Node(bool b=true, int n=0)
			:isLeaf(b), keyNum(n){}
	};
public:
	CBTree()
	{
		m_pRoot = NULL;  //創建一棵空的B樹
	}

	~CBTree()
	{
		clear();
	}

	bool insert(const T &key)    //向B數中插入新結點key
	{
		if (contain(key))  //檢查該關鍵字是否已經存在
		{
			return false;
		}
		else
		{
			if (m_pRoot==NULL)//檢查是否爲空樹
			{
				m_pRoot = new Node();
			}
			if (m_pRoot->keyNum==KEY_MAX) //檢查根節點是否已滿
			{
				Node *pNode = new Node();  //創建新的根節點
				pNode->isLeaf = false;
				pNode->pChild[0] = m_pRoot;
				splitChild(pNode, 0, m_pRoot);
				m_pRoot = pNode;  //更新根節點指針
			}
			insertNonFull(m_pRoot, key);
			return true;
		}
	}

	bool remove(const T &key)    //從B中刪除結點key
	{
		if (!search(m_pRoot, key))  //不存在
		{
			return false;
		}
		if (m_pRoot->keyNum==1)//特殊情況處理
		{
			if (m_pRoot->isLeaf)
			{
				clear();
				return true;
			}
			else
			{
				Node *pChild1 = m_pRoot->pChild[0];
				Node *pChild2 = m_pRoot->pChild[1];
				if (pChild1->keyNum==KEY_MIN&&pChild2->keyNum==KEY_MIN)
				{
					mergeChild(m_pRoot, 0);
					deleteNode(m_pRoot);
					m_pRoot = pChild1;
				}
			}
		}
		recursive_remove(m_pRoot, key);
		return true;
	}
	void display()const //打印樹的關鍵字
	{
		displayInConcavo(m_pRoot,KEY_MAX*10);
	}
	bool contain(const T &key)const   //檢查該key是否存在於B樹中
	{
		return search(m_pRoot, key);
	}
	void clear()                      //清空B樹
	{
		recursive_clear(m_pRoot);
		m_pRoot = NULL;
	}
private:
	//刪除樹
	void recursive_clear(Node *pNode)
	{
		if (pNode!=NULL)
		{
			if (!pNode->isLeaf)
			{
				for(int i=0; i<=pNode->keyNum; ++i)
					recursive_clear(pNode->pChild[i]);
			}
			deleteNode(pNode);
		}
	}

	//刪除節點
	void deleteNode(Node *&pNode)
	{
		if (pNode!=NULL)
		{
			delete pNode;
			pNode = NULL;
		}
	}

	//查找關鍵字
	bool search(Node *pNode, const T &key)const 
	{
		if (pNode==NULL)  //檢測節點指針是否爲空,或該節點是否爲葉子節點
		{
			return false;
		}
		else
		{
			int i;
			for (i=0; i<pNode->keyNum && key>*(pNode->keyValue+i); ++i)//找到使key<=pNode->keyValue[i]成立的最小下標i
			{
			}
			if (i<pNode->keyNum && key==pNode->keyValue[i])
			{
				return true;
			}
			else
			{
				if (pNode->isLeaf)   //檢查該節點是否爲葉子節點
				{
					return false;
				}
				else
				{
					return search(pNode->pChild[i], key);
				}
			}
		}
	}

	//分裂子節點
	void splitChild(Node *pParent, int nChildIndex, Node *pChild)  
	{
		//將pChild分裂成pLeftNode和pChild兩個節點
		Node *pRightNode = new Node();//分裂後的右節點
		pRightNode->isLeaf = pChild->isLeaf;
		pRightNode->keyNum = KEY_MIN;
		int i;
		for (i=0; i<KEY_MIN; ++i)//拷貝關鍵字的值
		{
			pRightNode->keyValue[i] = pChild->keyValue[i+CHILD_MIN];
		}
		if (!pChild->isLeaf)  //如果不是葉子節點,拷貝孩子節點指針
		{
			for (i=0; i<CHILD_MIN; ++i)
			{
				pRightNode->pChild[i] = pChild->pChild[i+CHILD_MIN];
			}
		}
		
		pChild->keyNum = KEY_MIN;  //更新左子樹的關鍵字個數

		for (i=pParent->keyNum; i>nChildIndex; --i)//將父節點中的nChildIndex後的所有關鍵字的值和子樹指針向後移一位
		{
			pParent->pChild[i+1] = pParent->pChild[i];
			pParent->keyValue[i] = pParent->keyValue[i-1];
		}
		++pParent->keyNum;  //更新父節點的關鍵字個數
		pParent->pChild[nChildIndex+1] = pRightNode;  //存儲右子樹指針
		pParent->keyValue[nChildIndex] = pChild->keyValue[KEY_MIN];//把節點的中間值提到父節點
	}

	//在非滿節點中插入關鍵字
	void insertNonFull(Node *pNode, const T &key)
	{
		int i = pNode->keyNum;  //獲取節點內關鍵字個數
		if (pNode->isLeaf)      //pNode是葉子節點
		{
			while (i>0&&key<pNode->keyValue[i-1])   //從後往前,查找關鍵字的插入位置
			{
				pNode->keyValue[i] = pNode->keyValue[i-1];  //向後移位
				--i;
			}
			pNode->keyValue[i] = key;  //插入關鍵字的值
			++pNode->keyNum; //更新節點關鍵字的個數
		}
		else//pNode是內節點
		{
			while(i>0&&key<pNode->keyValue[i-1])   //從後往前,查找關鍵字的插入的子樹
				--i;
			Node *pChild = pNode->pChild[i];  //目標子樹結點指針 
			if (pChild->keyNum==KEY_MAX)  //子樹節點已滿
			{
				splitChild(pNode, i, pChild);//分裂子樹節點
				if(key>pNode->keyValue[i])   //確定目標子樹
					pChild = pNode->pChild[i+1];
			}
			insertNonFull(pChild, key);  //插入關鍵字到目標子樹節點
		}
	}

	//用括號打印樹
	void displayInConcavo(Node *pNode, int count)const
	{
		if (pNode!=NULL)
		{
			int i, j;
			for (i=0; i<pNode->keyNum; ++i)
			{
				if (!pNode->isLeaf)
				{
					displayInConcavo(pNode->pChild[i], count-2);
				}
				for (j=count; j>=0; --j)
				{
					cout<<"-";
				}
				cout<<pNode->keyValue[i]<<endl;
			}
			if (!pNode->isLeaf)
			{
				displayInConcavo(pNode->pChild[i], count-2);
			}
		}
	}

	//合併兩個子節點
	void mergeChild(Node *pParent, int index)
	{
		Node *pChild1 = pParent->pChild[index];
		Node *pChild2 = pParent->pChild[index+1];
		//將pChild2數據合併到pChild1
		pChild1->keyNum = KEY_MAX;
		pChild1->keyValue[KEY_MIN] = pParent->keyValue[index];//將父節點index的值下移
		int i;
		for (i=0; i<KEY_MIN; ++i)
		{
			pChild1->keyValue[i+KEY_MIN+1] = pChild2->keyValue[i];
		}
		if (!pChild1->isLeaf)
		{
			for (i=0; i<CHILD_MIN; ++i)
			{
				pChild1->pChild[i+CHILD_MIN] = pChild2->pChild[i];
			}
		}

		//父節點刪除index的key,index後的往前移一位
		--pParent->keyNum;
		for(i=index; i<pParent->keyNum; ++i)
		{
			pParent->keyValue[i] = pParent->keyValue[i+1];
			pParent->pChild[i+1] = pParent->pChild[i+2];
		}
		deleteNode(pChild2);  //刪除pChild2
	}

	//遞歸的刪除關鍵字
    void recursive_remove(Node *pNode, const T &key)
	{
		int i=0;
		while(i<pNode->keyNum&&key>pNode->keyValue[i])
			++i;
		if (i<pNode->keyNum&&key==pNode->keyValue[i])//關鍵字key在節點pNode中
		{
			if (pNode->isLeaf)//pNode是個葉節點
			{
				//從pNode中刪除k
				--pNode->keyNum;
				for (; i<pNode->keyNum; ++i)
				{
					pNode->keyValue[i] = pNode->keyValue[i+1];
				}
				return;
			}
			else//pNode是個內節點
			{
				Node *pChildPrev = pNode->pChild[i];//節點pNode中前於key的子節點
				Node *pChildNext = pNode->pChild[i+1];//節點pNode中後於key的子節點
				if (pChildPrev->keyNum>=CHILD_MIN)//節點pChildPrev中至少包含CHILD_MIN個關鍵字
				{
					T prevKey = getPredecessor(pChildPrev); //獲取key的前驅關鍵字
				    recursive_remove(pChildPrev, prevKey);
					pNode->keyValue[i] = prevKey;     //替換成key的前驅關鍵字
					return;
				}
				else if (pChildNext->keyNum>=CHILD_MIN)//節點pChildNext中至少包含CHILD_MIN個關鍵字
				{
					T nextKey = getSuccessor(pChildNext); //獲取key的後繼關鍵字
					recursive_remove(pChildNext, nextKey);
					pNode->keyValue[i] = nextKey;     //替換成key的後繼關鍵字
					return;
				}
				else//節點pChildPrev和pChildNext中都只包含CHILD_MIN-1個關鍵字
				{
					mergeChild(pNode, i);
					recursive_remove(pChildPrev, key);
				}
			}
		}
		else//關鍵字key不在節點pNode中
		{
			Node *pChildNode = pNode->pChild[i];//包含key的子樹根節點
			if (pChildNode->keyNum==KEY_MIN)//只有t-1個關鍵字
			{
				Node *pLeft = i>0 ? pNode->pChild[i-1] : NULL;  //左兄弟節點
				Node *pRight = i<pNode->keyNum ? pNode->pChild[i+1] : NULL;//右兄弟節點
				int j;
				if (pLeft&&pLeft->keyNum>=CHILD_MIN)//左兄弟節點至少有CHILD_MIN個關鍵字
				{
					//父節點中i-1的關鍵字下移至pChildNode中
					for (j=pChildNode->keyNum; j>0; --j)  
					{
						pChildNode->keyValue[j] = pChildNode->keyValue[j-1];
					}
					pChildNode->keyValue[0] = pNode->keyValue[i-1];
					
					if (!pLeft->isLeaf)  
					{
						for (j=pChildNode->keyNum+1; j>0; --j) //pLeft節點中合適的子女指針移植到pChildNode中
						{
							pChildNode->pChild[j] = pChildNode->pChild[j-1];
						}
						pChildNode->pChild[0] = pLeft->pChild[pLeft->keyNum];
					}
					++pChildNode->keyNum;
					pNode->keyValue[i] = pLeft->keyValue[pLeft->keyNum-1];//pLeft節點中的最大關鍵字上升到pNode中
					--pLeft->keyNum;
				}
				else if (pRight&&pRight->keyNum>=CHILD_MIN)//右兄弟節點至少有CHILD_MIN個關鍵字
				{
					//父節點中i的關鍵字下移至pChildNode中
					pChildNode->keyValue[pChildNode->keyNum] = pNode->keyValue[i];
					++pChildNode->keyNum;
					pNode->keyValue[i] = pRight->keyValue[0];//pRight節點中的最小關鍵字上升到pNode中
					--pRight->keyNum;
					for (j=0; j<pRight->keyNum; ++j)
					{
						pRight->keyValue[j] = pRight->keyValue[j+1];
					}
					if (!pRight->isLeaf)  
					{
						pChildNode->pChild[pChildNode->keyNum] = pRight->pChild[0];//pRight節點中合適的子女指針移植到pChildNode中
					    for (j=0; j<=pRight->keyNum; ++j)
					    {
							pRight->pChild[j] = pRight->pChild[j+1];
					    }
					}
				}
				//左右兄弟節點都只包含CHILD_MIN-1個節點
				else if (pLeft)//與左兄弟合併
				{
					mergeChild(pNode, i-1);
					pChildNode = pLeft;
				}
				else if (pRight)//與右兄弟合併
				{
					mergeChild(pNode, i);

				}
			}
			recursive_remove(pChildNode, key);
		}
	}

	T getPredecessor(Node *pNode)//找到前驅關鍵字
	{
		while (!pNode->isLeaf)
		{
			pNode = pNode->pChild[pNode->keyNum];
		}
		return pNode->keyValue[pNode->keyNum-1];
	}

	T getSuccessor(Node *pNode)//找到後繼關鍵字
	{
		while (!pNode->isLeaf)
		{
			pNode = pNode->pChild[0];
		}
		return pNode->keyValue[0];
	}
	
private:
	Node * m_pRoot;  //B樹的根節點
};


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章