C++ 数据结构学习 ---- B树

1. 头文件

需要先引入C++ 数据结构学习 ---- 二叉树_孤城寻欢的博客-CSDN博客的BNode头文件

1.1 Btree

template <typename T> class BTree { //B-树模板类
protected:
	int _size; //存放的关键码总数
	int _m; //B-树的阶次,至少为3——创建时指定,一般不能修改
	BTNodePosi<T> _root; //根节点
	BTNodePosi<T> _hot; //BTree::search()最后访问的非空(除非树空)的节点位置
	void solveOverflow(BTNodePosi<T>); //因插入而上溢之后的分裂处理
	void solveUnderflow(BTNodePosi<T>); //因删除而下溢之后的合并处理
public:
	BTree(int m = 3) : _m(m), _size(0) //构造函数:默认为最低的3阶
	{
		_root = new BTNode<T>();
	}
	~BTree() { if (_root) release(_root); } //析构函数:释放所有节点
	int const order() { return _m; } //阶次
	int const size() { return _size; } //规模
	BTNodePosi<T>& root() { return _root; } //树根
	bool empty() const { return !_root; } //判空
	BTNodePosi<T> search(const T& e); //查找
	bool insert(const T& e); //插入
	bool remove(const T& e); //删除
}; //BTree

1.2 Dice

#include <ctime>
#include <Stdlib.h>

static int dice(int range) { return rand() % range; } //取[0, range)中的随机整数
static int dice(int lo, int hi) { return lo + rand() % (hi - lo); } //取[lo, hi)中的随机整数
static float dice(float range) { return rand() % (1000 * (int)range) / (float)1000.; }
static double dice(double range) { return rand() % (1000 * (int)range) / (double)1000.; }
static char dice() { return (char)(32 + rand() % 96); }

2. 相关函数

2.1 插入函数

 //将关键码e插入B树中
template <typename T> bool BTree<T>::insert(const T& e) {
	BTNodePosi<T> v = search(e); if (v) return false; //确认目标节点不存在
	Rank r = _hot->key.search(e); //此处的search来自Vector接口,在节点_hot的有序关键码向量中查找合适的插入位置
	_hot->key.insert(r + 1, e); //将新关键码插至对应的位置
	_hot->child.insert(r + 2, NULL); //创建一个空子树指针
    /*存在另一种方法,插入空子树*/
    //此时的节点的叶节点,所有的子树也是空的,所以直接在child向量末尾加一个空子树与_hot->child.insert(r + 2, NULL)是等价的
	_size++; //更新全树规模
	solveOverflow(_hot); //如有必要,需做分裂
	return true; //插入成功
}

2.2 删除函数

//从BTree树中删除关键码e
template <typename T> bool BTree<T>::remove(const T& e) { 
	BTNodePosi<T> v = search(e); if (!v) return false; //确认目标关键码存在
	Rank r = v->key.search(e); //确定目标关键码在节点v中的秩(由上,肯定合法)
	if (v->child[0]) { //若v非叶子,则e的后继必属于某叶节点
		BTNodePosi<T> u = v->child[r + 1]; //在右子树中一直向左,即可
		while (u->child[0]) u = u->child[0]; //找出e的后继
		v->key[r] = u->key[0]; v = u; r = 0; //并与之交换位置
	} //至此,v必然位于最底层,且其中第r个关键码就是待删除者
	v->key.remove(r); v->child.remove(r + 1); _size--; //删除e,以及其下两个外部节点之一
	solveUnderflow(v); //如有必要,需做旋转或合并
	return true;
}

2.3 搜索函数


//在B-树中查找关键码e
template <typename T> BTNodePosi<T> BTree<T>::search(const T& e) { 
	BTNodePosi<T> v = _root; _hot = NULL; //从根节点出发
	while (v) { //逐层查找
		Rank r = v->key.search(e); //在当前节点中,找到不大于e的最大关键码
		if ((0 <= r) && (e == v->key[r])) return v; //成功:在当前节点中命中目标关键码
		_hot = v; v = v->child[r + 1]; //否则,转入对应子树(_hot指向其父)——需做I/O,最费时间
	} //这里在向量内是二分查找,但对通常的_m可直接顺序查找
	return NULL; //失败:最终抵达外部节点
}

2.4 上溢函数

