树(8)

2022-12-07 20:20:40 浏览数 (1)

多路查找树

二叉树与B树

二叉树的问题分析:二叉树的操作效率较高,但是也存在问题。

  • (1)二叉树需要加载到内存里,如果二叉树的节点少,没有什么问题,但是如果二叉树节点很多(比如1亿),就存在如下问题。
  • (2)问题1:在构建二叉树时,需要多次进行I/O操作(海量数据存在数据库或文件中),节点海量,构建二叉树时,速度会有影响。
  • (3)问题2:节点海量,也会造成二叉树的高度很大,会降低操作速度。

多叉树

为了解决以上问题,就产生了多叉树的概念。

  • (1)在二叉树中,每个节点有数据项,最多有两个子节点。如果允许每个节点可以有更多的数据项和更多的子节点,就是多叉树(Mutiway Tree)。
  • (2)后面我们讲解的2-3树,2-3-4树就是多叉树,多叉树通过重新组织节点,减少树的高度,能对二叉树进行优化。
  • (3)举例说明(下面2-3树就是一颗多叉树)

B树

B树通过重新组织节点,降低树的高度,并且减少I/O读写次数来提升效率。

  • (1)如果B树通过重新组织节点,降低了树的高度。
  • (2)文件系统及数据库系统的设计者利用了磁盘预读原理,将一个节点的大小设为等于一个页(页的大小通常为4K),这样每个节点只需要一次I/O就可以完全载入。
  • (3)将树的度M设置为1024,在600亿个元素中最多只需要4次I/O操作就可以读取到想要的元素,B树(B )广泛应用于文件存储系统以及数据库系统中。

上图中,每个红圈都是一个节点,每个节点里有多个数据项。这里又需要引出两个概念:

  • (1)节点度:每个节点,下面的子树个数有几个。如果有两颗子树度为2。
  • (2)树度:所有节点里面,节点度最大的为树度。

2-3树

除了2-3树,还有2-3-4树等。概念和2-3树类似,也是一种B树。如图:

B树、B 树、B*树

B-Tree树即B树,B即Balanced,平衡的意思。有人把B-Tree翻译成B-树,容易让人产生误解。会以为B-树是一种树,而B树又是另一种树。实际上,B-Tree就是指的B树。

  • 我们所说的B树、B 树、B*树,首先得是一颗平衡树,平衡树的前提必须是一颗搜索树或者排序树。
1.B树介绍

前面以及介绍了2-3树和2-3-4树,他们就是B树(B-Tree也写成B-树),这里我们在做一个说明,我们在学习Mysql时,经常听说某种数据类型的索引是基于B树或者B 树的,如图:

说明:

  • (1)B树的阶:节点最多子节点个数。比如2-3树的阶是3,2-3-4树的阶是4.
  • (2)B-树的搜索,从根节点开始,对节点内的关键字(有序)序列进行二分查找,如果命中则结束,否则进入查询关键字所属范围的子节点;重复,直到所对应的子指针为空,或已经是叶子节点。
  • (3)关键字集合分部在整颗树中,即叶子节点和非叶子节点都存放数据。
  • (4)搜索有可能在非叶子节点结束。
  • (5)其搜索性能等价于在关键字全集内做一次二分查找。
