算法导论 第18章 B 树

前言

   B树和红黑树一样,也是一种平衡树。但是前者主要用在数据库系统或者文件系统中,有助于降低磁盘I/O次数;此外,B树可以有很多的子女,几十或者上千个不等,“分支因子”较大。


B树定义

   B树是递归定义的,每个节点,都可以看做是一棵以该节点为根的子B树。

《算法导论 第18章 B 树》

《算法导论 第18章 B 树》


B树高度

   《算法导论 第18章 B 树》


B树的操作

   本篇博客实现的B树提供以下操作:

   1、判空操作empty:返回一个bool值,表明树是否为空;

   2、插入操作insert:接受一个值,插入B树,无返回值;

   3、删除操作erase:接受一个值,在B树搜寻删除此值,返回一个bool值,表明是否删除成功;

   4、查找操作search:接受一个值,在B树中查找是否存在此值,返回一个bool值;

   5、输出操作sequentialPrint:按顺序输出B树中的所有关键字;

   6、清空操作clear:释放B树节点占用的资源,使其回到最初状态。


B树节点类型

   首先给出实现的B树的节点类型,C++实现

template < typename T, int degree = 3, typename Compare = less<T> >
struct node
{//B树节点类型,degree为度,默认为3,Compare比较器类型,默认小于
	static const int min_num = degree - 1;//每个节点的最小关键字数
	static const int max_num = 2 * degree - 1;//节点最大关键字数
	static Compare compare;//比较器
	int num = 0;//该节点关键字数目
	bool leaf = true;//是否为叶子节点
	T key[max_num];//关键字数组
	node *child[max_num + 1];//孩子节点数组

	static void setCompare(const Compare &c){ compare = c; }//设置比较器,由B树构造时设置
	node()
	{//默认构造函数
		for (int i = 0; i != max_num; ++i)
		{
			key[i] = T();
			child[i] = nullptr;
		}
		child[max_num] = nullptr;
	}
	int search(const T&)const;
	void insert(const T&);
	bool erase(int);
};

《算法导论 第18章 B 树》

设计理由:

   1、节点内的关键字均是有序的,那么为了加快查找速度,节点内采用二分查找,返回第一个不小于k的关键字索引;

   2、由于采用二分查找,那么关键字的存储最好使用连续的内存空间,因而选择数组;

   3、比较器类型是为了我们可以设置自定义数据类型的比较规则;由B树构造时触发设置。


B树实现代码,C++实现,注释详细,因此就不再画蛇添足叙述算法了

//B树

#include<iostream>
#include<vector>

using namespace std;

template < typename T, int degree = 3, typename Compare = less<T> >
struct node
{//B树节点类型,degree为度,默认为3
	static const int min_num = degree - 1;//每个节点的最小关键字数
	static const int max_num = 2 * degree - 1;//节点最大关键字数
	static Compare compare;
	int num = 0;//该节点关键字数目
	bool leaf = true;//是否为叶子节点
	T key[max_num];//关键字数组
	node *child[max_num + 1];//孩子节点数组

	static void setCompare(const Compare &c){ compare = c; }
	node()
	{//默认构造函数
		for (int i = 0; i != max_num; ++i)
		{
			key[i] = T();
			child[i] = nullptr;
		}
		child[max_num] = nullptr;
	}
	int search(const T&)const;
	void insert(const T&);
	bool erase(int);
};

//定义静态变量
template < typename T, int degree, typename Compare> const int node<T, degree, Compare>::max_num;
template < typename T, int degree, typename Compare> const int node<T, degree, Compare>::min_num;
template < typename T, int degree, typename Compare> Compare node<T, degree, Compare>::compare;

template < typename T, int degree = 3, typename Compare = less<T> >
int node<T, degree,Compare>::search(const T &k)const
{//节点内关键字查找
	int low = 0, high = num - 1;
	while (low < high)
	{//二分查找
		int mid = (low + high) / 2;
		if (!compare(k,key[mid]) && !compare(key[mid],k)) return mid;
		else if (compare(k,key[mid])) high = mid - 1;
		else low = mid + 1;
	}
	if (compare(key[low], k) && low < num - 1) ++low;
	return low;//返回第一个不小于k的关键字的索引
}

template < typename T, int degree = 3, typename Compare = less<T> >
void node<T, degree, Compare>::insert(const T &k)
{//节点内插入
	int i = num - 1;
	while (i >= 0 && compare(k,key[i]))
	{//找寻插入位置
		key[i + 1] = key[i];
		--i;
	}
	key[++i] = k;//插入
	++num;
}

