• 二叉搜索树


    目录

    一.概念

    二.基本操作

    1.建树

    2.查找元素

    3.插入元素

    4.删除元素

    5.遍历

    三.K模型与KV模型实现

    K模型模拟实现

    KV模型模拟实现

    测试函数


    一.概念

    二叉搜索树,若其不是空树测具有以下性质:

    • 若它的左子树不为空,则左子树上所有节点的值都小于根节点的值
    • 若它的右子树不为空,则右子树上所有节点的值都大于根节点的值
    • 它的左右子树也分别为二叉搜索树

    因其中序遍历的结果是有序的,故也称为二叉排序树

    二.基本操作

    1.建树

    使用模板建树,泛化使用范围。

    1. namespace key
    2. {
    3. template<class K>
    4. struct BSTreeNode
    5. {
    6. BSTreeNode* _left;
    7. BSTreeNode* _right;
    8. K _key;
    9. BSTreeNode(const K& key)
    10. :_left(nullptr)
    11. , _right(nullptr)
    12. , _key(key)
    13. { }
    14. };
    15. template<class K>
    16. class BSTree
    17. {
    18. typedef BSTreeNode Node;
    19. private:
    20. Node* _root = nullptr;
    21. }
    22. }

    2.查找元素

    思路:因为二叉搜索树满足如上性质,所以对于每个节点, 其左子树都比它小, 右子树都比它大。所以查找元素比当前节点值大就往右走,比当前节点值小就往左走。

    1. bool Find(const K& key)
    2. {
    3. //从根节点开始搜
    4. Node* cur = _root;
    5. //搜到底部
    6. while (cur)
    7. {
    8. if (cur->_key < key)
    9. {
    10. cur = cur->_right;
    11. }
    12. else if (cur->_key > key)
    13. {
    14. cur = cur->_left;
    15. }
    16. else
    17. {
    18. //相等了
    19. return true;
    20. }
    21. }
    22. return false;
    23. }

    3.插入元素

    思路:如果是空树的话直接赋值给root建树,当插入元素比当前节点值小时,往左走; 当插入元素比当前节点值大时,往右走。因其为无向图,我们应加入一个parent节点来记录当前节点(cur节点)前的位置。

    注意:

    1.因为二叉搜索树中不能有重复值,所以注意判重

    2.插入元素时不需要挪动其他节点,遍历到底即可。

    1. bool Insert(const K& key)
    2. {
    3. if (_root == nullptr)
    4. {
    5. _root = new Node(key);
    6. return true;
    7. }
    8. //遍历到底部
    9. Node* parent = nullptr;
    10. Node* cur = _root;
    11. while (cur)
    12. {
    13. if (cur->_key < key)
    14. {
    15. parent = cur;
    16. cur = cur->_right;
    17. }
    18. else if (cur->_key > key)
    19. {
    20. parent = cur;
    21. cur = cur->_left;
    22. }
    23. else
    24. {
    25. return false;
    26. }
    27. }
    28. //到底部之后 加入树
    29. cur = new Node(key);
    30. if (parent->_key < key)
    31. {
    32. parent->_right = cur;
    33. }
    34. else
    35. {
    36. parent->_left = cur;
    37. }
    38. return true;
    39. }

    4.删除元素

    删除元素是二叉搜索树的难点 观察开头给出的例图

    不难若删除一个节点 其有三种情况(删除的节点左右都为空的情况涵盖在了前两种)

    1.删除的节点左子树为空   如图中值为1的节点

    2.删除的节点右子树为空   如图中值为6的节点

    3.删除的节点左右子树都不为空  如图中值为14、3的节点

    下面对以上情况逐一进行讨论

    对于每个节点,其左子树都小于自己,其右子树都大于自己。

    1.若删除的节点的左子树为空,则使其父节点的指向该节点的 right,若该节点位于其父节点的左侧,则使父节点的left 指向该节点的 right,若在右侧,则使父节点的right 指向该节点的 right。

    2.若删除的节点的右子树为空,则使其父节点的指向该节点的 left,若该节点位于其父节点的左侧,则使父节点的left 指向该节点的 left,若在右侧,则使父节点的right 指向该节点的 left。

    3.若删除的节点左右子树都不为空

    采用替换法删除:

    基于搜索二叉树的性质 我们需要找到右子树的最小节点或左子树的最大节点来替换要删除的节点,再将这个找到的节点的子树继承给其父节点,即保证删除后仍然满足对于每个节点,其左子树都小于自己,其右子树都大于自己。

    注意:

    若删除的节点类似元素8, 其右子树

    的最小节点就是其右节点,需要特判。

    1. bool Erase(const K& key)
    2. {
    3. //删除有三种情况
    4. //1 删除的节点的左为空
    5. //2 删除的节点的右为空
    6. //3 删除的节点左右都空 ---> 替换删除法
    7. //即找左子树的最右节点(最大值) 或 右子树的最左节点代替即可
    8. //下即 右子树的最左节点
    9. Node* parent = nullptr;
    10. Node* cur = _root;
    11. while (cur)
    12. {
    13. if (cur->_key < key)
    14. {
    15. parent = cur;
    16. cur = cur->_right;
    17. }
    18. else if (cur->_key > key)
    19. {
    20. parent = cur;
    21. cur = cur->_right;
    22. }
    23. else
    24. {
    25. //相等删除
    26. //1 左为空 父亲指向我的右
    27. if (cur->_left == nullptr)
    28. {
    29. if (cur == _root)
    30. {
    31. _root = cur->_right;
    32. }
    33. else
    34. {
    35. // 看它是 父亲的左节点 还是右节点
    36. if (cur == parent->_left)
    37. {
    38. parent->_left = cur->_right;
    39. }
    40. else
    41. {
    42. parent->_right = cur->_right;
    43. }
    44. }
    45. // 终止条件 防止内存泄漏
    46. delete cur;
    47. }
    48. //2 右为空 父亲指向我的左
    49. else if (cur->_right == nullptr)
    50. {
    51. if (cur == _root)
    52. {
    53. _root = cur->_left;
    54. }
    55. else
    56. {
    57. if (cur == parent->_left)
    58. {
    59. parent->_left = cur->_left;
    60. }
    61. else
    62. {
    63. parent->_right = cur->_left;
    64. }
    65. }
    66. delete cur;
    67. }
    68. //3 左右都不为空 找右子树的最小值替换
    69. else
    70. {
    71. Node* RightMinParent = cur;
    72. Node* RightMin = cur->_right;
    73. while (RightMin->_left)
    74. {
    75. RightMinParent = RightMin;
    76. RightMin = RightMin->_left;
    77. }
    78. swap(cur->_key, RightMin->_key);
    79. //两种情况
    80. //1.一开始的rightmin 有左子树 把他的右子树给父亲的左边
    81. //2.一开始的rightmin 没有左子树
    82. if (RightMinParent->_left == RightMin)
    83. {
    84. RightMinParent->_left = RightMin->_right;
    85. }
    86. else
    87. {
    88. RightMinParent->_right = RightMin->_right;
    89. }
    90. delete RightMin;
    91. }
    92. //成功删除
    93. return true;
    94. }
    95. }
    96. return false;
    97. }

    5.遍历

    中序遍历按 左子树 根 右子树 的循序走即可

    1. void _InOrder(Node* root)
    2. {
    3. if (root == nullptr)
    4. return;
    5. _InOrder(root->_left);
    6. cout << root->_key << ' ';
    7. _InOrder(root->_right);
    8. }

    三.K模型与KV模型实现

    1.K模型:K模型即只有key作为关键码,结构中只需要存储Key即可,关键码即为需要搜索到
    的值。
    比如:给一个单词word,判断该单词是否拼写正确,具体方式如下

    • 以词库中所有单词集合中的每个单词作为key,构建一棵二叉搜索树
    • 在二叉搜索树中检索该单词是否存在,存在则拼写正确,不存在则拼写错误。

    2.KV模型:每一个关键码key,都有与之对应的值Value,即的键值对。该种方
    式在现实生活中非常常见:

    • 比如英汉词典就是英文与中文的对应关系,通过英文可以快速找到与其对应的中文,英文单词与其对应的中文就构成一种键值对;
    • 再比如统计单词次数,统计成功后,给定单词就可快速找到其出现的次数,单词与其出现次数就是就构成一种键值对。

    K模型模拟实现

    1. namespace key
    2. {
    3. //BS节点
    4. template<class K>
    5. struct BSTreeNode
    6. {
    7. BSTreeNode* _left;
    8. BSTreeNode* _right;
    9. K _key;
    10. BSTreeNode(const K& key)
    11. :_left(nullptr)
    12. , _right(nullptr)
    13. , _key(key)
    14. { }
    15. };
    16. template<class K>
    17. class BSTree
    18. {
    19. typedef BSTreeNode Node;
    20. public:
    21. bool Insert(const K& key)
    22. {
    23. //搜索树不会平衡 一直延伸即可 不会替换
    24. if (_root == nullptr)
    25. {
    26. _root = new Node(key);
    27. return true;
    28. }
    29. //遍历到底部
    30. Node* parent = nullptr;
    31. Node* cur = _root;
    32. while (cur)
    33. {
    34. if (cur->_key < key)
    35. {
    36. parent = cur;
    37. cur = cur->_right;
    38. }
    39. else if (cur->_key > key)
    40. {
    41. parent = cur;
    42. cur = cur->_left;
    43. }
    44. else
    45. {
    46. return false;
    47. }
    48. }
    49. //到底部之后
    50. cur = new Node(key);
    51. if (parent->_key < key)
    52. {
    53. parent->_right = cur;
    54. }
    55. else
    56. {
    57. parent->_left = cur;
    58. }
    59. return true;
    60. }
    61. bool Find(const K& key)
    62. {
    63. Node* cur = _root;
    64. //搜到底部
    65. while (cur)
    66. {
    67. if (cur->_key < key)
    68. {
    69. cur = cur->_right;
    70. }
    71. else if (cur->_key > key)
    72. {
    73. cur = cur->_left;
    74. }
    75. else
    76. {
    77. // 相等了
    78. return true;
    79. }
    80. }
    81. return false;
    82. }
    83. bool Erase(const K& key)
    84. {
    85. //删除有三种情况
    86. //1 删除的节点的左为空
    87. //2 删除的节点的右为空
    88. //3 删除的节点左右都空 ---> 替换删除法
    89. //即找左子树的最右节点(最大值) 或 右子树的最左节点代替即可
    90. //下即 右子树的最左节点
    91. Node* parent = nullptr;
    92. Node* cur = _root;
    93. while (cur)
    94. {
    95. if (cur->_key < key)
    96. {
    97. parent = cur;
    98. cur = cur->_right;
    99. }
    100. else if (cur->_key > key)
    101. {
    102. parent = cur;
    103. cur = cur->_right;
    104. }
    105. else
    106. {
    107. //相等删除
    108. //1 左为空 父亲指向我的右
    109. if (cur->_left == nullptr)
    110. {
    111. if (cur == _root)
    112. {
    113. _root = cur->_right;
    114. }
    115. else
    116. {
    117. // 看它是 父亲的左节点 还是右节点
    118. if (cur == parent->_left)
    119. {
    120. parent->_left = cur->_right;
    121. }
    122. else
    123. {
    124. parent->_right = cur->_right;
    125. }
    126. }
    127. // 终止条件 防止内存泄漏
    128. delete cur;
    129. }
    130. //2 右为空 父亲指向我的左
    131. else if (cur->_right == nullptr)
    132. {
    133. if (cur == _root)
    134. {
    135. _root = cur->_left;
    136. }
    137. else
    138. {
    139. if (cur == parent->_left)
    140. {
    141. parent->_left = cur->_left;
    142. }
    143. else
    144. {
    145. parent->_right = cur->_left;
    146. }
    147. }
    148. delete cur;
    149. }
    150. //3 左右都不为空 找右子树的最小值替换
    151. else
    152. {
    153. Node* RightMinParent = cur;
    154. Node* RightMin = cur->_right;
    155. while (RightMin->_left)
    156. {
    157. RightMinParent = RightMin;
    158. RightMin = RightMin->_left;
    159. }
    160. swap(cur->_key, RightMin->_key);
    161. //两种情况
    162. //1.一开始的rightmin 有左子树 把他的右子树给父亲的左边
    163. //2.一开始的rightmin 没有左子树
    164. if (RightMinParent->_left == RightMin)
    165. {
    166. RightMinParent->_left = RightMin->_right;
    167. }
    168. else
    169. {
    170. RightMinParent->_right = RightMin->_right;
    171. }
    172. delete RightMin;
    173. }
    174. //成功删除
    175. return true;
    176. }
    177. }
    178. return false;
    179. }
    180. void InOrder()
    181. {
    182. _InOrder(_root);
    183. cout << endl;
    184. }
    185. private:
    186. void _InOrder(Node* root)
    187. {
    188. if (root == nullptr)
    189. return;
    190. _InOrder(root->_left);
    191. cout << root->_key << ' ';
    192. _InOrder(root->_right);
    193. }
    194. Node* _root = nullptr;
    195. };
    196. };

    kv模型笔者不再赘述,原理相同,请读者自行理解。

    KV模型模拟实现

    1. namespace key_value
    2. {
    3. //BS节点
    4. template<class K, class V>
    5. struct BSTreeNode
    6. {
    7. BSTreeNode* _left;
    8. BSTreeNode* _right;
    9. K _key;
    10. V _value;
    11. BSTreeNode(const K& key, const V& value)
    12. :_left(nullptr)
    13. , _right(nullptr)
    14. , _key(key)
    15. ,_value(value)
    16. { }
    17. };
    18. template<class K, class V>
    19. class BSTree
    20. {
    21. typedef BSTreeNode Node;
    22. public:
    23. bool Insert(const K& key, const V& value)
    24. {
    25. //搜索树不会平衡 一直延伸即可 不会替换
    26. if (_root == nullptr)
    27. {
    28. _root = new Node(key, value);
    29. return true;
    30. }
    31. //遍历到底部
    32. Node* parent = nullptr;
    33. Node* cur = _root;
    34. while (cur)
    35. {
    36. if (cur->_key < key)
    37. {
    38. parent = cur;
    39. cur = cur->_right;
    40. }
    41. else if (cur->_key > key)
    42. {
    43. parent = cur;
    44. cur = cur->_left;
    45. }
    46. else
    47. {
    48. return false;
    49. }
    50. }
    51. //到底部之后
    52. cur = new Node(key, value);
    53. if (parent->_key < key)
    54. {
    55. parent->_right = cur;
    56. }
    57. else
    58. {
    59. parent->_left = cur;
    60. }
    61. return true;
    62. }
    63. // find 改为返回节点
    64. Node* Find(const K& key)
    65. {
    66. Node* cur = _root;
    67. //搜到底部
    68. while (cur)
    69. {
    70. if (cur->_key < key)
    71. {
    72. cur = cur->_right;
    73. }
    74. else if (cur->_key > key)
    75. {
    76. cur = cur->_left;
    77. }
    78. else
    79. {
    80. // 相等了
    81. return cur;
    82. }
    83. }
    84. // 返回空节点
    85. return cur;
    86. }
    87. bool Erase(const K& key)
    88. {
    89. //删除有三种情况
    90. //1 删除的节点的左为空
    91. //2 删除的节点的右为空
    92. //3 删除的节点左右都空 ---> 替换删除法
    93. //即找左子树的最右节点(最大值) 或 右子树的最左节点代替即可
    94. //下即 右子树的最左节点
    95. Node* parent = nullptr;
    96. Node* cur = _root;
    97. while (cur)
    98. {
    99. if (cur->_key < key)
    100. {
    101. parent = cur;
    102. cur = cur->_right;
    103. }
    104. else if (cur->_key > key)
    105. {
    106. parent = cur;
    107. cur = cur->_right;
    108. }
    109. else
    110. {
    111. //相等删除
    112. //1 左为空 父亲指向我的右
    113. if (cur->_left == nullptr)
    114. {
    115. if (cur == _root)
    116. {
    117. _root = cur->_right;
    118. }
    119. else
    120. {
    121. // 看它是 父亲的左节点 还是右节点
    122. if (cur == parent->_left)
    123. {
    124. parent->_left = cur->_right;
    125. }
    126. else
    127. {
    128. parent->_right = cur->_right;
    129. }
    130. }
    131. // 终止条件 防止内存泄漏
    132. delete cur;
    133. }
    134. //2 右为空 父亲指向我的左
    135. else if (cur->_right == nullptr)
    136. {
    137. if (cur == _root)
    138. {
    139. _root = cur->_left;
    140. }
    141. else
    142. {
    143. if (cur == parent->_left)
    144. {
    145. parent->_left = cur->_left;
    146. }
    147. else
    148. {
    149. parent->_right = cur->_left;
    150. }
    151. }
    152. delete cur;
    153. }
    154. //3 左右都不为空 找右子树的最小值替换
    155. else
    156. {
    157. Node* RightMinParent = cur;
    158. Node* RightMin = cur->_right;
    159. while (RightMin->_left)
    160. {
    161. RightMinParent = RightMin;
    162. RightMin = RightMin->_left;
    163. }
    164. swap(cur->_key, RightMin->_key);
    165. //两种情况
    166. //1.一开始的rightmin 有左子树 把他的右子树给父亲的左边
    167. //2.一开始的rightmin 没有左子树
    168. if (RightMinParent->_left == RightMin)
    169. {
    170. RightMinParent->_left = RightMin->_right;
    171. }
    172. else
    173. {
    174. RightMinParent->_right = RightMin->_right;
    175. }
    176. delete RightMin;
    177. }
    178. //成功删除
    179. return true;
    180. }
    181. }
    182. return false;
    183. }
    184. void InOrder()
    185. {
    186. _InOrder(_root);
    187. cout << endl;
    188. }
    189. private:
    190. void _InOrder(Node* root)
    191. {
    192. if (root == nullptr)
    193. return;
    194. _InOrder(root->_left);
    195. cout << root->_key << ":" << root->_value << endl;
    196. _InOrder(root->_right);
    197. }
    198. Node* _root = nullptr;
    199. };
    200. };

    测试函数

    最后留下三组测试函数:

    k模型

    1. void TestBSTree1()
    2. {
    3. int a[] = { 8, 3, 1, 10, 6, 4, 7, 14, 13 };
    4. BSTree<int> t1;
    5. for (auto e : a)
    6. {
    7. t1.Insert(e);
    8. }
    9. t1.InOrder();
    10. cout << endl;
    11. //t1.Erase(3);
    12. t1.Erase(8);
    13. t1.InOrder();
    14. for (auto e : a)
    15. {
    16. t1.Erase(e);
    17. t1.InOrder();
    18. }
    19. }

    kv模型

    1. void TestBSTree2()
    2. {
    3. BSTree dict;
    4. dict.Insert("string", "字符串");
    5. dict.Insert("left", "左边");
    6. dict.Insert("insert", "插入");
    7. //...
    8. string str;
    9. while (cin >> str)
    10. {
    11. BSTreeNode* ret = dict.Find(str);
    12. if (ret)
    13. {
    14. cout << ret->_value << endl;
    15. }
    16. else
    17. {
    18. cout << "无此单词,请重新输入" << endl;
    19. }
    20. }
    21. }
    22. void TestBSTree3()
    23. {
    24. // 统计次数
    25. string arr[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜",
    26. "苹果", "香蕉", "苹果", "香蕉","苹果","草莓", "苹果","草莓" };
    27. BSTreeint> countTree;
    28. for (const auto& str : arr)
    29. {
    30. auto ret = countTree.Find(str);
    31. if (ret == nullptr)
    32. {
    33. countTree.Insert(str, 1);
    34. }
    35. else
    36. {
    37. ret->_value++;
    38. }
    39. }
    40. countTree.InOrder();
    41. }

  • 相关阅读:
    华为机试真题 C++ 实现【字符串重新排列】【2022.11 Q4新题】
    shiro-反序列化漏洞
    低代码平台技术分享官 | 漫话iGIX前端设计模式
    多维分析预汇总应该怎样做才管用?
    jenkins自动化部署安装
    CDH集群集成外部Flink(改进版-与时俱进)
    TypeScript学习总结(一)
    【PyTorch深度学习项目实战100例】—— 基于RNN实现垃圾邮件辨别 | 第57例
    都说 C++ 没有 GC,RAII: 那么我算个啥?
    线上环境建设,要扛得住真刀真枪的考验
  • 原文地址:https://blog.csdn.net/Xiang2992/article/details/139422643