1. 程式人生 > >B-樹的插入和遍歷

B-樹的插入和遍歷

B-樹是一種平衡的多叉樹,一顆M階(M>2)的B樹,是一顆平衡的M路平衡搜尋樹,可以是空樹或者滿足下列性質:

1. 根節點至少有兩個孩子
2. 每個非根節點有[ [M/2],M]個孩子
3. 每個非根節點有[ [M/2] -1,M-1]個關鍵字,並且以升序排列
4. key[i]和key[i+1]之間的孩子節點的值介於key[i]、key[i+1]之間
5. 所有的葉子節點都在同一層
ps: [M/2]是向上取整

先看看B-樹節點的結構:

template<class K,size_t M = 3>
struct BTreeNode
{
	K _key[M];    //關鍵字
	BTreeNode<K, M>* _subs[M + 1];    //孩子
	BTreeNode<K, M>* _parent;
	size_t _size;    //實際關鍵字個數

	BTreeNode()
		:_parent(NULL)
		, _size(0)
	{
		memset(_subs, 0, sizeof(_subs));
	}

};


插入的幾種情況:

1.沒有節點,即root == NULL;

這個是最簡單,直接建立一個節點,將key值放入;

if (_root == NULL)
		{
			_root = new Node;
			_root->_key[0] = key;
			_root->_size++;
		}

2.有節點,找到要插入的位置,進行插入。完畢之後必須進行是否需要進行分裂的判斷。分裂保證第2,3條性質。分裂完後繼續判斷父親是否需要分裂,直到找到不需要分裂的位置結束。


3.每次插入一個key都會帶一個子節點(NULL)。以保證孩子比key多一個的性質;向上調整時,也可以看作是向父親接待你插入一個新的key,所帶子節點為分裂出來的節點(newNode)。


插入程式碼實現:

bool Insert(const K &key)
	{
		//沒有節點
		if (_root == NULL)
		{
			_root = new Node;
			_root->_key[0] = key;
			_root->_size++;
		}

		//找到key的節點
		pair<Node*,int> ret = Find(key);
		if (ret.second >= 0)
		{
			return false;   //已存在,不需要插入
		}

		//進行插入
		K newKey = key;
		Node *cur = ret.first;
		Node *sub = NULL;
		while (1)
		{
			InsertKey(newKey, cur, sub);  //在cur節點插入一個newKey ,所帶的孩子為sub

			//判斷分裂
			if (cur->_size < M)
			{
				return true;
			}
			//開始分裂
			Node *newNode = new Node;
			size_t mid = M / 2;
			int index = 0;
			//構造分裂出來的newNode

			size_t i = mid + 1;
			for (; i < cur->_size; i++)
			{
				//提取mid後面的資料到newNode中:key,左邊孩子
				newNode->_key[index] = cur->_key[i];
				newNode->_size++;
				newNode->_subs[index] = cur->_subs[i];
				++index;

				if (cur->_subs[i])  
				{
					Node *sb = cur->_subs[i];
					cur->_subs[i] = NULL;
					sb->_parent = newNode;
				}
				
			}

			newNode->_subs[index] = cur->_subs[i];
			cur->_size = cur->_size - newNode->_size - 1;

			if (cur->_subs[i])  
			{
				Node *sb = cur->_subs[i];
				cur->_subs[i] = NULL;
				sb->_parent = newNode;
			}

			if (cur->_parent == NULL)  //分裂的節點是第一個節點
			{
				_root = new Node;
				_root->_key[0] = cur->_key[mid];
				_root->_subs[0] = cur;
				cur->_parent = _root;
				_root->_subs[1] = newNode;
				newNode->_parent = _root;
				_root->_size++;

				return true;
			}
			else                     //分裂的節點有父親節點,需要判斷父親節點是否分裂
			{
				newKey = cur->_key[mid];
				cur = cur->_parent;
				sub = newNode;
			}

		}
	}
上述就說完了插入,下面來看看中序遍歷:

遍歷就十分簡單,直接看程式碼

