递归插入二叉搜索树:两种参数传递方式的差异解析
我在实现二叉搜索树的递归插入函数时,尝试了两种参数传递方式,却得到了完全不同的运行结果,想搞清楚二者的核心差异:
- 传参时用赋值运算符重新赋值参数
- 直接传递参数的新值,不使用赋值运算符
基础代码
class Node { constructor(key, left, right) { this.key = key || null; this.left = left || null; this.right = right || null; } } class Tree { constructor(arr) { this.tree = this.buildTree(arr); } // 插入函数在此处定义 } let tree = new Tree([3, 2, 4, 1, 6, 7, 5, 8, 10, 9, 12, 11]) tree.insert(100)
方式1:传参时使用赋值运算符
insert(value, node = this.tree) { if (node == null) return node = new Node(value) if (value == node.key) return node; // 避免重复值 if (value > node.key) node.right = this.insert(value, node = node.right); else if (value < node.key) node.left = this.insert(value, node = node.left); return node }
运行结果:新节点未插入正确位置,树中原有节点被删除,最终树结构仅剩插入路径上的部分节点(如根节点变为12,左子节点11,右子节点100)。
方式2:直接传递参数新值
insert(value, node = this.tree) { if (node == null) return node = new Node(value) if (value == node.key) return node; // 避免重复值 if (value > node.key) node.right = this.insert(value, node.right); else if (value < node.key) node.left = this.insert(value, node.left); return node }
运行结果:新节点被正确插入到树的对应位置,原有节点结构完整保留。
核心差异解析
两种方式的本质区别在于是否修改当前函数作用域内的node参数引用:
方式1的问题:
执行this.insert(value, node = node.right)时,会先完成node = node.right的赋值——把当前函数里的node变量(原本指向父节点)强制改成指向父节点的右子节点。
递归调用返回后,我们把返回值赋值给这个已经被修改的node的right属性(也就是父节点的右子节点的right属性),最后return的也是这个被修改后的子节点。
上层函数拿到这个返回的子节点后,会把它赋值给自己的right/left属性,相当于直接用下层子节点替换了原本的子节点,导致原树中该子节点的其他分支(比如4的右子节点原本是6,现在被替换成了更下层的节点)全部丢失,最终树结构被破坏。方式2的逻辑:
this.insert(value, node.right)只是把node.right的引用传递给递归函数,不会修改当前函数的node参数——当前函数的node始终指向原来的父节点。
递归返回后,新节点会被正确赋值给父节点的right/left属性,最后return的也是完整的父节点,上层函数拿到的是包含原有分支的节点,因此树的结构可以完整保留。
内容的提问来源于stack exchange,提问作者user667199

