• 二叉搜索树


    目录

    二叉搜索树概念

    二叉树搜索树的模拟实现

    1.插入 Insert

    2.Erase 删除结点(难点)

    3.InOder (中序遍历)

    4.Find 

    递归实现方式

    完整代码

    总结


    二叉搜索树概念

    其又称二叉排序树、二叉查找树。

    满足以下性质:
    非空左子树的key值小于根结点的key值;

    非空右子树的key值大于根结点的key值;

    左、右子树都是二叉搜索树。

    二叉树搜索树的模拟实现

    1.插入 Insert

     在树中,寻找适合key插入的位置,找到插入返回true ,已有重复key返回false.

    思路:

    比较key 和根结点的_key   如果key大于_key 往根的右子树去找   

    如果key小于_key 往根的左子树去找

    如果树中不存在已有的key 则必定走到空结点 ,链接上key的值即可

    如果树中存在key 结束查找 返回false 

    画图辅助理解

    在树中,插入key为五的结点:

    定义一个cur临时结点

    1--比较cur->_key 与key(下面简称比较)   key小于cur->_key   往左子树中查找 cur=cur->left

    2--比较   key>cur->_key   往右子树查找   cur=cur->right

    3--比较    key_key  往左子树查找  cur=cur->left

    4--比较     key>cur->_key    往右子树查找   cur=cur->right  

    5--cur为空  找到可以链接的位置 链接上结点

     以下有俩点需要注意:

    1.如果树为空 ,则需要先创建树

    2.链接结点,需要保存cur的父亲

    下面代码演示

    1. bool Insert(const K& key)
    2. {
    3. if (_root == nullptr)
    4. {
    5. _root = new Node(key);
    6. return true;
    7. }
    8. //找到适合插入的位置
    9. Node* cur = _root;
    10. Node* parent = nullptr;
    11. while (cur)
    12. {
    13. parent = cur; //更新parent
    14. if (cur->_key > key)
    15. {
    16. cur = cur->_left;
    17. }
    18. else if (cur->_key < key) //更新parent
    19. {
    20. cur = cur->_right;
    21. }
    22. else
    23. return false;
    24. }
    25. //准备插入 插入点一定是空结点
    26. cur = new Node(key);
    27. //判断插入父亲的左还是右
    28. if (parent->_key > key) parent->_left = cur;
    29. else parent->_right = cur;
    30. //插入成功
    31. return true;
    32. }

    2.Erase 删除结点(难点)

    关于二叉搜索树的删除相对不容易,需要考虑多种情况,下面就来详细解释。

    上图列举四种具有代表性的删除结点

    分别是:

    key结点的左和右子树都为空     key的左子树为空  key的右子树为空  key的左和右子树都不为空

    对于左子树右子树都为空 我们可以归于左子树或者右子树为空的一种中

    下文 只需要对 2  3  4三种方式进行剖析

    这三种方式都必须先找到cur

    找cur的方式与inser相同 ,在此不做介绍

    1)cur的左为空

    --1--判断cur是否为根结点   是则将cur->left赋值给_root 

    --2--判断cur是父亲的左还是右 ,链接cur->right

    2)cur的右为空

    该方式与1)类似 

    需要判断cur是否为root结点 

    不为root则需要判断是父亲的左还是右结点

    在此不做过多的介绍

    3)cur的左和右子树都不为空

    如果我们简单的删除cur ,则破坏搜索二叉树,因此我们引入替换法

    在cur的子树中,找到一个合适的值替换,使树仍为搜索二叉树

    找替换结点的方式:
    左子树中,找最大    

    右子树中,找最小

    如果我们要删除key为3的结点 因为cur的结点有左子树和右子树 所以需要找替换 

    我们找cur右子树的最小值 即右子树最左边的元素  4  

    交换 3 和 4的值  然后删除 rightmin的结点  

    要删除rightmin结点 需要注意该结点的右子树需要被链接到cur的父亲上  

    代码剖析

    1. bool Erase(const K& key)
    2. {
    3. //1.k的左为空
    4. //2.k的右为空
    5. //3.k左右都存在,替换删除
    6. Node* cur = _root;
    7. Node* parent = nullptr;
    8. while (cur)
    9. {
    10. parent = cur; //更新父亲
    11. if (cur->_key < key) cur = cur->_right;
    12. else if (cur->_key > key) cur = cur->_left;
    13. //找到了
    14. else
    15. {
    16. //1.左为空
    17. if (cur->_left == nullptr)
    18. {
    19. //删除点为根
    20. if (cur == _root) _root = cur->_right;
    21. //判断是父亲的左、右链接
    22. if (parent->_right == cur) //右链接
    23. {
    24. parent->_right = cur->_right;
    25. }
    26. else
    27. {
    28. parent->_left = cur->_right;
    29. }
    30. }
    31. //2.右为空
    32. else if (cur->_right == nullptr)
    33. {
    34. //删除点为根
    35. if (cur == _root) _root = cur->_left;
    36. //判断是父亲的左、右链接
    37. if (parent->_right == cur) //右链接
    38. {
    39. parent->_right = cur->_left;
    40. }
    41. else
    42. {
    43. parent->_left = cur->_left;
    44. }
    45. }
    46. //3.替换
    47. else
    48. {
    49. //找右数的最小 右子树的最左边元素
    50. Node* pminright = cur;
    51. Node* minright = cur->_right;
    52. //找到左树空就找到了
    53. while (minright->_left)
    54. {
    55. pminright = minright; //保存父亲结点
    56. minright = minright->_right;
    57. }
    58. cur->_key = minright->_key; //交换
    59. //判断是父亲的左子树还是右子树要被删除
    60. if (pminright->_left ==minright )
    61. pminright->_left = minright->_right;
    62. else
    63. pminright->_right= minright->_right;
    64. }
    65. }
    66. }
    67. delete(cur);
    68. return true;
    69. }

     

    3.InOder (中序遍历)

    规则:左子树 ——根结点——右子树

     

    遍历顺序  1  3  4  6  7  8  10  13  14 

    搜索二叉树的中序遍历可以将key按照大小遍历出

    对于一个类中,我们调用InOder函数 是无法传递私有变量_root 

    因为写一个子函数提供遍历

    1. void InOrder()
    2. {
    3. _InOrder(_root);
    4. }
    5. void _InOrder(Node* root)
    6. {
    7. if (root == nullptr) return;
    8. _InOrder(root->_left);
    9. cout << root->_key << ":" << root->_val << endl;
    10. _InOrder(root->_right);
    11. }

    4.Find 

    查找树中是否存在值为key的函数,找到返回结点指针,找不到返回空

    思路是从根结点开始 ,如果key 小于cur->_key 那么往左子树查找,大于则往右子树查找

    1. Node* Find(const K& key)
    2. {
    3. Node* cur = _root;
    4. while (cur)
    5. {
    6. if (cur->_key < key)
    7. cur = cur->_right;
    8. else if (cur->_key > key)
    9. cur = cur->_left;
    10. else
    11. return cur;
    12. }
    13. return nullptr; //没找到,返回空指针
    14. }

    递归实现方式

    关于递归的实现,其思路较简单 方法与循坏类似

    写代码有一条不成文的准则:

    能用循坏,尽量不用递归,避免栈溢出;

    对于二叉搜索树的递归 需要注意:
    --1--递归要传入结点参数,要写子函数调用

    --2--cur的链接,不需要保存父亲结点 引用cur的指针  

    完整代码
     

    展示key_value模型应用的代码

    1. #include<iostream>
    2. #include<string>
    3. using namespace std;
    4. namespace key_val
    5. {
    6. template<class K, class V> //keyvalue
    7. //node
    8. struct BSTreeNode
    9. {
    10. BSTreeNode<K, V>* _left;
    11. BSTreeNode<K, V>* _right;
    12. K _key;
    13. V _val;
    14. BSTreeNode(const K& key, const V& val)
    15. :_left(nullptr),
    16. _right(nullptr),
    17. _key(key),
    18. _val(val)
    19. {}
    20. };
    21. template<class K,class V>
    22. class BSTree
    23. {
    24. typedef BSTreeNode<K, V> Node;
    25. public:
    26. bool Insert(const K& key, const V& value)
    27. {
    28. if (_root == nullptr)
    29. {
    30. _root = new Node(key, value);
    31. return true;
    32. }
    33. //找到适合插入的位置
    34. Node* cur = _root;
    35. Node* parent = nullptr;
    36. while (cur)
    37. {
    38. parent = cur; //更新parent
    39. if (cur->_key > key)
    40. {
    41. cur = cur->_left;
    42. }
    43. else if (cur->_key < key) //更新parent
    44. {
    45. cur = cur->_right;
    46. }
    47. else
    48. return false;
    49. }
    50. //准备插入 插入点一定是空结点
    51. cur = new Node(key, value);
    52. //判断插入父亲的左还是右
    53. if (parent->_key > key) parent->_left = cur;
    54. else parent->_right = cur;
    55. //插入成功
    56. return true;
    57. }
    58. Node* Find(const K& key)
    59. {
    60. Node* cur = _root;
    61. while (cur)
    62. {
    63. if (cur->_key < key)
    64. cur = cur->_right;
    65. else if (cur->_key > key)
    66. cur = cur->_left;
    67. else
    68. return cur;
    69. }
    70. return nullptr; //没找到,返回空指针
    71. }
    72. bool Erase(const K& key)
    73. {
    74. //1.k的左为空
    75. //2.k的右为空
    76. //3.k左右都存在,替换删除
    77. Node* cur = _root;
    78. Node* parent = nullptr;
    79. while (cur)
    80. {
    81. parent = cur; //更新父亲
    82. if (cur->_key < key) cur = cur->_right;
    83. else if (cur->_key > key) cur = cur->_left;
    84. //找到了
    85. else
    86. {
    87. //1.左为空
    88. if (cur->_left == nullptr)
    89. {
    90. //删除点为根
    91. if (cur == _root) _root = cur->_right;
    92. //判断是父亲的左、右链接
    93. if (parent->_right == cur) //右链接
    94. {
    95. parent->_right = cur->_right;
    96. }
    97. else
    98. {
    99. parent->_left = cur->_right;
    100. }
    101. }
    102. //2.右为空
    103. else if (cur->_right == nullptr)
    104. {
    105. //删除点为根
    106. if (cur == _root) _root = cur->_left;
    107. //判断是父亲的左、右链接
    108. if (parent->_right == cur) //右链接
    109. {
    110. parent->_right = cur->_left;
    111. }
    112. else
    113. {
    114. parent->_left = cur->_left;
    115. }
    116. }
    117. //3.替换
    118. else
    119. {
    120. //找右数的最小 右子树的最左边元素
    121. Node* pminright = cur;
    122. Node* minright = cur->_right;
    123. //找到左树空就找到了
    124. while (minright->_left)
    125. {
    126. pminright = minright; //保存父亲结点
    127. minright = minright->_right;
    128. }
    129. cur->_key = minright->_key; //交换
    130. //判断是父亲的左子树还是右子树要被删除
    131. if (pminright->_left ==minright )
    132. pminright->_left = minright->_right;
    133. else
    134. pminright->_right= minright->_right;
    135. }
    136. }
    137. }
    138. delete(cur);
    139. return true;
    140. }
    141. void InOrder()
    142. {
    143. _InOrder(_root);
    144. }
    145. void _InOrder(Node* root)
    146. {
    147. if (root == nullptr) return;
    148. _InOrder(root->_left);
    149. cout << root->_key << ":" << root->_val << endl;
    150. _InOrder(root->_right);
    151. }
    152. private:
    153. Node* _root = nullptr;
    154. };

    总结

    本文重点学习搜索二叉树的插入和删除,它具有快速查找的功能,在查找具有O(logN)-O(N)的效率,关于搜索二叉树的实现,有较多的细节,也要仔细甄别。

  • 相关阅读:
    云计算(Docker)
    Json简介与基本使用
    Go语言中的defer关键字
    转发与重定向的使用
    tar 和 zip 打包压缩命令
    Vue生命周期
    481、神奇字符串
    系统打印服务已关闭,竟然是它的问题!
    【oncmdmsg 鼠标】2023/8/19 上午9:50:14
    阿里CTO程立:阿里巴巴的开源历程、理念和实践
  • 原文地址:https://blog.csdn.net/m0_73299809/article/details/134066527