【数据结构】高效的多路平衡搜索树---B-树


B树是为磁盘或其他直接存取的辅助设备而设计的一种多路平衡搜索树,许多数据库系统使用B树或B树的变种来存储信息。

一、引入B树的原因

前面我们介绍了高效的二叉搜索树AVL树红黑树,为什么还要出现B树?当你使用AVL、红黑树时,一次只能获取一个键值的信息,鉴于计算机的局部性原理B树可以至多存储M-1个键值的信息

数据规模大到内存已不足以容纳时,常规的平衡二叉树效率是很低的。因为查找过程对外存的访问次数过多读取物理地址连续的1000个字节,与读取单个字节几乎没有区别。通过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)所有的叶子结点都在同一层。

三、B-树的结构

template<class K, size_t M>
struct BTreeNode
{
	// 多给一个关键字:为简化分裂的逻辑
	K _keys[M];     // 关键字的集合
	BTreeNode<K, M>* _pSub[M + 1];    // 孩子的集合
	BTreeNode<K, M>* _pParent;
	size_t _size; // 有效关键字的个数
	BTreeNode() //构造函数
		: _size(0)
		, _pParent(NULL)
	{
		for (size_t idx = 0; idx < M + 1; ++idx)
			_pSub[idx] = NULL;
	}
};

四、B-树的查找

我们使用pair构建一个模板类,第一个数据类型为Node*,表示结点的地址,第二个数据类型为int,表示在B树某个结点的下标。

设计思路:与二叉搜索树的查找类似,如果寻找的key值大于当前节点的值,则向右偏移;若小于节点的值,则到这个节点左边的孩子节点去找。如果节点已经存在,则返回当前节点及下标,若节点不存在,返回当前节点及-1。

pair<Node*, int> Find(const K& key)
	{
		Node* pCur = _pRoot;
		Node* pParent = NULL;
		while (pCur)
		{
			size_t idx = 0;
			//下标必须小于关键字key的个数	
			while (idx < pCur->_size)
			{
				//小于当前节点的值跳出循环指向孩子节点,大于在此节点内向右偏移,相等返回当前节点
				if (key < pCur->_keys[idx])
					break;
				else if (key > pCur->_keys[idx])
					++idx;
				else
					return pair<Node*, int>(pCur,idx);
			}
			pParent = pCur;
			pCur = pCur->_pSub[idx];
		}
		return pair<Node*, int>(pParent, -1);
	}
1

调整节点内的元素并插入
我们以插入{10,30,20,40,50,38}为例,取M = 3,即关键字个数最多有两个,图示如下:

在这里插入图片描述

void _InsertKey(Node* pCur, const K& key, Node* pSub)
	{
		int end = pCur->_size - 1;
		while (end >= 0)
		{
			if (key < pCur->_keys[end])
			{
				pCur->_keys[end + 1] = pCur->_keys[end];//向后移动
				pCur->_pSub[end + 2] = pCur->_pSub[end + 1];//键值移动时带上孩子
			}
			else
				break;
			--end;
		}
		pCur->_keys[end + 1] = key;
		pCur->_pSub[end + 2] = pSub;
		pCur->_size++;
		if (pSub)
		{
			pSub->_pParent = pCur;
		}
	}
