删除二叉搜索树中的结点
给定一个二叉搜索树的根节点 root 和一个值 key,删除二叉搜索树中的 key 对应的节点,并保证二叉搜索树的性质不变。返回二叉搜索树(有可能被更新)的根节点的引用。
一般来说,删除节点可分为两个步骤:
实例:
我们删除一个结点,首先要找到这个结点,然而,寻找这个结点,会有两种情况:
1.没到了 2.没找到
对于1,没找到,说明树不会被修改,将原来的树原封不动返回。
对于2,找到了,肯定返回的是最终删除了指定元素的树。
这里对2进行重点分析,这个题的思路和递归代码相结合的时候,有些地方很难理解,一会在代码解析
部分会重点讲解,这里重点理解思路
那对于每一个结点,我们是怎么处理的呢?
对于每一个结点,我们要分成四种情况:
当前结点左右子树都为空
:直接删除这个结点,不用改变结构当前结点左子树不为空,右子树为空
:用左子树替代当前结点当前结点左子树为空,右子树不为空
:用右子树替代当前结点当前结点左右子树都不为空
: 上面就是对每个结点具体的操作步骤了。
那具体代码如何组织呢,看下面的解析:
这个题目是:删除二叉搜索树中的节点
那么如果删除了,我们期望的返回值就是,删除过指定节点之后的二叉树。
如果没删除,就返回一个空指针。
所以,这个函数的返回值是TreeNode*
,二叉树节点指针类型。
参数,很明显,要传入这个树的根节点和目标值key
所以,函数返回值和参数如下
TreeNode* deleteNode(TreeNode* root, int key){//函数体
}
这里按照解题思路中的逻辑来处理代码
TreeNode* deleteNode(TreeNode* root, int key) {//递归终止的条件:遍历完这个树,没有找到目标结点if (root == NULL) return root;//当遍历到的val == key时if (root->val == key) {//1.遍历到结点的左右子树都为空,//那直接删除这个结点,并向删除结点的父节点返回NULL,相当于是用NULl代替了删除结点if (root->left == NULL && root->right == NULL) {delete root;//删除结点return NULL;//向上一层返回NULL,代表用NULL代替了待删除结点}//2.遍历到结点左子树不为空,右子树为空,那用左子树来代替这个结点else if (root->right == NULL && root->left != NULL) {TreeNode* retNode = root->left;delete root;//删除结点return retNode;//向上一层返回刚才保存的左子树,//也可以理解为用左子树代替了待删除结点}//3.遍历到结点右子树不为空,左子树为空,那用右子树来代替这个结点else if (root->right != NULL && root->left == NULL) {TreeNode* retNode = root->right;delete root;//删除结点return retNode;//向上返回待删除结点右子树}//4.遍历到结点左右子树都不为空,//将左子树放在右子树的最左侧结点的左子树上,并用右子树代替待删除结点else {//找到右子树最左侧的结点TreeNode* cur = root->right;while (cur->left != NULL) {cur = cur->left;}//将待删除结点的左子树放在右子树左侧结点的左子树上cur->left = root->left;//用tem暂存待删除结点,因为,一会要改变root的值TreeNode* tmp = root;//一会删除tmp就行了//用root的右子树替代rootroot = root->right;//删除原来的rootdelete tmp;//返回现在修改过后的rootreturn root;}}//如果当前遍历结点大于key值,说明key在当前结点的左侧,往当前结案的左侧遍历if (root->val > key) root->left = deleteNode(root->left, key);//如果当前遍历结点小于key值,说明key在当前结点的右侧,王当前结点的右侧遍历if (root->val < key) root->right = deleteNode(root->right, key);return root;
}
这里用一个例子来展示一下代码是怎么运行的:
给出树的根节点是root,目标结点key是3,也就是要删除3这个结点。
现在代码开始运行:
判断当前root是不是NULL => 不是,不用执行return NULL
判断当前root->val是不是key => 不是,不用执行删除修改代码
判断当前root->val是不是大于key => 是,执行递归代码。
为了区分,我们将第一次调用递归函数传入的
root
称为root_0
将第二次调用递归函数传入的root
称为root_1
。
如果所示,
第一次传入的root_0
当前是整个树的根节点。
按照代码执行流程,判断了root_0的val是大于key的,所以执行了相应的递归函数
。
所以,第二次传入的root_1
是根节点的左孩子。
那第二次递归函数做了哪些操作呢?
进入第二次调用函数传入root_1
,那函数再次重头开始执行
判断当前
roor_1
是不是NULL
=> 不是,不执行return NULL
判断当前root_1
的val
是不是key3 => 是,指向判断里面的代码
判断当前root_1
的左右是不是都有孩子 = > 执行左右孩子都存在的代码
返回修改过后的root_1
注意看上图
root_1
是第二次调用递归函数所得到的结果,这个结果赋值给了第一次调用递归函数时root_0的左子树, 那就相当于是将root_0
的左子树修改成了删除后的样子。
最终返回root_0
也就是返回整个树的根,代码执行完毕。
到这里就讲解完毕了,如果还是不明白,结合思路,代码,将这个例子自己理一遍思路。
对于开始接触递归的小伙伴,总是觉得思考起来很困难,很抽象。
就对于这个题来说,如果要找的key是1
那应该怎么向上传值呢?
那按照同样的思路,最底下一层调用递归函数传入的参数会是2这个结点的左子树,也就是NULL
。
然后会将NULL
赋值给2的左子树
。继续往执行,会返回2这个结点,将2这个结点赋值给结点3的左子树
。
同样,向上返回3这个结点,将3这个结点返回给5的左子树
。最后返回root。
所以递归函数,一层一层的,很像是宝塔。我们开始从它尖向下寻找,知道找到了目标,或者找到了最底下一层。
然后开始从最底下一层往上传值,下面一层返回的值,其实是上面一个层函数的组成部分,同样,上面一层函数返回的值又是上上面一层函数的组成部分。直到返回到塔尖。
我们在组织代码的时候,脑子里要有向下寻找的过程,更要有一层一层向上回溯的过程。
本文发布于:2024-01-28 23:59:13,感谢您对本站的认可!
本文链接:https://www.4u4v.net/it/170645756011244.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
留言与评论(共有 0 条评论) |