目录
题目描述:
给定一个二叉搜索树的根节点 root 和一个值 key,删除二叉搜索树中的 key 对应的节点,并保证二叉搜索树的性质不变。返回二叉搜索树(有可能被更新)的根节点的引用。
要求算法时间复杂度为 O(h),h 为树的高度。
分析:
首先我们要明确:实现的是一个函数,最终需要返回删除之后的树的根节点。
由于这是一棵二叉搜索树,所以对于要删除的节点我们需要找到他并进行删除操作:
如果当前节点值比key大,则需要删除当前节点的左子树中key对应的值;root.left = deleteNode(root.left,key);
如果当前节点值比key小,则需要删除当前节点的右子树中key对应的值; root.right = deleteNode(root.right,key);
当前节点等于key,则需要删除当前节点,并保证二叉搜索树的性质不变
在删除当前节点时,我们会遇到3种情况,在下面的叙述中,我们均以删除节点5为例子。
情况一:当前节点没有左子树,直接返回当前节点的右孩子即可。(上层指针跳过了当前节点5,指向了右孩子7,就相当于删除了5)
情况二:当前节点没有右子树,直接返回当前节点的左孩子即可。(上层指针跳过了当前节点5,指向了左孩子3,就相当于删除了5)
情况三:当前节点既有左子树,又有右子树。
(这里可能有人会问,如果既没有左子树,又没有右子树怎么办? 这里把这种情况合并到情况一中,返回右子树,也就是返回NULL)
第三种情况比较复杂,这里介绍两种方法:
方法一:
将root的左子树放到root的右子树的最小节点的左子树上。
这句话听起来计较绕,看图会很清晰,删除节点5,把5的左子树【2,3,4】看做一个整体,全部移动到右子树的最小节点6的左子树上。
为什么是右子树的最小节点的左子树? 这是根据二叉搜索树的性质定的,也可以选择把root的右子树移动到左子树的最大节点大的右孩子上,目的都是保证二叉搜索树的性质不变。
代码:
class Solution { public: //寻找以t为根的最小节点 TreeNode* find_Min(TreeNode* t){ if (t==NULL) return NULL; while (t->left!=NULL) t=t->left; return t; } TreeNode* deleteNode(TreeNode* root, int key) { if (root==NULL) return NULL; //查找的递归出口,找不到,不做操作 if (root->val==key){ //找到了要删除 if (root->left==NULL) return root->right; //情况一 else if (root->right==NULL) return root->left; //情况二 else { //情况三:方法一 TreeNode* t=find_Min(root->right); //右子树的最小节点 t->left=root->left; //移动左子树 return root->right; //返回新的根 } } if (root->val > key) root->left=deleteNode(root->left, key); //往左找 if (root->val < key) root->right=deleteNode(root->right, key); //往右找 return root; //返回根 } };
方法二:
用右子树的最小节点的值替换root的值,再递归的删除替换节点。
如图,要删除节点5,先找到右子树的最小节点6,然后用6替换5,现在问题就转化成了在root的右子树里删除替换的节点6。这样一直递归下去,情况三最终一定会变成情况一二那种。然后完成删除操作。
代码:
class Solution { public: //寻找以t为根的最小节点 TreeNode* find_Min(TreeNode* t){ if (t==NULL) return NULL; while (t->left!=NULL) t=t->left; return t; } TreeNode* deleteNode(TreeNode* root, int key) { if (root==NULL) return NULL; //查找的递归出口,找不到,不做操作 if (root->val==key){ //找到了要删除 if (root->left==NULL) return root->right; //情况一 else if (root->right==NULL) return root->left; //情况二 else { //情况三:方法二 TreeNode* t=find_Min(root->right);//右子树的最小节点 root->val=t->val; //用最小值代替删除的节点(相当于删除了) root->right=deleteNode(root->right,root->val); //递归的去删除右子树的替代节点 } } if (root->val > key) root->left=deleteNode(root->left, key); //往左找 if (root->val < key) root->right=deleteNode(root->right, key); //往右找 return root; //返回根 } };
两种删除方法的比较:
很明显,第一种方法:直接将左子树移植到右子树的最小节点上,更容易理解,而且删除操作是一步到位的,
第二种方法呢,递归的去删除,每次都要去替换删除结点,可能会执行很多次的替换。
虽然第二种方法比较难一点儿,但是教科书上一般都讲的是第二种方法,因为第二种方法删除后,二叉搜索树的性质更优。
同样一棵树,都是删除节点5,但是第二种方法的删除之后的树高度更小,更优。