「这是我参与11月更文挑战的第13天,活动详情查看:2021最后一次更文挑战」
删除二叉搜索树的节点
题目
给定一个二叉搜索树的根节点 root 和一个值 key,删除二叉搜索树中的 key 对应的节点,并保证二叉搜索树的性质不变。返回二叉搜索树(有可能被更新)的根节点的引用。
一般来说,删除节点可分为两个步骤:
首先找到需要删除的节点; 如果找到了,删除它。
解法
删除二叉搜索树的节点相比较昨天二叉搜索树的插入操作要更为复杂,插入操作只需要找到合适位置的空节点插入即可,删除操作就要复杂的多,主要分为以下几种操作
- 整棵树都没有找到对应的节点,那么不必进行删除操作
- 树中存在要插入的节点,存在以下情况:
- 该节点为叶子节点(即不存在左右子节点),直接将这个节点删除即可
- 该节点存在左子节点,不存在右子节点,那么删除该节点,该节点的位置由左子节点补位
- 该节点存在右子节点,不存在左子节点,那么删除该节点,该节点的位置由右子节点补位
- 该节点左右子节点都存在,那么删除该节点,右节点补位,原先的左子节点移动右子节点这颗子树的最左侧节点的左孩子上(因为最左侧节点是这颗子树上val最小的,而原先的左子节点的val值比这还小,移动该节点的左孩子上符合二叉搜索树特性) 以上就是二叉搜索树删除操作的五种情况,前四种都很好理解,第五种,我画了一个图,如下:
使用递归来写该函数,三要素如下
递归的参数和返回值: 首先我们可以很容易的知道,参数需要传入要搜索的树的根节点和key值,对于返回值而言,要在树中删除一个节点,首先就是改变其父节点对它的指向(至于某某情况下,将该节点左子树更换位置,我们先不考虑),那么我们就需要知道它的父节点,以及它是其的左节点还是右节点,如果简单的记录它的节点以及位置,操作逻辑都会复杂不少,我们可以根据递归的回溯特性,返回补位节点供父节点改变指向(上一层递归)
递归的终止条件: 如果传入的node为空,返回null供上一层连接,递归终止
递归的单层逻辑: 如果该node为要删除的节点,根据上面五种情况,指定补位节点返回,如果不为空,根据val和key值,判断是在左子树递归还是右子树,然后返回该节点供上一层父节点链接
var deleteNode = function(root, key) {
const deleteKeyNode = function(node,key) {
if(node === null) return null;
if (node === preRootNode){
node.right = deleteKeyNode(node.right,key);
return node;
}
if(node.val === key) {
if(!node.left && !node.right){
return null;
}
else if(!node.left && node.right){
return node.right;
}
else if(node.left && !node.right){
return node.left;
}
else{
let tempNode = node.right;
while(tempNode.left){
tempNode = tempNode.left;
}
tempNode.left = node.left;
return node.right;
}
}
if(node.val > key) node.left = deleteKeyNode(node.left,key);
else if(node.val < key) node.right = deleteKeyNode(node.right,key);
return node;
}
let preRootNode = new TreeNode(0);
preRootNode.right = root;
deleteKeyNode(preRootNode,key);
return preRootNode.right;
};