邓俊辉数据结构学习-8-2-B树

B树

概述

动机: B树实现高速I/O

640K如何"满足"任何实际需求了-- 源自比尔·盖茨的一个笑话

前提知识

  1. 高速缓存

    • 为什么高速缓存有效?

      • 不同容量的存储器,访问速度差异悬殊,磁盘和内存访问速度的量级相差$>10^5$

        • 如果将访问内存比喻为1秒,那么访问外存则相当于1天
        • 因此我们需要尽量减少IO次数
  2. 分级I/O
    • 俩个相邻存储级别之间的数据传输,统称为IO操作
    • CPU -> RAM -> DISK -> ARRAY
    • 访问速度依次递减,存储容量依次递增
    • 常用的数据要放在最前面
  3. 从磁盘读取1B和读写1KB几乎一样快

问题:

B树为什么更宽更矮

B树实际也是BST,但是其对节点的定义方式不一样, 其定义的节点为超级节点

超级节点的概念:

如果我们将2代节点合并,并把它们都踩扁了,就会拥有4路,3个关键码

同理:每d代合并,就会拥有m =2^d路,m-1个关键码

  • 多级存储系统中使用B树,可以针对外部查找,大大减少IO次数

    • 对于常规BST来说,IO次数太高

      • 对于AVL树来说,如果访问1G数据,10^9个关键码,一次只读取一个数据得到logN = 30 需要30次IO访问。
    • 对于B树来说,则批量访问数据,一次读入一个超级节点
    • 因此B树的主要目的就是为了减少IO次数

B树定义

  • m阶B树:即m路平衡搜索树

对于m阶B树来说

关键码:

  • 上限:有不超过m-1个关键码, 即n <= m - 1个关键码——

    $$ K_1 < K_2 < K_3 < ... < K_(n-1) < K_n $$

  • 下限:

    对于根节点来说,则只要有不少于1个关键码就行

    对于其他节点来说,有不少于$\lceil \frac{m}{2} \rceil - 1 <= n$ 个关键码

分枝数

  • 上限:有不超过m个分支, 即n + 1 <= m个分支数——

    $$ A_0 < A_1 < A_2 < A_3 < ... < A_(n-1) < A_n $$

  • 下限:内部节点的分支数也不能太少

    对于根节点来说: 2 <= n + 1 "但树根是比较特殊的存在

    对于其他节点来说:有不少于$\lceil \frac{m}{2} \rceil <= n + 1$个分支

简单举例:

  • 2-4 树 —— 4阶B树,分支在2-4之内 这个树比较特殊??
  • 3-5 树 —— 5阶B树,分支在3-5之内
  • 3-6 树
  • 4-7 树
  • 4-8 树 等等

B树的性质

  • 所有叶节点深度统一相等
  • 外部节点深度统一相等
    • 外部节点就是叶节点的数值为空,但不存在的孩子。
    • 外部节点其实所有树都存在,但只不过我们不关心而已,但在接下来的RB??和B??,我们将比较关心这一点
    • important{:.error} B树中,外部节点更加名副其实,意味着查找失败,因此需要计算外部节点
  • 对于B树来说,B树的高度相对于外部节点来说。因此,空的B树高度为0,一个节点的B树高度为1。

实现

查找

一般来说,B树的词条极多,所以其存在外存之中。

B树查找的核心:只载入必需的节点,尽可能减少IO操作{:.info}

其大概思想就是将根节点存放在内存之中,当需要某些数据时,进行B树的查找,将其从外存中读入到内存。鉴于之前

谈过的数据的局部性。我们将新的查找到的超级节点也放到内存之中。因此在同样查找的情况下,B树的树高更低,

所执行的IO次数自然就少。因此我们借助比较小的内存,就可以实现大规模数据的高效操作

实例:

  1. 从根节点开始, 将第一个节点的所有key读入内存之中,执行一次顺序查找,如果查找成功,直接返回,如果查找失败

    按照失败位置,借助引用转向读取下一节点。

  2. 将下一节点的所有key读入内存之中,再次进行一次顺序查找。

