B树(B-tree)

B树(B-tree)

B树(B-tree)是一种自平衡的多路查找树,主要用于磁盘或其他直接存取的辅助存储设备

B树能够保持数据有序,并允许在对数时间内完成查找、插入及删除等操作

这种数据结构常被应用在数据库和文件系统的实现上

B树的特点包括:

B树为了表示节点个数 通常称为 M阶树

1.M阶树每个结点至多有M棵子树(M>=2)

2.每个非根节点至少有 M/2(向上取整)个孩子,至多有M个孩子。

3.每个叶子节点至少有 M/2-1 个关键字,至多有M-1个关键字,并以升序排列

4.所有叶子节点都在同一层

5.非叶子节点的关键字个数等于其孩子数减一

6.所有叶子节点不含有任何信息

按照子节点数 y:非根节点至少有 M/2(向上取整)个孩子,至多有M个孩子

M = 3, 2 <= y <= 3, 因此称为:2-3树

M = 4, 2 <= y <= 4, 因此称为:2-3-4树

M = 5, 3 <= y <= 5, 因此称为:3-5树

M = 7, 4 <= y <= 7, 因此称为:4-7树

B树的高度对于含有n个关键字的m阶B树,其高度为O(log n)

这种数据结构通过减少定位记录时所经历的中间过程,从而加快存取速度

与自平衡的二叉查找树不同,B树为系统大块数据的读写操作做了优化

下面先看 B树节点的定义

csharp 复制代码
class BTNode<T> where T : IComparable<T>
{
    private BTNode<T> parentNode;        // 父节点
    private List<T> keyList;             // 关键字向量
    private List<BTNode<T>> childList;   // 子节点向量(其长度总比key多一)
}

B树每个节点存储的关键字是从小到大有序的:keyList 是从小到大排序的

B树 关键字个数 比 子节点 个数少 1 个,为什么?

因为 关键字 和 子节点 可以理解为这么样一个排序,假设一个 5 阶树的一个节点

csharp 复制代码
childList[0],keyList[0],childList[1],keyList[1],childList[2]

可以看到 子节点 和 关键字 是交替出现的,并且 子节点 比 关键字 个数多 1 个

并且还有一个隐藏的信息

1.子树中所有关键字的值,比其右侧的关键字都小

2.子树中所有关键字的值,比其左侧的关键字都大

什么意思呢?

1.(childList[0] 子树下所有节点的关键字) 小于 keyList[0]

  1. keyList[0] 小于 (childList[1] 子树下所有节点的关键字)

看下图 一个 5阶树

Node0 包含 三个关键字(25,39,66),四个子节点(Node1,Node2,Node3,Node4)

25 对应 keyList[0]

39 对应 keyList[1]

66 对应 keyList[2]

Node0 包含 两个关键字 (5, 13) < 25

Node1 包含 两个关键字 25 < (28, 30) < 39

Node2 包含 两个关键字 39 < (40, 55) < 66

Node3 包含 两个关键字 66 < (67, 68, 90)

B树在操作 插入、删除的过程中往往会导致

上溢:节点个数大于 B树限制

下移:节点个数小于 B树限制

需要通过一系列操作将树恢复平衡

B树操作逻辑
查询: number

1.根节点作为当前节点

  1. number 顺次与当前节点的关键字作比较

如果 number < keyList[index],则 number 一定在 childList[index],令当前节点= childList[index] 循环执行 2

如果 number = keyList[index],则 在关键字中找到 了number,查询完成,返回节点

如果 number > keyList[index], index++,如果 index >= childList.Count,查找失败,返回,否则继续循环执行 2 ,比较下一个 关键字 keyList[index]

我在查询操作逻辑中隐含保留了一个 hot 节点,这个 hot 节点是最接近 number 的节点,如果查询成功,则 hot 不再起作用,如果查询失败,在 插入操作中 是有用处的

插入: number

1.先执行查询操作,如果查询到节点,则说明已经存在,不再添加,返回

2.查询逻辑中 保存的 hot 节点是最接近 numbe 的节点,我们将 number 插入到hot节点

3.令 number 与 keyList 中的数据比较

如果 keyList[i] < number, 将 number 插入到 keyList第 i + 1 位置,然后 childList 第 i + 2 位置插入一个空节点