//关键码插入后若节点上溢,则做节点分裂处理
template <typename T> void BTree<T>::solveOverflow(BTNodePosi<T> v) {
	while (_m <= v->key.size()) { //除非当前节点并未上溢
		Rank s = _m / 2; //轴点(此时应有_m = key.size() = child.size() - 1)
		BTNodePosi<T> u = new BTNode<T>(); //注意:新节点已有一个空孩子
		for (Rank j = 0; j < _m - s - 1; j++) { //v右侧_m-s-1个孩子及关键码分裂为右侧节点u
			u->child.insert(j, v->child.remove(s + 1)); //逐个移动效率低
			u->key.insert(j, v->key.remove(s + 1)); //此策略可改进
		}
		u->child[_m - s - 1] = v->child.remove(s + 1); //移动v最靠右的孩子
		if (u->child[0]) //若u的孩子们非空,则
			for (Rank j = 0; j < _m - s; j++) //令它们的父节点统一
				u->child[j]->parent = u; //指向u
		BTNodePosi<T> p = v->parent; //v当前的父节点p
		if (!p) { _root = p = new BTNode<T>(); p->child[0] = v; v->parent = p; } //若p空则创建之
		Rank r = 1 + p->key.search(v->key[0]); //p中指向v的指针的秩
		p->key.insert(r, v->key.remove(s)); //轴点关键码上升
		p->child.insert(r + 1, u);  u->parent = p; //新节点u与父节点p互联
		v = p; //上升一层,如有必要则继续分裂——至多O(logn)层
	} //while
}

2.5 下溢函数

//关键码删除后若节点下溢,则做节点旋转或合并处理
template <typename T> void BTree<T>::solveUnderflow(BTNodePosi<T> v) {
    while ((_m + 1) / 2 > v->child.size()) { //除非当前节点并未下溢
        BTNodePosi<T> p = v->parent;
        if (!p) { //已到根节点(两个孩子即可)
            if (!v->key.size() && v->child[0]) {
                //但倘若作为树根的v已不含关键码,却有(唯一的)非空孩子,则
                /*DSA*/printf("collapse\n");
                _root = v->child[0]; _root->parent = NULL; //这个节点可被跳过
                v->child[0] = NULL; release(v); //并因不再有用而被销毁
            } //整树高度降低一层
            return;
        }
        Rank r = 0; while (p->child[r] != v) r++;
        //确定v是p的第r个孩子——此时v可能不含关键码,故不能通过关键码查找
        //另外,在实现了孩子指针的判等器之后,也可直接调用Vector::find()定位
        /*DSA*/printf("\nrank = %d", r);
        // 情况1:向左兄弟借关键码
        if (0 < r) { //若v不是p的第一个孩子,则
            BTNodePosi<T> ls = p->child[r - 1]; //左兄弟必存在
            if ((_m + 1) / 2 < ls->child.size()) { //若该兄弟足够“胖”,则
                /*DSA*/printf(" ... case 1\n");
                v->key.insert(0, p->key[r - 1]); //p借出一个关键码给v(作为最小关键码)
                p->key[r - 1] = ls->key.remove(ls->key.size() - 1); //ls的最大关键码转入p
                v->child.insert(0, ls->child.remove(ls->child.size() - 1));
                //同时ls的最右侧孩子过继给v
                if (v->child[0]) v->child[0]->parent = v; //作为v的最左侧孩子
                return; //至此,通过右旋已完成当前层(以及所有层)的下溢处理
            }
        } //至此,左兄弟要么为空,要么太“瘦”
     // 情况2:向右兄弟借关键码
        if (p->child.size() - 1 > r) { //若v不是p的最后一个孩子,则
            BTNodePosi<T> rs = p->child[r + 1]; //右兄弟必存在
            if ((_m + 1) / 2 < rs->child.size()) { //若该兄弟足够“胖”,则
                /*DSA*/printf(" ... case 2\n");
                v->key.insert(v->key.size(), p->key[r]); //p借出一个关键码给v(作为最大关键码)
                p->key[r] = rs->key.remove(0); //rs的最小关键码转入p
                v->child.insert(v->child.size(), rs->child.remove(0));
                //同时rs的最左侧孩子过继给v
                if (v->child[v->child.size() - 1]) //作为v的最右侧孩子
                    v->child[v->child.size() - 1]->parent = v;
                return; //至此,通过左旋已完成当前层(以及所有层)的下溢处理
            }
        } //至此,右兄弟要么为空,要么太“瘦”
     // 情况3:左、右兄弟要么为空(但不可能同时),要么都太“瘦”——合并
        if (0 < r) { //与左兄弟合并
            /*DSA*/printf(" ... case 3L\n");
            BTNodePosi<T> ls = p->child[r - 1]; //左兄弟必存在
            ls->key.insert(ls->key.size(), p->key.remove(r - 1)); p->child.remove(r);
            //p的第r - 1个关键码转入ls,v不再是p的第r个孩子
            ls->child.insert(ls->child.size(), v->child.remove(0));
            if (ls->child[ls->child.size() - 1]) //v的最左侧孩子过继给ls做最右侧孩子
                ls->child[ls->child.size() - 1]->parent = ls;
            while (!v->key.empty()) { //v剩余的关键码和孩子,依次转入ls
                ls->key.insert(ls->key.size(), v->key.remove(0));
                ls->child.insert(ls->child.size(), v->child.remove(0));
                if (ls->child[ls->child.size() - 1]) ls->child[ls->child.size() - 1]->parent = ls;
            }
            release(v); //释放v
        }
        else { //与右兄弟合并
            /*DSA*/printf(" ... case 3R\n");
            BTNodePosi<T> rs = p->child[r + 1]; //右兄弟必存在
            rs->key.insert(0, p->key.remove(r)); p->child.remove(r);
            //p的第r个关键码转入rs,v不再是p的第r个孩子
            rs->child.insert(0, v->child.remove(v->child.size() - 1));
            if (rs->child[0]) rs->child[0]->parent = rs; //v的最右侧孩子过继给rs做最左侧孩子
            while (!v->key.empty()) { //v剩余的关键码和孩子,依次转入rs
                rs->key.insert(0, v->key.remove(v->key.size() - 1));
                rs->child.insert(0, v->child.remove(v->child.size() - 1));
                if (rs->child[0]) rs->child[0]->parent = rs;
            }
            release(v); //释放v
        }
        v = p; //上升一层,如有必要则继续旋转或合并——至多O(logn)层
    } //while
}

