<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>算法与数据结构 on codedump notes</title>
    <link>https://www.codedump.info/zh/tags/%E7%AE%97%E6%B3%95%E4%B8%8E%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/</link>
    <description>Recent content in 算法与数据结构 on codedump notes</description>
    <generator>Hugo</generator>
    <language>zh</language>
    <lastBuildDate>Mon, 15 Jun 2020 22:44:21 +0800</lastBuildDate>
    <atom:link href="https://www.codedump.info/zh/tags/%E7%AE%97%E6%B3%95%E4%B8%8E%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>B树、B&#43;树索引算法原理（下）</title>
      <link>https://www.codedump.info/zh/post/20200615-btree-2/</link>
      <pubDate>Mon, 15 Jun 2020 22:44:21 +0800</pubDate>
      <guid>https://www.codedump.info/zh/post/20200615-btree-2/</guid>
      <description>&lt;blockquote&gt;&#xA;&lt;p&gt;这一段时间由于在阅读boltdb代码的缘故，找机会学习了B树及B+树的算法原理，这个系列会花两个篇幅分别介绍这两种数据结构的实现，其用于数据库索引中的基本原理。&lt;/p&gt;&lt;/blockquote&gt;&#xA;&lt;p&gt;在&lt;a href=&#34;https://www.codedump.info/post/20200609-btree-1/&#34;&gt;上一篇文章&lt;/a&gt;中，介绍了数据库索引的简单概念，以及B树的结构及核心算法，这一篇将继续介绍B树的变形B+树。&lt;/p&gt;&#xA;&lt;h1 class=&#34;heading&#34; id=&#34;b树的定义及性质&#34;&gt;&#xA;  B+树的定义及性质&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#b%e6%a0%91%e7%9a%84%e5%ae%9a%e4%b9%89%e5%8f%8a%e6%80%a7%e8%b4%a8&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h1&gt;&#xA;&lt;p&gt;B+树之于B树，最大的不同在于：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;B树的数据可以存储在内部节点上，也可以存储在叶子节点上。&lt;/li&gt;&#xA;&lt;li&gt;而在B+树中，内部节点上仅存放数据的索引，数据只存储在叶子节点上。在内部节点中的键值，被称为“索引”，由于是数据索引，因此可能出现同一个键值，既出现在内部节点，也出现在叶子节点中的情况。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;内部节点的“索引”，应该满足以下条件：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;大于左边子树的最大键值；&lt;/li&gt;&#xA;&lt;li&gt;小于等于右边子树的最小键值。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;同时，B+树为了方便范围查询，叶子节点之间还用指针串联起来。&lt;/p&gt;&#xA;&lt;p&gt;以下是一颗B+树的典型结构：&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&lt;figure class=&#34;&#34;&gt;&#xA;&#xA;    &lt;div&gt;&#xA;        &lt;img loading=&#34;lazy&#34; alt=&#34;b&amp;#43;tree&#34; src=&#34;https://www.codedump.info/media/imgs/20200615-btree-2/b&amp;#43;tree.png&#34; &gt;&#xA;    &lt;/div&gt;&#xA;&#xA;    &#xA;    &lt;div class=&#34;caption-container&#34;&gt;&#xA;        &lt;figcaption&gt; b+tree &lt;/figcaption&gt;&#xA;    &lt;/div&gt;&#xA;    &#xA;&lt;/figure&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;由于采用了这样的结构，B+树对比B树有以下优点：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;索引节点上由于只有索引而没有数据，所以索引节点上能存储比B树更多的索引，这样树的高度就会更矮。按照我们上一篇中介绍数据库索引的内容，这种面向磁盘的数据结构，树的高度越矮，磁盘寻道的次数就会越少。&lt;/li&gt;&#xA;&lt;li&gt;因为数据都集中在叶子节点了，而所有叶子节点的高度相同，那么可以在叶子节点中增加前后指针，指向同一个父节点的相邻兄弟节点，给范围查询提供遍历。比如这样的SQL语句：&lt;code&gt;select * from tbl where t &amp;gt; 10&lt;/code&gt;，如果使用B+树存储数据的话，可以首先定位到数据为10的节点，再沿着它的next指针一路找到所有在该叶子节点右边的叶子节点数据返回。而如果使用B树结构，由于数据既可以存储在内部节点也可以存储在叶子节点，范围查询可想而知是很繁琐的。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h1 class=&#34;heading&#34; id=&#34;核心算法&#34;&gt;&#xA;  核心算法&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e6%a0%b8%e5%bf%83%e7%ae%97%e6%b3%95&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h1&gt;&#xA;&lt;h2 class=&#34;heading&#34; id=&#34;插入算法&#34;&gt;&#xA;  插入算法&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e6%8f%92%e5%85%a5%e7%ae%97%e6%b3%95&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;B+树的插入算法与B树的很相近，都是：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;首先判断待插入数据节点是否已经溢出，如果是就首先拆分成两个节点，然后再插入数据。&lt;/li&gt;&#xA;&lt;li&gt;由于内部节点上的数据是索引，所以在插入完成之后调整父节点指针。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;比如在下图的B+树中，向这里插入新的数据&lt;code&gt;10&lt;/code&gt;：&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&lt;figure class=&#34;&#34;&gt;&#xA;&#xA;    &lt;div&gt;&#xA;        &lt;img loading=&#34;lazy&#34; alt=&#34;slide01b&#34; src=&#34;https://www.codedump.info/media/imgs/20200615-btree-2/slide01b.gif&#34; &gt;&#xA;    &lt;/div&gt;&#xA;&#xA;    &#xA;    &lt;div class=&#34;caption-container&#34;&gt;&#xA;        &lt;figcaption&gt; slide01b &lt;/figcaption&gt;&#xA;    &lt;/div&gt;&#xA;    &#xA;&lt;/figure&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;由于插入节点&lt;code&gt;[7,11]&lt;/code&gt;在插入之后并没有溢出，所以可以直接变成&lt;code&gt;[7,10,11]&lt;/code&gt;：&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&lt;figure class=&#34;&#34;&gt;&#xA;&#xA;    &lt;div&gt;&#xA;        &lt;img loading=&#34;lazy&#34; alt=&#34;slide01c&#34; src=&#34;https://www.codedump.info/media/imgs/20200615-btree-2/slide01c.gif&#34; &gt;&#xA;    &lt;/div&gt;&#xA;&#xA;    &#xA;    &lt;div class=&#34;caption-container&#34;&gt;&#xA;        &lt;figcaption&gt; slide01c &lt;/figcaption&gt;&#xA;    &lt;/div&gt;&#xA;    &#xA;&lt;/figure&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;而如下图的B+树中，插入数据&lt;code&gt;4&lt;/code&gt;：&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&lt;figure class=&#34;&#34;&gt;&#xA;&#xA;    &lt;div&gt;&#xA;        &lt;img loading=&#34;lazy&#34; alt=&#34;slide02b&#34; src=&#34;https://www.codedump.info/media/imgs/20200615-btree-2/slide02b.gif&#34; &gt;&#xA;    &lt;/div&gt;&#xA;&#xA;    &#xA;    &lt;div class=&#34;caption-container&#34;&gt;&#xA;        &lt;figcaption&gt; slide02b &lt;/figcaption&gt;&#xA;    &lt;/div&gt;&#xA;    &#xA;&lt;/figure&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;由于所在节点&lt;code&gt;[2,3,5]&lt;/code&gt;在插入之后数据溢出，因此需要分裂为两个新的节点，同时调整父节点的索引数据：&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&lt;figure class=&#34;&#34;&gt;&#xA;&#xA;    &lt;div&gt;&#xA;        &lt;img loading=&#34;lazy&#34; alt=&#34;slide02g&#34; src=&#34;https://www.codedump.info/media/imgs/20200615-btree-2/slide02g.gif&#34; &gt;&#xA;    &lt;/div&gt;&#xA;&#xA;    &#xA;    &lt;div class=&#34;caption-container&#34;&gt;&#xA;        &lt;figcaption&gt; slide02g &lt;/figcaption&gt;&#xA;    &lt;/div&gt;&#xA;    &#xA;&lt;/figure&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;&lt;code&gt;[2,3,4,5]&lt;/code&gt;分裂成了&lt;code&gt;[2,3]&lt;/code&gt;和&lt;code&gt;[4,5]&lt;/code&gt;，因此需要在这两个节点之间新增一个索引值，这个值应该满足：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;大于左子树的最大值。&lt;/li&gt;&#xA;&lt;li&gt;小于等于右子树的最小值。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;综上，需要在父节点中新增索引&lt;code&gt;4&lt;/code&gt;和两个指向新节点的指针。&lt;/p&gt;&#xA;&lt;h2 class=&#34;heading&#34; id=&#34;删除算法&#34;&gt;&#xA;  删除算法&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e5%88%a0%e9%99%a4%e7%ae%97%e6%b3%95&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;B+树的删除算法，与B树类似，分为以下几步：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;首先查询到键值所在的叶子节点，删除该叶子节点的数据。&lt;/li&gt;&#xA;&lt;li&gt;如果删除叶子节点之后的数据数量，满足B+树的平衡条件，则直接返回不用往下走了。&lt;/li&gt;&#xA;&lt;li&gt;否则，就需要做平衡操作：&#xA;&lt;ul&gt;&#xA;&lt;li&gt;如果该叶子节点的左右兄弟节点的数据量可以借用，就借用过来满足平衡条件。&lt;/li&gt;&#xA;&lt;li&gt;否则，就只能与相邻的兄弟节点合并成一个新的子节点了。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;在上面平衡操作中，如果是进行了合并操作，就需要向上修正父节点的指针：删除被合并节点的键值以及指针。由于做了删除操作，可能父节点也会不平衡，那么就按照前面的步骤也对父节点进行重新平衡操作，这样一直到某个节点平衡为止。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;下面结合&lt;a href=&#34;http://www.mathcs.emory.edu/~cheung/Courses/554/Syllabus/3-index/B-tree=delete1.html&#34;&gt;B-tree=delete1&lt;/a&gt;、&lt;a href=&#34;http://www.mathcs.emory.edu/~cheung/Courses/554/Syllabus/3-index/B-tree=delete2.html&#34;&gt;B-tree=delete2&lt;/a&gt; 的图示对删除算法展开具体的分析。&lt;/p&gt;&#xA;&lt;h3 class=&#34;heading&#34; id=&#34;从叶子节点中删除数据&#34;&gt;&#xA;  从叶子节点中删除数据&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e4%bb%8e%e5%8f%b6%e5%ad%90%e8%8a%82%e7%82%b9%e4%b8%ad%e5%88%a0%e9%99%a4%e6%95%b0%e6%8d%ae&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h3&gt;&#xA;&lt;p&gt;从叶子节点中删除数据分为三种情况：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;删除之后的数据量足够，不需要进行重平衡操作；&lt;/li&gt;&#xA;&lt;li&gt;删除之后的数据量不够，但是可以从兄弟节点那里借用数据，重新达到平衡；&lt;/li&gt;&#xA;&lt;li&gt;删除之后的数据量不够，兄弟节点的数据也不够，那么需要合并成一个新的节点，同时在父节点中删除索引和指针。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;以下针对后面两种需要做重平衡的操作展开分析。&lt;/p&gt;&#xA;&lt;h4 class=&#34;heading&#34; id=&#34;借用兄弟节点数据进行重平衡操作&#34;&gt;&#xA;  借用兄弟节点数据进行重平衡操作&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e5%80%9f%e7%94%a8%e5%85%84%e5%bc%9f%e8%8a%82%e7%82%b9%e6%95%b0%e6%8d%ae%e8%bf%9b%e8%a1%8c%e9%87%8d%e5%b9%b3%e8%a1%a1%e6%93%8d%e4%bd%9c&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h4&gt;&#xA;&lt;p&gt;在下图中，从叶子节点中删除数据之后，只剩下数据&lt;code&gt;[11]&lt;/code&gt;：&lt;/p&gt;</description>
    </item>
    <item>
      <title>B树、B&#43;树索引算法原理（上）</title>
      <link>https://www.codedump.info/zh/post/20200609-btree-1/</link>
      <pubDate>Tue, 09 Jun 2020 18:40:46 +0800</pubDate>
      <guid>https://www.codedump.info/zh/post/20200609-btree-1/</guid>
      <description>&lt;blockquote&gt;&#xA;&lt;p&gt;这一段时间由于在阅读boltdb代码的缘故，找机会学习了B树及B+树的算法原理，这个系列会花两个篇幅分别介绍这两种数据结构的实现，其用于数据库索引中的基本原理。&lt;/p&gt;&lt;/blockquote&gt;&#xA;&lt;h1 class=&#34;heading&#34; id=&#34;b树数据库索引原理&#34;&gt;&#xA;  B树数据库索引原理&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#b%e6%a0%91%e6%95%b0%e6%8d%ae%e5%ba%93%e7%b4%a2%e5%bc%95%e5%8e%9f%e7%90%86&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h1&gt;&#xA;&lt;p&gt;在一堆数据中查找一个数据时，常用的数据结构有二叉查找树（binary search tree，简称BST）、哈希桶等。以BST为例，常见的实现有AVT、红黑树等，由于这类型的树是平衡的，每次比较操作都会去掉当前数据量一半的数据，因此查找的时间复杂度为&lt;code&gt;O(log2n)&lt;/code&gt;。&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&lt;figure class=&#34;&#34;&gt;&#xA;&#xA;    &lt;div&gt;&#xA;        &lt;img loading=&#34;lazy&#34; alt=&#34;bst-example&#34; src=&#34;https://www.codedump.info/media/imgs/20200609-btree-1/bst-example.png&#34; &gt;&#xA;    &lt;/div&gt;&#xA;&#xA;    &#xA;    &lt;div class=&#34;caption-container&#34;&gt;&#xA;        &lt;figcaption&gt; bst-example &lt;/figcaption&gt;&#xA;    &lt;/div&gt;&#xA;    &#xA;&lt;/figure&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;但是这类型数据结构的问题在于，由于每个节点只能容纳一个数据，导致树的高度很高，逻辑上挨着的节点数据可能离的很远。如果是在内存中操作数据的话，这样问题并不大。&lt;/p&gt;&#xA;&lt;p&gt;考虑在磁盘中存储数据的情况，与内存相比，读写磁盘有以下不同点：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;读写磁盘的速度相比内存读写慢很多。&lt;/li&gt;&#xA;&lt;li&gt;因为上面的原因，因此每次读写磁盘的单位要比读写内存的最小单位大很多。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;因为读写磁盘的这个特点，因此对应的数据结构应该尽量的满足“局部性原理”：“当一个数据被用到时，其附近的数据也通常会马上被使用”，为了满足局部性原理，应该：&lt;strong&gt;将逻辑上相邻的数据在物理上也尽量存储在一起&lt;/strong&gt;。这样才能减少读写磁盘的数量。&lt;/p&gt;&#xA;&lt;p&gt;所以，对比起一个节点只能存储一个数据的BST类数据结构来，要求这种数据结构在形状上更“胖”、更加“扁平”，即：每个节点能容纳更多的数据，这样就能降低树的高度，同时让逻辑上相邻的数据都能尽量的存储在物理上也相邻的硬盘空间上，减少磁盘读写。&lt;/p&gt;&#xA;&lt;p&gt;以下图为例：&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&lt;figure class=&#34;&#34;&gt;&#xA;&#xA;    &lt;div&gt;&#xA;        &lt;img loading=&#34;lazy&#34; alt=&#34;disk-ds&#34; src=&#34;https://www.codedump.info/media/imgs/20200609-btree-1/disk-ds.png&#34; &gt;&#xA;    &lt;/div&gt;&#xA;&#xA;    &#xA;    &lt;div class=&#34;caption-container&#34;&gt;&#xA;        &lt;figcaption&gt; disk-ds &lt;/figcaption&gt;&#xA;    &lt;/div&gt;&#xA;    &#xA;&lt;/figure&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;图中从根节点出发，查找数据14的过程中，经过的第二个节点中有键值&lt;code&gt;[3,7,13]&lt;/code&gt;，这三个值在“逻辑”上是相邻的，如果它们在磁盘上的存储也能做到在“物理”上相邻，那么只需要一次读操作就能把这个节点的数据从磁盘上加载到内存中进行数据比较，这样整个查找过程就只需要两次磁盘读操作。&lt;/p&gt;&#xA;&lt;p&gt;在这里，一个节点越“胖”，意味着扇出（fanout）越大，同时高度越低，这两个性质决定了：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;高扇出：邻近键值的数据局部性更好。&lt;/li&gt;&#xA;&lt;li&gt;低高度：遍历期间的寻道次数更少。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;可以证明，查找数据的次数（searchnum）与degree、以及数据总量有以下关系：&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&lt;figure class=&#34;&#34;&gt;&#xA;&#xA;    &lt;div&gt;&#xA;        &lt;img loading=&#34;lazy&#34; alt=&#34;btree-num&#34; src=&#34;https://www.codedump.info/media/imgs/20200609-btree-1/btree-num.png&#34; &gt;&#xA;    &lt;/div&gt;&#xA;&#xA;    &#xA;    &lt;div class=&#34;caption-container&#34;&gt;&#xA;        &lt;figcaption&gt; btree-num &lt;/figcaption&gt;&#xA;    &lt;/div&gt;&#xA;    &#xA;&lt;/figure&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;B树和B+树就是两种利用磁盘局部性原理进行优化的树结构，B+树基于B树做了一些改进，这里首先将介绍B树的原理。本系列将用两篇文章讲解这两种数据结构的原理，并且提供Python实现代码。&lt;/p&gt;&#xA;&lt;h1 class=&#34;heading&#34; id=&#34;b树的定义及性质&#34;&gt;&#xA;  B树的定义及性质&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#b%e6%a0%91%e7%9a%84%e5%ae%9a%e4%b9%89%e5%8f%8a%e6%80%a7%e8%b4%a8&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h1&gt;&#xA;&lt;p&gt;在B树中，分为两种节点：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;内部节点（internal node）：存储了数据以及指向其子节点的指针。&lt;/li&gt;&#xA;&lt;li&gt;叶子节点（leaf node）：与内部节点不同的是，叶子节点只存储数据，并没有子节点。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;一个数据，既可能存在内部节点上，也可能存在叶子节点上，这一点是与后面讲到的B+树最大的不同，后者只会将数据存储在叶子节点上。&lt;/p&gt;&#xA;&lt;p&gt;创建B树时，需要输入一个degree参数（以下简写为t），该参数决定了每个节点上数据量的多少，即节点的“胖”、“瘦”程度，而节点的胖瘦程度又会影响整棵树的高度，因为越胖的节点树高度就会越矮。&lt;/p&gt;&#xA;&lt;p&gt;为了维持B树的平衡性，需要满足以下的属性：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;在每个节点上的键值，以递增顺序排列，即&lt;code&gt;node.keys[i] &amp;lt;= node.keys[i+1]&lt;/code&gt;。&lt;/li&gt;&#xA;&lt;li&gt;在一个键值左边的子树，其键值大于该键值右边子树的所有键值，即&lt;code&gt;node.keys[i] &amp;gt; max(node.child[i]的所有键值)&lt;/code&gt;；同时，在一个键值右边的子树，其键值的最小值都不小于该键值，即&lt;code&gt;node.keys[i] &amp;lt;= min(node.child[i + 1]的所有键值)&lt;/code&gt;。具体情况可以在下面的图中进行说明。&lt;/li&gt;&#xA;&lt;li&gt;在内部节点中，指向子节点的指针数量总是存储数据节点的数量+1，即：&lt;code&gt;num(node.child) = num(node.keys) + 1&lt;/code&gt;。&lt;/li&gt;&#xA;&lt;li&gt;所有叶子节点的高度一致。&lt;/li&gt;&#xA;&lt;li&gt;无论是内部节点还是叶子节点，其存储的键值数量在&lt;code&gt;[t-1,2t-1]&lt;/code&gt;之间，如果数量不满足此条件，需要做重平衡操作。如果少于&lt;code&gt;t-1&lt;/code&gt;，需要借用或合并数据；反之，如果数据量大于&lt;code&gt;2t-1&lt;/code&gt;，则需要分裂成两个节点。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;我们来看下面的图示，该图中的B树，t参数的值为2（&lt;strong&gt;需要特别说明的是，一棵树中每个存储数据的地方，应该既有键值（key）也有数据（value），本文中为了简单起见，存储的数据只有键值。&lt;/strong&gt;）：&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&lt;figure class=&#34;&#34;&gt;&#xA;&#xA;    &lt;div&gt;&#xA;        &lt;img loading=&#34;lazy&#34; alt=&#34;btree-example&#34; src=&#34;https://www.codedump.info/media/imgs/20200609-btree-1/btree-example.png&#34; &gt;&#xA;    &lt;/div&gt;&#xA;&#xA;    &#xA;    &lt;div class=&#34;caption-container&#34;&gt;&#xA;        &lt;figcaption&gt; btree-example &lt;/figcaption&gt;&#xA;    &lt;/div&gt;&#xA;    &#xA;&lt;/figure&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;在上图中：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;由于&lt;code&gt;t=2&lt;/code&gt;，所有所有节点的键值数量在&lt;code&gt;[1,3]&lt;/code&gt;之间。&lt;/li&gt;&#xA;&lt;li&gt;所有叶子节点的高度相同。&lt;/li&gt;&#xA;&lt;li&gt;以左边的内部节点为例，其第一个键值为3，即该节点的&lt;code&gt;keys[0]=3&lt;/code&gt;，而该键值的左边子树的键值为&lt;code&gt;[1,2]&lt;/code&gt;，都小于3，即&lt;code&gt;keys[0]&amp;gt;max(child[0]的所有键值)&lt;/code&gt;；而其右边子树的键值为&lt;code&gt;[4,5,6]&lt;/code&gt;，都不小于3，即&lt;code&gt;keys[0]&amp;lt;=min(child[1]的所有键值)&lt;/code&gt;。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h1 class=&#34;heading&#34; id=&#34;b树算法原理&#34;&gt;&#xA;  B树算法原理&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#b%e6%a0%91%e7%ae%97%e6%b3%95%e5%8e%9f%e7%90%86&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h1&gt;&#xA;&lt;p&gt;了解了B树的性质，下面讨论B树中的两个核心操作：插入及删除。这两个操作的核心，都是在操作如果破坏了B树的平衡性之后，进行重新平衡以满足B树的性质。&lt;/p&gt;&#xA;&lt;h2 class=&#34;heading&#34; id=&#34;插入数据&#34;&gt;&#xA;  插入数据&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e6%8f%92%e5%85%a5%e6%95%b0%e6%8d%ae&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;向B树中插入一个数据，可能会导致节点的数据变满，即不满足上面提到的节点数据数量在&lt;code&gt;[t,2t-1]&lt;/code&gt;这个性质。此时需要对节点进行分裂节点操作：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;将数据变满（即节点数据量为&lt;code&gt;2t&lt;/code&gt;）的节点，分为左右两个数据量分别为&lt;code&gt;t-1&lt;/code&gt;的节点，同时将中间的数据提升到父节点的合适位置上。&lt;/li&gt;&#xA;&lt;li&gt;如果父节点由于新增了这个被提升的数据导致了变满，就继续上面的分裂节点操作。&lt;/li&gt;&#xA;&lt;li&gt;沿着树向上一直执行该操作，直到不再变满为止。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;分裂操作的示意图如下：&lt;/p&gt;</description>
    </item>
  </channel>
</rss>
