• 【高阶数据结构】B树


    目录

    1.B树

    2.B+树和B树的不同

    3.B*树


    B树较于哈希红黑树的优势:外查找:读取磁盘数据   ; B树的高度更低,对磁盘的进行I/O操作的次数更少(磁盘的性能比内存差得多);

    1.B树

    1.1.B树的概念:

    1. 根节点至少有两个孩子       (空树除外)
    2. 每个分支节点都包含k-1个关键字和k个孩子,其中 ceil(m/2) ≤ k ≤ m ceil是向上取整函数     (分支节点都是依靠叶节点分裂得到,孩子依靠分裂得到)
    3. 每个叶子节点都包含k-1个关键字,其中 ceil(m/2) ≤ k ≤ m       (叶子节点没有孩子)
    4. 所有的叶子节点都在同一层   (因为分裂向上插入)
    5. 每个节点中的关键字从小到大排列,节点当中k-1个元素正好是k个孩子包含的元素的值域划分  ( 所有左孩子 <  关键字  < 所有右孩子 )
    6. 每个结点的结构为:(n,A0,K1,A1,K2,A2,… ,Kn,An)其中,Ki(1≤i≤n)为关键子,且Ki < Ki+1(1<= i <= n-1)  Ai(0≤i≤n)为指向子树根结点的指针。且Ai所指子树所有结点中的关键字均小于Ki+1。n为结点中关键字的个数,满足ceil(m/2)-1≤n≤m-1。

    分裂:所有元素都是插入关键字,孩子是指向的节点指针;数组满了,分裂出brother(new出来)节点将【mid+1,r】拷贝给brother,如果父节点为空就,创建新的父节点,父节点的左边为原数组,有孩子为brother;

    1. #pragma once
    2. #include
    3. #include
    4. using namespace std;
    5. template<class T, size_t N>
    6. class Node {
    7. public:
    8. Node()
    9. {
    10. //孩子最多为N个,关键字比孩子少一个
    11. //需要多开辟一个,来判断数组元素满
    12. _key.resize(N);
    13. _children.resize(N+1, nullptr);
    14. _parent = nullptr;
    15. _size = 0;
    16. }
    17. ~Node()
    18. {
    19. }
    20. public:
    21. //关键字
    22. vector _key;
    23. //孩子数据
    24. vector*> _children;
    25. //父亲节点
    26. Node* _parent;
    27. //有效个数
    28. size_t _size;
    29. };
    30. template<class T, size_t N>
    31. class BTree {
    32. public:
    33. typedef Node Node;
    34. //返回节点和下标
    35. pairint> Find(T val)
    36. {
    37. Node* parent = nullptr;
    38. Node* cur = _root;
    39. //找到合适位置,从左向右找 比当前元素大,判断是否有左孩子
    40. while (cur)
    41. {
    42. size_t i = 0;
    43. while (i < cur->_size)
    44. {
    45. //找到比我大的了
    46. if ( val < cur->_key[i] )
    47. break;
    48. else if ( val > cur->_key[i] )
    49. i++;
    50. else//元素存在了,退出
    51. return make_pair(cur, i);
    52. }
    53. //保存父亲
    54. parent = cur;
    55. cur = cur->_children[i];
    56. }
    57. //不存在,返回父亲和-1
    58. return make_pair(parent, -1);
    59. }
    60. void _Insert(T val, Node* parent, Node* child)
    61. {
    62. int end = parent->_size - 1;
    63. //从最后一个元素挪数据
    64. while (end >= 0)
    65. {
    66. //目标元素更小,挪
    67. if (val < parent->_key[end])
    68. {
    69. parent->_key[end + 1] = parent->_key[end];
    70. parent->_children[end + 2] = parent->_children[end + 1];//挪右孩子
    71. end--;
    72. }
    73. else
    74. break;
    75. }
    76. //合适的插入位置
    77. parent->_key[end + 1] = val;
    78. parent->_children[end + 2] = child;//右孩子
    79. if (child)//指向父亲
    80. child->_parent = parent;
    81. parent->_size++;
    82. }
    83. bool Insert(T val)
    84. {
    85. //第一个插入的元素
    86. if (_root == nullptr)
    87. {
    88. _root = new Node;
    89. _root->_key[0] = val;
    90. _root->_size++;
    91. }
    92. else
    93. {
    94. //查找是否存在
    95. pairint> pr = Find(val);
    96. if (pr.second >= 0)//元素已存在,不用在插入
    97. return false;
    98. //该插入的位置
    99. Node* parent = pr.first;
    100. Node* child = nullptr;
    101. _Insert(val, parent, child);
    102. while (true)
    103. {
    104. if (parent->_size < N)
    105. return true;
    106. else//数组满,分离
    107. {
    108. //分裂 [l, mid-1] [mid+1, r]
    109. int mid = N / 2;
    110. size_t i = 0;
    111. size_t j = mid + 1;
    112. //分离兄弟节点
    113. Node* brother = new Node;
    114. for (; j < N; j++, i++)
    115. {
    116. brother->_key[i] = parent->_key[j];
    117. brother->_children[i] = parent->_children[j];
    118. //有孩子节点,brother做新父亲
    119. if (parent->_children[j])
    120. parent->_children[j]->_parent = brother;
    121. //分裂给兄弟的关键字和孩子置空
    122. parent->_children[j] = nullptr;
    123. parent->_key[j] = T();
    124. }
    125. //还有一个右孩子给brother
    126. brother->_children[i] = parent->_children[j];
    127. if (parent->_children[j])
    128. parent->_children[j]->_parent = brother;
    129. parent->_children[j] = nullptr;
    130. //分裂完处理个数;
    131. brother->_size = i;
    132. parent->_size -= (i + 1);//减去兄弟和中间
    133. //中间值向上插入
    134. T midKey = parent->_key[mid];
    135. parent->_key[mid] = T();
    136. //头节点
    137. if (parent->_parent == nullptr)
    138. {
    139. _root = new Node;
    140. _root->_key[0] = midKey;
    141. _root->_children[0] = parent;
    142. _root->_children[1] = brother;
    143. _root->_size = 1;
    144. //孩子的父亲为头
    145. parent->_parent = _root;
    146. brother->_parent = _root;
    147. break;
    148. }
    149. else
    150. {
    151. //非头重新插入
    152. _Insert(midKey, parent->_parent, brother);
    153. //继续循环,看上一层是否满了
    154. parent = parent->_parent;
    155. }
    156. }
    157. }
    158. }
    159. }
    160. public:
    161. private:
    162. Node* _root = nullptr;
    163. };

     测试代码:

    1. #include"BTree.h"
    2. void TestBtree()
    3. {
    4. int a[] = { 53, 139, 75, 49, 145, 36, 101 };
    5. BTree<int, 3> t;
    6. for (auto e : a)
    7. {
    8. t.Insert(e);
    9. }
    10. }
    11. int main()
    12. {
    13. TestBtree();
    14. }

    2.B+树和B树的不同

    1. 分支节点的子树指针与关键字个数相同
    2. 分支节点的子树指针p[i]指向关键字值大小在[k[i],k[i+1])区间之间
    3. 所有叶子节点增加一个链接指针链接在一起
    4. 所有关键字及其映射数据都在叶子节点出现

    B+树的特性:

    1. 所有关键字都出现在叶子节点的链表中,且链表中的节点都是有序的。
    2. 不可能在分支节点中命中。 
    3. 分支节点相当于是叶子节点的索引,叶子节点才是存储数据的数据层。

    3.B*树

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

    区别就是分裂方式不同:不同当前节点和下一个兄弟节点都满才分裂,都区1/3构成一个new节点,提高利用率, 最多3/1被浪费;

  • 相关阅读:
    MySQL | MySQL不区分大小写配置
    pdf转jpg的方法【ps和工具方法】
    剩下的数。
    【UE数字孪生学习笔记】 Gameplay框架之TArray
    在 TypeScript 中declare module 关键字用法
    【和小白一起练习CTF】攻防世界:web基础练习题(2)
    spring boot使用自定义过滤器实现接口认证
    Selenium基础 — Selenium对cookie的操作
    电脑重装系统后鼠标动不了该怎么解决
    智慧公厕:将科技融入日常生活的创新之举
  • 原文地址:https://blog.csdn.net/m0_72964546/article/details/134047268