template <typename T,int degree = 3,class Compare>
bool node<T, degree, Compare>::erase(int index)
{//节点内删除
	for (int i = index + 1; i != num; ++i)
		key[i - 1] = key[i];
	--num;
	return true;
}

template < typename T, int degree = 3, typename Compare = less<T> >
class Btree
{//B树
public:
	typedef node<T, degree, Compare>					node;
	typedef Compare										comp;
private:
	node *root;
	Compare compare;
	void destroy(node*);//销毁树
	void split(node*, int);//分割节点
	void insert_aux(node*, const T&);//插入辅助
	bool erase_aux(node*, const T&);//删除辅助
	void merge(node*, int);//合并节点
	T erasePredecessor(node*);//删除前驱
	T eraseSuccessor(node*);//删除后继
	void borrowFromRightSibling(node*, int);//向右兄弟借关键字
	void borrowFromLeftSibling(node*, int);//向左兄弟借关键字
	void print(node*)const;//按顺序打印
public:
	Btree() :root(nullptr), compare(Compare()){ node::setCompare(compare); }
	Btree(const Compare &c) :root(nullptr), compare(c){ node::setCompare(c); }
	bool empty()const { return root == nullptr; }
	void insert(const T&);
	bool search(const T&)const;
	void sequentialPrint()const { print(root); }
	void clear()
	{
		destroy(root); 
		root = nullptr;
	}
	bool erase(const T &k) { return erase_aux(root, k); }
	~Btree(){ destroy(root); }
};

template <typename T,int degree,class Compare>
bool Btree<T,degree, Compare>::search(const T &k)const
{//在B树中查找k
	node *curr = root;
	while (curr != nullptr)
	{
		int index = curr->search(k);//在当前节点查找
		if (!compare(k, curr->key[index]) && !compare(curr->key[index], k)) return true;//若存在
		else if (compare(k,curr->key[index]))//若k小于index处的关键,则在其左边查找
			curr = curr->child[index];
		else curr = curr->child[index + 1];//否则在右边查找
	}
	return false;
}

template <typename T, int degree, class Compare>
void Btree<T, degree, Compare>::split(node *curr, int index)
{//将curr所指节点的index处孩子节点分割成两个节点,两边各degree - 1个关键字,第degree个关键字上升到curr中
	node *new_child = new node,*old_child = curr->child[index];
	T k = old_child->key[degree - 1];
	for (int first = 0; first != degree - 1; ++first)//将原节点的后一半关键字复制到新节点
		new_child->key[first] = old_child->key[first + degree];
	if (!old_child->leaf)//如果不是叶子
		for (int first = 0; first != degree; ++first)//则还要复制一半的孩子节点指针
			new_child->child[first] = old_child->child[first + degree];
	new_child->leaf = old_child->leaf;
	new_child->num = degree - 1;//新节点关键字数
	old_child->num -= degree;//原节点关键字数减半
	for (int last = curr->num - 1; last >= index; --last)//将curr中index(包括)之后的关键字全部后移一个位置
		curr->key[last + 1] = curr->key[last];
	for (int last = curr->num; last > index; --last)//将curr中index(不包括)之后的孩子指针全部后移一个位置
		curr->child[last + 1] = curr->child[last];
	curr->key[index] = k;//在curr中的index处填上升上来的关键字
	curr->child[index + 1] = new_child;//index后的孩子指针指向新节点
	++curr->num;
}

template <typename T,int degree,class Compare>
void Btree<T, degree, Compare>::insert_aux(node *curr, const T &k)
{//插入辅助函数
	if (curr->leaf) curr->insert(k);//若当前节点是叶子,则直接插入
	else
	{//否则
		int index = curr->search(k);//找到第一个不小于k的关键字索引
		if (compare(curr->key[index], k)) ++index;//极端情况,该节点所有关键字均小于k
		if ((curr->child[index])->num == node::max_num)
		{//若该所引处的孩子节点关键字数已满
			split(curr, index);//则将其从该处分割
			if (compare(curr->key[index], k))//分割后上升上来的关键字若小于k
				++index;//则将要到新生成的节点中继续插入
		}
		insert_aux(curr->child[index], k);//递归插入
	}
}

template <typename T, int degree, class Compare>
void Btree<T, degree, Compare>::insert(const T &k)
{//插入关键字
	if (root == nullptr)
	{//如果在空树中插入第一个关键字
		root = new node;
		root->insert(k);
		return;
	}
	else if (root->num == node::max_num)
	{//否则如果根节点满
		node *p = root;
		root = new node;//树将会长高
		root->child[0] = p;
		root->leaf = false;
		split(root, 0);//树根分裂
	}
	insert_aux(root, k);
}