代码语言:javascript复制
    public class Consts
    {
        public const int M = 3;                  // B树的最小度数
        public const int KeyMax = 2 * M - 1;     // 节点包含关键字的最大个数
        public const int KeyMin = M - 1;         // 非根节点包含关键字的最小个数
        public const int ChildMax = KeyMax   1;  // 孩子节点的最大个数
        public const int ChildMin = KeyMin   1;  // 孩子节点的最小个数
    }

    public class BTreeNode
    {
        private bool leaf;
        public int[] keys;
        public int keyNumber;
        public BTreeNode[] children;
        public int blockIndex;
        public int dataIndex;

        public BTreeNode(bool leaf)
        {
            this.leaf = leaf;
            keys = new int[Consts.KeyMax];
            children = new BTreeNode[Consts.ChildMax];
        }

        /// <summary>在未满的节点中插入键值</summary>
        /// <param name="key">键值</param>
        public void InsertNonFull(int key)
        {
            var index = keyNumber - 1;

            if (leaf == true)
            {
                // 找到合适位置,并且移动节点键值腾出位置
                while (index >= 0 && keys[index] > key)
                {
                    keys[index   1] = keys[index];
                    index--;
                }

                // 在index后边新增键值
                keys[index   1] = key;
                keyNumber = keyNumber   1;
            }
            else
            {
                // 找到合适的子孩子索引
                while (index >= 0 && keys[index] > key) index--;

                // 如果孩子节点已满
                if (children[index   1].keyNumber == Consts.KeyMax)
                {
                    // 分裂该孩子节点
                    SplitChild(index   1, children[index   1]);

                    // 分裂后中间节点上跳父节点
                    // 孩子节点已经分裂成2个节点,找到合适的一个
                    if (keys[index   1] < key) index  ;
                }

                // 插入键值
                children[index   1].InsertNonFull(key);
            }
        }

        /// <summary>分裂节点</summary>
        /// <param name="childIndex">孩子节点索引</param>
        /// <param name="waitSplitNode">待分裂节点</param>
        public void SplitChild(int childIndex, BTreeNode waitSplitNode)
        {
            var newNode = new BTreeNode(waitSplitNode.leaf);
            newNode.keyNumber = Consts.KeyMin;

            // 把待分裂的节点中的一般节点搬到新节点
            for (var j = 0; j < Consts.KeyMin; j  )
            {
                newNode.keys[j] = waitSplitNode.keys[j   Consts.ChildMin];

                // 清0
                waitSplitNode.keys[j   Consts.ChildMin] = 0;
            }

            // 如果待分裂节点不是也只节点
            if (waitSplitNode.leaf == false)
            {
                for (var j = 0; j < Consts.ChildMin; j  )
                {
                    // 把孩子节点也搬过去
                    newNode.children[j] = waitSplitNode.children[j   Consts.ChildMin];

                    // 清0
                    waitSplitNode.children[j   Consts.ChildMin] = null;
                }
            }

            waitSplitNode.keyNumber = Consts.KeyMin;

            // 拷贝一般键值到新节点
            for (var j = keyNumber; j >= childIndex   1; j--)
                children[j   1] = children[j];

            children[childIndex   1] = newNode;
            for (var j = keyNumber - 1; j >= childIndex; j--)
                keys[j   1] = keys[j];

            // 把中间键值上跳至父节点
            keys[childIndex] = waitSplitNode.keys[Consts.KeyMin];

            // 清0
            waitSplitNode.keys[Consts.KeyMin] = 0;

            // 根节点键值数自加
            keyNumber = keyNumber   1;
        }

        /// <summary>根据节点索引顺序打印节点键值</summary>
        public void PrintByIndex()
        {
            int index;
            for (index = 0; index < keyNumber; index  )
            {
                // 如果不是叶子节点, 先打印叶子子节点. 
                if (leaf == false) children[index].PrintByIndex();

                Console.Write("{0} ", keys[index]);
            }

            // 打印孩子节点
            if (leaf == false) children[index].PrintByIndex();
        }

        /// <summary>查找某键值是否已经存在树中</summary>
        /// <param name="key">键值</param>
        /// <returns></returns>
        public BTreeNode Find(int key)
        {
            int index = 0;
            while (index < keyNumber && key > keys[index]) index  ;

            // 该key已经存在, 返回该索引位置节点
            if (keys[index] == key) return this;

            // key 不存在,并且节点是叶子节点
            if (leaf == true) return null;

            // 递归在孩子节点中查找
            return children[index].Find(key);
        }
    }

    public class BTree
    {
        public BTreeNode Root { get; private set; }

        public BTree() { }

        /// <summary>根据节点索引顺序打印节点键值</summary>
        public void PrintByIndex()
        {
            if (Root == null)
            {
                Console.WriteLine("空树");
                return;
            }

            Root.PrintByIndex();
        }

        /// <summary>查找某键值是否已经存在树中</summary>
        /// <param name="key">键值</param>
        /// <returns></returns>
        public BTreeNode Find(int key)
        {
            if (Root == null) return null;

            return Root.Find(key);
        }

        /// <summary>新增B树节点键值</summary>
        /// <param name="key">键值</param>
        public void Insert(int key)
        {
            if (Root == null)
            {
                Root = new BTreeNode(true);
                Root.keys[0] = key;
                Root.keyNumber = 1;
                return;
            }

            if (Root.keyNumber == Consts.KeyMax)
            {
                var newNode = new BTreeNode(false);

                newNode.children[0] = Root;
                newNode.SplitChild(0, Root);

                var index = 0;
                if (newNode.keys[0] < key) index  ;

                newNode.children[index].InsertNonFull(key);
                Root = newNode;
            }
            else
            {
                Root.InsertNonFull(key);
            }
        }
    }

调用

代码语言:javascript复制
        static void Main(string[] args)
        {
            BTree tree = new BTree();
            Random random = new Random();
            for (int i = 0; i < 10; i  )
            {
                tree.Insert(random.Next(100));
            }
            tree.PrintByIndex();
            Console.Read();
        }
2.B 树介绍

B 树是B树的变体,也是一种多路搜索树。

B 树的说明:

  • (1)B 树的搜索与B树也基本相同,区别是B 树只有达到叶子节点才命中(B树可以在非叶子节点命中),其性能也等价于在关键字全集做一次二分查找。
  • (2)所有关键子都出现在叶子节点的链表中(即数据只能在叶子节点【也叫稠密索引】),且链表中的关键字(数据)恰好是有序的。
  • (3)不可能在非叶子节点命中。第一个箭头指向的5是索引并不是数据,而真正的数据5节点是通过下面路径找到的。
  • (4)非叶子节点相当于是叶子节点的索引(稀疏索引),叶子系欸DNA相当于是存储(关键字)数据的数据层。
  • (5)更适合文件搜索系统。
  • (6)B树和B 树各有自己的应用场景,不能说B 树完全B树好,反之亦然。

如果不用B 树的结构管理数据,下图中有9组数据每组3个那么总共有27个数据。放在单链表中的排列就会是{5,8,9,10,15,18.....28.....99}。

如果需要去检索除28,那么就会逐个遍历去找效率会非常低。如果不想这么去操作,这时候就需要进行分组。将它们每3个分成一组,那么{5,8,9,10,15,18.....28.....99}这个列表就会被分成9段。每一段有3个数据。

这个时候再去找28就会非常快,就相当于砍掉了2/3个节点数。

代码参考:https://github.com/justcoding121/Advanced-Algorithms/blob/develop/src/Advanced.Algorithms/DataStructures/Tree/B Tree.cs

3.B*树介绍

B*树是B 树的变体,在B 树的非根和非叶子节点再增加指向兄弟的指针。

  • (1)B*树定义了非叶子节点关键字个数至少为(2/3) * M,即块的最低使用率为2/3,而B 树的块的最低使用率为B 树的1/2。
  • (2)从第一个特点我们可以看出,B*树分配新节点的概率比B 树要低,空间使用效率更高。

0 人点赞