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

如何使用栈替代递归实现白灰黑三色DFS及节点回溯标记

非递归三色DFS实现思路

原有实现无法完成回溯标记的核心原因是:栈中仅存储了节点编号,无法区分「节点首次访问,待遍历邻接节点」和「节点所有邻接节点处理完成,待回溯标记状态」两个阶段,自然无法触发父节点转Black的逻辑。

解决方法是给栈元素增加访问状态标记,用二元组(节点id, 是否已完成邻接节点处理)作为栈元素,完全模拟递归调用的栈帧生命周期:

  • 当弹出的元素标记为「未完成邻接处理」时,对应递归函数刚进入节点的阶段:标记节点为Gray,再把当前节点以「已完成邻接处理」的状态重新压栈,之后压入所有邻接节点等待处理
  • 当弹出的元素标记为「已完成邻接处理」时,对应递归函数处理完所有子节点准备返回的阶段:此时所有子节点的状态已经确定,直接判断当前节点是否所有邻接节点都是Black安全节点,是则标记当前节点为Black,否则标记为可达环的非安全节点

完整状态规则

  • 0 = White:节点未访问
  • 1 = Gray:节点在当前DFS路径中,处理中
  • 2 = Black:节点及所有子节点处理完成,为安全节点

修正后可运行代码

class Solution {
private:
    vector<int> state;
    bool dfs(vector<vector<int>>& graph, int start) {
        stack<pair<int, bool>> stk;
        stk.push({start, false});
        while (!stk.empty()) {
            auto [curr, is_processed] = stk.top();
            stk.pop();
            // 回溯阶段:所有邻接节点处理完成,标记当前节点状态
            if (is_processed) {
                bool is_safe = true;
                for (int neighbor : graph[curr]) {
                    if (state[neighbor] != 2) {
                        is_safe = false;
                        break;
                    }
                }
                state[curr] = is_safe ? 2 : 1;
                continue;
            }
            // 首次访问节点的处理逻辑
            if (state[curr] == 1) return false; // 碰到处理中节点,存在环
            if (state[curr] == 2) continue; // 已经是确认过的安全节点,跳过
            // 先压入回溯阶段的标记,再压入邻接节点
            stk.push({curr, true});
            state[curr] = 1;
            // 倒序压入邻接节点保证遍历顺序和递归一致,不要求顺序可直接正序压
            for (auto it = graph[curr].rbegin(); it != graph[curr].rend(); ++it) {
                int neighbor = *it;
                if (state[neighbor] == 1) return false; // 邻接节点在处理中,直接判定有环
                if (state[neighbor] == 0) stk.push({neighbor, false});
            }
        }
        return state[start] == 2;
    }
public:
    vector<int> eventualSafeNodes(vector<vector<int>>& graph) {
        int n = graph.size();
        state.resize(n, 0);
        vector<int> res;
        for (int i = 0; i < n; ++i) {
            if (state[i] == 2 || dfs(graph, i)) {
                res.push_back(i);
            }
        }
        return res;
    }
};

注意点

  • 原有代码存在一个语法bug:visited.resize((graph.size(),0))多嵌套了一层括号,会被解析为逗号表达式,实际初始化的数组长度为0,运行时会出现内存越界。
  • 该实现时间复杂度为O(N+E),和递归版本完全一致,每个节点、每条边仅会被处理一次。
  • 栈中每个节点最多被压入两次(首次访问、回溯标记),空间复杂度为O(N)。

内容的提问来源于stack exchange,提问作者Minh Pham

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.29 04:06:18