B树:(一种自平衡的搜索树)

根据 Knuth 的定义,一个 m 阶的B树是一个有以下属性的树:
              每一个节点最多有 m 个子节点
              每一个非叶子节点(除根节点)最少有 ⌈m/2⌉ 个子节点
              如果根节点不是叶子节点,那么它至少有两个子节点
              有 k 个子节点的非叶子节点拥有 k − 1 个键
              所有的叶子节点都在同一层
              每一个内部节点的键将节点的子树分开。
       例如,如果一个内部节点有3个子节点(子树),那么它就必须有两个键: a1 和 a2 。左边子树的所有值都必须小于 a1 ,中间子树的所有值都必须在 a1 和a2 之间,右边子树的所有值都必须大于 a2 。


内部节点
       内部节点是除叶子节点和根节点之外的所有节点。它们通常被表示为一组有序的元素和指向子节点的指针。每一个内部节点拥有最多 U 个,最少 L 个子节点。元素的数量总是比子节点指针的数量少一(元素的数量在 L-1 和 U-1 之间)。U 必须等于 2L 或者 2L-1;因此,每一个内部节点都至少是半满的。U 和 L 之间的关系意味着两个半满的节点可以合并成一个合法的节点,一个全满的节点可以被分裂成两个合法的节点(如果父节点有空间容纳移来的一个元素)。这些特性使得在B树中删除或插入新的值时可以调整树来保持B树的性质。


根节点

       根节点拥有的子节点数量的上限和内部节点相同,但是没有下限。例如,当整个树中的元素数量小于 L-1 时,根节点是唯一的节点并且没有任何子节点。

叶子节点

       叶子节点对元素的数量有相同的限制,但是没有子节点,也没有指向子节点的指针。
       一个深度为n+1 的B树可以容纳的元素数量大约是深度为 n 的B树的 U 倍,但是搜索、插入和删除操作的开销也会增加。和其他的平衡树一样,这一开销增加的速度远远慢于元素数量的增加。
       一些平衡树只在叶子节点中存储值,而且叶子节点和内部节点使用不同的结构。B树在每一个节点中都存储值,所有的节点有着相同的结构。然而,因为叶子节点没有子节点,所以可以通过使用专门的结构来提高B树的性能。

#include"Vector.h"
#include"BinTree.h"

using namespace std;

#define BTNodePosi(T) BTNode<T> * //B-树节点位置

template <typename T> 
struct BTNode 
{ 
    //B-树节点模板类

    // 成员(为简化描述起见统一开放,读者可根据需要进一步封装)
    BTNodePosi(T) parent; //父节点

    Vector<T> key; //关键码向量

    Vector<BTNodePosi(T)> child; //孩子向量(其长度总比key多一)

    // 构造函数(注意:BTNode只能作为根节点创建,而且初始时有0个关键码和1个空孩子指针)
    BTNode()
    {
        parent = NULL; 
        child.insert(0, NULL);
    }

    BTNode(T e, BTNodePosi(T) lc = NULL, BTNodePosi(T) rc = NULL) 
    {
       parent = NULL; //作为根节点,而且初始时
       key.insert(0, e); //只有一个关键码,以及
       child.insert(0, lc); child.insert(1, rc); //两个孩子
       if (lc)
       {
           lc->parent = this;
       }
       if (rc)
       {
           rc->parent = this;
       }
	}

};


template <typename T>
class BTree 
{ 
    //B-树模板类
    protected:

        int _size; //存放的关键码总数

        int _order; //B-树的阶次,至少为3——创建时指定,一般不能修改

        BTNodePosi(T) _root; //根节点


        BTNodePosi(T) _hot; //BTree::search()最后访问的非空(除非树空)的节点位置

        void solveOverflow(BTNodePosi(T) v); //因插入而上溢之后的分裂处理

        void solveUnderflow(BTNodePosi(T) v); //因删除而下溢之后的合并处理

