必须用中序前驱(左子树最右节点)或中序后继(右子树最左节点)替换,二者均满足BST性质;推荐统一用后继,因其查找只需向右子树一路向左,逻辑更简洁、边界更易处理。

删除节点时遇到“左子树最大值”还是“右子树最小值”?选哪个不影响正确性,但影响实现简洁度
二叉搜索树(BST)中删除一个有左右子树的节点,必须用它的前驱(左子树最右节点)或后继(右子树最左节点)来替代。两者都满足BST性质,但选 right subtree's minimum 更常用——因为找它只需一路向左,代码更直白,不容易写错边界。
常见错误是试图直接复制值再递归删后继节点,却忘了更新父指针或没处理后继本身有右子树的情况。后继节点最多只有右子树(因为它是最左节点),所以替换后只需把它的右子树挂到其父节点上。
- 若用前驱(
left subtree's maximum),要一路向右,逻辑对称但容易在空指针判断上多出一层嵌套 - 无论选前驱还是后继,被删节点的父节点指针必须被修正;如果用值替换法,必须确保后续删除操作不会破坏树结构
- 推荐统一用后继:找
findMin(node->right),然后用该节点的值覆盖待删节点,再调用删除函数删掉那个后继节点
递归删除函数里,为什么返回 Node* 而不是 void?
因为删除可能改变子树根节点(比如删叶子节点,父节点某个指针要置为 nullptr;删只有一个孩子的节点,父节点要跳过它直接连孩子)。返回 Node* 是为了让上层调用能接收新子树根并更新自己的左/右指针。
不返回会导致悬空指针或内存泄漏——尤其在删单孩子节点时,若只修改了局部变量 node,原父节点的指针仍指向已被释放的内存。
立即学习“C++免费学习笔记(深入)”;
- 函数签名应为
Node* deleteNode(Node* root, int key) - 每个分支最后都要
return root,包括递归调用后的赋值:root->right = deleteNode(root->right, key); - 不要在递归调用后手动设
root->left = nullptr之类,那是干扰返回值逻辑
删除后继节点时,为什么不能直接 delete 后继再 return?
后继节点可能带有右子树。例如待删节点是 5,其右子树为 8 -> 6 -> 7(6 是后继),那么删掉 6 后,7 必须接到 8 下面,否则丢失。
常见坑是写成 delete successor; return root;,这会让 7 永远断连。正确做法是:先保存后继的右子树,再把后继的值拷贝过来,最后用 deleteNode 递归删后继——这样它会自动处理后继带右子树的情形。
- 后继节点一定没有左子树(定义决定),但可能有右子树,必须保留
- 不要手写“摘下后继+拼接右子树”的逻辑,复用已有的
deleteNode函数更安全 - 如果硬要手动处理,需检查
successor->right是否非空,并将其挂到successor的父节点对应位置——但这增加了父指针维护成本
如何避免 delete 后访问野指针?
最典型的错误是在 delete node; 之后还读写 node->left 或给它赋值。C++ 不会自动清空指针,node 变成悬垂指针,后续访问就是未定义行为。
解决方案不是靠运气,而是明确 delete 前后责任划分:只要执行了 delete,这个指针变量就失效,不能再用于任何解引用操作。所有逻辑必须在 delete 前完成。
- delete 前必须完成值拷贝、子树接管、父链接更新等全部操作
- delete 后立即把指针设为
nullptr(如Node* tmp = successor; successor = nullptr; delete tmp;),虽不能防误用,但配合调试器能更快暴露问题 - 使用智能指针(如
std::unique_ptr<node></node>)可从根本上规避此类问题,但需重构整个树接口,权衡是否值得
真正麻烦的从来不是找到后继,而是删完之后那几行指针交接——稍一疏忽,树就碎了,而且这种 bug 往往在特定数据下才暴露,不容易测全。


