bool Insert(const K& key)
	{
		//空树
		if (_pRoot == NULL)
		{
			_pRoot = new Node;
			_pRoot->_keys[0] = key;
			_pRoot->_size = 1;
			_pRoot->_pParent = NULL;
			return true;
		}
		//寻找插入位置
		pair<Node*, int> pos = Find(key);
		//键值已经存在,直接退出
		if (pos.second != -1)
			return false;
		//插入
		Node* pCur = pos.first;
		Node* pSub = NULL;
		K k = key;
		while (1)
		{
			_InsertKey(pCur, k, pSub);//插入键值k
			if (pCur->_size < M)
				return true;
			//分裂节点
			size_t mid = pCur->_size >> 1;
			Node* NewNode = new Node;
			size_t i = mid + 1;
			size_t j = 0;
			for (; i < pCur->_size; ++i, ++j)
			{
				NewNode->_keys[j] = pCur->_keys[i];//复制关键字
				NewNode->_pSub[j] = pCur->_pSub[i];//复制孩子
				if (NewNode->_pSub[j])//孩子存在就指向新节点
					NewNode->_pSub[j]->_pParent = NewNode;
				NewNode->_size++;
			}
			//将最后一个右孩子也复制过来
			NewNode->_pSub[NewNode->_size] = pCur->_pSub[i];
			if (pCur->_pSub[i])
				pCur->_pSub[i]->_pParent = NewNode;
			pCur->_size = pCur->_size - NewNode->_size - 1;
			//如果分裂的结点为根节点
			if (pCur == _pRoot)
			{
				_pRoot = new Node;
				_pRoot->_keys[0] = pCur->_keys[mid];
				_pRoot->_size = 1;
				_pRoot->_pSub[0] = pCur;
				pCur->_pParent = _pRoot;
				_pRoot->_pSub[1] = NewNode;
				NewNode->_pParent = _pRoot;
				return true;
			}
			else
			{
				k = pCur->_keys[mid];
				pCur = pCur->_pParent;
				pSub = NewNode;
			}
		}
	}

在这里插入图片描述

五、B-树的中序遍历

B树的中序遍历思想与二叉搜索树基本一致,从最左边递归遍历即可,但是在循环内部是无法对根节点最后一个元素的子树进行递归,即40,38,50,因此跳出循环后,需要再多递归一次。

void _InOrder(Node* pRoot)
	{
		if (pRoot)
		{
			for (size_t idx = 0; idx < pRoot->_size; ++idx)
			{
				_InOrder(pRoot->_pSub[idx]);
				cout << pRoot->_keys[idx] << " ";
			}
			_InOrder(pRoot->_pSub[pRoot->_size]);
		}
	}
  • 销毁B树
    B树的销毁与B树的中序遍历思想一模一样,注意节点的最后一个元素就不会出错。
void _Destroy(Node* root)
{
     if (root == NULL)
	 return;
     for (size_t idx = 0; idx < root->_size; ++idx)
     {
	  _Destroy(root->_pSub[idx]);
     }
      _Destroy(root->_pSub[root->_size]);
}
  • 源代码及注释
#pragma once
#include <iostream>
using namespace std;
template<class K, size_t M>
struct BTreeNode
{
	// 多给一个关键字:为简化分裂的逻辑
	K _keys[M];     // 关键字的集合
	BTreeNode<K, M>* _pSub[M + 1];    // 孩子的集合
	BTreeNode<K, M>* _pParent;
	size_t _size; // 有效关键字的个数
	BTreeNode()
		: _size(0)
		, _pParent(NULL)
	{
		for (size_t idx = 0; idx < M + 1; ++idx)
			_pSub[idx] = NULL;
	}
};

