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

图状数据结构多重可变借用问题:DFS记忆化找最长路径的替代方案

嘿,我明白你遇到的这个头疼的问题了!在实现DFS+记忆化找树/多根树最长路径时,多重可变引用的问题确实很棘手,尤其是在有所有权/借用规则的语言里(比如Rust)。我给你几个可行的解决方案,应该能帮你绕开这个坑:

解决方案1:分离不可变图结构与记忆化缓存

这是最直接的思路——把只读的图结构和需要修改的记忆化缓存完全分开。图初始化后就不再改动,DFS时只对独立的缓存进行可变操作,这样就不会出现同一数据被多个可变引用持有的冲突了。

举个伪代码例子(以Rust为例,其他语言思路类似):

// 定义不可变的图结构,只存储节点和边的关系
#[derive(Debug)]
struct Graph {
    nodes: std::collections::HashMap<u32, Vec<u32>>, // 键是节点ID,值是子节点ID列表
}

impl Graph {
    // 计算整个图的最长路径
    pub fn longest_path(&self) -> usize {
        let mut memo = std::collections::HashMap::new();
        let mut max_length = 0;

        // 遍历所有节点(处理多根树的情况,每个根节点都要算一次)
        for &node_id in self.nodes.keys() {
            let current_length = self.dfs(node_id, &mut memo);
            if current_length > max_length {
                max_length = current_length;
            }
        }

        max_length
    }

    // 带记忆化的DFS函数
    fn dfs(&self, node_id: u32, memo: &mut std::collections::HashMap<u32, usize>) -> usize {
        // 先查缓存,存在直接返回
        if let Some(&length) = memo.get(&node_id) {
            return length;
        }

        // 没有缓存的话,递归计算所有子节点的最长路径
        let mut max_child_length = 0;
        if let Some(children) = self.nodes.get(&node_id) {
            for &child_id in children {
                let child_length = self.dfs(child_id, memo);
                if child_length > max_child_length {
                    max_child_length = child_length;
                }
            }
        }

        // 当前节点的最长路径 = 子节点最长路径 + 1(自身)
        let current_length = max_child_length + 1;
        memo.insert(node_id, current_length);
        current_length
    }
}

这里图始终是不可变引用,缓存是单独的可变HashMap,两者完全独立,完美避开了多重可变引用的问题。

解决方案2:用内部可变性处理节点内的缓存

如果更倾向于把记忆化缓存放在节点内部,那可以用内部可变性(比如Rust里的RefCell,Java里的AtomicReference等)来绕开编译期的借用检查。这种方式允许你在持有不可变节点引用的同时,修改节点内部的缓存字段。

示例代码:

use std::cell::RefCell;
use std::collections::HashMap;

struct Node {
    children: Vec<u32>,
    memo: RefCell<Option<usize>>, // 用RefCell包裹缓存,实现内部可变性
}

struct Graph {
    nodes: HashMap<u32, Node>,
}

impl Graph {
    pub fn longest_path(&self) -> usize {
        let mut max_length = 0;
        for &node_id in self.nodes.keys() {
            max_length = max_length.max(self.dfs(node_id));
        }
        max_length
    }

    fn dfs(&self, node_id: u32) -> usize {
        let node = self.nodes.get(&node_id).unwrap();
        
        // 先检查缓存
        let memo_borrow = node.memo.borrow();
        if let Some(&length) = memo_borrow.as_ref() {
            return length;
        }
        drop(memo_borrow); // 手动释放借用,避免后续修改时冲突

        // 递归计算子节点
        let mut max_child_length = 0;
        for &child_id in &node.children {
            max_child_length = max_child_length.max(self.dfs(child_id));
        }

        // 更新缓存
        let current_length = max_child_length + 1;
        *node.memo.borrow_mut() = Some(current_length);
        current_length
    }
}

注意这种方式要小心运行时的借用冲突(比如同一时间多次可变借用),不过在DFS的递归流程里,因为是深度优先,同一节点不会被同时访问多次,所以不会出问题。

备选思路:拓扑排序+动态规划

虽然作业要求用DFS+记忆化,但如果上面的方法还是有问题,你可以考虑这个等价的思路:因为你的图是树/多根树,本质是有向无环图(DAG),可以先做拓扑排序,然后从叶子节点往根节点反向遍历,用动态规划计算每个节点的最长路径。这种方法不需要递归,也完全避开了可变引用的问题,效率还可能更高。

步骤大概是:

  • 对图进行拓扑排序,得到节点的线性顺序
  • 初始化一个DP数组,每个节点的初始值为1(自身长度)
  • 从拓扑序的末尾(叶子节点)往前遍历,对每个节点,更新其所有父节点的DP值为max(父节点当前DP值, 子节点DP值 + 1)
  • 最后DP数组中的最大值就是最长路径长度

内容的提问来源于stack exchange,提问作者Nemanja Beric

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.20 08:56:08