题目描述
给定一个二叉搜索树的根节点 root 和一个值 key,删除二叉搜索树中的 key 对应的节点,并保证二叉搜索树的性质不变。返回二叉搜索树(有可能被更新)的根节点的引用。
问题分析
递归法: 先递归找到节点,然后删除节点,删除节点比较复杂。
删除节点会遇到以下五种情况:
1.情况一:没找到删除节点,那么不对二叉树做任何处理,遍历到空节点直接返回
2.情况二:左右孩子都为空,即为叶子节点,直接删除节点
3.情况三:被删除节点的左孩子不为空,右孩子为空,删除节点,左孩子补位
4.情况四:被删除节点的左孩子为空,右孩子不为空,删除节点,右孩子补位
5.情况五:被删除节点左右孩子都不为空,将删除节点左子树放到删除节点的右子树的最左节点的左孩子上,然后右孩子补位。
迭代法: 要考虑如何删除节点,即要知道节点的父节点,将节点删除后,父节点要指向删除节点的孩子节点,其他逻辑是一样的。
做到这道题前最好先看:力扣:701. 二叉搜索树中的插入操作。
要检验删除是否正确可以用二叉树层次遍历。
代码实现
// 编程软件:VS2019
// 参考书籍:代码随想录
#include<iostream>
using namespace std;
// 定义二叉树
struct TreeNode {
int val;
TreeNode* left;
TreeNode* right;
TreeNode(int v):val(v),left(nullptr),right(nullptr){}
};
/*
* 递归法:先递归找到节点,然后删除节点,删除节点比较复杂。
* 删除节点会遇到以下五种情况:
* 1.情况一:没找到删除节点,那么不对二叉树做任何处理,遍历到空节点直接返回
* 2.情况二:左右孩子都为空,即为叶子节点,直接删除节点
* 3.情况三:被删除节点的左孩子不为空,右孩子为空,删除节点,左孩子补位
* 4.情况四:被删除节点的左孩子为空,右孩子不为空,删除节点,右孩子补位
* 5.情况五:被删除节点左右孩子都不为空,将删除节点左子树放到删除节点的右子树的最左节点的左孩子上,然后右孩子补位
*/
TreeNode* deleteNode1(TreeNode* root, int key) {
// 情况一:没找到删除的节点,遍历到空节点直接返回
if (root == NULL) return root; // 注意这里要直接返回root
// 找到节点
if (root->val == key) {
// 情况二:左右孩子都为空,即为叶子节点,直接删除节点
if (root->left == NULL && root->right == NULL) {
TreeNode* tmp = root;
delete tmp; // C++要注意释放内存
return NULL; // 返回NULL
}
// 情况三:被删除节点的左孩子不为空,右孩子为空,删除节点,左孩子补位
else if (root->left != NULL && root->right == NULL){
TreeNode* tmp = root;
root = root->left;
delete tmp; // C++要注意释放内存
return root; // 返回左孩子
}
// 情况四:被删除节点的左孩子为空,右孩子不为空,删除节点,右孩子补位
else if (root->left == NULL && root->right != NULL) {
TreeNode* tmp = root;
root = root->right;
delete tmp; // C++要注意释放内存
return root; // 返回右孩子
}
// 情况五:被删除节点左右孩子都不为空,将要删除节点的左子树放到删除节点的右子树的最左节点的左孩子上,然后右孩子补位
else {
TreeNode* cur = root->right;
// 找到要删除节点的右子树的最左节点
while (cur->left != NULL) {
cur = cur->left;
}
cur->left = root->left;
TreeNode* tmp = root;
root = root->right;
delete tmp; // C++要注意释放内存
return root; // 返回右孩子
}
}
if (key < root->val) root->left = deleteNode1(root->left, key); // 小于,往左子树
if (key > root->val) root->right = deleteNode1(root->right, key); // 大于,往右子树
return root; // 向上一层返回节点
}
/*
* 迭代法:要考虑如何删除节点,即要知道节点的父节点,将节点删除后,父节点要指向删除节点的孩子节点
* 其他逻辑是一样的。
*/
TreeNode* deleteNodeOperation(TreeNode* target) {
if (target == NULL) return target;
// 情况二:左右孩子都为空,即为叶子节点,直接删除节点
if (target->left == NULL && target->right == NULL) {
TreeNode* tmp = target;
delete tmp; // C++要注意释放内存
return NULL; // 返回NULL
}
// 情况三:被删除节点的左孩子不为空,右孩子为空,删除节点,左孩子补位
else if (target->left != NULL && target->right == NULL) {
TreeNode* tmp = target;
target = target->left;
delete tmp; // C++要注意释放内存
return target; // 返回左孩子
}
// 情况四:被删除节点的左孩子为空,右孩子不为空,删除节点,右孩子补位
else if (target->left == NULL && target->right != NULL) {
TreeNode* tmp = target;
target = target->right;
delete tmp; // C++要注意释放内存
return target; // 返回右孩子
}
// 情况五:被删除节点左右孩子都不为空,将要删除节点的左子树放到删除节点的右子树的最左节点的左孩子上,然后右孩子补位
else {
TreeNode* cur = target->right;
while (cur->left != NULL) {
cur = cur->left;
}
cur->left = target->left;
TreeNode* tmp = target;
target = target->right;
delete tmp; // C++要注意释放内存
return target; // 返回右孩子
}
}
TreeNode* deleteNode2(TreeNode* root, int key) {
if (root == NULL) return NULL;
TreeNode* cur = root;
TreeNode* pre = NULL; // 记录cur的父节点,用来删除cur
while (cur) {
if (cur->val == key) break; // 找到节点直接退出循环
pre = cur;
if (key < cur->val) cur = cur->left; // 小于,往左子树方向
else cur = cur->right; // 大于,往右子树方向
}
if (pre == NULL) { //如果二叉搜索树只有根节点
return deleteNodeOperation(cur);
}
if(cur==NULL)
// pre用于判断删除的节点是左孩子还是右孩子
if (pre->left && pre->left->val==key) {
pre->left = deleteNodeOperation(cur);
}
if (pre->right && pre->right->val == key) {
pre->right = deleteNodeOperation(cur);
}
// 为什么这里没有情况一,因为没找到删除节点,也就是不对二叉树做任何处理,直接忽略
return root;
}
int main() {
TreeNode* root = new TreeNode(2);
root->left = new TreeNode(1);
root->right = new TreeNode(7);
root->right->left = new TreeNode(5);
root->right->right = new TreeNode(9);
root->right->left->left = new TreeNode(4);
root->right->left->right = new TreeNode(6);
root->right->right->left = new TreeNode(8);
root->right->right->right = new TreeNode(10);
TreeNode* res = deleteNode2(root, 7);
cout << res->val << endl;
}
// 结果:2