【数据结构】浅析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树的特点,可以知道,插入的位置只能是叶子节点。

    分裂是怎么一回事呢?看下图

《【数据结构】浅析B树》

《【数据结构】浅析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();
}
    原文作者:B树
    原文地址: https://blog.csdn.net/pointer_y/article/details/53174480
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