• 理解实现搜索二叉树


    目录

    性质

    实现

    结点和树的构造

    插入

    中序遍历

    二叉搜索树的查找

    递归查找

    删除

    1.缺少孩子时

    2.满孩子时:

    拷贝构造

    析构函数--销毁树

     赋值

    插入的递归写法

    删除的递归写法


    性质

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


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


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

    实现

    结点和树的构造

    使用模板参数来替换内置类型,结点有左右子树;

    初始化列表初始化结点;

    构造树时直接头结点置空;

    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. };
    13. template <class k>
    14. class BSTree
    15. {
    16. typedef BSTreeNode Node;
    17. public:
    18. private:
    19. Node* _root=nullptr;
    20. };

    插入

    插入一个值,必须要确保比结点小的,存在左树;比结点大的,存在右树。

    插入结点时,要连接新结点和其父亲结点,那么要注意保留父亲结点

    cur:来找结点将要插入的位置

    parent:保留父亲结点,方便连接

    1. bool Insert(const k& key)
    2. {
    3. //空树时
    4. if (_root==nullptr)
    5. {
    6. _root = new Node(key);
    7. return true;
    8. }
    9. //刚开始父亲就等于根节点
    10. //cur--确定插入结点位置
    11. Node* parent = _root;
    12. Node* cur = _root;
    13. while (cur)
    14. {
    15. if (key > cur->_key)
    16. {
    17. parent = cur;
    18. cur = cur->_right;
    19. }
    20. else if (key < cur->_key)
    21. {
    22. parent = cur;
    23. cur = cur->_left;
    24. }
    25. else
    26. {
    27. //相等不能插入
    28. returnf false;
    29. }
    30. }
    31. //找到位置后,创建结点,进行连接
    32. cur = new Node(key);
    33. if (key > parent->_key)
    34. {
    35. parent->_right = cur;
    36. }
    37. else
    38. {
    39. parent->_left = cur;
    40. }
    41. return true;
    42. }

    中序遍历

    遍历我们使用递归调用

    推荐将调用函数封装一层,这样在类外调用时,省去了传参那一步骤,若不封装,直接是public,调用遍历时涉及到传根结点的地址,地址为成员私有,不方便获取。

    1. public:
    2. //中序遍历
    3. void InOrder()
    4. {
    5. _InOrder(_root);
    6. }
    7. private:
    8. void _InOrder(Node* root)
    9. {
    10. if (root == nullptr)
    11. return;
    12. _InOrder(root->_left);
    13. cout << root->_key << " ";
    14. _InOrder(root->_right);
    15. }
    16. Node* _root=nullptr;
    17. };

    二叉搜索树的查找

    平衡搜索二叉树中查找一个数的时间复杂度为 O(N),从它最坏情况->退化成单链表,就可以体现出来。

    查找一个值:确定一个指针来找,比较指针位置结点与要查找值;

    小值走左树,大值走右树,相等返回真,找不到返回假。

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

    递归查找

    只要是递归,就要在外封装一层,因为涉及到传地址,地址私有,封装一层不需要我们手动去传地址,更加方便。

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

    删除

    删除有以下三种情况:

    1.缺少孩子时

    a.删除该结点且使被删除节点的双亲结点指向被删除节点的左孩子结点
    b.删除该结点且使被删除节点的双亲结点指向被删除结点的右孩子结点

    2.满孩子时:

    c.在它的右子树中寻找最小的结点,用它的值填补到被删除节点中,再来处理该结点的删除问题

    1. //删除
    2. bool Erase(const k& key)
    3. {
    4. Node* parent = _root;
    5. Node* cur = _root;
    6. //先查找
    7. while (cur)
    8. {
    9. if (key > cur->_key)
    10. {
    11. parent = cur;
    12. cur = cur->_right;
    13. }
    14. else if (key < cur->_key)
    15. {
    16. parent = cur;
    17. cur = cur->_left;
    18. }
    19. else //找到了
    20. {
    21. //一个孩子--左/右为空
    22. //两个孩子---替换
    23. if (cur->_left==nullptr)
    24. {
    25. //判断父亲的左还是右指向孩子
    26. if (cur==parent->_left)
    27. {
    28. parent->_left = cur->_right;
    29. }
    30. else
    31. {
    32. parent->_right = cur->_right;
    33. }
    34. delete cur;
    35. }
    36. else if (cur->_right == nullptr)
    37. {
    38. if (cur == parent->_left)
    39. {
    40. parent->_left = cur->_left;
    41. }
    42. else
    43. {
    44. parent->_right = cur->_left;
    45. }
    46. delete cur;
    47. }
    48. else //两孩子都不为空
    49. {
    50. //找右树最小结点(左树最大结点)
    51. //还要找到其父亲,替换后删minright,其右子树可能不为空,要连接父亲结点
    52. Node* minParent = cur;
    53. Node* minRight = cur->_right;
    54. while (minRight->_left)
    55. {
    56. minParent = minRight;
    57. minRight = minRight->_left;
    58. }
    59. //值代替删除节点的值
    60. //cur->_key = minRight->_key;
    61. swap(minRight->_key, cur->_key);
    62. //连接父亲结点和删除节点的右孩子
    63. if (minParent->_left==minRight)
    64. {
    65. minParent->_left = minRight->_right;
    66. }
    67. else
    68. {
    69. minParent->_right = minRight->_right;
    70. }
    71. delete minRight;
    72. }
    73. return true;
    74. }
    75. }
    76. //没找到
    77. return false;
    78. }

    拷贝构造

    注意,结点属于内置类型,若没有写拷贝构造的话,赋值时,默认是浅拷贝,若显示写了析构函数,会出现野指针的问题,同一个位置被析构两次。

    这里递归构造树:

    1. private:
    2. //复制结点构造树
    3. Node* CopyTree(Node* root)
    4. {
    5. if (root==nullptr)
    6. {
    7. return nullptr;
    8. }
    9. Node* copyNode = new Node(root->_key);
    10. copyNode->_left = CopyTree(root->_left);
    11. copyNode->_right = CopyTree(root->_right);
    12. return copyNode;
    13. }
    14. public:
    15. BSTree()
    16. {}
    17. //拷贝构造
    18. BSTree(const BSTree& t) //写了拷贝构造,就不会默认生成构造函数,手动创建
    19. {
    20. _root = CopyTree(t._root);
    21. }

    析构函数--销毁树

    1. private:
    2. //销毁树
    3. void DestoryTree(Node* root)
    4. {
    5. if (root==nullptr)
    6. {
    7. return;
    8. }
    9. DestoryTree(root->_left);
    10. DestoryTree(root->_right);
    11. delete root;
    12. }
    13. public:
    14. //析构
    15. ~BSTree()
    16. {
    17. DestoryTree(_root);
    18. _root = nullptr;
    19. }

     赋值

    自定义类型的赋值,默认也是调用构造拷贝的,这里我们用现代写法:

    参数传参是拷贝构造,生成临时变量,交换后返回即可:

    1. //赋值,现代写法
    2. BSTree& operator=(BSTree t)
    3. {
    4. swap(_root, t._root);
    5. return *this;
    6. }

    插入的递归写法

    非递归插入--之前是保留父节点,cur找到位置后,创建结点与父结点连接
    递归插入--这里直接用根节点的引用依次递归就能解决问题

    1. public:
    2. //递归插入
    3. bool InsertR(const k& key)
    4. {
    5. return _InsertR(_root, key);
    6. }
    7. private:
    8. bool _InsertR(Node*& root, const k& key)
    9. {
    10. //等于空的时候插入
    11. if (root==nullptr)
    12. {
    13. root = new Node(key);
    14. return true;
    15. }
    16. if (key > root->_key)
    17. return _InsertR(root->_right, key);
    18. else if (key < root->_key)
    19. return _InsertR(root->_left, key);
    20. else
    21. return false;
    22. }

    删除的递归写法

    这里和插入一样,都是传指针的引用;

    先查找,找到的话判断左右孩子是否为空,有空则将另一边给root

    root是上一结点左树或右树的引用,直接获得连接关系

    如果两边都不为空,则用替换法,找到要删除结点的右子树的最小值

    最小结点值与被删结点互换,保留的搜索二叉树的结构

    转换为要删除最小节点(最小节点的左肯定为空

    递归去删除:从原先要删结点的右子树开始,查找删除最小节点

    1. public:
    2. //递归删除
    3. bool EraseR(const k& key)
    4. {
    5. return _EraseR(_root,key);
    6. }
    7. private:
    8. //递归删除
    9. bool _EraseR(Node*& root, const k& key)
    10. {
    11. //等于空则找不到
    12. if (root == nullptr)
    13. {
    14. return false;
    15. }
    16. if (key > root->_key)
    17. return _EraseR(root->_right, key);
    18. else if (key < root->_key)
    19. return _EraseR(root->_left, key);
    20. else //相等--开始删除
    21. {
    22. //保存要删除的代码,方便删除
    23. Node* del = root;
    24. if (root->_left==nullptr)
    25. {
    26. root = root->_right;
    27. }
    28. else if (root->_right == nullptr)
    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->_key, minRight->_key);
    40. //递归删除,已经互换了值,在子树中删除,左一定为空
    41. return _EraseR(root->_right, key);
    42. }
    43. delete del;
    44. return true;
    45. }
    46. }

  • 相关阅读:
    Go基础学习【2】
    java计算机毕业设计ssm+jsp计算机视频学习网站
    Typescript的高级tricks(in,keyof,Partial,Pick,Exclude等)
    【Vue】创建项目
    21、Flink 的table API与DataStream API 集成(完整版)
    中小学生使用全光谱台灯对眼睛好不好?2023口碑好的护眼台灯推荐
    PCB元件创建
    undo log
    [附源码]java毕业设计朋辈帮扶系统
    2022牛客多校九 B-Two Frogs(概率DP+前缀和优化)
  • 原文地址:https://blog.csdn.net/weixin_53316121/article/details/126016624