js算法-拓扑排序
1.5.1、邻接表格式:
图计算的有效数据格式是 邻接表格式,实际的图数据可能不是这种格式的,但是很容易转化成邻接表格式。
// 邻接表 格式 图数据
const graph = {
'A': ['B', 'C'],
'B': ['D'],
'C': ['D'],
'D': [],
};
图的应用场景:
- 任务调度:确定任务的执行顺序(如
A → B → C)。- 依赖管理:如
npm包的依赖解析。- 课程安排:根据先修课程生成学习顺序。
- 编译顺序:确定源文件的编译顺序。
图的算法:
-
算法目的:判断是否有环,确定执行顺序
-
拓扑排序:
实现思路:
- 计算入度:统计每个节点的入度(指向该节点的边的数量)。
- 初始化队列:将所有入度为 0 的节点加入队列(这些节点无前置依赖,可作为起点)。
- 生成拓扑序列:
- 从队列取出节点,加入结果序列。
- 遍历该节点的邻接节点,将其入度减 1。
- 若邻接节点入度变为 0,加入队列。
- 判断合法性:若结果序列长度等于节点总数,则为有效拓扑排序(无环);否则图有环,无法生成拓扑序列。
js实现:
/** * 拓扑排序(Kahn 算法) * @param {Object} graph 邻接表表示的有向图,格式:{ 节点: [相邻节点列表], ... } * @returns {Object} { order: 拓扑序列数组(无环时), hasCycle: 是否有环 } */ function topologicalSort(graph) { // 1. 初始化入度表 const inDegree = {}; const nodes = Object.keys(graph); // 所有节点初始入度为 0 nodes.forEach(node => { inDegree[node] = 0; }); // 计算每个节点的入度(统计被指向的次数) nodes.forEach(node => { graph[node].forEach(neighbor => { inDegree[neighbor]++; }); }); // 2. 初始化队列,存入所有入度为 0 的节点 const queue = []; nodes.forEach(node => { if (inDegree[node] === 0) { queue.push(node); } }); // 3. 生成拓扑序列 const topoOrder = []; while (queue.length > 0) { const current = queue.shift(); // 取出入度为 0 的节点 topoOrder.push(current); // 加入结果序列 // 遍历当前节点的邻接节点,入度减 1 graph[current].forEach(neighbor => { inDegree[neighbor]--; // 若邻接节点入度变为 0,加入队列 if (inDegree[neighbor] === 0) { queue.push(neighbor); } }); } // 4. 判断是否有环(序列长度等于节点总数则无环) const hasCycle = topoOrder.length !== nodes.length; return { order: hasCycle ? [] : topoOrder, // 有环时返回空序列 hasCycle }; }使用示例:
// 示例 1:无环图(DAG) const dagGraph = { 'A': ['B', 'C'], 'B': ['D'], 'C': ['D'], 'D': [] }; console.log(topologicalSort(dagGraph)); // 输出:{ order: ['A', 'B', 'C', 'D'](或其他合法顺序), hasCycle: false } // 示例 2:有环图 const cyclicGraph = { 'A': ['B'], 'B': ['C'], 'C': ['A'] // B→C→A→B 形成环 }; console.log(topologicalSort(cyclicGraph)); // 输出:{ order: [], hasCycle: true }
1.5.2、实际数据使用拓扑排序:
实际的原始DAG图数据是分开存储的(即 nodes 和 edges 分别存储),我们需要先将其转换为邻接表(Adjacency List)格式,然后再进行拓扑排序。如
-
数据结构示例
原始数据:const nodes = ['A', 'B', 'C', 'D']; // 所有节点 const edges = [ { from: 'A', to: 'B' }, // A → B { from: 'A', to: 'C' }, // A → C { from: 'B', to: 'D' }, // B → D { from: 'C', to: 'D' }, // C → D ];将其转换为邻接表格式:
const graph = { 'A': ['B', 'C'], 'B': ['D'], 'C': ['D'], 'D': [], }; -
编写一个函数,将
nodes和edges转换为邻接表:function buildGraph(nodes, edges) { const graph = {}; // 初始化所有节点 nodes.forEach(node => { graph[node] = []; }); // 构建邻接表 edges.forEach(edge => { const { from, to } = edge; graph[from].push(to); }); return graph; } // 示例 const nodes = ['A', 'B', 'C', 'D']; const edges = [ { from: 'A', to: 'B' }, { from: 'A', to: 'C' }, { from: 'B', to: 'D' }, { from: 'C', to: 'D' }, ]; const graph = buildGraph(nodes, edges); console.log(graph); // 输出: { A: ['B', 'C'], B: ['D'], C: ['D'], D: [] } -
然后继续拓扑排序

浙公网安备 33010602011771号