template <typename T,int degree,class Compare>
void Btree<T, degree, Compare>::merge(node *curr, int index)
{//合并curr中index处的孩子节点和其右兄弟,此时两者均恰好只有degree - 1个关键字
	node *left = curr->child[index], *right = curr->child[index + 1];
	left->key[degree - 1] = curr->key[index];//curr中index处关键字先下降
	for (int i = 0; i != right->num; ++i)//复制right所有关键字过来
		left->key[degree + i] = right->key[i];
	for (int i = index + 1; i != curr->num; ++i)//将curr中index之后的关键字前移1
		curr->key[i - 1] = curr->key[i];
	if (!left->leaf)//如果不是叶子
		for (int i = 0; i <= right->num; ++i)//则还要移动right的孩子指针
			left->child[degree + i] = right->child[i];
	for (int i = index + 2; i <= curr->num; ++i)//移动curr中index+2(包括)指针前移1,index处不必被覆盖
		curr->child[i - 1] = curr->child[i];
	--curr->num; ++left->num;
	left->num += right->num;
	delete right;
}

template <typename T,int degree,class Compare>
T Btree<T, degree, Compare>::erasePredecessor(node *curr)
{//删除前驱,在情况2.a中被调用
	if (curr->leaf)
	{//若是叶子,则说明已经可以删除最后一个元素,即前驱了
		T tmp = curr->key[curr->num - 1];
		--curr->num;
		return tmp;//返回前驱
	}
	//否则是内节点,继续递归向下?
	else if (curr->child[curr->num]->num == node::min_num)
	{//若最后一个孩子关键字数目已达最小值
		if (curr->child[curr->num - 1]->num > node::min_num)//若左兄弟有多余关键字
			borrowFromLeftSibling(curr, curr->num);//那么借一个
		else merge(curr, curr->num - 1);//否则只有合并了
	}
	return erasePredecessor(curr->child[curr->num]);//继续向下递归
}

template <typename T,int degree,class Compare>
T Btree<T, degree, Compare>::eraseSuccessor(node *curr)
{//删除后继,在情况2.b中被调用
	if (curr->leaf)
	{//若是叶子节点,则直接删除最前面元素,即后继
		T tmp = curr->key[0];
		curr->erase(0);
		return tmp;
	}
	//否则,是内节点,继续向下?
	else if (curr->child[0]->num == node::min_num)
	{//若第一个孩子关键字数目已达最小值
		if (curr->child[1]->num > node::min_num)//若右兄弟有足够关键字
			borrowFromRightSibling(curr, 0);//则借一个
		else merge(curr, 0);//否则只有合并了
	}
	return eraseSuccessor(curr->child[0]);//继续向下递归
}

template <typename T,int degree,class Compare>
void Btree<T, degree, Compare>::borrowFromRightSibling(node *curr, int index)
{//curr中index孩子向其右兄弟借一个关键字
	node *left = curr->child[index], *right = curr->child[index + 1];
	left->key[left->num] = curr->key[index];//先将curr中index处的关键字添入该子树根,left所指
	curr->key[index] = right->key[0];//再用右兄弟(right所指)第一个关键字覆盖curr中index槽位
	for (int i = 1; i != right->num; ++i)//将右兄弟从1开始的所有关键字前移1
		right->key[i - 1] = right->key[i];
	if (!left->leaf)
	{//若并非叶子,则还要设置相关孩子指针域
		left->child[left->num + 1] = right->child[0];//右兄弟第一个孩子成为left最后一个孩子
		for (int i = 1; i <= right->num; ++i)//前移右兄弟的孩子指针数组
			right->child[i - 1] = right->child[i];
	}
	++left->num; --right->num;
}

template <typename T,int degree,class Compare>
void Btree<T, degree, Compare>::borrowFromLeftSibling(node *curr, int index)
{
	--index;//移到左兄弟槽位
	node *left = curr->child[index], *right = curr->child[index + 1];
	right->insert(curr->key[index]);
	curr->key[index] = left->key[left->num - 1];
	if (!right->leaf)
	{//非叶子,移动指针
		for (int i = right->num; i >= 0; --i)
			right->child[i + 1] = right->child[i];
		right->child[0] = left->child[left->num];
	}
	--left->num;
}