...

  1. 到达最后的时候,如果依然失败,会进入到外部节点,此时我们认为查找失败(P.s当然,我们也可以在外部节点接入

    层次更低B树,从而构成一个整体上很大的B树。

不难发现,整个B树查找的过程其实就是在顺序查找,IO操作,顺序查找,IO操作不停重复的过程。

B树的失败查找,一定结束于外部节点处。

B树的算法时间主要消耗在俩个方面

  1. IO上面,因此,树的高度h很重要
  2. 在每个超级节点上,我们都执行顺序查找(在小规模情况下,顺序查找要比二分查找快)

一个B树有N个内部节点,就一定有N+1个外部节点,从物理意义上理解,N个内部节点代表N种成功的可能,

自然对于N+1种失败的可能。

插入

对于插入来说,自然要合理使用search接口(插入必定插入在叶子节点,说实话,树的插入没有位置的选项,所以只能

插入,然后让树自己去选择位置)。search帮我们找到了叶子节点_hot,然后我们通过find接口找到

_hot节点中大于target的第一个关键码,然后在这里新插入

BTree<T>::insert(const T &e)
{
    auto ret = search(e);
    if(ret) return false; // 保证目标元素不存在
    ++_size;
    // search查找失败后,_hot就是叶节点
    // 我们要在_hot中插入e
    auto retIter = find(_hot->key.begin(), _hot->key.end(), [=](T t){
                                                    if(e>=t)    return true;
                                                    return false;});
    _hot->key.insert(retIter, e);

插入关键码后,自然也要插入孩子指针,而实际上如下图所示,因为使叶子节点

我们并不需要去寻找位置,只要在child向量中push_back一个空指针即可

    // 理论上的插入
    //int posnum = retIter - _hot->key.begin();
    //_hot->child.insert(_hot->child.begin() + posnum + 1, nullptr );
    // 但是叶子节点后面全是外部节点,所以都是nullptr,不需要再特定位置插入
    _hot->child.push_back(nullptr);
    // 插入之后,可能会导致这个节点的关键码超过m-1,从而溢出,违反了B树的定义。
    solveOverflow(_hot);
}
/*
 superNode-_hot                    ┌───┐
                                   │ e │
┌─────────┐      ┌───┬───┬───┬───┬─┴─┬─┴─┬───┬───┐
│ key-set │───?  │ s1│ o │ o │ o │█a█│ b │ o │ e1│
├─────────┤    ┌─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┐
│child-set│───?│nul│nul│nul│nul│nul│NUL│nul│nul│nul│
└─────────┘    └───┴───┴───┴───┴───┴───┴───┴───┴───┘
┌─────────┐      ┌───┬───┬───┬───┬───┬───┬───┬───┬───┐
│ key-set │───?  │ s1│ o │ o │ o │█a█│ e │ b │ o │ e1│
├─────────┤    ┌─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┐
│child-set│───?│nul│nul│nul│nul│nul│NUL│   │nul│nul│nul│
└─────────┘    └───┴───┴───┴───┴───┴───┴───┴───┴───┴───┘
                                         ^
                                        这里为理论上需要插入的新节点
*/

tips

参照这幅图,我们不难发现几个规律,只要我们将key向量稍稍挪位,就可以得到一个逻辑上很清晰的关系,在秩为r的小 节点上,其左孩子在child向量中的秩r,右孩子为r + 1

插入完成之后,我们会发现这个新的B树可能会跳出去,不再符合B树的定义,我们需要使用上溢操作将其调整回来

          ┌───┬───┬───┐                      ┌───┬───┬───┐                  ┌───┬───┬───┬───┐
          │400│470│500│                   p->│400│470│500│               p->│400│440│470│500│
          └───┼───┴───┤                      └───┼───┴───┤                  └───┼───┼───┴───┤
        ┌─────┘       └─┐                ┌───────┘       └─┐                ┌───┘   │       └─┐
        │               │                │                 │                │       │         │
        ▼               ▼                ▼                 ▼                ▼       ▼          ▼
  ┌───┬───┬───┐   ┌───┬───┬───┐    ┌───┬───┬───┬───┐ ┌───┬───┬───┐    ┌───┬───┐    ┌───┐     ┌───┬───┬───┐
  │410│430│450│   │520│540│570│ v->│410│430│440│450│ │520│540│570│ v->│410│430│ u->│450│     │520│540│570│
  └───┴───┴───┘   └───┴───┴───┘    └───┴───┴───┴───┘ └───┴───┴───┘    └───┴───┘    └───┘     └───┴───┴───┘
                                            mid
         ┌────────────┐                 ┌───────────┬───┐                  ┌───────────┐
         │ orgin tree │                 │insert node│440│                  │ overflow  │
         └────────────┘                 └───────────┴───┘                  └───────────┘

                        ┌───┐
                        │470│
                        ╳───╳
                       ╱     ╲
                    ╱─╱       ╲─────╲
                   ╱                 ╲
          ┌───┬───▼                 ┌─▼─┐
          │400│440│                 │500│
          └───┴───╳                 └───╳
                 ╱                       ╲
       ╱────────╱                        ╲
      ▼                                   ▼
┌───┬───┬───┐                       ┌───┬───┬───┐
│410│430│450│                       │520│540│570│
└───┴───┴───┘                       └───┴───┴───┘
     ┌───────────┐
     │ overflow  │
     └───────────┘                                                                       

当节点发生上溢的时候,我们选取中位数,将其调整上去,然后将原来的节点分裂成俩部分

具体一点的方法就是:

  1. 在集和v中找到中位数440
  2. 让中位数右边的集独立出去(编程做法就是新建一个集和,拷贝原来,删除原来)
  3. 中位数升上去(利用find在p中找到大于中位数的第一个值,得到秩,有了秩就可以为所欲为了)
  4. 删除边角料,然后让一些小节点回指superNode
  5. 递归处理v的父亲p

删除

对于删除来说,同样要好好利用search接口。和之前BST的删除方法一样,B树的节点真实孩子要么为0,要么一定大于等于2

因此对于这个删除来说,必须要依然保持BST的有序性,因此就要利用节点中序后继的做法

BTree<T>::remove(const T &e)
{
    BTNodePos(T) v = search(e);
    if(!v)  return false;
    --_size;
    auto rIter = find(v->key.begin(), v->key.end(), e);
    assert(rIter != v->key.end());
    int r = rIter - v->key.begin(); 

在节点不是叶子节点的情况下,我们使用其中序后继,且其中序后继一定是叶子节点

    if(v->child[0]) // 如果v不是叶子节点, 就交换其成为后继
    {
        BTNodePos(T) w = v->child[r + 1];
        while(w->child[0] ) w = w->child[0];   // 此时w就是v的后继叶子节点
        // 为什么? 因为在B树中,如果一个节点左子树为空,那么右子树一定为空。且其一定为叶子节点,这是定义
        // 而一个节点的中序后继节点没有左子树,
        v->key[r] = w->key[0];
        v = w; r = 0;
    } // 此时v处于最底层,删除v
    v->key.erase(v->key.begin());
    v->child.erase(v->child.begin() + r + 1);
    solveUnderflow(v);
    return true;
} 

因此,同插入一样,所有的删除一定在最底层发生

对于删除来说,因为B树的关键码下限存在,所以可能会发生下溢情况,此时我们可以通过旋转和合并来解决

                                                 y                                  x
        ┌───┬───┬───┐                      ┌───┬───┬───┐                      ┌───┬───┬───┐
        │400│470│500│                    P │400│470│500│                    P │400│450│500│
        └───┼───┼───┘                      └───┼───┼───┘                      └───┼───┼───┘
      ┌─────┘   └┐                     ┌───────┘   └─┐                    ┌───────┘   └──┐
      │          │                     │             │                    │              │
      ▼          ▼                     ▼             ▼                    ▼              ▼
┌───┬───┬───┐  ┌───┐             ┌───┬───┬───┐       ┌┐             ┌───┬───┐          ┌───┐
│410│430│450│  │490│            V│410│430│450│x     U││            V│410│430│         U│470│y
└───┴───┴───┘  └───┘             └───┴───┴───┘       └┘             └───┴───┘          └───┘
       ┌────────────┐                 ┌───────────┬───┐                  ┌───────────┐
       │ orgin tree │                 │delete node│490│                  │  rotate   │
       └────────────┘                 └───────────┴───┘                  └───────────┘
这是一颗2-4树,关键码的范围为1-3
我们发现V比较富裕,V > (m -1)/2 - 1 所以向V借一个节点,V也不会发生下溢,所以U借走了P中的y,P又
借走了V中的x,看起来像发生了旋转一样
    ┌───┬───┬───┐           ┌───┬───┬───┐         ┌───┐   ┌───┐        ┌───┬───┐
    │400│470│500│          P│400│470│500│        P│400│   │500│        │400│500│
    └───┼───┼───┘           └───┼───┼───┘         └───┤   ├───┘        └───┼───┘
  ┌─────┘   └┐          ┌───────┘   └─┐       ┌───────┘   └─┐           ┌──┘
  │          │          │             │       │             │           │
  ▼          ▼          ▼             ▼       ▼             ▼           ▼
┌───┐      ┌───┐      ┌───┐           ┌┐    ┌───┐     ┌───┐ ┌┐        ┌───┬───┐
│430│      │490│    V │430│          U││   V│430│     │470│U││        │430│450│
└───┘      └───┘      └───┘           └┘    └───┘     └───┘ └┘        └───┴───┘
   ┌────────────┐      ┌───────────┬───┐     ┌───────────┬───┐    ┌───────────┐
   │ orgin tree │      │delete node│490│     │down angel │490│    │   merge   │
   └────────────┘      └───────────┴───┘     └───────────┴───┘    └───────────┘
依然是一颗2-4树,如果之前的V已经处于崩溃边缘的话,此时就没有兄弟借钱给U了,此时就需要天使融资了。
从P中下来一个天使,这个天使节点就是U和V中间共同的老爹节点。将V和U合并,我们发现P中就会消失一个节点,
自然有连续崩溃的可能性,因此同Overflow一样,依然需要递归处理,
        ┌───┐                   ┌───┬───┐
        │470│                   │470│500│
        ├───┤                   ├───┼───┘             │   │                │
  ┌─────┘   └┐          ┌───────┘   └─┐       ┌───────┘   └─┐           ┌──┘
  │          │          │             │       │             │           │
  ▼          ▼          ▼             ▼       ▼             ▼           ▼
┌───┐      ┌───┐      ┌───┐           ┌┐    ┌───┐     ┌───┐ ┌┐        ┌───┬───┐
│430│      │490│      │430│           ││    │430│     │470│ ││        │430│450│
└───┘      └───┘      └───┘           └┘    └───┘     └───┘ └┘        └───┴───┘
   ┌────────────┐      ┌───────────┬───┐     ┌───────────┬───┐    ┌───────────┐
   │ orgin tree │      │delete node│490│     │down angel │490│    │   merge   │
   └────────────┘      └───────────┴───┘     └───────────┴───┘    └───────────┘
当天使节点来自根节点的时候,此时就会形成一个虚根,没有任何作用,自然直接处理掉这个虚根,
让根指向它的孩子节点。

具体的实际代码如下,

// 节点的下溢,做节点的旋转或者合并处理
template <typename T>
void
BTree<T>::solveUnderflow(BTNodePos(T) v)
{
    if(v->child.size() >= (_order + 1) / 2) // _order + 1) / 2 就是向上取整的操作
        return ; // 递归基:当前节点不满足下溢情况
    BTNodePos(T) p = v->parent;
    if(!p) // v已经是根节点,此时没有孩子的下限
    {
        if(!v->key.size() && v->child[0])
        {
            // 根节点没有关键码,却有非空孩子,此时应对根节点孩子发生合并的情况。可以直接跳过
            _root = v->child[0];
            _root->parent = nullptr;
            v->child[0] = nullptr;
            delete v;
        }
        return ;
    }
    size_t r = 0;
    // 此时v中的目标节点已经被删除了,很可能不含有关键码
    while(p->child[r] != v) r++; // 确定v是p的第几个孩子
    // 情况1: 左顾,向左兄弟借码
    if(r > 0) // 首先得有左兄弟
    {
        BTNodePos(T) ls = p->child[p->child.begin() + r - 1]; // v的左爹为y, ls的老幺为x
        if(ls->child.size() > (_order + 1) / 2) // 必须大于下限
        {
            v->key.insert(v->key.begin(), p->key[r - 1]);   // v向p借一个码y
            p->key[r - 1] = ls->key[ls->key.size() - 1];    // p向ls借一个码x
            v->child.insert(v->child.begin(), ls->child[ls->child.size() - 1]);//同时将x的右孩子过继给y
            ls->key.erase(ls->key.end() - 1);
            ls->child.erase(ls->child.end() - 1);
            if(v->child[0]) v->child[0]->parent = v;
            return ;
        }
    }
    // 情况2: 右盼,向右兄弟借码
    if(r < p->child.size() - 1)
    {
        BTNodePos(T) rs = p->child[p->child.begin() + r + 1]; // v的右爹为y,rs的老大为x
        if(rs->child.size() > (_order + 1) / 2) // 右孩子足够胖,大于下限就足够胖
        {
            v->key.insert(v->key.end(), p->key[r]); //
            p->key[r] = rs->key[0];
            v->child.insert(v->child.end(), rs->child[0]); // 将x的左孩子过继给y
            rs->key.erase(rs->key[0]);
            if(rs->child[0])
                rs->child[0]->parent = v;
            rs->child.erase(rs->child[0]);
        }
    }
    // 情况3: 左顾右盼失败,要么其没有左右兄弟(但不可能同时),要么左右兄弟太瘦,此时需要从上面下来一个天使, 合并
    if(0 < r) // 和左兄弟合并,当然也可以先和右兄弟合并,随个人喜好
    {
        BTNodePos(T) ls = p->child[r - 1];
        ls->key.push_back(p->key[r - 1]);
        p->key.erase(p->key.begin() + r - 1);
        ls->child.push_back(v->child[0]);
        for(int i = 0; i < v->key.size(); ++i)
        {
            ls->key.push_back(v->key[i]);
            ls->child.push_back(v->child[i+1]);
        }
        if(v->child[0])
        {
            for(int i = 0; i < v->child.size(); ++i)
            {
                v->child[i]->parent = ls;
                delete v->child[i];
            }
            v->child.erase(v->child.begin(), v->child.end());
        }
        p->child.erase[p->child.begin() + r];
    }
    else// 和右兄弟合并
    {
        BTNodePos(T) rs = p->child[r + 1];
        rs->key.push_back(p->key[r]); // 把天使先合并过来
        p->key.erase(p->key.begin() + r);  //
        v->child.push_back(rs->child[0]);
        for(int i = 0; i < rs->key.size(); ++i)
        {
            v->key.push_back(rs->key[i]);
            v->child.push_back(rs->child[i+1]);
        }
        if(rs->child[0])
        {
            for(int i = 0; i < rs->child.size(); ++i)
            {
                rs->child[i]->parent = v;
                delete rs->child[i];
            }
            rs->child.erase(rs->child.begin(), rs->child.end());
        }
        p->child.erase[p->child.begin() + r];
    }
    solveUnderflow(p);
    return ;
}

B树总结

对于B树来说,可能经常遇到它,一直不太明白它比红黑树优秀在哪里。现在明白其主要功能在于减少树的高度

从而减少IO次数。

对于B树来说,主要掌握的就是其插入与删除。首先要会自己画,明白有哪些情况,然后怎么处理。知道这些后

编程只是实现而已。

  1. 插入:

    • 因为B树的搜索知道外部节点才会停止,所以插入都是发生在最底层的叶子节点。当插入的节点超过m-1之后

      就会需要重新调整。我们采取的策略是让其进行分裂,将中位关键码飞上去,然后原本的节点分为俩个关键码差不多的

      节点, 依次递归调整。

  2. 删除:
    • 对于删除来说,稍稍有些复杂,大致需要考虑俩个问题

      • 没法生下溢之前,因为要删除节点,所以要考虑节点后面的元素怎么弥补,仍然让B树保持有序性。这里我们

        采用之前BST使用的中序后继的方法,让删除统一在底层叶子节点发生。

      • 发生下溢之后,节点的关键码过于少,数量低于$\lceil \frac{m}{2} \rceil -1$,需要重新调整
        • 如果左右兄弟大于$\lceil \frac{m}{2} \rceil -1$这个数目,就可以配合他们共同的老爹进行旋转
        • 如果左右兄弟都不行,则让他们的老爹下来,将俩兄弟和老爹合并成一个新的节点。

原文地址:https://www.cnblogs.com/patientcat/p/9720329.html

时间: 2024-11-02 08:19:07

邓俊辉数据结构学习-8-2-B树的相关文章

邓俊辉数据结构学习-8-1-伸展树

高级搜索树--伸展树 对于维护平衡因子,感觉很麻烦,希望抛弃掉平衡因子,使用更加潇洒的模式. 要求: 对于伸展树来说,也不做过多掌握 主要明白利用数据的局部性,我们可以实施的新策略 概述 背景知识补充: 数据局部性 刚被访问过得数据很快会被再次访问 因此这一次访问过的节点,极有可能再次被访问, 能够实现这种特性的树就是伸展树--就像自适应链表一样 新的名词: 自适应链表 在某一段时间内,将经常需要访问的元素尽可能的放到链表前面 大概实现:就是将上次访问过的节点移动到链表的前端 电脑缓存会充分利用

清华大学邓俊辉老师的数据结构在线课程

前几天想重新把数据结构学一遍,于是乎,翻出以前上数据结构的课件,orz...知识遗忘的速度太快了,想找个视频跟着看.令我意想不到的是,我居然能搜索到清华大学邓俊辉老师的数据结构课程,当时把我激动的啊,我要感谢互联网让教育变得那么open.能让我这个二本学校的学生听清华老师的课程,这真的是一件很幸福很幸福的事情. 一流的学校的教育方式就是那么高大上,用OJ提交作业,系统对代码进行黑盒测试...这个课程一直持续到来年1.5日才结束.这么好的资源一定要和大家分享:http://www.xuetangx

Coursera 数据结构 清华 邓俊辉 第十二章 漫谈快排

引言 ? ? 本文从多个方面讲解了快速排序的知识点,包括快排分而治之的思想,以及他与归并排序注重点的不同,快排的性能,包括最优最差以及平均性能,并以均匀分布为例,证明了快排的平均性能是1.39*(n+1)logn,接下来又从熵的角度说明了一下快排为什么下界只能达到nlogn,为什么堆排比快排慢,而基排又能够逃脱nlogn的界限,因为nlogn只针对基于比较的排序算法 ? ? 快排的思想 ? ? 快排采取分而治之的思想,将待排序的序列分为两个子序列S分为SL和SR两个子序列,这个思想跟归并排序有点

《数据结构:邓俊辉版》——冒泡排序

1.思路 每次都是相邻两个数之间进行比较: 每轮比较之后总是把最大的数或者最小的数筛选出来. 2.源码 #include <memory> void BubbleSort(int szArray[], int nLen); void main() { int szArray[] = {6,4,8,1,9,13}; BubbleSort(szArray, _countof(szArray)); getchar(); } void BubbleSort(int szArray[], int nLe

《数据结构:邓俊辉版》——并归排序

void MergeSort(int low, int high) { int mid = (low + high) / 2; if (high - low < 1) { return; } MergeSort(low, mid); MergeSort(mid + 1, high); Merge(low, mid, high); } void Merge(int low, int mid, int high) { int* A = g_szArray + low; int llen = mid

《数据结构:邓俊辉版》——交换排序

void SelectSort(int nLen) { for (int i = 0; i < nLen; i++) { int j = i; int nMax = i; while (j < nLen) { if (g_szArray[j] > g_szArray[nMax]) { nMax = j; } j++; } int tmp = g_szArray[i]; g_szArray[i] = g_szArray[nMax]; g_szArray[nMax] = tmp; } } 原

《数据结构:邓俊辉版》——插入排序

void InsertSort(int nLen) { for (int i = 1; i < nLen; i++) { int j = i - 1; while (g_szArray[j] > g_szArray[i]) { j--; if (j < 0) { break; } } if (j == i - 1) { continue; } int tmp = g_szArray[i]; int k = i; while (k > j) { g_szArray[k] = g_sz

树结构的自定义及基本算法(Java数据结构学习笔记)

数据结构可以归类两大类型:线性结构与非线性结构,本文的内容关于非线性结构:树的基本定义及相关算法.关于树的一些基本概念定义可参考:维基百科 树的ADT模型: 根据树的定义,每个节点的后代均构成一棵树树,称为子树.因此从数据类型来讲,树.子树.树节点是等同地位,可将其看作为一个节点,用通类:Tree表示.如下图所示: 图:Tree ADT模型示意图 可采用"父亲-儿子-兄弟"模型来表示树的ADT.如图所示,除数据项外,分别用三个引用表示指向该节点的父亲,儿子,兄弟. 图:"父亲

数据结构(三)--- B树(B-Tree)

   文章图片代码来自邓俊辉老师的课件 概述 上图就是 B-Tree 的结构,可以看到这棵树和二叉树有点不同---"又矮又肥".同时子节点可以有若干个小的子节点构成.那么这样一棵树又有什么作用呢? 动机 我们知道电脑的访问内存比访问外的存I/O操作快了,但是内存的容量大小又只有那么一点点(相对于外存),所以计算机访问的过程常常使用高速缓存.使用高速缓存也是在以下两个事实想出的策略. 而B-Tree这种结构就是根据这种情况被发掘出来的.下图 m 指的是每次的数据块数量 B-Tree 介绍