2.6 释放函数

//释放节点值
template <typename T>void release(T& e) {
    e = NULL;
}
//释放节点空间
template <typename T>void release(BTNodePosi<T>& e) {
    delete e;
}

3. 完整代码

#include "BTNode.h"

template <typename T> class BTree { //B-树模板类
protected:
	int _size; //存放的关键码总数
	int _m; //B-树的阶次,至少为3——创建时指定,一般不能修改
	BTNodePosi<T> _root; //根节点
	BTNodePosi<T> _hot; //BTree::search()最后访问的非空(除非树空)的节点位置
	void solveOverflow(BTNodePosi<T>); //因插入而上溢之后的分裂处理
	void solveUnderflow(BTNodePosi<T>); //因删除而下溢之后的合并处理
public:
	BTree(int m = 3) : _m(m), _size(0) //构造函数:默认为最低的3阶
	{
		_root = new BTNode<T>();
	}
	~BTree() { if (_root) release(_root); } //析构函数:释放所有节点
	int const order() { return _m; } //阶次
	int const size() { return _size; } //规模
	BTNodePosi<T>& root() { return _root; } //树根
	bool empty() const { return !_root; } //判空
	BTNodePosi<T> search(const T& e); //查找
	bool insert(const T& e); //插入
	bool remove(const T& e); //删除
}; //BTree

 //将关键码e插入B树中
template <typename T> bool BTree<T>::insert(const T& e) {
	BTNodePosi<T> v = search(e); if (v) return false; //确认目标节点不存在
	Rank r = _hot->key.search(e); //此处的search来自Vector接口,在节点_hot的有序关键码向量中查找合适的插入位置
	_hot->key.insert(r + 1, e); //将新关键码插至对应的位置
	_hot->child.insert(r + 2, NULL); //创建一个空子树指针
    /*存在另一种方法,插入空子树*/
    //此时的节点的叶节点,所有的子树也是空的,所以直接在child向量末尾加一个空子树与_hot->child.insert(r + 2, NULL)是等价的
	_size++; //更新全树规模
	solveOverflow(_hot); //如有必要,需做分裂
	return true; //插入成功
}

//从BTree树中删除关键码e
template <typename T> bool BTree<T>::remove(const T& e) { 
	BTNodePosi<T> v = search(e); if (!v) return false; //确认目标关键码存在
	Rank r = v->key.search(e); //确定目标关键码在节点v中的秩(由上,肯定合法)
	if (v->child[0]) { //若v非叶子,则e的后继必属于某叶节点
		BTNodePosi<T> u = v->child[r + 1]; //在右子树中一直向左,即可
		while (u->child[0]) u = u->child[0]; //找出e的后继
		v->key[r] = u->key[0]; v = u; r = 0; //并与之交换位置
	} //至此,v必然位于最底层,且其中第r个关键码就是待删除者
	v->key.remove(r); v->child.remove(r + 1); _size--; //删除e,以及其下两个外部节点之一
	solveUnderflow(v); //如有必要,需做旋转或合并
	return true;
}

