B-树/B+树/B*树详解(查找+插入)

本文深入探讨了B树、B+树和B*树的概念及其差异,解析了它们在大规模数据存储中的应用价值,并通过示例代码展示了B树的插入与查找操作。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言:

    为了能够更好的理解什么是B树,这里先对所有的动态查找树做下总结:

动态二叉树总共分为:二叉查找树(Binary Search Tree),平衡二叉查找树(Balanced Binary Search Tree),红黑树(Red-Black Tree ),B-tree/B+tree/B*tree。前三者是典型的二叉查找树结构,其查找的时间复杂度O(log2N)与树的深度相关,那么降低树的高度自然就可以提高查找效率。那么如何解决降低树的高度的问题?

    现在假设这么个场景咱们有面对这样一个实际问题:就是大规模数据存储中,实现索引查询这样一个实际背景下,树节点存储的元素数量是有限的(如果元素数量非常多的话,查找就退化成节点内部的线性查找了),这样导致二叉查找树结构由于树的深度过大而造成磁盘I/O读写过于频繁,进而导致查询效率低下,那么如何减少树的深度(当然是不能减少查询的数据量),一个基本的想法就是:采用多叉树结构(由于树节点元素数量是有限的,自然该节点的子树数量也就是有限的)。


对于多叉树主要分为三种:B-tree/B+tree/B*tree

一、Btree:

一棵M阶(M>2)的B树,是一棵平衡的M路平衡搜索树,可以是空树或者满足一下性质:
1. 根节点至少有两个孩子
2. 每个非根节点有[ ,M]个孩子
3. 每个非根节点有[ -1,M-1]个关键字,并且以升序排列
4. key[i]和key[i+1]之间的孩子节点的值介于key[i]、key[i+1]之间
5. 所有的叶子节点都在同一层


二、B+树

三、B*树


    这里只分别讲一下他们的区别于联系

他们的结构如下面三个图所示:B树还算是正常的多叉树,B+树和B*树是在B树的基础上发展的。

B树与B+树的区别:

1.所有的叶子节点包含了全部关键子信息,及指向含有这些关键字记录的指针,且叶子结点本身依关键字的大小自小而大的顺序链接。 (而B 树的叶子节点并没有包括全部需要查找的信息)

 2.所有的非终端结点可以看成是索引部分,结点中仅含有其子树根结点中最大(或最小)关键字。 (而B 树的非终节点也包含需要查找的有效信息)


B+树与B*树的区别:

B+树的分裂:当一个结点满时,分配一个新的结点,并将原结点中1/2的数据复制到新结点,最后在父结点中增加新结点的指针;B+树的分裂只影响原结点和父结点,而不会影响兄弟结点,所以它不需要指向兄弟的指针。

B*树的分裂:当一个结点满时,如果它的下一个兄弟结点未满,那么将一部分数据移到兄弟结点中,再在原结点插入关键字,最后修改父结点中兄弟结点的关键字(因为兄弟结点的关键字范围改变了);如果兄弟也满了,则在原结点与兄弟结点之间增加新结点,并各复制1/3的数据到新结点,最后在父结点增加新结点的指针。

所以,B*树分配新结点的概率比B+树要低,空间使用率更高;


B树的插入和查找

#ifndef __BTREE_H__
#define __BTREE_H__

#include <iostream>
using namespace std;

template<class K,int M = 3>
struct BTreeNode
{
	K _key[M];                   //多放一个空间方便分裂
	BTreeNode<K, M>* _subs[M + 1];
	BTreeNode<K, M>* _parent;
	size_t _size;

	BTreeNode()
		:_size(0)
	{
		_parent = NULL;
		for (int i = 0; i <= M; i++)
		{
			_subs[i] = NULL;
		}
	}
};


template<class K,class V>
struct Pair
{
	K _first;
	V _second;

	Pair(const K& key, const V& value)
		:_first(key)
		, _second(value)
	{}
};

template<class K,int M = 3>
class BTree
{
	typedef BTreeNode<K, M> Node;
public:
	BTree()
		:_root(NULL)
	{}

	Pair<K, int> Find(const K& key)
	{
		Node* cur = _root;
		Node* parent = NULL;
		int index = 0;
		while (cur)
		{
			while (index < cur->_size)
			{
				if (cur->_key[index] < key)
					++index;
				else if (cur->_key[index] == key)
				{
					return Pair<K, M>(cur, index);
				}
				else
					break;
			}
			parent = cur;
			cur = cur->_subs[index];//在当前节点往下找
			index = 0;
		}
		return Pair<K, M>(parent, -1);//如果没找到就返回父亲节点的位置,然后在父亲节点的位置插入
	}

	bool Insert(const K& key)
	{
		//没有节点,直接修改root
		if (_root == NULL)
		{
			_root = new Node();
			_root->_key[0] = key; 
			_root->_size++;
			return true;
		}

		Pair<K, M> pair = Find(key);
		if (pair._second != -1)
		{
			return -1;//已经存在
		}
		Node* cur = pair._first;
		Node* parent = cur->_parent;
		K insertKey = key;

		while (1)
		{
			_InsertKey(cur,insertKey);//直接插入

			//小于M,无需分裂
			if (cur->_size < M)
			{
				return true;
			}

			//分裂调整
			int mid = (cur->_size - 1) / 2;//减1是为了防止M为偶数
			Node* right = new Node();
			int index = 0;
			//拷贝_key[]
			for (int i = mid + 1; i < cur->_size; i++)
			{
				right->_key[index++] = cur->_key[i];
				++right->_size;
			}
			index = 0;
			//拷贝subs[]
			for (int i = mid + 1; i < cur->_size; ++i)
			{
				right->_subs[index++] = cur->_subs[i];
				if (cur->_subs[i])
				{
					cur->_subs[i]->_parent = right;
				}
			}
			insertKey = cur->_key[mid];
			cur->_size = (cur->_size - 1) / 2;
			if (parent == NULL)//cur为根节点
			{
				Node* tmp = new Node();
				tmp->_key[0] = insertKey;
				++tmp->_size;
				tmp->_subs[0] = cur;
				tmp->_subs[1] = right;
				cur->_parent = right->_parent = tmp;
				_root = tmp;
				break;
			}
			else
			{
				right->_parent = parent;
				index = M - 1;
				while (parent->_subs[index] != cur)
				{
					parent->_subs[index + 1] = parent->_subs[index];
					--index;
				}

				parent->_subs[index + 1] = right;
				cur = parent;
				parent = cur->_parent;
			}
		}
		return true;
	}

	void _InsertKey(Node* cur, cosnt K& key)
	{
		//插入排序
		it index = cur->_size - 1;
		while (index >= 0 && key < cur->_key[index])
		{
			cur->_key[index + 1] = cur->_key[index];
			--index;
		}
		cur->_key[index + 1] = key;
		++cur->_size;
	}
protected:
	Node* _root;
};






#endif //__BTREE_H__





 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值