• 二叉搜索树详讲


    目录

    1. 二叉搜索树

    1.1 二叉搜索树的概念

    1.2 二叉搜索树的操作

     1.2.1 二叉树的查找

     1.2.2 二叉树的插入

     1.2.3  二叉树的删除

    总结


    1. 二叉搜索树

    1.1 二叉搜索树的概念

    二叉搜索树又被称为二叉排序树,它可能是一个空树,也可能是满足一下特征的二叉树:

    • 若它的左子树不为空,则左子树的所有节点上的值都小于根节点上的值
    • 若它的右子树不为空,则右子树的所有节点上的值都大于根节点上的值
    • 它的左右子树也满足上述特征

    这就是一个标准的二叉搜索树

    1.2 二叉搜索树的操作

     1.2.1 二叉树的查找

        实现步骤:

    • 从根节点出发开始查找,如果当前值比目标值要大,则在右子树上进行查找
    • 如果当前值比目标值要小,则在左子树上进行查找
    • 如果当前值等于目标值,则返回true
    • 如果当走到了空节点仍没有找到,则返回false

    让我们来看看代码是如何实现的:

    1. 非递归写法

    1. bool Find(const K& val)
    2. {
    3. //从root开始查找
    4. Node* cur = root;
    5. //如果当cur为空,仍没有找到则返回false
    6. while (cur)
    7. {
    8. //比当前值小则在左子树查找
    9. if (cur->val > val)
    10. {
    11. cur = cur->left;
    12. }
    13. //比当前值大则在右子树查找
    14. else if (cur->val < val)
    15. {
    16. cur = cur->right;
    17. }
    18. //相等则直接返回true
    19. else
    20. {
    21. return true;
    22. }
    23. }
    24. return false;
    25. }

    2. 递归写法

    因为在类内不能直接传参,因此封装了一层函数

    1. bool FindR(const K&val)
    2. {
    3. return _FindR(root,val);
    4. }
    5. bool _FindR(Node*& root, const K& val)
    6. {
    7. //如果root为空则表示没有找到,返回空
    8. if (!root)
    9. {
    10. return false;
    11. }
    12. //如果小于模板值,则在左子树中查找
    13. if (root->val < val)
    14. {
    15. return _FindR(root->right, val);
    16. }
    17. //如果大于模板值,则在右子树中查找
    18. else if (root->val > val)
    19. {
    20. return _FindR(root->left, val);
    21. }
    22. //如果等于目标值,则返回true
    23. else
    24. {
    25. return true;
    26. }
    27. }

    1.2.2 二叉树的插入

    实现步骤:

    • 如果树为空,则将插入的节点直接赋值给root
    • 如果树不为空,则根据二叉搜索树的性质找到插入位置

     如果我们需要插入9这个节点,那么我们的步骤是什么呢?

    1. 从根节点出发开始查找,如果当前值比9要大,则在右子树上进行查找
    2. 如果当前值比9要小,则在左子树上进行查找
    3. 如果当前值等于9,则返回false(当前设置不允许插入相同的元素)
    4. 如果当走到了空节点,则此时就是要插入的位置

    那我们的代码是怎么实现的呢?

    1. 非递归写法

    1. bool insert(const K& val)
    2. {
    3. //如果是空树
    4. if (!root)
    5. {
    6. root = new Node(val);
    7. return true;
    8. }
    9. //如果不是空树
    10. else
    11. {
    12. //需要建立一个父节点来链接插入的节点
    13. Node* parent = nullptr;
    14. Node* cur = root;
    15. while (cur)
    16. {
    17. if (cur->val < val)
    18. {
    19. parent = cur;
    20. cur = cur->right;
    21. }
    22. else if (cur->val > val)
    23. {
    24. parent = cur;
    25. cur = cur->left;
    26. }
    27. else
    28. {
    29. return false;
    30. }
    31. }
    32. //cur走到了空节点,也就是我们要插入的位置
    33. Node* newnode = new Node(val);
    34. //还需要判断插入的是左子树还是右子树
    35. if (parent->val < val)
    36. {
    37. parent->right = newnode;
    38. }
    39. else
    40. {
    41. parent->left = newnode;
    42. }
    43. return true;
    44. }
    45. }

    2. 递归写法

    1. bool InsertR(const K& val)
    2. {
    3. return _InsertR(root, val);
    4. }
    5. bool _InsertR(Node*& root, const K& val)
    6. {
    7. //如果为空,就是插入的位置
    8. if (!root)
    9. {
    10. root = new Node(val);
    11. return true;
    12. }
    13. //如果当前值小于目标值,则在左子树处查找位置
    14. if (root->val < val)
    15. {
    16. return _InsertR(root->right, val);
    17. }
    18. //如果当前值大于目标值,则在右子树处查找位置
    19. else if (root->val > val)
    20. {
    21. return _InsertR(root->left, val);
    22. }
    23. //如果当前值等于目标值,返回false
    24. else
    25. {
    26. return false;
    27. }
    28. }

    与普通的非递归写法不同的是,不需要判断要插入左子树还是右子树

     如果我们以递归写法来插入9这个值,我们会发现9最后要插入的位置就是10这个节点的左边,又因为root的10的左节点的别名,因此我们只需要将root的值赋成9这个节点即可

    1.2.3  二叉树的删除

    实现步骤:

    1. 首先查找元素是否在二叉搜索树中,如果不存在,则返回false,
    2. 否则要删除的结点可能分下面四种情况:
    • 要删除的结点无孩子结点
    • 要删除的结点只有左孩子结点
    • 要删除的结点只有右孩子结点
    • 要删除的结点有左、右孩子结点
       

    而我们可以发现当无孩子结点这种情况也可以归纳在只有左结点或者右结点这个情况中

    那么代码实现如下:

    1. 非递归写法

    1. bool erase(const K& val)
    2. {
    3. Node* cur = root;
    4. Node* parent = nullptr;
    5. while (cur)
    6. {
    7. if (cur->val < val)
    8. {
    9. parent = cur;
    10. cur = cur->right;
    11. }
    12. else if (cur->val > val)
    13. {
    14. parent = cur;
    15. cur = cur->left;
    16. }
    17. //如果找到了目标值
    18. else
    19. {
    20. //记录一下删除的节点
    21. Node* del = cur;
    22. if (!cur->left)
    23. {
    24. if (!parent)
    25. {
    26. cur = cur->right;
    27. }
    28. else
    29. {
    30. if (parent->left == cur)
    31. {
    32. parent->left = cur->right;
    33. }
    34. else
    35. {
    36. parent->right = cur->right;
    37. }
    38. }
    39. delete del;
    40. }
    41. else if (!cur->right)
    42. {
    43. if (!parent)
    44. {
    45. cur = cur->left;
    46. }
    47. else
    48. {
    49. if (parent->left == cur)
    50. {
    51. parent->left = cur->left;
    52. }
    53. else
    54. {
    55. parent->right = cur->left;
    56. }
    57. }
    58. delete del;
    59. }
    60. //左右子树都不为空
    61. else
    62. {
    63. Node* minparent = cur;
    64. Node* minright = cur->right;
    65. //找到右子树的最小值
    66. while (minright->left)
    67. {
    68. minparent = minright;
    69. minright = minright->left;
    70. }
    71. //交换最小值和目标值的大小
    72. swap(cur->val, minright->val);
    73. if (minparent->left == minright)
    74. {
    75. minparent->left = minright->right;
    76. }
    77. else
    78. {
    79. minparent->right = minright->right;
    80. }
    81. //删除替换的节点
    82. delete minright;
    83. }
    84. return true;
    85. }
    86. }
    87. return false;
    88. }

    2. 递归写法

    1. bool EraseR(const K& val)
    2. {
    3. return _EraseR(root, val);
    4. }
    5. bool _EraseR(Node*& root, const K& val)
    6. {
    7. //如果为空,则说明目标值不存在,返回false
    8. if (!root)
    9. {
    10. return false
    11. }
    12. if (root->val < val)
    13. {
    14. return _EraseR(root->right, val);
    15. }
    16. else if (root->val > val)
    17. {
    18. return _EraseR(root->left, val);
    19. }
    20. //找到目标结点
    21. else
    22. {
    23. //如果左子树为空
    24. if (!root->left)
    25. {
    26. root = root->right;
    27. }
    28. else if (!root->right)
    29. {
    30. root = root->left;
    31. }
    32. else
    33. {
    34. Node* minright = root->right;
    35. while (minright->left)
    36. {
    37. minright = minright->left;
    38. }
    39. swap(root->val, minright->val);
    40. return _EraseR(root->right, minright->val);
    41. }
    42. }
    43. }

    总结

    递归写法和非递归写法的区别:

    1. 递归写法在删除和插入操作中不需要去判断目标值为其父值的左子树还是右子树
    2. 递归写法突出的就是代码量少
    3. 如果二叉搜索树不太均匀,递归写法可能会造成栈溢出

  • 相关阅读:
    Datawhale机器学习学习总结22-11-27
    Word处理控件Aspose.Words功能演示:在 Python 中比较两个 Word 文档
    安装对应版本pytorch和torchvision
    云课五分钟-07安装Opera失败-版本不匹配
    SpringBoot--@ModelAttribute--使用/实例
    YOLOv8 来了,快速上手实操
    强制类型转换有哪几种?
    rancher部署nginx服务
    Java内部类详解
    SQLite 3.44.0 发布!
  • 原文地址:https://blog.csdn.net/Rinki123456/article/details/125994420