//在B-树中查找关键码e
template <typename T> BTNodePosi<T> BTree<T>::search(const T& e) { 
	BTNodePosi<T> v = _root; _hot = NULL; //从根节点出发
	while (v) { //逐层查找
		Rank r = v->key.search(e); //在当前节点中,找到不大于e的最大关键码
		if ((0 <= r) && (e == v->key[r])) return v; //成功:在当前节点中命中目标关键码
		_hot = v; v = v->child[r + 1]; //否则,转入对应子树(_hot指向其父)——需做I/O,最费时间
	} //这里在向量内是二分查找,但对通常的_m可直接顺序查找
	return NULL; //失败:最终抵达外部节点
}


//关键码插入后若节点上溢,则做节点分裂处理
template <typename T> void BTree<T>::solveOverflow(BTNodePosi<T> v) {
	while (_m <= v->key.size()) { //除非当前节点并未上溢
		Rank s = _m / 2; //轴点(此时应有_m = key.size() = child.size() - 1)
		BTNodePosi<T> u = new BTNode<T>(); //注意:新节点已有一个空孩子
		for (Rank j = 0; j < _m - s - 1; j++) { //v右侧_m-s-1个孩子及关键码分裂为右侧节点u
			u->child.insert(j, v->child.remove(s + 1)); //逐个移动效率低
			u->key.insert(j, v->key.remove(s + 1)); //此策略可改进
		}
		u->child[_m - s - 1] = v->child.remove(s + 1); //移动v最靠右的孩子
		if (u->child[0]) //若u的孩子们非空,则
			for (Rank j = 0; j < _m - s; j++) //令它们的父节点统一
				u->child[j]->parent = u; //指向u
		BTNodePosi<T> p = v->parent; //v当前的父节点p
		if (!p) { _root = p = new BTNode<T>(); p->child[0] = v; v->parent = p; } //若p空则创建之
		Rank r = 1 + p->key.search(v->key[0]); //p中指向v的指针的秩
		p->key.insert(r, v->key.remove(s)); //轴点关键码上升
		p->child.insert(r + 1, u);  u->parent = p; //新节点u与父节点p互联
		v = p; //上升一层,如有必要则继续分裂——至多O(logn)层
	} //while
}


//关键码删除后若节点下溢,则做节点旋转或合并处理
template <typename T> void BTree<T>::solveUnderflow(BTNodePosi<T> v) {
    while ((_m + 1) / 2 > v->child.size()) { //除非当前节点并未下溢
        BTNodePosi<T> p = v->parent;
        if (!p) { //已到根节点(两个孩子即可)
            if (!v->key.size() && v->child[0]) {
                //但倘若作为树根的v已不含关键码,却有(唯一的)非空孩子,则
                /*DSA*/printf("collapse\n");
                _root = v->child[0]; _root->parent = NULL; //这个节点可被跳过
                v->child[0] = NULL; release(v); //并因不再有用而被销毁
            } //整树高度降低一层
            return;
        }
        Rank r = 0; while (p->child[r] != v) r++;
        //确定v是p的第r个孩子——此时v可能不含关键码,故不能通过关键码查找
        //另外,在实现了孩子指针的判等器之后,也可直接调用Vector::find()定位
        /*DSA*/printf("\nrank = %d", r);
        // 情况1:向左兄弟借关键码
        if (0 < r) { //若v不是p的第一个孩子,则
            BTNodePosi<T> ls = p->child[r - 1]; //左兄弟必存在
            if ((_m + 1) / 2 < ls->child.size()) { //若该兄弟足够“胖”,则
                /*DSA*/printf(" ... case 1\n");
                v->key.insert(0, p->key[r - 1]); //p借出一个关键码给v(作为最小关键码)
                p->key[r - 1] = ls->key.remove(ls->key.size() - 1); //ls的最大关键码转入p
                v->child.insert(0, ls->child.remove(ls->child.size() - 1));
                //同时ls的最右侧孩子过继给v
                if (v->child[0]) v->child[0]->parent = v; //作为v的最左侧孩子
                return; //至此,通过右旋已完成当前层(以及所有层)的下溢处理
            }
        } //至此,左兄弟要么为空,要么太“瘦”
     // 情况2:向右兄弟借关键码
        if (p->child.size() - 1 > r) { //若v不是p的最后一个孩子,则
            BTNodePosi<T> rs = p->child[r + 1]; //右兄弟必存在
            if ((_m + 1) / 2 < rs->child.size()) { //若该兄弟足够“胖”,则
                /*DSA*/printf(" ... case 2\n");
                v->key.insert(v->key.size(), p->key[r]); //p借出一个关键码给v(作为最大关键码)
                p->key[r] = rs->key.remove(0); //rs的最小关键码转入p
                v->child.insert(v->child.size(), rs->child.remove(0));
                //同时rs的最左侧孩子过继给v
                if (v->child[v->child.size() - 1]) //作为v的最右侧孩子
                    v->child[v->child.size() - 1]->parent = v;
                return; //至此,通过左旋已完成当前层(以及所有层)的下溢处理
            }
        } //至此,右兄弟要么为空,要么太“瘦”
     // 情况3:左、右兄弟要么为空(但不可能同时),要么都太“瘦”——合并
        if (0 < r) { //与左兄弟合并
            /*DSA*/printf(" ... case 3L\n");
            BTNodePosi<T> ls = p->child[r - 1]; //左兄弟必存在
            ls->key.insert(ls->key.size(), p->key.remove(r - 1)); p->child.remove(r);
            //p的第r - 1个关键码转入ls,v不再是p的第r个孩子
            ls->child.insert(ls->child.size(), v->child.remove(0));
            if (ls->child[ls->child.size() - 1]) //v的最左侧孩子过继给ls做最右侧孩子
                ls->child[ls->child.size() - 1]->parent = ls;
            while (!v->key.empty()) { //v剩余的关键码和孩子,依次转入ls
                ls->key.insert(ls->key.size(), v->key.remove(0));
                ls->child.insert(ls->child.size(), v->child.remove(0));
                if (ls->child[ls->child.size() - 1]) ls->child[ls->child.size() - 1]->parent = ls;
            }
            release(v); //释放v
        }
        else { //与右兄弟合并
            /*DSA*/printf(" ... case 3R\n");
            BTNodePosi<T> rs = p->child[r + 1]; //右兄弟必存在
            rs->key.insert(0, p->key.remove(r)); p->child.remove(r);
            //p的第r个关键码转入rs,v不再是p的第r个孩子
            rs->child.insert(0, v->child.remove(v->child.size() - 1));
            if (rs->child[0]) rs->child[0]->parent = rs; //v的最右侧孩子过继给rs做最左侧孩子
            while (!v->key.empty()) { //v剩余的关键码和孩子,依次转入rs
                rs->key.insert(0, v->key.remove(v->key.size() - 1));
                rs->child.insert(0, v->child.remove(v->child.size() - 1));
                if (rs->child[0]) rs->child[0]->parent = rs;
            }
            release(v); //释放v
        }
        v = p; //上升一层,如有必要则继续旋转或合并——至多O(logn)层
    } //while
}

