上篇文章分享了如何创建一个二叉排序树, 这篇文章来分享如何删除二叉排序树的节点
其实逻辑比较简单,
- 如果删除的是叶子,就直接删除;
- 如果删除的是只含有一个子树,那么将子树的第一个节点代替当前节点;
- 如果删除的节点有两个子树,那么就用当前节点中序遍历的下一个节点。
这样做的目的是为了,删除节点之后,整棵树仍然是二叉排序树。下面来看代码实现
准备数据
javascript
const treeData = [5, 7, 6, 3, 8, 7, 4, 1, 2];
const insertNode = (tree, node) => {
if (node.value <= tree.value) {
if (tree.left) insertNode(tree.left, node);
else tree.left = node;
} else {
if (tree.right) insertNode(tree.right, node);
else tree.right = node;
}
};
const generateTree2 = (data) => {
const root = {};
root.value = data[0];
for (let i = 1; i < data.length; i++) {
insertNode(root, { value: data[i], left: null, right: null });
}
return root;
};
const tree = generateTree2(treeData);
这是上篇文章分享的创建排序二叉树的代码,将一个无序数组转成排序二叉树。
下面是这个排序二叉树的 json 结构
javascript
{
value: 5,
right: {
value: 7,
left: {
value: 6,
left: null,
right: {
value: 7,
left: null,
right: null,
},
},
right: {
value: 8,
left: null,
right: null,
},
},
left: {
value: 3,
left: {
value: 1,
left: null,
right: {
value: 2,
left: null,
right: null,
},
},
right: {
value: 4,
left: null,
right: null,
},
},
}
了解更多创建二叉树的内容,可以看这篇文章:🥳每日一练-二叉排序的构建-JS简易版 - 掘金。这里就不讲解了
删除节点
删除节点有三种情况:
- 如果删除的是叶子,就直接删除;
- 如果删除的是只含有一个子树,那么将子树的第一个节点代替当前节点;
- 如果删除的节点有两个子树,那么就用当前节点中序遍历的下一个节点。
说实话,这个代码我一开始没写出来,奔着三种情况,还特意写了三个处理不同情况的函数。最后问了 GPT 这个代码怎么写,它给出了非常漂亮的回答
javascript
function deleteNode(root, key) {
if (root === null) return null;
if (key < root.value) {
root.left = deleteNode(root.left, key);
} else if (key > root.value) {
root.right = deleteNode(root.right, key);
} else {
// 当前节点是要删除的节点
// 如果左子树为空,返回右子树
if (root.left === null) return root.right;
// 如果右子树为空,返回左子树
if (root.right === null) return root.left;
// 找到右子树中最小的节点
let minNode = root.right;
while (minNode.left) {
minNode = minNode.left;
}
// 删除右子树中最小的节点
root.right = deleteNode(root.right, minNode.value);
// 用右子树中最小的节点替换当前节点
minNode.left = root.left;
minNode.right = root.right;
root = minNode;
}
return root;
}
代码的开头会有两个判断,判断要删除的节点和当前遍历到的节点的大小。要么小于,就继续往左子树方向遍历;要么大于,就继续往右子树方向遍历;
当要删除的节点和当前遍历到的节点的大小相等的时候,就开始执行删除操作了。删除的过程也是依据上面的三种情况来的。
如果左子树为空,就返回右子树;如果右子树为空,就返回左子树;这其中的逻辑就包含了如果左右子树都为空的情况。
如果两个子树都不为空,就找到右子树中最小的节点 minNode,并且将 minNode 替代当前节点的位置返回上一层调用。
为什么需要是右子树中最小的节点呢,因为一个节点要替代当前节点的话,必须要满足两个要求。第一,那就是这个节点的值要大于当前节点的左子树所有节点;第二,并且小于右子树的所有节点。共两个
那右子树中最小的节点就满足这个要求,首先因为是在右子树,所以必然大于左子树,满足第一个要求。又因为是右子树的最小的,所以满第二个要求
而右子树中最小的节点又是当前节点中序遍历的下一个节点。论证完美😍
那么要怎么找到这个 minNode,就要从右子树的第一个节点,不断往左遍历,直到遍历到叶子节点,就找到了 minNode
找到了 minNode,下一步就是将 minNode 取代当前节点的位置。代码中处理地很妙。先将 minNode 从二叉排序树删除,然后将当前节点的左右子树分别交给 minNode,然后 root 直接变成 minNode,最后 return;
代码中
root.right = deleteNode(root.right, minNode.value)
很妙,它完美地处理了 minNode 就是当前节点的右子树的情况,避免了指向自我的。属实学到了。
测试代码实际效果:
javascript
const printNode = (tree) => {
if (!tree) return null;
printNode(tree.left);
console.log(tree.value);
printNode(tree.right);
};
const newTree = deleteNode(tree, 3);
printNode(newTree);
// 1
// 2
// 4
// 5
// 6
// 7
// 7
// 8
const newTree2 = deleteNode(tree, 5);
printNode(newTree2);
// 1
// 2
// 3
// 4
// 6
// 7
// 7
// 8
printNode
是一个以中序遍历输出二叉树的方法。
代码第二个测试用例,删除的是二叉排序树的根节点,也成功。这也是我对这个代码赞不绝口的地方。
总结
这篇文章分享了如何删除二叉排序的节点。文中给出的代码不是我想出来的,是 GPT 帮我想出来的,属实很妙
喜欢就关注一下吧❤️,你的点赞和关注是我不断分享的动力