跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客GitHub 精选镜像工具UI配色美学隐私政策关于联系
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
C++算法

算法实战:课程表环检测与 Trie 前缀树实现

通过两道经典算法题讲解图论与数据结构。第一部分使用 DFS 三色标记法解决课程表问题中的有向环检测;第二部分实现 Trie 前缀树,利用空间换时间优化字符串匹配。文中提供 C++ 代码实现及复杂度分析,帮助理解 DFS 应用场景与树形结构设计。

数字游民发布于 2026/3/23更新于 2026/5/2917K 浏览
算法实战:课程表环检测与 Trie 前缀树实现

在算法题库的中高频题目中,图论中的环检测和树形结构的设计是两个绕不开的坎。

本文通过两道经典题目——207. 课程表 和 208. 实现 Trie (前缀树),来深入理解 DFS(深度优先搜索)在不同场景下的妙用,以及如何亲手设计一个高效的数据结构。


一、课程表 (Course Schedule)

1. 题目核心:图的有向环检测

题目给定了一组课程的依赖关系(比如想学 A 必须先学 B),问我们能不能修完所有课。这本质上是在问:这个有向图中是否存在环? 如果存在环(例如 A->B->C->A),就会形成'死锁',导致无法完成。

2. 解题法宝:三色标记法 (DFS)

为了防止 DFS 陷入死循环,并准确判断'当前路径'是否有环,我们需要三个状态,而不仅仅是访问过/没访问过。

  • 0 (白色/White):未访问。完全没探索过的未知区域。
  • 1 (灰色/Gray):正在访问中。当前侦探正在走这条路,还没走到底。如果 DFS 过程中撞见了 1,说明咬到了自己的尾巴,发现环!
  • 2 (黑色/Black):已完成。这条路已经走到底并退出来了,确认是安全的(无环)。
3. 代码实现 (C++)

在使用 DFS 遍历邻接表时,最容易犯的错误就是混淆循环下标和邻居节点的值,代码中已重点标注。

class Solution {
    vector<vector<int>> g; // 邻接表:存图
    vector<int> sign;      // 三色标记数组

    // 思路:基于三色标记法的 DFS 思路
    bool dfs(int i) {
        sign[i] = 1; // 标记为 1 (灰色/正在访问),表示'正在这条路上'
        
        // 遍历当前节点 i 的所有邻居
        for (int j = 0; j < g[i].size(); ++j) {
            int neighbor = g[i][j]; // 【关键】取出真正的邻居节点,千万别用成 j
            
            // 情况一:撞到了'正在访问'的节点 (sign=1)
            // 说明我们绕了一圈又回到了当前路径上的某个点!这就是环!
            if (sign[neighbor] == 1) {
                 ;
            }
            
            
             (sign[neighbor] == ) {
                 ((neighbor)) {
                     ; 
                }
            }
            
            
        }
        
        
        sign[i] = ;
         ;
    }

:
    {
        
        g.(numCourses, <>());
        sign.(numCourses, ); 
        
        
        
         ( i = ; i < prerequisites.(); ++i) {
             prev = prerequisites[i][];
             curr = prerequisites[i][];
            g[curr].(prev);
        }
        
        
        
         ( i = ; i < numCourses; ++i) {
            
             (sign[i] == ) {
                 flag = (i);
                 (flag == ) { 
                     ;   
                }
            }
        }
         ;
    }
};
return
true
// 情况二:是新节点 (sign=0),继续递归深入
if
0
if
dfs
return
true
// 下级汇报有环,我也返回有环
// 情况三:sign=2 (安全节点),直接跳过
// 注意:不能在这里 return false,因为还要检查别的邻居!
// 结束了,标记为 2 (黑色/安全),表示此节点及其后代都没环
2
return
false
public
bool canFinish(int numCourses, vector<vector<int>>& prerequisites)
// 1. 初始化
assign
vector
int
assign
0
// 初始化为 0 (白色)
// 2. 建图:把边列表转换为邻接表
// [1, 0] 表示修 1 必须先修 0,即流向为 0 -> 1
for
int
0
size
int
0
int
1
push_back
// 3. 遍历每一门课,逐个让 DFS 去探路
// 注意:必须遍历所有课程 numCourses,防止图是非连通的
for
int
0
// 只有没去过的才需要查 (sign=0)
if
0
bool
dfs
if
true
// dfs 返回 true 说明有环
return
false
// 有环 = 完不成
return
true
4. 复杂度分析
  • 时间复杂度:O(V + E)。
    • V 是课程数(节点),E 是依赖关系数(边)。
    • DFS 过程中,每个节点最多由 0 变 1 再变 2 一次,每条边最多被检查一次。
  • 空间复杂度:O(V + E)。
    • 邻接表 g 需要 O(V + E) 的空间。
    • 标记数组 sign 需要 O(V) 的空间。
    • 递归栈的深度最大为 O(V)。

二、实现 Trie (前缀树/字典树)

1. 题目核心:空间换时间

Trie 是一种专门处理字符串匹配的树形结构。

  • 特点:每个节点代表一个字符的位置,通常有 26 个分支(对应 'a'-'z')。
  • 优势:利用字符串的公共前缀来减少查询时间。查找 "apple" 和 "app" 会走同一条路。