如果 keyList 中的关键字 > number,将 number 插入到 keyList 第 0 个位置,然后 childList 第 1 个位置插入一个空节点

  1. 如果 hot 节点发生上溢,做分裂处理

C#代码实现如下

B树节点定义

csharp 复制代码
    /// <summary>
    /// B-树节点定义
    /// </summary>
    /// <typeparam name="T"></typeparam>
    class BTNode<T> where T : IComparable<T>
    {
        private BTNode<T> parentNode;       // 父节点
        private List<T> keyList;            // 关键字向量
        private List<BTNode<T>> childList;  // 子节点向量(其长度总比key多一)

        public BTNode()
        {
            keyList = new List<T>();
            childList = new List<BTNode<T>>();
            childList.Insert(0, null);
        }

        public BTNode(T t, BTNode<T> lc, BTNode<T> rc)
        {
            parentNode = null;
            keyList.Insert(0, t);
            // 左右孩子
            childList.Insert(0, lc);
            childList.Insert(1, rc);

            if (null != lc)
            {
                lc.parentNode = this;
            }
            if (null != rc)
            {
                rc.parentNode = this;
            }
        }

        public BTNode<T> ParentNode
        {
            get { return parentNode; }
            set { parentNode = value; }
        }
            
        private List<T> KeyList
        {
            get { return keyList; }
            set { keyList = value; }
        }

        public int KeyCount
        {
            get { return KeyList.Count; }
        }

        public void InsertKey(int index, T key)
        {
            KeyList.Insert(index, key);
        }

        public T GetKey(int index)
        {
            return KeyList[index];
        }

        public void RemoveKeyAt(int index)
        {
            KeyList.RemoveAt(index);
        }

        public void SetKey(int index, T key)
        {
            KeyList[index] = key;
        }

        private List<BTNode<T>> ChildList
        {
            get { return childList; }
            set { childList = value; }
        }

        public void InsertChild(int index, BTNode<T> node)
        {
            ChildList.Insert(index, node);
            if (null != node)
            {
                node.parentNode = this;
            }
        }

        public BTNode<T> GetChild(int index)
        {
            return ChildList[index];
        }

        public void AddChild(BTNode<T> node)
        {
            InsertChild(ChildList.Count, node);
        }

        public BTNode<T> RemoveChildAt(int index)
        {
            BTNode<T> node = ChildList[index];
            ChildList.RemoveAt(index);
            return node;
        }

        public void SetChild(int index, BTNode<T> node)
        {
            ChildList[index] = node;
        }

        public int ChildCount
        {
            get { return ChildList.Count; }
        }
    }

B树实现