template<class K, size_t M>
class BTree
{
	typedef BTreeNode<K, M> Node;
public:
	BTree()
		: _pRoot(NULL)
	{}
        ~BTree()
	{
		_Destroy(_pRoot);
	}
	//查找值为key的结点
	pair<Node*, int> Find(const K& key)
	{
		Node* pCur = _pRoot;
		Node* pParent = NULL;
		while (pCur)
		{
			size_t idx = 0;
			//下标必须小于关键字key的个数	
			while (idx < pCur->_size)
			{
				//小于当前节点的值跳出循环指向孩子节点,大于在此节点内向右偏移,相等返回当前节点
				if (key < pCur->_keys[idx])
					break;
				else if (key > pCur->_keys[idx])
					++idx;
				else
					return pair<Node*, int>(pCur,idx);
			}
			pParent = pCur;
			pCur = pCur->_pSub[idx];
		}
		return pair<Node*, int>(pParent, -1);
	}
	//插入
	bool Insert(const K& key)
	{
		//空树
		if (_pRoot == NULL)
		{
			_pRoot = new Node;
			_pRoot->_keys[0] = key;
			_pRoot->_size = 1;
			_pRoot->_pParent = NULL;
			return true;
		}
		//寻找插入位置
		pair<Node*, int> pos = Find(key);
		//键值已经存在,直接退出
		if (pos.second != -1)
			return false;
		//插入
		Node* pCur = pos.first;
		Node* pSub = NULL;
		K k = key;
		while (1)
		{
			_InsertKey(pCur, k, pSub);//插入键值k
			if (pCur->_size < M)
				return true;
			//分裂节点
			size_t mid = pCur->_size >> 1;
			Node* NewNode = new Node;
			size_t i = mid + 1;
			size_t j = 0;
			for (; i < pCur->_size; ++i, ++j)
			{
				NewNode->_keys[j] = pCur->_keys[i];//复制关键字
				NewNode->_pSub[j] = pCur->_pSub[i];//复制孩子
				if (NewNode->_pSub[j])//孩子存在就指向新节点
					NewNode->_pSub[j]->_pParent = NewNode;
				NewNode->_size++;
			}
			//将最后一个右孩子也复制过来
			NewNode->_pSub[NewNode->_size] = pCur->_pSub[i];
			if (pCur->_pSub[i])
				pCur->_pSub[i]->_pParent = NewNode;
			pCur->_size = pCur->_size - NewNode->_size - 1;
			//如果分裂的结点为根节点
			if (pCur == _pRoot)
			{
				_pRoot = new Node;
				_pRoot->_keys[0] = pCur->_keys[mid];
				_pRoot->_size = 1;
				_pRoot->_pSub[0] = pCur;
				pCur->_pParent = _pRoot;
				_pRoot->_pSub[1] = NewNode;
				NewNode->_pParent = _pRoot;
				return true;
			}
			else
			{
				k = pCur->_keys[mid];
				pCur = pCur->_pParent;
				pSub = NewNode;
			}
		}
	}
	//中序遍历
	void InOrder()
	{
		cout << " InOrder:";
		_InOrder(_pRoot);
		cout << endl;
	}

private:
	//中序遍历
	void _InOrder(Node* pRoot)
	{
		if (pRoot)
		{
			for (size_t idx = 0; idx < pRoot->_size; ++idx)
			{
				_InOrder(pRoot->_pSub[idx]);
				cout << pRoot->_keys[idx] << " ";
			}
			_InOrder(pRoot->_pSub[pRoot->_size]);
		}
	}
	//插入key值
	void _InsertKey(Node* pCur, const K& key, Node* pSub)
	{
		int end = pCur->_size - 1;
		while (end >= 0)
		{
			if (key < pCur->_keys[end])
			{
				pCur->_keys[end + 1] = pCur->_keys[end];//向后移动
				pCur->_pSub[end + 2] = pCur->_pSub[end + 1];//键值移动时带上孩子
			}
			else
				break;
			--end;
		}
		pCur->_keys[end + 1] = key;
		pCur->_pSub[end + 2] = pSub;
		pCur->_size++;
		if (pSub)
		{
			pSub->_pParent = pCur;
		}
	}
        //销毁B树
	void _Destroy(Node* root)
	{
		if (root == NULL)
			return;
		for (size_t idx = 0; idx < root->_size; ++idx)
		{
			_Destroy(root->_pSub[idx]);
		}
		_Destroy(root->_pSub[root->_size]);
	}
private:
	Node* _pRoot;
};
#include "BTree.h"
void Test1()
{
	BTree<int, 3> _bt;
	_bt.Insert(10);
	_bt.Insert(30);
	_bt.Insert(20);
	_bt.Insert(40);
	_bt.Insert(50);
	_bt.Insert(38);
	_bt.Insert(35);
	_bt.InOrder();
}
int main()
{
	Test1();
	system("pause");
	return 0;
}
©️2020 CSDN 皮肤主题: 技术黑板 设计师: CSDN官方博客 返回首页
实付0元
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值