template <typename T,int degree,class Compare>
bool Btree<T, degree, Compare>::erase_aux(node *curr, const T &k)
{//删除辅助函数
	int index = curr->search(k);//找到第一个不小于k的关键字
	if (curr->leaf && (!compare(curr->key[index], k) && !compare(k, curr->key[index])))
		return curr->erase(index);//情况1,关键字在叶子
	else if (curr->leaf) return false;//不在叶子节点,则删除失败,不存在该关键字
	if (!curr->leaf && (!compare(curr->key[index], k) && !compare(k, curr->key[index])))
	{//情况2,关键字在该内节点。则使用该关键字的前驱或者后继代替
		if (curr->child[index]->num > node::min_num)
		{//情况2.a,其左孩子有足够的关键字,即至少degree - 1个,则使用前驱代替。
			//删除其前驱,并返回前驱关键字,以覆盖该关键字
			curr->key[index] = erasePredecessor(curr->child[index]);
			return true;
		}
		else if (curr->child[index + 1]->num > node::min_num)
		{//情况2.b,否则其右孩子有足够关键字,则使用后继代替
			curr->key[index] = eraseSuccessor(curr->child[index + 1]);//同上
			return true;
		}
		else
		{//否则,由于该关键字左右孩子的关键字数均处于最少,则不能采用前驱或者后继代替,那么合并左右孩子以及该关键字
			merge(curr, index);//将curr节点的index处相关关键字和孩子合并
			return erase_aux(curr->child[index], k);
		}
	}
	else
	{//情况3,关键字不在该节点,处于该关键字的左子树中
		if (compare(curr->key[index], k)) ++index;//极端情况,当curr中所有关键字均比k小时出现
		if (curr->child[index]->num == node::min_num)
		{//若左子树关键字数已到达最小值
			if (index < curr->num && curr->child[index + 1]->num > node::min_num)
				//情况3.a,存在右兄弟,且有足够节点,即至少degree - 1个,则向其借一个
				borrowFromRightSibling(curr, index);
			else if (index > 0 && curr->child[index - 1]->num > node::min_num)
				//情况3.b,存在左兄弟且关键字数足够,类似于3.a
				borrowFromLeftSibling(curr, index);
			else
			{//3.c,左/右兄弟均只有degree - 1个关键字,那么合并节点
				if (index == curr->num) --index;//呼应上述极端情况下的节点合并
				merge(curr, index);
				if (curr == root && curr->num == 0)
				{//若当前curr是根,且仅存一个元素
					root = curr->child[index];
					delete curr;//那么树高降1
					return erase_aux(root, k);
				}
			}
		}
		return erase_aux(curr->child[index], k);
	}
}

template <typename T,int degree,class Compare>
void Btree<T, degree, Compare>::print(node *curr)const
{//打印整棵树
	if (curr->leaf)
	{//若是叶子节点,则打印全部关键字
		cout << "[ ";
		for (int i = 0; i != curr->num; ++i)
			cout << curr->key[i] << ' ';
		cout << ']' << endl;
	}
	else
	{//否则
		cout << '{' << endl;
		for (int i = 0; i <= curr->num; ++i)
		{//依次打印孩子和关键字
			print(curr->child[i]);
			if (i < curr->num)
				cout << curr->key[i] << endl;
		}
		cout << '}' << endl;
	}
}

template <typename T,int degree,class Compare>
void Btree<T, degree, Compare>::destroy(node *curr)
{//销毁B树
	if (curr == nullptr) return;
	if (curr->leaf) delete curr;//若是叶子,直接销毁
	else
	{//否则
		for (int i = 0; i <= curr->num; ++i)//依次销毁所有孩子后
			destroy(curr->child[i]);
		delete curr;//再销毁该节点
	}
}

int main()
{
	/*Btree<char> bt;
	vector<char> cvec = {'P','C','M','T','X','A','B','D','E',
		'F','J','K','L','N','O','Q','R','S','U','V','Y','Z'};
	for (size_t i = 0; i != cvec.size(); ++i)
		bt.insert(cvec[i]);
	cout << boolalpha << bt.search('A') << endl;
	bt.erase('L');
	bt.erase('M');
	bt.erase('K');
	bt.erase('A');
	bt.erase('X');
	bt.erase('Y');
	bt.erase('J');
	cout << boolalpha << bt.search('A') << endl;
	bt.sequentialPrint();*/
	Btree<int,10> bt;
	for (int i = 0; i != 10000; ++i)
		bt.insert(i);
	for (int i = 0; i != 100; ++i)
		bt.erase(3 * i);
	bt.sequentialPrint();
	getchar();
	return 0;
}

习题 18.1-1

    因为节点中的关键字数目至少为1,那么查找方向至少有两个,因而度至少要为2.


习题18.1-4

   (2t)^(h+1) – 1


习题18.1-5

   2-3-4树


习题 18.2-2

    根满之后分裂,会出现一次冗余读。


习题 18.3-2

    见erase和erase_aux代码,非常简洁。







    原文作者:B树
    原文地址: https://blog.csdn.net/zilingxiyue/article/details/44859861
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