当前位置: 首页 > news >正文

拓扑排序应用——火星词典

解密火星词典:拓扑排序的神奇应用

图的构建和拓扑排序基础

大家好!今天我们来解决一道非常有趣且经典的算法题——“火星词典”。这道题表面上看起来是关于字符串比较,但其核心思想却巧妙地指向了图论中的一个重要算法:拓扑排序

如果你对拓扑排序还不熟悉,别担心!这篇文章会带你一步步把问题拆解,将一个看似复杂的问题,转化为我们熟悉的模型,并最终用代码实现它。
在这里插入图片描述

问题初探:外星人的排序规则

首先,让我们理解一下题目。我们拿到了一本外星人语言的词典,里面的单词是按照外星字母表的顺序排好的。我们的任务是,根据这些排好序的单词,反推出这个外星字母表的顺序。

举个例子:words = ["wrt", "wrf"]

  1. 这两个单词的前两个字母 wr 都是一样的。
  2. 我们看第一个不同的地方:第三个字母,tf
  3. 因为在词典里,"wrt" 排在 "wrf" 的前面,这告诉我们一个关键信息:在外星字母表中,字母 t 一定排在字母 f 的前面

这就是解题的突破口!词典中相邻两个单词的顺序,为我们提供了字母之间先后关系的线索。

“Aha!”时刻:从先后关系到有向图

tf 前面”,这种“A precedes B”的关系,是不是很像什么?没错,这正是有向图中的一条边!

我们可以把每个出现过的字母看作图中的一个节点(Vertex)
把字母之间的先后关系看作图中的一条有向边(Directed Edge)

tf 前面 => 可以表示为一条从 t 指向 f 的边:t -> f

这条边意味着,在最终的字母表排序中,t 必须出现在 f 的左边。

如果我们把所有单词两两比较,就能得到一系列这样的先后关系,从而构建出一个完整的有向图。例如,对于 words = ["wrt","wrf","er","ett","rftt"]

  1. "wrt" vs "wrf" => t -> f
  2. "wrf" vs "er" => w -> e
  3. "er" vs "ett" => r -> t
  4. "ett" vs "rftt" => e -> r

把这些关系整合起来,我们就得到了一个图:

现在问题就转化成了:找到这个有向图的一个线性序列,使得图中所有的节点都在这个序列中,并且对于图中任意一条从 uv 的边,u 在序列中都出现在 v 的前面。

这,正是拓扑排序的定义!

宏伟蓝图

现在我们有了清晰的思路,可以制定一个三步走的战略:

  1. 数据预处理:遍历所有单词,找出所有出现过的唯一字母,作为我们图中的节点。
  2. 构建有向图:再次遍历单词列表,两两比较相邻的单词,根据第一个不同的字母找到先后关系,并在图中添加有向边。同时,我们还需要记录每个节点的“入度”(即有多少条边指向它)。
  3. 拓扑排序 (Kahn’s 算法)
    • 找到所有入度为 0 的节点,把它们放入一个队列中。这些是没有“先修课程”的字母,可以作为字母表的开头。
    • 当队列不为空时,从中取出一个节点,加入到我们的结果字符串中。
    • 接着,遍历这个节点的所有“邻居”(它指向的节点),并将这些邻居的入度减 1。这相当于“完成了一门先修课”。
    • 如果某个邻居的入度在减 1 后变成了 0,就把它也加入队列。
    • 重复这个过程,直到队列为空。

最后,检查一下结果的合法性。如果结果字符串的长度等于我们找到的唯一字母的数量,说明所有字母都成功排序,拓扑排序完成!否则,说明图中存在环(例如 a->b, b->a),这是一种矛盾的顺序,不存在合法的字母表。

代码实现(你的思路,我的解读)

下面,我们来看看你的代码是如何完美实现这个三步走战略的。

class Solution {
public:string alienOrder(vector<string>& words) {// Step 1: 数据预处理int indegree[26];int kinds = 0; // 用来统计唯一字符的数量for(int i = 0; i < 26; i++) indegree[i] = -1; // -1 表示该字符不存在for(const string& s : words){for(char ch : s){if (indegree[ch - 'a'] == -1) { // 第一次见到这个字符indegree[ch - 'a'] = 0;   // 标记为存在,初始入度为0kinds++;}}}// Step 2: 构建图和计算入度vector<vector<int>> graph(26); // 邻接表表示图for(int i = 0; i < words.size() - 1; i++){string cur = words[i];string next = words[i+1];int minLens = min(cur.size(), next.size());int j = 0;for(; j < minLens; j++) {if(cur[j] != next[j]){// 找到第一对不同字符,添加一条边,并更新入度addEdge(cur[j] - 'a', next[j] - 'a', graph, indegree);break;}}// 处理特殊情况: ["abc", "ab"] 是无效的if(j == minLens && cur.size() > next.size()) return "";}// Step 3: 拓扑排序char queue[26]; // 用一个字符数组模拟队列int l = 0, r = 0;// 将所有入度为0的节点入队for(int i = 0; i < 26; i++){if(indegree[i] == 0) queue[r++] = i + 'a';}stringstream ss; // 用于高效拼接结果字符串while(l < r){char cur = queue[l++];ss << cur;// 遍历当前节点的所有邻居for(int next : getEdgeFrom(cur - 'a', graph)){// 将邻居的入度减1if(--indegree[next] == 0){// 如果入度变为0,则入队queue[r++] = next + 'a';}}}// 最后一步:检查结果是否合法return ss.str().size() == kinds ? ss.str() : "";}// 辅助函数:添加边void addEdge(int u, int v, vector<vector<int>>& V, int indegree[]){V[u].push_back(v);indegree[v]++;}// 辅助函数:获取一个节点的所有出边const vector<int>& getEdgeFrom(int u, const vector<vector<int>>& V){return V[u];}
};

代码解读:

  1. indegree 数组:这个数组一物多用,非常巧妙。初始值 -1 判断字符是否存在,0 或正数则记录其入度。
  2. 建图逻辑:代码准确地找到了相邻单词的第一个不同点来建立依赖关系,并且正确处理了 ["abc", "ab"] 这种前缀关系导致的无效情况。
  3. 模拟队列:你用一个 char 数组和两个指针 l, r 实现了一个简单的队列,这在处理小规模数据时是完全可行的,并且效率很高。
  4. 合法性检查:最后通过比较结果字符串长度和唯一字符数 kinds 来判断是否存在环,这是拓扑排序中检测环的经典方法。
总结

“外星人词典”是一个非常棒的例子,它告诉我们如何将一个具体问题抽象成一个我们熟悉的数学模型(有向图),然后应用经典的算法(拓扑排序)来解决它。这个“建模-求解”的思维过程是算法学习中至关重要的一环。

希望这篇解析能帮助你彻底理解这道题的精髓!Happy coding!


文章转载自:

http://3j7ONzuc.tmxtr.cn
http://wizsYxFu.tmxtr.cn
http://rhAvFnpg.tmxtr.cn
http://CW44aJVG.tmxtr.cn
http://GTthS2WJ.tmxtr.cn
http://bhwV7jgh.tmxtr.cn
http://AFLsdZdJ.tmxtr.cn
http://dBY8JZyg.tmxtr.cn
http://Rzm0lABB.tmxtr.cn
http://tkZFbCyl.tmxtr.cn
http://tA0fGTEc.tmxtr.cn
http://92oAE5kO.tmxtr.cn
http://DzbhhqWZ.tmxtr.cn
http://qgSLaiby.tmxtr.cn
http://Y4dOKb1T.tmxtr.cn
http://d2Qjbwie.tmxtr.cn
http://jzqPySfO.tmxtr.cn
http://gwl21xyl.tmxtr.cn
http://kGjAz03L.tmxtr.cn
http://pNZYpvgv.tmxtr.cn
http://1am8t8Ep.tmxtr.cn
http://iu1Gftjy.tmxtr.cn
http://HKlrtbWr.tmxtr.cn
http://j4RsHxOH.tmxtr.cn
http://sHrsvxPH.tmxtr.cn
http://5VLCv9QU.tmxtr.cn
http://BqgI8J2w.tmxtr.cn
http://Pf9mIqDs.tmxtr.cn
http://aJthxa4w.tmxtr.cn
http://jTqhbGqU.tmxtr.cn
http://www.dtcms.com/a/384079.html

相关文章:

  • Afsim沿高程运动
  • PADS查看板子Pins数
  • Photoshop - Photoshop 创建照片晕影
  • 树形数据结构之树状基础-算法赛
  • 基于QGIS的DEM数据下载与预处理指南
  • 接口自动化概念篇
  • 酶活性随着温度变化的预测(多项式模型和单项式的模型对比)
  • 数据库范式(Normalization)
  • 怎么永久删除.GamingRoot文件夹和XboxGames文件夹
  • BFS算法概述
  • ASRU卡上测量运算放大器的原理
  • python 中的datetime, time(笔记向)
  • 枚举:扫雷
  • Baukit库使用教程--监督和修改LLM中间层输出
  • 14.ImGui-DX11虚表hook(一)-认识虚表
  • 15.渗透-.Linux基础命令(六)-用户管理(group文件)
  • 数字赋能农业:多场景智慧农业解决方案与平台实践解析
  • App Router vs. Pages Router:我应该如何选择?
  • 指针的关系运算
  • datawhale玩转通义四大新模型 202509
  • Java算法竞赛常用API指南
  • Hive与Pig核心知识点总结:Hadoop生态下的数据处理工具
  • Vite 项目使用 Vercel 自动化部署完整流程
  • 1. 点云与图像等进行多传感器融合 形成bev鸟瞰图,在鸟瞰图上进行物理层/逻辑层的车道线,离散,红绿灯,标识牌的标注,给鸟瞰图赋予语义
  • affordance数据集列表
  • 第11课:监控与日志系统
  • [硬件电路-213]:电流和电压的正在价值在于承载和携带可控的信息
  • XSS漏洞挖掘:核心知识点与标准化利用流程全解析
  • C++ unordered_map 与 map 的比较及选用
  • VTK基础(02):VTK中的数据结构