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

Rust中如何为只读TreeNode实现带visited的非递归栈式DFS?

Rust中基于显式栈的TreeNode非递归DFS实现

针对你遇到的问题,这里提供两种可行的实现方案,无需依赖节点值的唯一性,也能完美适配Rust中的Rc<RefCell<TreeNode>>结构:

方案一:无需额外visited集合(推荐)

这种方法通过在栈中存储节点+访问标记的元组,彻底规避了跟踪已访问节点的需求,是迭代DFS的最优实现方式。

核心思路:

  • 栈中每个元素是(节点引用, 是否已访问)的元组
  • 首次弹出未标记的节点时,按DFS顺序将子节点入栈,再处理当前节点(调整入栈顺序可实现先序/中序/后序遍历)

以先序遍历为例,代码实现如下:

use std::rc::Rc;
use std::cell::RefCell;

pub struct TreeNode {
    pub val: i32,
    pub left: Option<Rc<RefCell<TreeNode>>>,
    pub right: Option<Rc<RefCell<TreeNode>>>,
}

fn dfs_preorder(root: Option<Rc<RefCell<TreeNode>>>) {
    let mut stack = vec![(root, false)];
    
    while let Some((node_opt, visited)) = stack.pop() {
        let node = match node_opt {
            Some(n) => n,
            None => continue,
        };
        
        if !visited {
            // 栈是后进先出,先入栈右子节点再入栈左子节点,保证左子节点优先处理
            stack.push((node.borrow().right.clone(), false));
            stack.push((node.borrow().left.clone(), false));
            // 先序遍历:立即处理当前节点
            println!("{}", node.borrow().val);
        }
        // 中序遍历:将处理节点代码移至else块,入栈顺序改为「右→当前(标记已访问)→左」
        // 后序遍历:将处理节点代码移至else块,入栈顺序改为「当前(标记已访问)→右→左」
    }
}

该方案优势:无需额外内存存储已访问节点,逻辑简洁,完全符合Rust安全规则,无原始指针操作。

方案二:使用visited集合(适配C++习惯)

如果坚持要类似C++中存储指针的方式实现visited集合,可以通过获取TreeNode实例的原始内存地址作为唯一标识——每个Rc指向的TreeNode实例内存地址唯一(即使节点值相同),可用于跟踪已访问节点。

代码实现如下:

use std::rc::Rc;
use std::cell::RefCell;
use std::collections::HashSet;
use std::ptr;

pub struct TreeNode {
    pub val: i32,
    pub left: Option<Rc<RefCell<TreeNode>>>,
    pub right: Option<Rc<RefCell<TreeNode>>>,
}

fn dfs_with_visited(root: Option<Rc<RefCell<TreeNode>>>) {
    let mut stack = Vec::new();
    let mut visited = HashSet::new();
    
    if let Some(root_node) = root {
        // 获取TreeNode的原始地址作为唯一标识
        let root_ptr = ptr::addr_of!(*root_node.borrow());
        stack.push(root_node);
        visited.insert(root_ptr);
        // 先序处理根节点
        println!("{}", stack.last().unwrap().borrow().val);
    }
    
    while let Some(node) = stack.last() {
        let node_ref = node.borrow();
        
        // 优先访问左子节点
        if let Some(left) = &node_ref.left {
            let left_ptr = ptr::addr_of!(*left.borrow());
            if !visited.contains(&left_ptr) {
                stack.push(left.clone());
                visited.insert(left_ptr);
                println!("{}", left.borrow().val);
                continue;
            }
        }
        
        // 左子节点已访问,访问右子节点
        if let Some(right) = &node_ref.right {
            let right_ptr = ptr::addr_of!(*right.borrow());
            if !visited.contains(&right_ptr) {
                stack.push(right.clone());
                visited.insert(right_ptr);
                println!("{}", right.borrow().val);
                continue;
            }
        }
        
        // 左右子节点均已访问,弹出当前节点
        stack.pop();
    }
}

这里使用std::ptr::addr_of!安全获取TreeNode实例的原始地址,既不违反Rust安全规则,又能实现类似C++指针的唯一标识功能。由于树是只读状态,borrow()不会出现可变借用冲突,无需担心panic。

内容的提问来源于stack exchange,提问作者Lajos Nagy

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.23 15:36:20