csharp 复制代码
    /// <summary>
    /// B-树
    /// </summary>
    /// <typeparam name="T"></typeparam>
    class BTree<T> where T : IComparable<T>
    {
        private int _order;         // 介次
        protected BTNode<T> _root;  // 跟节点
        protected BTNode<T> _hot;   // search() 最后访问的非空节点位置

        public BTree(int order)
        {
            _order = order;
        }

        public BTNode<T> Root
        {
            get { return _root; }
            set { _root = value; }
        }

        /// <summary>
        /// 查找
        /// </summary>
        public BTNode<T> Search(T t)
        {
            BTNode<T> v = Root; // 从根节点触发
            _hot = null;

            while (null != v)
            {
                int index = -1;
                for (int i = 0; i < v.KeyCount; ++i)
                {
                    int compare = v.GetKey(i).CompareTo(t);
                    if (compare <= 0)
                    {
                        index = i;
                        if (compare == 0)
                        {
                            break;
                        }
                    }
                }

                // 若成功,则返回
                if (index >= 0 && v.GetKey(index).CompareTo(t) == 0)
                {
                    return v;
                }

                _hot = v;
                // 沿引用转至对应的下层子树,并载入其根
                v = v.ChildCount > (index + 1) ? v.GetChild(index + 1) : null;
            }
            // 若因 null == v 而退出,则意味着抵达外部节点
            return null; // 失败
        }

        /// <summary>
        /// 插入
        /// </summary>
        public bool Insert(T t)
        {
            BTNode<T> node = Search(t);
            if (null != node)
            {
                return false;
            }

            int index = -1;
            for (int i = 0; i < _hot.KeyCount; ++i)
            {
                int compare = _hot.GetKey(i).CompareTo(t);
                if (compare <= 0)
                {
                    index = i;
                    if (compare == 0)
                    {
                        break;
                    }
                }
            }

            _hot.InsertKey(index + 1, t);      // 将新关键码插至对应的位置
            _hot.InsertChild(index + 2, null);      // 创建一个空子树指针

            SolveOverflow(_hot); // 如发生上溢,需做分裂

            return true;
        }

        /// <summary>
        /// 删除
        /// </summary>
        public bool Remove(T t)
        {
            BTNode<T> node = Search(t);
            if (null == node)
            {
                return false;
            }

            int index = -1;
            for (int i = 0; i < node.KeyCount; ++i)
            {
                if(node.GetKey(i).CompareTo(t) == 0)
                {
                    index = i;
                    break;
                }
            }

            // node 不是叶子节点
            if (null != node.GetChild(0))
            {
                BTNode<T> u = node.GetChild(index + 1); // 在右子树中一直向左,即可
                while (null != u.GetChild(0))
                {
                    u = u.GetChild(0); // 找到 t 的后继(必需于某叶节点)
                }
                // 至此,node 必然位于最底层,且其中第 r 个关键码就是待删除者

                node.SetKey(index, u.GetKey(0));
                node = u;  // 并与之交换位置
                index = 0;
            }

            node.RemoveKeyAt(index);
            node.RemoveChildAt(index + 1);

            SolveUnderflow(node); // 如有必要,需做旋转或合并

            return false;
        }

        /// <summary>
        /// 上溢:因插入而上溢后的分裂处理
        /// </summary>
        private void SolveOverflow(BTNode<T> v)
        {
            if (_order >= v.ChildCount)
            {
                return; //递归基:当前节点并未上溢
            }

            int s = _order / 2; //轴点(此时应有_order = key.Count = child.Count - 1)
            BTNode<T> u = new BTNode<T>(); //注意:新节点已有一个空孩子
            for (int j = 0; j < _order - s - 1; j++)
            { //v右侧_order-s-1个孩子及关键码分裂为右侧节点u
                BTNode<T> node = v.GetChild(s + 1);
                v.RemoveChildAt(s + 1);
                u.InsertChild(j, node); //逐个移动效率低

                T key = v.GetKey(s + 1);
                v.RemoveKeyAt(s + 1);
                u.InsertKey(j, key); //此策略可改进
            }

            BTNode<T> node2 = v.GetChild(s + 1);
            v.RemoveChildAt(s + 1);
            u.SetChild(_order - s - 1, node2); //移动v最靠右的孩子
            if (null != u.GetChild(0)) //若u的孩子们非空,则
            {
                for (int j = 0; j < _order - s; j++) //令它们的父节点统一
                {
                    u.GetChild(j).ParentNode = u; //指向u
                }
            }
                
            BTNode<T> p = v.ParentNode; //v当前的父节点p
            if (null == p)
            {
                _root = p = new BTNode<T>();
                p.SetChild(0, v);
                v.ParentNode = p;
            } //若p空则创建之

            int index = -1;
            for (int i = 0; i < p.KeyCount; ++i)
            {
                int compare = p.GetKey(i).CompareTo(v.GetKey(0));
                if (compare <= 0)
                {
                    index = i;
                    if (compare == 0)
                    {
                        break;
                    }
                }
            }

            int r = 1 + index; //p中指向u的指针的秩

            T key2 = v.GetKey(s);
            v.RemoveKeyAt(s);
            p.InsertKey(r, key2); //轴点关键码上升
            p.InsertChild(r + 1, u); 
            u.ParentNode = p; //新节点u与父节点p互联
            SolveOverflow(p); //上升一层,如有必要则继续分裂------至多递归O(logn)层
        }

        /// <summary>
        /// 下溢:因删除而下溢后的合并处理
        /// </summary>
        /// <param name="node"></param>
        private void SolveUnderflow(BTNode<T> v)
        {
            if ((_order + 1) / 2 <= v.ChildCount) return; //递归基:当前节点并未下溢
            BTNode<T> p = v.ParentNode;
            if (null == p)
            { //递归基:已到根节点,没有孩子的下限
                if (v.KeyCount <= 0 && null != v.GetChild(0))
                {
                    //但倘若作为树根的v已不含关键码,却有(唯一的)非空孩子,则
                    /*DSA*/
                    _root = v.GetChild(0);
                    _root.ParentNode = null; //这个节点可被跳过
                    v.SetChild(0, null); //release(v); //并因不再有用而被销毁
                } //整树高度降低一层
                return;
            }
            int r = 0;
            while (p.GetChild(r) != v)
            {
                r++;
            }

            //确定v是p的第r个孩子------此时v可能不含关键码,故不能通过关键码查找
            //另外,在实现了孩子指针的判等器之后,也可直接调用Vector::find()定位
            /*DSA*/
            // 情况1:向左兄弟借关键码
            if (0 < r)
            { //若v不是p的第一个孩子,则
                BTNode<T> ls = p.GetChild(r - 1); //左兄弟必存在
                if ((_order + 1) / 2 < ls.ChildCount)
                { //若该兄弟足够"胖",则
                  /*DSA*/
                    v.InsertKey(0, p.GetKey(r - 1)); //p借出一个关键码给v(作为最小关键码)
                    T key = ls.GetKey(ls.KeyCount - 1);
                    ls.RemoveKeyAt(ls.KeyCount - 1);
                    p.SetKey(r - 1, key); //ls的最大关键码转入p

                    BTNode<T> node = ls.GetChild(ls.ChildCount - 1);
                    ls.RemoveChildAt(ls.ChildCount - 1);
                    //同时ls的最右侧孩子过继给v
                    //作为v的最左侧孩子
                    v.InsertChild(0, node);
                    return; //至此,通过右旋已完成当前层(以及所有层)的下溢处理
                }
            } //至此,左兄弟要么为空,要么太"瘦"
              // 情况2:向右兄弟借关键码
            if (p.ChildCount - 1 > r)
            { //若v不是p的最后一个孩子,则
                BTNode<T> rs = p.GetChild(r + 1); //右兄弟必存在
                if ((_order + 1) / 2 < rs.ChildCount)
                { //若该兄弟足够"胖",则
                  /*DSA*/
                    v.InsertKey(v.KeyCount, p.GetKey(r)); //p借出一个关键码给v(作为最大关键码)
                    T key = rs.GetKey(0);
                    rs.RemoveKeyAt(0);
                    p.SetKey(r, key); //rs的最小关键码转入p

                    BTNode<T> node = rs.GetChild(0);
                    rs.RemoveChildAt(0);
                    v.InsertChild(v.ChildCount, node);
                    //同时rs的最左侧孩子过继给v
                    if (null != v.GetChild(v.ChildCount - 1)) //作为v的最右侧孩子
                    {
                        v.GetChild(v.ChildCount - 1).ParentNode = v;
                    }
                    return; //至此,通过左旋已完成当前层(以及所有层)的下溢处理
                }
            } //至此,右兄弟要么为空,要么太"瘦"
              // 情况3:左、右兄弟要么为空(但不可能同时),要么都太"瘦"------合并
            if (0 < r)
            { //与左兄弟合并
              /*DSA*/
                BTNode<T> ls = p.GetChild(r - 1); //左兄弟必存在
                T key = p.GetKey(r - 1);
                p.RemoveKeyAt(r - 1);
                ls.InsertKey(ls.KeyCount, key);
                p.RemoveChildAt(r);

                //p的第r - 1个关键码转入ls,v不再是p的第r个孩子
                BTNode<T> node = v.GetChild(0);
                v.RemoveChildAt(0);
                ls.InsertChild(ls.ChildCount, node);//v的最左侧孩子过继给ls做最右侧孩子

                while (v.KeyCount > 0)
                { //v剩余的关键码和孩子,依次转入ls
                    T key2 = v.GetKey(0);
                    v.RemoveKeyAt(0);
                    ls.InsertKey(ls.KeyCount, key2);

                    BTNode<T> node2 = v.GetChild(0);
                    v.RemoveChildAt(0);
                    ls.InsertChild(ls.ChildCount, node2);
                }
                //release(v); //释放v
            }
            else
            { //与右兄弟合并
              /*DSA*/
               // printf(" ... case 3R\n");
                BTNode<T> rs = p.GetChild(r + 1); //右兄弟必存在
                T key = p.GetKey(r);
                    p.RemoveKeyAt(r);
                rs.InsertKey(0, key); p.RemoveChildAt(r);
                //p的第r个关键码转入rs,v不再是p的第r个孩子

                BTNode<T> node = v.GetChild(v.ChildCount - 1);
                v.RemoveChildAt(v.ChildCount - 1);
                rs.InsertChild(0, node);
                if (null != rs.GetChild(0))
                {
                    rs.GetChild(0).ParentNode = rs; //v的最左侧孩子过继给ls做最右侧孩子
                }
                while (v.KeyCount > 0)
                { //v剩余的关键码和孩子,依次转入rs
                    T key2 = v.GetKey(v.KeyCount - 1);
                    v.RemoveKeyAt(v.KeyCount - 1);
                    rs.InsertKey(0, key2);

                    BTNode<T> node2 = v.GetChild(v.ChildCount - 1);
                    v.RemoveChildAt(v.ChildCount - 1);
                    rs.InsertChild(0, node2);
                    if (null != rs.GetChild(0))
                    {
                        rs.GetChild(0).ParentNode = rs;
                    }
                }
                //release(v); //释放v
            }
            SolveUnderflow(p); //上升一层,如有必要则继续分裂------至多递归O(logn)层
        }

        /// <summary>
        /// 层序遍历,获取所有节点,切是按照每一层节点返回
        /// </summary>
        /// <param name="node"></param>
        /// <returns></returns>
        public List<List<BTNode<T>>> TraverseLevelList(BTNode<T> node)
        {
            List<List<BTNode<T>>> listResult = new List<List<BTNode<T>>>();
            if (null == node)
            {
                return listResult;
            }

            Queue<BTNode<T>> queue = new Queue<BTNode<T>>();
            queue.Enqueue(node);
            while (queue.Count > 0)
            {
                int count = queue.Count;
                List<BTNode<T>> list = new List<BTNode<T>>();

                while(count > 0)
                {
                    --count;
                    node = queue.Dequeue();
                    if (null == node)
                    {
                        continue;
                    }

                    list.Add(node);
                    for (int i = 0; i < node.ChildCount; ++i)
                    {
                        queue.Enqueue(node.GetChild(i));
                    }
                }
                listResult.Add(list);
            }
            return listResult;
        }

        /// <summary>
        /// 层序遍历,获取所有节点
        /// </summary>
        /// <param name="node"></param>
        /// <returns></returns>
        public List<BTNode<T>> TraverseLevel(BTNode<T> node)
        {
            List<BTNode<T>> list = new List<BTNode<T>>();
            if (null == node)
            {
                return list;
            }

            Queue<BTNode<T>> queue = new Queue<BTNode<T>>();
            queue.Enqueue(node);
            while (queue.Count > 0)
            {
                node = queue.Dequeue();
                if (null == node)
                {
                    continue;
                }

                list.Add(node);
                for (int i = 0; i < node.ChildCount; ++i)
                {
                    queue.Enqueue(node.GetChild(i));
                }
            }
            return list;
        }
    }
相关推荐
wheeldown8 小时前
【数据结构】选择排序
数据结构·算法·排序算法
躺不平的理查德12 小时前
数据结构-链表【chapter1】【c语言版】
c语言·开发语言·数据结构·链表·visual studio
阿洵Rain12 小时前
【C++】哈希
数据结构·c++·算法·list·哈希算法
Leo.yuan12 小时前
39页PDF | 华为数据架构建设交流材料(限免下载)
数据结构·华为
半夜不咋不困12 小时前
单链表OJ题(3):合并两个有序链表、链表分割、链表的回文结构
数据结构·链表
忘梓.13 小时前
排序的秘密(1)——排序简介以及插入排序
数据结构·c++·算法·排序算法
y_m_h16 小时前
leetcode912.排序数组的题解
数据结构·算法
1 9 J16 小时前
数据结构 C/C++(实验三:队列)
c语言·数据结构·c++·算法
921正在学习编程16 小时前
数据结构之二叉树前序,中序,后序习题分析(递归图)
c语言·数据结构·算法·二叉树
毕竟秋山澪16 小时前
岛屿数量 广搜版BFS C#
数据结构·算法·宽度优先