You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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));
    }
}

这里的核心技巧是:

  1. 先把需要递归的子节点克隆出来,这样当前节点的borrow()借用会立刻释放(因为clone()之后,borrow()的作用域就结束了)。
  2. 修改父节点时,确保没有同时持有父节点或当前节点的冲突借用——比如这里修改父节点用的是borrow_mut(),而当前节点只用了borrow()(不可变),两者指向不同的节点,所以不会冲突。

总结

Rust的借用检查虽然麻烦,但只要摸透规则就不难应对:能通过返回值传递状态的,就别直接修改父节点;真要修改的话,一定要控制好借用的生命周期,别让冲突的借用同时存在。

备注:内容来源于stack exchange,提问作者Nithin Gowda

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.17 08:08:05