    public:
        BTree(int order = 3) : _order(order), _size(0) //构造函数:默认为最低的3阶
        {
            _root = new BTNode<T>();
        }

        ~BTree()
        { 
            if (_root)
            {
                release(_root);
            }
        } //析构函数:释放所有节点

        int const order() 
        { 
            return _order;
        } //阶次

        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


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


template <typename T> 
bool BTree<T>::insert(const T& e)
{ //将关键码e插入B树中
    BTNodePosi(T) v = search(e);
    if (v)
    {
        return false; //确认目标节点不存在
    }
    Rank r = _hot->key.search(e); //在节点_hot的有序关键码向量中查找合适的插入位置
    BTree <T>::_hot->key.insert(r + 1, e); //将新关键码插至对应的位置
    BTree <T>::_hot->child.insert(r + 2, NULL); //创建一个空子树指针
    BTree <T>::_size++; //更新全树规模
    solveOverflow(_hot); //如有必要,需做分裂
    return true; //插入成功
}


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


template <typename T> 
bool BTree<T>::remove(const T& e)
{ 
    //从BTree树中删除关键码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);
    BTree <T>::_size--; //删除e,以及其下两个外部节点之一
    solveUnderflow(v); //如有必要,需做旋转或合并
    return true;
}


template <typename T> //关键码删除后若节点下溢,则做节点旋转或合并处理
void BTree<T>::solveUnderflow(BTNodePosi(T) v)
{
    if ((_order + 1) / 2 <= v->child.size())
    {
        return; //递归基:当前节点并未下溢
    }
    BTNodePosi(T) p = v->parent;
    if (!p) 
    { 
        //递归基:已到根节点,没有孩子的下限
        if (!v->key.size() && v->child[0]) 
        {
            //但倘若作为树根的v已不含关键码,却有(唯一的)非空孩子,则
            BTree <T>::_root = v->child[0];
            BTree <T>::_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()定位

    // 情况1:向左兄弟借关键码
    if (0 < r) 
    { 
        //若v不是p的第一个孩子,则
        BTNodePosi(T) ls = p->child[r - 1]; //左兄弟必存在
        if ((BTree <T>::_order + 1) / 2 < ls->child.size())
        { //若该兄弟足够“胖”,则
            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 ((BTree <T>::_order + 1) / 2 < rs->child.size())
        { //若该兄弟足够“胖”,则
            v->key.insert(v->key.size(), p->key[r]); //p借出一个关键码给v(作为最大关键码)
            p->key[r] = rs->key.remove(0); //ls的最小关键码转入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) 
    { //与左兄弟合并
        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 
    { //与右兄弟合并
        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的最左侧孩子过继给ls做最右侧孩子
        }
        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
    }
    solveUnderflow(p); //上升一层,如有必要则继续分裂——至多递归O(logn)层
    return;
}


int main()
{
    BTree<int>obj;
    for (int i = 0; i < 10; i++)
    {
        obj.insert(i);
    }
    cout << obj.empty() << endl;
    cout << ((obj.search(1) != NULL) ? "founded" : "not founded!") << endl;
    obj.remove(1);
    cout << ((obj.search(1) != NULL) ? "founded" : "not founded!") << endl;
    cout << ((obj.search(100) != NULL) ? "founded" : "not founded!") << endl;
    system("pause");
    return 0;
}

以上代码参考自清华大学邓俊辉《数据结构C++版》,并对其原有代码进行整理,并给出一个简单的测试用例,B树适用于读写相对大的数据块的存储系统,例如磁盘。B树减少定位记录时所经历的中间过程,从而加快存取速度。B树这种数据结构可以用来描述外部存储。这种数据结构常被应用在数据库和文件系统的实现上。

发布了69 篇原创文章 · 获赞 33 · 访问量 1201

猜你喜欢

转载自blog.csdn.net/dosdiosas_/article/details/105634682
今日推荐