DFS算法中传递父节点并修改时遭遇BorrowMutError运行时错误的实现需求
DFS算法中传递父节点并修改时遭遇BorrowMutError运行时错误的实现需求
兄弟,我太懂你这糟心的问题了——在Rust里写DFS操作二叉树,还要修改父节点,分分钟就触发BorrowMutError,这都是Rust那严格到“不近人情”的借用检查在搞事情。咱们先捋清楚问题根源,再给你一个能跑通的完整实现,顺便说说怎么避开这类坑。
问题根源
你遇到的错误本质上是违反了Rust的借用规则:同一时间,一个值不能同时存在可变借用和不可变借用,也不能存在多个可变借用。在DFS递归过程中,如果你拿着当前节点的不可变借用(比如node.borrow()),同时又去获取父节点的可变借用(parent.borrow_mut()),或者递归时没释放之前的借用,就会触发这个错误。
针对distribute_coins的完整实现
咱们就以你给出的硬币分配问题为例,给你一个正确的DFS实现——其实不用直接修改父节点,通过返回值传递节点的硬币“盈余”就能完成逻辑,完美避开借用冲突:
use std::{cell::RefCell, rc::Rc}; #[derive(Debug, PartialEq, Eq)] pub struct TreeNode { pub val: i32, pub left: Option<Rc<RefCell<TreeNode>>>, pub right: Option<Rc<RefCell<TreeNode>>>, } #[allow(dead_code)] impl TreeNode { #[inline] pub fn new(val: i32) -> Self { TreeNode { val, left: None, right: None, } } } pub fn distribute_coins(root: Option<Rc<RefCell<TreeNode>>>) -> i32 { let mut total_moves = 0; // 内部DFS函数,返回当前节点需要传递给父节点的硬币数量(正为多,负为缺) fn dfs(node: Option<Rc<RefCell<TreeNode>>>, moves: &mut i32) -> i32 { match node { Some(node_rc) => { // 先递归处理左右子树——这里先克隆子节点,避免持有当前节点的借用 let left_excess = dfs(node_rc.borrow().left.clone(), moves); let right_excess = dfs(node_rc.borrow().right.clone(), moves); // 计算当前节点的硬币盈余:现有硬币 + 左右子树传递的 - 自己留1个 let excess = node_rc.borrow().val + left_excess + right_excess - 1; // 移动次数是盈余的绝对值,不管多了还是少了都要移动 *moves += excess.abs(); // 把盈余传递给父节点 excess } None => 0, // 空节点没有硬币,盈余为0 } } dfs(root, &mut total_moves); total_moves }
如果必须直接修改父节点怎么办?
要是你的业务场景真的需要直接修改父节点,那一定要注意借用的作用域,及时释放不需要的借用,比如这样写:
// 示例:DFS中直接修改父节点的值 fn dfs(current: Option<Rc<RefCell<TreeNode>>>, parent: Option<Rc<RefCell<TreeNode>>>) { if let Some(current_rc) = current { // 先克隆左右子节点,避免一直持有当前节点的借用 let left_child = current_rc.borrow().left.clone(); let right_child = current_rc.borrow().right.clone(); // 修改父节点的逻辑——这里要确保当前节点的借用已经释放 if let Some(parent_rc) = parent { // 给父节点的值加上当前节点的值 parent_rc.borrow_mut().val += current_rc.borrow().val; } // 递归处理左右子树,把当前节点作为父节点传递 dfs(left_child, Some(current_rc.clone())); dfs(right_child, Some(current_rc)); } }
这里的核心技巧是:
- 先把需要递归的子节点克隆出来,这样当前节点的
borrow()借用会立刻释放(因为clone()之后,borrow()的作用域就结束了)。 - 修改父节点时,确保没有同时持有父节点或当前节点的冲突借用——比如这里修改父节点用的是
borrow_mut(),而当前节点只用了borrow()(不可变),两者指向不同的节点,所以不会冲突。
总结
Rust的借用检查虽然麻烦,但只要摸透规则就不难应对:能通过返回值传递状态的,就别直接修改父节点;真要修改的话,一定要控制好借用的生命周期,别让冲突的借用同时存在。
备注:内容来源于stack exchange,提问作者Nithin Gowda
相关产品推荐
相关产品推荐

