1. 程式人生 > >【資料結構】淺析B樹

【資料結構】淺析B樹

一、B樹的概念

B樹,概括來說是一個節點可以擁有多於2個子節點的平衡多叉樹。

特點:

1> 根節點至少有兩個子節點

2>每個非跟節點節點有(M/2)-1至M-1個key

3>每個非根節點有[M/2 ,M]個孩子

4>key[i]和key[i+1]之間的孩子節點的值介於key[i]、key[i+1]之間

5> 所有的葉子節點都在同一層

二、B樹的應用

由於增刪查改的效率十分高(時間複雜度log M-1為底N的對數的樣子),B樹普遍應用於資料庫和檔案系統。一般會將M定義的非常大,這樣B樹的高度就很低,每一個節點中可以使用二分查詢,所以B樹效率高。缺點就是比較耗記憶體。

三、實現一個B樹

1.節點內容

    根據B樹的特點,一個節點應該包含一個大小為M-1的key陣列,但是考慮到B樹在插入的時候需要先插入節點然後在進行分裂,所以key陣列給M大的空間。其次,應該包含一個節點指標型別的陣列,儲存指向孩子的指標,大小應為M+1。還有,一個指向父節點的指標,一個表示當前結點key值實際數量的size。

2.節點插入

1>空樹時,直接呼叫節點的建構函式,new一個root。

2>非空時,先查詢樹,若已經存在要插入的關鍵碼,則插入失敗。若不存在,讓Find()函式返回插入位置的指標。那麼,Find不僅要返回key值是否在樹內(bool),還要返回要插入節點的位置(Node*)。所以,我們使用庫裡存在的一個型別pair,它可以帶回兩個返回值。

3>接下來,把key值插入Find返回的位置。

4>檢測插入後key值的個數是否超過M-1,若沒有,則插入成功,若超過,則需要分裂。根據B樹的特點,可以知道,插入的位置只能是葉子節點。

    分裂是怎麼一回事呢?看下圖



分裂是插入的難點,理解了分裂,插入就沒什麼問題了。

3.程式碼實現

#include<iostream>

using namespace std;

template<class K , int M>
struct BTreeNode
{
	typedef BTreeNode<K, M> Node;
	K _keys[M];   //多給出一個位置是為了方便分裂。
	Node* _sub[M + 1];
	Node* _parent;
	size_t _size;  //記錄實際關鍵字的個數
	BTreeNode()
		:_parent(NULL)
		, _size(0)
	{
		for (size_t i = 0; i < M; i++)
		{
			_keys[i] = K();
			_sub[i] = 0;
		}
		_sub[M] = 0;
	}
};

template<class K,int M>
class BTree
{
public:
	typedef BTreeNode<K, M> Node;
	BTree()
		:_root(NULL)
	{}
	void InOrder()
	{
		_InOrder(_root);
	}
	~BTree()
	{
		_Destory(_root);
	}
	bool Insert(const K& key)
	{
		if (NULL == _root)
		{
			_root = new Node();
			_root->_keys[0] = key;
			_root->_size++;
			return true;
		}
		pair<Node*, size_t> tmp = _Find(key);
		if (tmp.second != -1)  //已經存在關鍵值為key,則不能插入
		{
			return false;
		}
		Node* cur = tmp.first;
		Node* sub = NULL;
		K newkey = key;
		while (1)
		{
			_Insertkey(cur,newkey,sub);  //先將key放進要插的節點
			//判斷結點的關鍵字數目是否符合標準。
			if (cur->_size < M)  //該節點上插入後關鍵字數目正常
				return true;

			//數目超過規定值需要進行分裂
			while (cur->_size >= M)
			{
				size_t mid = cur->_size / 2;
				//1.分裂出新的結點
				Node* NewNode = new Node;
				for (size_t i = mid+1; i < cur->_size; i++)//mid之後的key值給新結點
				{
					int j = 0;
					NewNode->_keys[j] = cur->_keys[i];
					NewNode->_size++;
					//cur->_size--;  //注意此處先不要哦改動cur的size,否則會影響下一個迴圈
					cur->_keys[i] = K();  //賦給新結點後cur對應的key應置成初始值
					j++;
				}
				int j = 0;
				for (size_t i = mid+1 ; i < cur->_size+1; i++)
				{
					NewNode->_sub[j] = cur->_sub[i];
					if (NewNode->_sub[j])
						NewNode->_sub[j]->_parent = NewNode;
					j++;
					cur->_sub[i] = NULL;
				}
				if (cur == _root)  //創建出新的根節點
				{
					Node* tmp = new Node();
					tmp->_keys[0] = cur->_keys[mid];
					cur->_keys[mid] = K();
					cur->_size=mid;
					tmp->_size++;
					tmp->_sub[0] = cur;
					cur->_parent = tmp;
					tmp->_sub[1] = NewNode;
					NewNode->_parent = tmp;
					_root = tmp;
					return true;
				}
				newkey = cur->_keys[mid];
				cur->_keys[mid] = K();
				cur->_size = mid;   
				sub = NewNode;
			}
			
			cur = cur->_parent;
		}
	}

protected:
	void _Destory(Node* root)
	{
		if (NULL == root)
			return;

		size_t i = 0;
		for (; i < root->_size; i++)
		{
			_Destory(root->_sub[i]);
			delete root->_sub[i];
		}
		_Destory(root->_sub[i]);
		delete root->_sub[i];
	}
	void _InOrder(Node* root)
	{
		if (NULL == root)
			return;

		size_t i = 0;
		for (; i < root->_size; i++)
		{
			_InOrder(root->_sub[i]);
			cout << root->_keys[i]<<" ";

		}
		_InOrder(root->_sub[i]);
	}
	pair<Node*,size_t> _Find(const K& key)
	{
		Node* cur = _root;
		Node* parent = NULL;
		while (cur)
		{
			size_t i = 0;
			while (i< cur->_size)  //找當前結點的key
			{
				
				if (cur->_keys[i] < key)
					i++;
				else if (cur->_keys[i]>key)
					//cur = cur->_sub[i];
					break;
				else   //找到了和傳入key相等的關鍵字
					return pair<Node* ,size_t>(cur,i);
			}
			parent = cur;
			cur = cur->_sub[i];
		}
		return pair<Node* , size_t>(parent, -1);  //沒找到
	}
	void _Insertkey(Node* cur,const K& key,Node*sub)
	{
		int i = cur->_size-1;
		while (i >=0)
		{
			if (cur->_keys[i] > key)
			{
				//移動關鍵字的位置
				cur->_keys[i + 1] = cur->_keys[i];

				//移動子樹的位置
				cur->_sub[i + 2] = cur->_sub[i + 1];
				i--;
			}
			else
				break;
		}
		//i記錄著要插入位置的前一個
		cur->_keys[i + 1] = key;
		cur->_sub[i + 2] = sub;
		if (sub)
			sub->_parent = cur;
		cur->_size++;
	}
private:
	Node* _root;
};

void TestBTree()
{
	BTree<int, 3> bt;
	int a[] = { 53, 75, 139, 49, 145, 36, 101 };
	for (int i = 0; i < sizeof(a) / sizeof(a[0]); i++)
	{
		bt.Insert(a[i]);
	}
	bt.InOrder();
}