• C++ 二叉搜索树BinarySearchTree


    目录

    一.概念

    二.分部模拟实现(K模型)

    1.二叉树结点

    2.二叉搜索树构建

    3.查找(非递归)

    4.插入(非递归)

    5.删除(非递归)

    6.查找(递归)

     7.插入(递归)

    8.删除(递归)

    三.模拟实现总代码(K模型)

    四.模拟实现(KV模型)

    五.二叉搜索树的应用

    1.K模型

    2.KV模型

    六.二叉搜索树的性能


    前言:二叉搜索树是为学习map和set做的铺垫,了解了二叉搜索树的特性,有助于更好的理解map和set。

    一.概念

            二叉搜索树有称二叉排序树,它可以是一颗空树,或者是具有以下行者的二叉树:

    ① 若它的左子树不为空,则左子树上所有结点的值都小于根结点的值

    ② 若它的右子树不为空,则右子树上所有结点的值都大于根结点的值

    ③ 它的左右子树也分别为二叉搜索树

    二.分部模拟实现(K模型)

    1.二叉树结点

            _key为该二叉树值,_left为左结点,_right为右结点。

    1. template<class K>
    2. struct BSTreeNode
    3. {
    4. BSTreeNode* _left;
    5. BSTreeNode* _right;
    6. K _key;
    7. BSTreeNode(const K& key)
    8. : _left(nullptr)
    9. , _right(nullptr)
    10. , _key(key)
    11. {}
    12. };

    2.二叉搜索树构建

            实现二叉搜索树的构造、拷贝构造、赋值重载、析构函数。

            拷贝构造是一定要实现的,通过子函数CopyTree通过前序遍历进行依次构造结点,而构造函数用编译器默认生成的就够了,但是因为实现了拷贝构造,编译器就不会生成默认的构造,因此使用C++11的关键字default来强制编译器生成构造。

            赋值重载是现代写法,析构函数也是通过子函数DestoryTree,通过递归依次释放结点。

    1. template<class K>
    2. class BSTree
    3. {
    4. typedef BSTreeNode Node;
    5. private:
    6. void DestroyTree(Node* root)
    7. {
    8. if (root == nullptr)
    9. {
    10. return;
    11. }
    12. DestroyTree(root->_left);
    13. DestroyTree(root->_right);
    14. delete root;
    15. }
    16. Node* CopyTree(Node* root)
    17. {
    18. if (root == nullptr)
    19. {
    20. return nullptr;
    21. }
    22. Node* copyNode = new Node(root->_key);
    23. CopyTree(root->_left);
    24. CopyTree(root->_right);
    25. return copyNode;
    26. }
    27. public:
    28. // C++11,强制让编译器自己生成构造
    29. BSTree() = default;
    30. BSTree(const BSTree& t)
    31. {
    32. _root = CopyTree(t._root);
    33. }
    34. BSTree& operator=(BSTree t)
    35. {
    36. swap(_root, t._root);
    37. return *this;
    38. }
    39. ~BSTree()
    40. {
    41. DestroyTree(_root);
    42. _root = nullptr;
    43. }
    44. private:
    45. Node* _root = nullptr;
    46. };

    3.查找(非递归)

            遍历寻找,如果要查找的key值大于该结点的key值,就往该结点的右子树走,如果要查找的key值小于该结点的key值,就往该结点的左子树走。

    1. bool Find(const K& key)
    2. {
    3. Node* cur = _root;
    4. while (cur)
    5. {
    6. if (cur->_key < key)
    7. {
    8. cur = cur->_right;
    9. }
    10. else if(cur->_key > key)
    11. {
    12. cur = cur->_left;
    13. }
    14. else
    15. {
    16. return true;
    17. }
    18. }
    19. return false;
    20. }

    4.插入(非递归)

            找到位置就插入进去。,如果是空树,直接插入作为根结点。

            需要定义一个parent变量,这样在找到插入的位置后,可以让该插入结点的父节点parent链接上该新插入的结点。

            前面与查找类似,如果要查找的key值大于该结点的key值,就往该结点的右子树走,如果要查找的key值小于该结点的key值,就往该结点的左子树走,如果相等了,就不能插入了,直接返回false。

            找到要插入的位置之后,new出要插入的新结点,然后判断这个结点的值是大于parent还是小于parent,如果大于就放在右边,如果小于就放在左边。

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

    5.删除(非递归)

            删除需要考虑的比较多。

            首先也是需要定义一个parent变量,这样删除掉结点后,可以让其parent连接到删除结点的孩子结点。

            接着也是与查找类似,在找到之后,就要分情况讨论。

            第一种情况是要删除的结点的左孩子为空:这种情况首先要注意要删除的结点是根结点的情况,因为是根结点,所有parent是空,因此要单独写,让新的根结点变为原根结点的右结点。另外就是正常情况,要判断删除的这个结点的值是parent的左孩子结点还是右孩子结点,如果是左孩子结点,就让其parent的左孩子结点链接上该结点的右孩子结点,如果是右孩子结点,就让parent的右孩子结点链接上该结点的右孩子结点。

            第二种情况是要删除的结点的右孩子为空:这种情况与第一种情况类似,也是要注意特殊情况,但是这种情况是让新的根结点变为原根结点的左结点。然后再判断删除的这个结点时parent的左孩子结点还是右孩子结点,如果是左孩子结点,就让parent的左孩子结点链接上该结点的左孩子结点,如果是右孩子结点,就让parent的右孩子结点链接上该结点的左孩子结点。

            第三种情况是要删除的结点的左右孩子都不为空:出现这种情况时,不能直接删除掉需要删除的结点,而是需要替代删除,保证二叉搜索树的特性。这种替代有两种方法(第一种是用该结点的左子树的最大结点替代,第二种是用该结点的右子树的最小结点替代),这里我们采用第二种方法。首先需要定义两个变量,一个是用来找到这个去替代的结点minRight,另一个是该替代的结点的父节点minParent,这里要注意定义minParent时,要让其等于cur,如果让其等于nullptr,那么如果该结点的左结点没有结点了就会导致minParent一直为空,出现野指针问题。定义了之后,通过while循环找到该结点右子树的最小结点(即该结点右子树的最左的结点),接下来可以交换要删除结点的值和替代结点的值,也可以直接赋值,因为替代结点即将被删除,是否交换并不重要。接下来再判断替代结点时minParent的左孩子结点还是右孩子结点,如果是左孩子结点,就让minParent的左孩子节点链接上替代结点的右孩子节点,如果是右孩子结点,就让minParent的右孩子结点链接上替代结点的右孩子结点。

    1. bool Erase(const K& key)
    2. {
    3. Node* parent = nullptr;
    4. Node* cur = _root;
    5. while (cur)
    6. {
    7. if (cur->_key < key)
    8. {
    9. parent = cur;
    10. cur = cur->_right;
    11. }
    12. else if (cur->_key > key)
    13. {
    14. parent = cur;
    15. cur = cur->_left;
    16. }
    17. // 找到要删除的结点了
    18. else
    19. {
    20. // 左孩子为空
    21. if (cur->_left == nullptr)
    22. {
    23. if (cur == _root)
    24. {
    25. _root = cur->_right;
    26. }
    27. else
    28. {
    29. if (cur == parent->_left)
    30. {
    31. parent->_left = cur->_right;
    32. }
    33. else
    34. {
    35. parent->_right = cur->_right;
    36. }
    37. }
    38. delete cur;
    39. }
    40. // 右孩子为空
    41. else if (cur->_right == nullptr)
    42. {
    43. if (cur == _root)
    44. {
    45. _root = cur->_left;
    46. }
    47. else
    48. {
    49. if (cur == parent->_left)
    50. {
    51. parent->_left = cur->_left;
    52. }
    53. else
    54. {
    55. parent->_right = cur->_left;
    56. }
    57. }
    58. delete cur;
    59. }
    60. // 两个孩子都不为空
    61. else
    62. {
    63. // 用右子树的最小结点去替代
    64. Node* minParent = cur;
    65. Node* minRight = cur->_right;
    66. while (minRight->_left)
    67. {
    68. minParent = minRight;
    69. minRight = minRight->_left;
    70. }
    71. swap(minRight->_key, cur->_key);
    72. if (minParent->_left == minRight)
    73. {
    74. minParent->_left = minRight->_right;
    75. }
    76. else
    77. {
    78. minParent->_right = minRight->_right;
    79. }
    80. delete minRight;
    81. }
    82. return true;
    83. }
    84. }
    85. return false;
    86. }

    6.查找(递归)

            递归写法就采用子函数的方法,查找、插入、删除都是如此。

             如果要查找的值比此结点大,就递归调用该结点的右子树结点,如果要查找的值比此结点小,就递归调用该结点的左子树结点,如果相等了就返回true,遇到空结点就返回false。

    1. bool FindR(const K& key)
    2. {
    3. return _FindR(_root, key);
    4. }
    5. private:
    6. bool _FindR(Node* root, const K& key)
    7. {
    8. if (root == nullptr)
    9. {
    10. return false;
    11. }
    12. if (root->_key < key)
    13. {
    14. return _FindR(root->_right, key);
    15. }
    16. else if (root->_key > key)
    17. {
    18. return _FindR(root->_left, key);
    19. }
    20. else
    21. {
    22. return true;
    23. }
    24. }

     7.插入(递归)

            与递归查找法类似,只是插入是遇到空会创建插入结点,返回true,相等时返回false。

    1. bool InsertR(const K& key)
    2. {
    3. return _InsertR(_root, key);
    4. }
    5. private:
    6. bool _InsertR(Node* root, const K& key)
    7. {
    8. if (root == nullptr)
    9. {
    10. root = new Node(key);
    11. return true;
    12. }
    13. if (root->_key < key)
    14. {
    15. return _InsertR(root->_right, key);
    16. }
    17. else if (root->_key > key)
    18. {
    19. return _InsertR(root->_left, key);
    20. }
    21. else
    22. {
    23. return false;
    24. }
    25. }

    8.删除(递归)

            前面就是查找,找到之后,也是要分三种情况讨论。

            这里我们注意形参root的变量类型是Node*&,是指针的引用。

            第一种左为空:因为是指针引用,因此直接让root等于root的右子树结点,因为是引用,所以如果上一个递归调用的是root->_right那么这个root就是上一个root->_right的别名,因此就相当于上一个的root->_right = 当前的root->_right,这样就相当于删除掉了这个root。

            第二种右为空:与第一种类似,root = root->_left,把_right改为_left即可。

            第三种左右都不为空:这个就只需要定义一个替代变量minRight即可,然后通过while找到这个变量,这个一定要交换,不能赋值更改,因为接下来是要使用递归的。要注意这里递归的是root的右子树结点(不能是当前结点,原因是在交换之后,该结点已经不满足二叉搜索树的特性了,用该结点就会出错),其右子树结点及其右子树所有结点都是满足二叉搜索树的特性的,这样递归之后,就满足上面两种情况之一,就可以完成删除了。

    1. bool EraseR(const K& key)
    2. {
    3. return _EraseR(_root, key);
    4. }
    5. private:
    6. bool _EraseR(Node*& root, const K& key)
    7. {
    8. if (root == nullptr)
    9. {
    10. return false;
    11. }
    12. if (root->_key < key)
    13. {
    14. return _EraseR(root->_right, key);
    15. }
    16. else if (root->_key > key)
    17. {
    18. return _EraseR(root->_left, key);
    19. }
    20. else
    21. {
    22. Node* del = root;
    23. // 删除
    24. // 左为空
    25. if (root->_left == nullptr)
    26. {
    27. root = root->_right;
    28. }
    29. // 右为空
    30. else if (root->_right == nullptr)
    31. {
    32. root = root->_left;
    33. }
    34. // 都不为空
    35. else
    36. {
    37. Node* minRight = root->_right;
    38. while (minRight->_left)
    39. {
    40. minRight = minRight->_left;
    41. }
    42. swap(root->_key, minRight->_key);
    43. return _EraseR(root->_right, key);
    44. }
    45. delete del;
    46. return true;
    47. }
    48. }

    三.模拟实现总代码(K模型)

    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. void DestroyTree(Node* root)
    21. {
    22. if (root == nullptr)
    23. {
    24. return;
    25. }
    26. DestroyTree(root->_left);
    27. DestroyTree(root->_right);
    28. delete root;
    29. }
    30. Node* CopyTree(Node* root)
    31. {
    32. if (root == nullptr)
    33. {
    34. return nullptr;
    35. }
    36. Node* copyNode = new Node(root->_key);
    37. CopyTree(root->_left);
    38. CopyTree(root->_right);
    39. return copyNode;
    40. }
    41. public:
    42. // C++11,强制让编译器自己生成构造
    43. BSTree() = default;
    44. BSTree(const BSTree& t)
    45. {
    46. _root = CopyTree(t._root);
    47. }
    48. BSTree& operator=(BSTree t)
    49. {
    50. swap(_root, t._root);
    51. return *this;
    52. }
    53. ~BSTree()
    54. {
    55. DestroyTree(_root);
    56. _root = nullptr;
    57. }
    58. bool Insert(const K& key)
    59. {
    60. if (_root == nullptr)
    61. {
    62. _root = new Node(key);
    63. return true;
    64. }
    65. Node* parent = nullptr;
    66. Node* cur = _root;
    67. while (cur)
    68. {
    69. if (cur->_key < key)
    70. {
    71. parent = cur;
    72. cur = cur->_right;
    73. }
    74. else if (cur->_key > key)
    75. {
    76. parent = cur;
    77. cur = cur->_left;
    78. }
    79. else
    80. {
    81. return false;
    82. }
    83. }
    84. cur = new Node(key);
    85. if (parent->_key < key)
    86. {
    87. parent->_right = cur;
    88. }
    89. else
    90. {
    91. parent->_left = cur;
    92. }
    93. return true;
    94. }
    95. // const Node* Find(const K& key)
    96. bool Find(const K& key)
    97. {
    98. Node* cur = _root;
    99. while (cur)
    100. {
    101. if (cur->_key < key)
    102. {
    103. cur = cur->_right;
    104. }
    105. else if (cur->_key > key)
    106. {
    107. cur = cur->_left;
    108. }
    109. else
    110. {
    111. return true;
    112. }
    113. }
    114. return false;
    115. }
    116. bool Erase(const K& key)
    117. {
    118. Node* parent = nullptr;
    119. Node* cur = _root;
    120. while (cur)
    121. {
    122. if (cur->_key < key)
    123. {
    124. parent = cur;
    125. cur = cur->_right;
    126. }
    127. else if (cur->_key > key)
    128. {
    129. parent = cur;
    130. cur = cur->_left;
    131. }
    132. // 找到要删除的结点了
    133. else
    134. {
    135. // 左孩子为空
    136. if (cur->_left == nullptr)
    137. {
    138. if (cur == _root)
    139. {
    140. _root = cur->_right;
    141. }
    142. else
    143. {
    144. if (cur == parent->_left)
    145. {
    146. parent->_left = cur->_right;
    147. }
    148. else
    149. {
    150. parent->_right = cur->_right;
    151. }
    152. }
    153. delete cur;
    154. }
    155. // 右孩子为空
    156. else if (cur->_right == nullptr)
    157. {
    158. if (cur == _root)
    159. {
    160. _root = cur->_left;
    161. }
    162. else
    163. {
    164. if (cur == parent->_left)
    165. {
    166. parent->_left = cur->_left;
    167. }
    168. else
    169. {
    170. parent->_right = cur->_left;
    171. }
    172. }
    173. delete cur;
    174. }
    175. // 两个孩子都不为空
    176. else
    177. {
    178. // 用右子树的最小结点去替代
    179. Node* minParent = cur;
    180. Node* minRight = cur->_right;
    181. while (minRight->_left)
    182. {
    183. minParent = minRight;
    184. minRight = minRight->_left;
    185. }
    186. swap(minRight->_key, cur->_key);
    187. if (minParent->_left == minRight)
    188. {
    189. minParent->_left = minRight->_right;
    190. }
    191. else
    192. {
    193. minParent->_right = minRight->_right;
    194. }
    195. delete minRight;
    196. }
    197. return true;
    198. }
    199. }
    200. return false;
    201. }
    202. // 递归写法:
    203. bool FindR(const K& key)
    204. {
    205. return _FindR(_root, key);
    206. }
    207. bool InsertR(const K& key)
    208. {
    209. return _InsertR(_root, key);
    210. }
    211. bool EraseR(const K& key)
    212. {
    213. return _EraseR(_root, key);
    214. }
    215. void InOrder()
    216. {
    217. _InOrder(_root);
    218. }
    219. private:
    220. bool _FindR(Node* root, const K& key)
    221. {
    222. if (root == nullptr)
    223. {
    224. return false;
    225. }
    226. if (root->_key < key)
    227. {
    228. return _FindR(root->_right, key);
    229. }
    230. else if (root->_key > key)
    231. {
    232. return _FindR(root->_left, key);
    233. }
    234. else
    235. {
    236. return true;
    237. }
    238. }
    239. bool _InsertR(Node* root, const K& key)
    240. {
    241. if (root == nullptr)
    242. {
    243. root = new Node(key);
    244. return true;
    245. }
    246. if (root->_key < key)
    247. {
    248. return _InsertR(root->_right, key);
    249. }
    250. else if (root->_key > key)
    251. {
    252. return _InsertR(root->_left, key);
    253. }
    254. else
    255. {
    256. return false;
    257. }
    258. }
    259. bool _EraseR(Node*& root, const K& key)
    260. {
    261. if (root == nullptr)
    262. {
    263. return false;
    264. }
    265. if (root->_key < key)
    266. {
    267. return _EraseR(root->_right, key);
    268. }
    269. else if (root->_key > key)
    270. {
    271. return _EraseR(root->_left, key);
    272. }
    273. else
    274. {
    275. Node* del = root;
    276. // 删除
    277. // 左为空
    278. if (root->_left == nullptr)
    279. {
    280. root = root->_right;
    281. }
    282. // 右为空
    283. else if (root->_right == nullptr)
    284. {
    285. root = root->_left;
    286. }
    287. // 都不为空
    288. else
    289. {
    290. Node* minRight = root->_right;
    291. while (minRight->_left)
    292. {
    293. minRight = minRight->_left;
    294. }
    295. swap(root->_key, minRight->_key);
    296. return _EraseR(root->_right, key);
    297. }
    298. delete del;
    299. return true;
    300. }
    301. }
    302. Node* _root = nullptr;
    303. void _InOrder(Node* root)
    304. {
    305. if (root == nullptr)
    306. {
    307. return;
    308. }
    309. _InOrder(root->_left);
    310. cout << root->_key << " ";
    311. _InOrder(root->_right);
    312. }
    313. };
    314. }

    四.模拟实现(KV模型)

            这个和K模型的很类似,就大致实现一下,这里用修改的是递归版本的。

            只需要增加一下value变量,增加一下模板,注意key加const。

    1. namespace key_value
    2. {
    3. template<class K, class V>
    4. struct BSTreeNode
    5. {
    6. BSTreeNode* _left;
    7. BSTreeNode* _right;
    8. const K _key;
    9. V _value;
    10. BSTreeNode(const K& key, const V& value)
    11. :_left(nullptr)
    12. , _right(nullptr)
    13. , _key(key)
    14. , _value(value)
    15. {}
    16. };
    17. template<class K, class V>
    18. class BSTree
    19. {
    20. typedef BSTreeNode Node;
    21. public:
    22. void InOrder()
    23. {
    24. _InOrder(_root);
    25. cout << endl;
    26. }
    27. Node* FindR(const K& key)
    28. {
    29. return _FindR(_root, key);
    30. }
    31. bool InsertR(const K& key, const V& value)
    32. {
    33. return _InsertR(_root, key, value);
    34. }
    35. bool EraseR(const K& key)
    36. {
    37. return _EraseR(_root, key);
    38. }
    39. private:
    40. bool _EraseR(Node*& root, const K& key)
    41. {
    42. if (root == nullptr)
    43. return false;
    44. if (root->_key < key)
    45. {
    46. return _EraseR(root->_right, key);
    47. }
    48. else if (root->_key > key)
    49. {
    50. return _EraseR(root->_left, key);
    51. }
    52. else
    53. {
    54. Node* del = root;
    55. // 删除
    56. if (root->_left == nullptr)
    57. {
    58. root = root->_right;
    59. }
    60. else if (root->_right == nullptr)
    61. {
    62. root = root->_left;
    63. }
    64. else
    65. {
    66. Node* minRight = root->_right;
    67. while (minRight->_left)
    68. {
    69. minRight = minRight->_left;
    70. }
    71. swap(root->_key, minRight->_key);
    72. return _EraseR(root->_right, key);
    73. }
    74. delete del;
    75. return true;
    76. }
    77. }
    78. bool _InsertR(Node*& root, const K& key, const V& value)
    79. {
    80. if (root == nullptr)
    81. {
    82. root = new Node(key, value);
    83. return true;
    84. }
    85. if (root->_key < key)
    86. return _InsertR(root->_right, key, value);
    87. else if (root->_key > key)
    88. return _InsertR(root->_left, key, value);
    89. else
    90. return false;
    91. }
    92. Node* _FindR(Node* root, const K& key)
    93. {
    94. if (root == nullptr)
    95. return nullptr;
    96. if (root->_key < key)
    97. {
    98. return _FindR(root->_right, key);
    99. }
    100. else if (root->_key > key)
    101. {
    102. return _FindR(root->_left, key);
    103. }
    104. else
    105. {
    106. return root;
    107. }
    108. }
    109. void _InOrder(Node* root)
    110. {
    111. if (root == nullptr)
    112. return;
    113. _InOrder(root->_left);
    114. cout << root->_key << ":" << root->_value << endl;
    115. _InOrder(root->_right);
    116. }
    117. private:
    118. Node* _root = nullptr;
    119. };
    120. }

    五.二叉搜索树的应用

    1.K模型

            K模型只有key作为关键码,结构中只需要存储key即可,关键码即为需要搜索到的值。

    例子:判断单词是否拼写正确

            用词库中的单词集合中的每个单词作为key,构建二叉搜索树。

    2.KV模型

            每一个关键码key,都有与之对应的值value,即的键值对

    例子:①英汉词典,中英文的对应关系, 通过英文可以快速找到对应的中文。

               ②统计单词的次数,单词与其出现次数构成键值对。

    六.二叉搜索树的性能

    对于相同的元素,如果key插入的次序不同,可能会得到不同结果的二叉搜索树。

            这里要注意二叉搜索树的效率是O(logN),而不是O(log2N)  。

            最优情况下,二叉搜索树为完全二叉树(或者接近完全二叉树),其比较次数是log2N。

            最差情况下,二叉搜索树退化为单支树(或者类似单支),其比较次数为logN

            因此,我们接下来要学习平衡二叉搜索树(AVL树、红黑树),这个就使二叉搜索树趋近于完全二叉树,尽可能保证搜索效率。

  • 相关阅读:
    【R包开发:入门】 简介+ 包的结构
    对数据进行切片操作slice()函数
    【负荷预测】布谷鸟(CS)算法优化BP神经网络的负荷及天气预测(Matlab代码实现)
    Uber:Java中的不稳定单元测试处理
    从应用访问Pod元数据-DownwardApi的应用
    第二证券:产业资本真金白银传递市场信心
    浅谈AI人体姿态识别技术的先进性及安防视频监控应用场景
    C Primer Plus(6) 中文版 第6章 C控制语句:循环 6.7 逗号运算符
    MockJs
    python-json校验-jsonpath
  • 原文地址:https://blog.csdn.net/qq_60750110/article/details/125968915