//释放节点值
template <typename T>void release(T& e) {
    e = NULL;
}
//释放节点空间
template <typename T>void release(BTNodePosi<T>& e) {
    delete e;
}


#include <iostream>
#include"Dice.h"

using namespace std;



template <typename T> void  testBTree(int m, int n) {
    BTree<T> bt(m);
    while (bt.size() < n) {
        T e = dice( (T)n * 3 ); //[0, 3n)范围内的e
        switch (dice(3)) {
        case 0: { //查找,成功率 <= 33.3%
            cout << "搜索"<<e<<"...";//printf("Searching for "); print(e); printf(" ... ");
            BTNodePosi<T> p = bt.search(e);
            if(p) cout << "存在!" << endl;
            else  cout << "不存在!" << endl;
            break;
        }
        case 1: { //删除,成功率 <= 33.3%
            cout << "删除" << e << "...";// printf("Removing "); print(e); printf(" ... ");
            if ( bt.remove(e) ) cout << "删除成功!"<<e << endl;
            else cout << "不存在" << endl;
            break;
        }
        default: {//插入,成功率 == 100%
            cout << "插入" << e << "...";//printf("Inserting "); print(e); printf(" ... ");
            int oldSize = bt.size();
            if( bt.insert(e) ) cout << "插入成功!" << e << endl;
            else cout << "插入失败!" << endl;
            break;
        }
        }
    }
    /*
    while (bt.size() > 0) {
        T e = dice((T)n * 3); //[0, 3n)范围内的e
        cout << "删除" << e << "...";
       // printf("Removing "); print(e); printf(" ... ");
        if (bt.remove(e)) cout << "删除成功!" << endl;
        else cout << "不存在!" << endl;
       // bt.remove(e) ?
       //     printf("Done\n"), print(bt) :
       //     printf("not exists\n");
    }
    */
}

int main() {
    srand((unsigned int)time(NULL));
    testBTree<int>(2,4); 
   
	system("pause");
}

4. 运行结果及截图

 以上相关代码参考邓俊辉老师的《c++语言版数据结构》!

猜你喜欢

转载自blog.csdn.net/qq_58240448/article/details/127888060
今日推荐