void _Inorder(Node *root)
	{
		if (root == NULL)
		{
			return;
		}
		int i = 0;
		for (; i < root->_size; i++)    //遍歷所有的key才是這個節點遍歷完
		{
			_Inorder(root->_subs[i]);
			cout << root->_key[i] << " ";
		}

		_Inorder(root->_subs[i]);
	}

總體實現程式碼:

//B-樹
template<class K,size_t M = 3>
struct BTreeNode
{
	K _key[M];    //關鍵字
	BTreeNode<K, M>* _subs[M + 1];    //孩子
	BTreeNode<K, M>* _parent;
	size_t _size;    //實際關鍵字個數

	BTreeNode()
		:_parent(NULL)
		, _size(0)
	{
		memset(_subs, 0, sizeof(_subs));
	}

};

template<class K,size_t 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->_key[0] = key;
			_root->_size++;
		}

		//找到key的節點
		pair<Node*,int> ret = Find(key);
		if (ret.second >= 0)
		{
			return false;   //已存在,不需要插入
		}

		//進行插入
		K newKey = key;
		Node *cur = ret.first;
		Node *sub = NULL;
		while (1)
		{
			InsertKey(newKey, cur, sub);

			//判斷分裂
			if (cur->_size < M)
			{
				return true;
			}
			//開始分裂
			Node *newNode = new Node;
			size_t mid = M / 2;
			int index = 0;
			//構造分裂出來的newNode

			size_t i = mid + 1;
			for (; i < cur->_size; i++)
			{
				//提取mid後面的資料到newNode中:key,左邊孩子
				newNode->_key[index] = cur->_key[i];
				newNode->_size++;
				newNode->_subs[index] = cur->_subs[i];
				++index;

				if (cur->_subs[i])  
				{
					Node *sb = cur->_subs[i];
					cur->_subs[i] = NULL;
					sb->_parent = newNode;
				}
				
			}

			newNode->_subs[index] = cur->_subs[i];
			cur->_size = cur->_size - newNode->_size - 1;

			if (cur->_subs[i])  
			{
				Node *sb = cur->_subs[i];
				cur->_subs[i] = NULL;
				sb->_parent = newNode;
			}

			if (cur->_parent == NULL)  
			{
				_root = new Node;
				_root->_key[0] = cur->_key[mid];
				_root->_subs[0] = cur;
				cur->_parent = _root;
				_root->_subs[1] = newNode;
				newNode->_parent = _root;
				_root->_size++;

				return true;
			}
			else
			{
				newKey = cur->_key[mid];
				cur = cur->_parent;
				sub = newNode;
			}

		}
	}

	void InsertKey(const K &key, Node *cur, Node *sub)  //cur要被差的節點,sub為key所帶的孩子
	{
		assert(cur);
		int end = cur->_size - 1;
		while (end >= 0)
		{
			if (cur->_key[end] < key)
			{
				break;
			}
			else   //後移
			{
				cur->_key[end + 1] = cur->_key[end];
				cur->_subs[end + 2] = cur->_subs[end + 1];
			}
			end--;
		}

		//找到插入位置;
		cur->_key[end + 1] = key;
		cur->_subs[end + 2] = sub;
		cur->_size++;
		if (sub)
			sub->_parent = cur;
	}

	pair<Node*, int> Find(const K &key)
	{
		Node *cur = _root;
		Node *parent = NULL;

		while (cur)
		{
			size_t i = 0;
			for (; i < cur->_size;)
			{
				if (cur->_key[i] < key)
				{
					++i;
				}
				else if (cur->_key[i] > key)
				{
					break;
				}
				else    //key已經出現在樹中
				{
					return make_pair(cur, 0);
				}
			}
			parent = cur;
			cur = cur->_subs[i];
		}

		return make_pair(parent, -1); //沒有找到key
	}

	void Inorder()
	{
		_Inorder(_root);
	}

protected:
	void _Inorder(Node *root)
	{
		if (root == NULL)
		{
			return;
		}
		int i = 0;
		for (; i < root->_size; i++)
		{
			_Inorder(root->_subs[i]);
			cout << root->_key[i] << " ";
		}

		_Inorder(root->_subs[i]);
	}
protected:
	Node *_root;
};