1. 程式人生 > >【演算法和資料結構】平衡查詢樹之B樹

【演算法和資料結構】平衡查詢樹之B樹

  以B-樹的效能總是等價於二分查詢(與M值無關),也就沒有B樹平衡的問題;由於M/2的限制,在插入結點時,如果結點已滿,需要將結點分裂為兩個各佔M/2的結點;刪除結點時,需將兩個不足M/2的兄弟結點合併。

下面簡單說明分裂:

  

下面對B-樹進行實現
#pragma once

//3階B樹
template<class K, int M = 3>
struct BTreeNode
{
	K _keys[M];//儲存有M-1個key,多一個是為了方便分裂
	BTreeNode<K, M>* _subs[M + 1];//儲存有M個subs,多一個是為了方便分裂
	BTreeNode<K, M>* _parent;
	size_t _size;//陣列中存在的有效關鍵字的個數
	BTreeNode()
		:_parent(NULL)
		, _size(0)
	{
		int i = 0;
		for (; i <= M; ++i)
		{
			_keys[i] = 0;
			_subs[i] = NULL;
		}
		_keys[i] = 0;
	}
};
template<class K,class V>//結構體實現K,V形式
struct Pair
{
	K _first;
	V _second;
	Pair(const  K& key = k(), const V& value = V())
		:_first(key)
		, _second(value)
	{}
};
template<class K, int M = 3>
class BTree
{
	typedef BTreeNode<K, M> Node;
public:
	BTree()
		:_root(NULL)
	{}
	bool Insert(const K& key)//插入節點
	{
		if (_root == NULL)
		{
			_root = new Node;
			_root->_size++;
			_root->_keys[0] = key;
			return true;
		}

		Pair<Node*, int> ret= Find(key);
		if (ret._second != -1)//判斷key是否已經存在
			return false;
		//在節點cur中插入key和sub
		Node* cur = ret._first;
		K insertkey = key;
		Node* sub = NULL;
		while (1)
		{
			_InsertKey(cur, insertkey, sub);
			if (cur->_size < M)
				return true;
			//插入資料後,節點關鍵字個數大於M-1,需分裂節點,拷貝右半部分
			int mid = (cur->_size - 1) / 2;//找到中間值,進行上移
			int index = 0;
			Node* tmp = new Node;//tmp分裂出來的右半部分,左半部分在cur中

			//拷貝key和subs
			for (size_t i = mid + 1; i < cur->_size; ++i)
			{
				//拷貝key
				tmp->_keys[index++] = tmp->_keys[i];
				tmp->_size++;
				//拷貝subs
				tmp->_subs[index++] = cur->_subs[i];
				if (cur->_subs[i])
					cur->_subs[i]->_parent = tmp;
			}

			cur->_size = (cur->_size - 1) / 2;//更新cur(分裂後的左半部分)大小
			if (cur->_parent == NULL)//插入分裂後上移的元素
			{
				_root = new Node;
				_root->_keys[0] = cur->_keys[mid];
				_root->_size = 1;
				_root->_subs[0] = cur;
				_root->_subs[1] = tmp;

				cur->_parent = _root;
				tmp->_parent = _root;
				break;
			}
			else
			{
				insertkey = cur->_keys[mid];
				sub = tmp;
				cur = cur->_parent;//上移
			}
		}
		return true;
	}
	Pair<Node*, int> Find(const K& key) //查詢key,返回節點及對應節點中陣列下標
	{
		Node* parent = NULL;
		Node* cur = _root;
		while (cur)
		{
			size_t index = 0;
			while (index < cur->_size)//遍歷整個節點關鍵字
			{
				if (key == cur->_keys[index])
					return Pair<Node*, int>(cur, index);
				else if (key > cur->_keys[index])
					index++;
				else//小於_key[index] --> 結束迴圈,在_key[index]所在節點查詢
					break;
			}
			parent = cur;
			cur = cur->_subs[index];
		}
		return Pair<Node*, int>(parent, -1);//沒有找到,注意返回cur的父結點和-1
	}
	void InOrder()//中序遍歷輸出
	{
		_InOrder(_root);
	}
private:
	void _InsertKey(Node* cur,const K& key, Node* sub)//插入key值
	{
		int index = cur->_size - 1;//從後向前比較移位
		while (index >= 0 && key < cur->_keys[index])//後面的資料(包括_sub[])向後移
		{
			cur->_keys[index + 1] = cur->_keys[index];
			cur->_subs[index + 2] = cur->_subs[index + 1];//畫圖分析,_subs[]移動兩位
			--index;
		}
		cur->_keys[index + 1] = key;
		cur->_subs[index + 2] = sub;
		if (sub)
			sub->_parent = cur;
		++cur->_size;
	}
	void _InOrder(Node* root)
	{
		if (root == NULL)
		{
			return;
		}
		for (int i = 0; i < _root->_size; ++i)
		{
			_InOrder(root->_subs[i]);
			cout << root->_keys[i] << " ";
		}
	}
protected:
	Node* _root;
};

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

B+

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

       1.其定義基本與B-樹同,除了以下幾點不同。

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

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

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

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

B+的搜尋與B-樹也基本相同,區別是B+樹只有達到葉子結點才命中(B-樹可以在非葉子結點命中),其效能也等價於在關鍵字全集做一次二分查詢。

B*

  B*B+樹的變體,在B+樹的非根和非葉子結點再增加指向兄弟的指標。