2. 优化思路:万能的 Find 函数

题目要求实现 search(查完整单词)和 startsWith(查前缀)。为了代码复用,我们设计一个辅助函数 find(word),用返回值区分三种状态:

  • 返回 0:路断了,不存在。
  • 返回 1:路通了,但没遇到结束标记,说明前缀存在。
  • 返回 2:路通了,且遇到了 end=true,说明完整单词存在。
3. 代码实现 (C++)

关于析构函数:在 LeetCode 刷题中,为了代码简洁和运行速度,通常省略手动 delete 内存的步骤(程序结束时系统会统一回收)。但在实际工程开发中,必须写析构函数来防止内存泄漏。

struct Node {
    Node* son[26]{}; // 26 个分支,代表 'a'-'z',初始化为空指针
    bool end = false; // 标记:走到这里是否是一个完整的单词
};

class Trie {
    // 根节点,初始为空
    Node* root = new Node();

    // 【核心逻辑】万能查找函数
    // 返回值含义:0=不存在,1=前缀存在,2=完整单词存在
    int find(string word) {
        Node* cur = root;
        for (char c : word) {
            c -= 'a'; // 将字符映射为 0-25 的索引
            // 如果路断了 (空指针),直接返回 0
            if (cur->son[c] == nullptr) {
                return 0;
            }
            cur = cur->son[c]; // 继续向下走
        }
        // 循环走完,说明路通了。
        // 检查 end 标记:如果是 true 返回 2,否则返回 1
        return cur->end ? 2 : 1;
    }

public:
    // 构造函数
    Trie() {}

    // 插入单词:类似'造路'的过程
    void insert(string word) {
        Node* cur = root;
        for (char c : word) {
            c -= 'a';
            // 如果前方没路,就 new 一个新节点出来
            if (cur->son[c] == nullptr) {
                cur->son[c] = new Node();
            }
            cur = cur->son[c];
        }
        // 单词走完,打上结束标记
        cur->end = true;
    }

    // 查找完整单词:要求 find 返回 2
    bool search(string word) {
        return find(word) == 2;
    }

    // 查找前缀:只要 find 返回不是 0 (即 1 或 2 都可以)
    bool startsWith(string prefix) {
        return find(prefix) != 0;
    }
};
4. 复杂度分析

假设我们要插入或查询的字符串长度为 L。

  • 时间复杂度:O(L)。
    • 无论是插入、查找还是前缀匹配,我们都只需要遍历一次字符串,树的高度由字符串长度决定,与字典中存了多少个词无关。
  • 空间复杂度:O(N * L * 26)。
    • 最坏情况下(没有公共前缀),每个字符都需要一个新节点。
    • N 是单词数量,L 是平均长度,26 是字符集大小。
    • 实际应用中,由于前缀复用,真实空间占用会远小于最坏情况。

总结

  • 课程表教会了我们如何用 DFS + 三色标记法 解决图的环检测问题。
  • Trie 教会了我们如何用 树形结构 + 指针 极其高效地处理字符串前缀问题。

目录

  1. 一、课程表 (Course Schedule)
  2. 1. 题目核心:图的有向环检测
  3. 2. 解题法宝:三色标记法 (DFS)
  4. 3. 代码实现 (C++)
  5. 4. 复杂度分析
  6. 二、实现 Trie (前缀树/字典树)
  7. 1. 题目核心:空间换时间
  8. 2. 优化思路:万能的 Find 函数
  9. 3. 代码实现 (C++)
  10. 4. 复杂度分析
  11. 总结
  • 💰 8折买阿里云服务器限时8折了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 🤖 一键搭建Deepseek满血版了解详情
  • 一键打造专属AI 智能体了解详情
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • Python 业余时间变现指南:兼职接单、爬虫技术与风险规避
  • 七款主流大模型英文降重能力实测与对比
  • 哈希表实现原理与代码详解
  • Java 核心面试题精选与深度解析
  • 机器人全身控制(WBC)原理详解
  • Docker 性能优化:为何必须及时清理 exited 状态容器
  • OpenClaw 网关与子节点完整配对指南:构建分布式 AI 助手网络
  • 数据结构:栈与队列详解
  • AI 时代前端设计稿生成实战:三种高效工具流
  • Linux 系统简介
  • Altium Designer AI 实战:PCB 设计全流程高效指南
  • Claude Code Superpowers:让 AI 像资深工程师一样工作
  • TRAE AI 智能体实现 Vue 3 + Node.js + MySQL 全栈项目实战
  • Sudachi 开源模拟器跨平台使用指南
  • Python 能做什么?常见应用场景与学习指南
  • Git 远程协作从安装到提交:常见问题的实战解决方案
  • learn-claude-code:从零理解 AI Agent 设计与实现
  • MacBook 配置 Java 开发环境指南
  • C++ 继承入门:从概念定义到默认成员函数详解
  • DeerFlow 2.0:基于 LangGraph 的开源智能体编排框架

相关免费在线工具

  • 加密/解密文本

    使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online

  • Gemini 图片去水印

    基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online

  • HTML转Markdown

    将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online