js算法-拓扑排序

1.5.1、邻接表格式:

图计算的有效数据格式是 邻接表格式,实际的图数据可能不是这种格式的,但是很容易转化成邻接表格式。

// 邻接表 格式 图数据
const graph = {
  'A': ['B', 'C'],
  'B': ['D'],
  'C': ['D'],
  'D': [],
};

图的应用场景

  • 任务调度:确定任务的执行顺序(如 A → B → C)。
  • 依赖管理:如 npm 包的依赖解析。
  • 课程安排:根据先修课程生成学习顺序。
  • 编译顺序:确定源文件的编译顺序。

图的算法

  • 算法目的:判断是否有环,确定执行顺序

  • 拓扑排序:

    实现思路:

    1. 计算入度:统计每个节点的入度(指向该节点的边的数量)。
    2. 初始化队列:将所有入度为 0 的节点加入队列(这些节点无前置依赖,可作为起点)。
    3. 生成拓扑序列:
      • 从队列取出节点,加入结果序列。
      • 遍历该节点的邻接节点,将其入度减 1。
      • 若邻接节点入度变为 0,加入队列。
    4. 判断合法性:若结果序列长度等于节点总数,则为有效拓扑排序(无环);否则图有环,无法生成拓扑序列。

    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图数据是分开存储的(即 nodesedges 分别存储),我们需要先将其转换为邻接表(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': [],
    };
    
  • 编写一个函数,将 nodesedges 转换为邻接表:

    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: [] }
    
  • 然后继续拓扑排序

posted @ 2025-08-07 12:28  吴飞ff  阅读(33)  评论(0)    收藏  举报