std::priority_queue默认为最大堆,而字典序最小拓扑序需最小堆,故须显式指定小顶堆比较器;同时必须检查res.size()是否等于节点总数以可靠判环。

为什么 std::priority_queue 不能直接用于拓扑排序的贪心选点?
因为默认的 std::priority_queue 是最大堆,而拓扑排序中我们希望每次取出当前入度为 0 且编号(或字典序)最小的节点——这需要最小堆语义。直接用 std::priority_queue<int></int> 会优先弹出最大编号节点,结果不符合“字典序最小拓扑序”要求。
解决方法是显式指定比较器:std::priority_queue<int vector>, greater<int>></int></int>,或者自定义比较逻辑(比如按节点名排序)。注意:仅改容器类型不够,必须确认底层比较行为与贪心目标一致。
- 若图节点是字符串或自定义结构体,必须提供
operator 或传入 lambda(C++20 起支持 lambda 作为模板参数) -
greater<int></int>对应升序(小根堆),但需配合vector作为底层容器;用deque会编译失败 - 不要误用
priority_queue<int vector>, less<int>></int></int>—— 这仍是默认最大堆,和不写一样
如何在维护入度的同时,把新入度为 0 的节点及时加入 priority_queue?
关键在于:每次从队列弹出一个节点 u 后,遍历其所有邻接点 v,对每个 v 执行 indeg[v]--;一旦 indeg[v] == 0,立刻 push 到优先队列。这个“即时插入”保证了贪心选择始终在当前可用节点集中进行。
常见错误是批量收集再插入,或漏掉某条边的入度更新,导致节点永远无法入队,最终结果长度小于节点总数(说明有环或遗漏)。
- 初始化时,所有
indeg[u] == 0的节点必须全部push,不能只推一个 - 图用邻接表存,推荐
vector<vector>></vector>,索引即节点 ID;若节点编号不连续(如字符串),需先离散化映射到0..n-1 - 入度数组大小必须覆盖所有可能节点 ID,否则越界访问(尤其用
int indeg[N]时 N 要足够)
priority_queue 拓扑排序比普通队列慢吗?什么时候该换?
是的,每次 push/ pop 是 O(log n),整体复杂度从 O(V + E) 变成 O((V + E) log V)。但这是为满足字典序最小拓扑序付出的合理代价。
如果题目只要求“任意一种合法拓扑序”,就该用 queue 或 vector + 线性扫描——此时 priority_queue 不仅慢,还掩盖了环检测逻辑(比如队列空但结果长度不足,未必代表有环,可能是贪心剪枝过早)。
- LeetCode 269、210 等题明确要求“字典序最小”,才必须用
priority_queue - 当节点数
V > 1e5且边稀疏时,priority_queue带来的常数开销会明显可见,可考虑用 pb_ds 的配对堆(非标准库)优化 - 调试时可在每次
pop后打印当前节点,快速验证是否按预期顺序出队(如 0,1,2… 而非 2,0,1…)
完整可运行片段里最容易被忽略的边界点
不是算法逻辑,而是输入解析和环检测的耦合方式。很多人写完主循环就结束,但没检查最终结果长度是否等于节点总数 —— 这才是判断有环的唯一可靠依据。用 priority_queue 时尤其容易误判:某个节点因贪心延迟入队,但其实它本该更早出现,结果被当作“不可达”跳过。
- 必须在 while 循环结束后加一句:
if (res.size() != n) return {};(空 vector 表示有环) - 初始化入度数组时,别只扫边列表,要确保每个节点(包括孤立点)都被纳入计数范围
- 若输入含重边,
indeg[v]--会被重复执行,导致入度减多;应在建图时去重,或用set存邻接关系
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











