insert函数必须返回当前子树根节点,遇nullptr新建节点并返回;递归调用后需显式赋值(如root->left = insert(root->left, val));重复值直接丢弃。

怎么写一个能用的 insert 函数(不崩、不重复、不漏节点)
二叉搜索树的插入不是简单比大小就递归完事。常见错误是没处理空指针、忘了返回新节点导致链断开,或者没检查重复值导致逻辑错乱。
核心原则:每次递归必须返回当前子树根节点,让父节点能正确接住;重复值直接丢弃(BST 通常不允许重复)。
-
insert函数签名建议用TreeNode* insert(TreeNode* root, int val),别用引用传参——C++ 里容易误以为改了root就等于改了上层指针 - 遇到
nullptr就新建节点并返回,这是唯一创建节点的地方 - 递归调用后必须显式赋值:比如
root->left = insert(root->left, val),否则修改不会反映到树结构上 - 如果要求支持重复值(如计数 BST),得额外加字段,不能只靠
val比较
查不到节点时 find 返回什么最安全
返回 nullptr 是标准做法,但容易在后续解引用时报 segmentation fault。真正的问题不在返回值,而在调用方有没有检查。
典型翻车场景:直接写 if (find(root, 5)->val == 5),一旦没找到,nullptr->val 立刻崩溃。
立即学习“C++免费学习笔记(深入)”;
- 永远先判空再访问成员:
TreeNode* node = find(root, 5); if (node) { ... } - 不要把
find当布尔函数用,它返回的是指针,语义是“给你节点地址”,不是“是否存在” - 如果只是判断存在性,单独写个
exists函数更清晰,内部复用find即可
删除节点为什么比插入难得多
删叶子节点和删单子节点还好办;真正卡住人的是删有两个子节点的节点——你得找中序后继(右子树最小值)或前驱(左子树最大值)来顶替,还要保证替换后仍是 BST。
常见坑是:找到后继后,只复制了值,没真正删掉后继节点,导致数据残留;或者删后继时没更新父节点指针,树结构断裂。
- 删双子节点时,推荐统一用右子树最小值(即
minNode(root->right))替代,逻辑更对称 - 替换完值后,必须递归删除那个最小节点——注意这次删除一定落在右子树的最左端,所以最多只有一个子节点,不会再次触发双子分支
- 别在删除函数里 new 新节点,也不该修改被删节点的左右指针以外的内容
用 std::unique_ptr<treenode></treenode> 能省心吗
能自动管理内存,但会改变所有接口签名和递归逻辑,初学者反而更容易写错。比如 insert 得改成 std::unique_ptr<treenode> insert(std::unique_ptr<treenode>&& root, int val)</treenode></treenode>,移动语义一搞混,节点就提前释放了。
真实项目里值得上智能指针,但练手或面试实现 BST 时,裸指针 + 明确的 delete 更可控。
- 如果坚持用
unique_ptr,所有递归调用都得用std::move传递所有权,比如root->left = insert(std::move(root->left), val) -
find这种只读操作,参数应该用const std::unique_ptr<treenode>&</treenode>,避免意外转移 - 调试时打印地址会看到
get()返回的裸指针,别把它当普通指针去delete
BST 的难点从来不在算法思想,而在于指针操作的每一步是否真的改变了树的拓扑连接。少一次赋值、多一次 new、漏一个空检查,整棵树就 quietly 不工作了。










