JavaScript 实现常见图算法

109 阅读4分钟

JavaScript 实现图算法及适用场景

下面是几种常见图算法的 JavaScript 实现及其适用场景分析。

1. 深度优先搜索 (DFS)

实现代码

class Graph {
  constructor() {
    this.adjacencyList = new Map();
  }

  addVertex(vertex) {
    if (!this.adjacencyList.has(vertex)) {
      this.adjacencyList.set(vertex, []);
    }
  }

  addEdge(v1, v2) {
    this.adjacencyList.get(v1).push(v2);
    this.adjacencyList.get(v2).push(v1); // 无向图
  }

  dfs(start) {
    const visited = new Set();
    const result = [];

    const dfsHelper = (vertex) => {
      if (!vertex) return null;
      visited.add(vertex);
      result.push(vertex);
      
      const neighbors = this.adjacencyList.get(vertex);
      for (const neighbor of neighbors) {
        if (!visited.has(neighbor)) {
          dfsHelper(neighbor);
        }
      }
    };

    dfsHelper(start);
    return result;
  }
}

// 使用示例
const graph = new Graph();
graph.addVertex('A');
graph.addVertex('B');
graph.addVertex('C');
graph.addVertex('D');
graph.addEdge('A', 'B');
graph.addEdge('A', 'C');
graph.addEdge('B', 'D');
graph.addEdge('C', 'D');

console.log(graph.dfs('A')); // ['A', 'B', 'D', 'C']

适用场景

  • 拓扑排序
  • 检测图中的环
  • 寻找连通分量
  • 解决迷宫问题
  • 路径查找(不一定是最短路径)

2. 广度优先搜索 (BFS)

实现代码

class Graph {
  // ...之前的代码...

  bfs(start) {
    const queue = [start];
    const visited = new Set([start]);
    const result = [];

    while (queue.length) {
      const current = queue.shift();
      result.push(current);

      const neighbors = this.adjacencyList.get(current);
      for (const neighbor of neighbors) {
        if (!visited.has(neighbor)) {
          visited.add(neighbor);
          queue.push(neighbor);
        }
      }
    }

    return result;
  }
}

// 使用示例
const graph = new Graph();
graph.addVertex('A');
graph.addVertex('B');
graph.addVertex('C');
graph.addVertex('D');
graph.addEdge('A', 'B');
graph.addEdge('A', 'C');
graph.addEdge('B', 'D');
graph.addEdge('C', 'D');

console.log(graph.bfs('A')); // ['A', 'B', 'C', 'D']

适用场景

  • 寻找无权图中的最短路径
  • 社交网络中查找特定距离的联系人
  • 网络爬虫
  • 广播消息(如网络广播)
  • GPS导航系统寻找最短路径

3. Dijkstra 算法(最短路径)

实现代码

class PriorityQueue {
  constructor() {
    this.values = [];
  }

  enqueue(val, priority) {
    this.values.push({ val, priority });
    this.sort();
  }

  dequeue() {
    return this.values.shift();
  }

  sort() {
    this.values.sort((a, b) => a.priority - b.priority);
  }

  isEmpty() {
    return this.values.length === 0;
  }
}

class WeightedGraph {
  constructor() {
    this.adjacencyList = new Map();
  }

  addVertex(vertex) {
    if (!this.adjacencyList.has(vertex)) {
      this.adjacencyList.set(vertex, []);
    }
  }

  addEdge(v1, v2, weight) {
    this.adjacencyList.get(v1).push({ node: v2, weight });
    this.adjacencyList.get(v2).push({ node: v1, weight });
  }

  dijkstra(start, end) {
    const distances = {};
    const previous = {};
    const pq = new PriorityQueue();
    const path = [];
    let smallest;

    // 初始化距离和前驱节点
    for (const vertex of this.adjacencyList.keys()) {
      if (vertex === start) {
        distances[vertex] = 0;
        pq.enqueue(vertex, 0);
      } else {
        distances[vertex] = Infinity;
        pq.enqueue(vertex, Infinity);
      }
      previous[vertex] = null;
    }

    while (!pq.isEmpty()) {
      smallest = pq.dequeue().val;

      if (smallest === end) {
        // 构建路径
        while (previous[smallest]) {
          path.push(smallest);
          smallest = previous[smallest];
        }
        break;
      }

      if (smallest || distances[smallest] !== Infinity) {
        for (const neighbor of this.adjacencyList.get(smallest)) {
          // 计算新距离
          const candidate = distances[smallest] + neighbor.weight;
          const nextNeighbor = neighbor.node;
          
          if (candidate < distances[nextNeighbor]) {
            // 更新距离
            distances[nextNeighbor] = candidate;
            // 更新前驱节点
            previous[nextNeighbor] = smallest;
            // 入队
            pq.enqueue(nextNeighbor, candidate);
          }
        }
      }
    }

    return path.concat(smallest).reverse();
  }
}

// 使用示例
const graph = new WeightedGraph();
graph.addVertex('A');
graph.addVertex('B');
graph.addVertex('C');
graph.addVertex('D');
graph.addVertex('E');
graph.addVertex('F');

graph.addEdge('A', 'B', 4);
graph.addEdge('A', 'C', 2);
graph.addEdge('B', 'E', 3);
graph.addEdge('C', 'D', 2);
graph.addEdge('C', 'F', 4);
graph.addEdge('D', 'E', 3);
graph.addEdge('D', 'F', 1);
graph.addEdge('E', 'F', 1);

console.log(graph.dijkstra('A', 'E')); // ['A', 'C', 'D', 'F', 'E']

适用场景

  • GPS导航系统
  • 网络路由协议(如OSPF)
  • 交通路线规划
  • 电信网络设计
  • 任何需要找到两点之间最短路径的场景(权重为正)

4. Kruskal 算法(最小生成树)

实现代码

class DisjointSet {
  constructor() {
    this.parent = {};
    this.rank = {};
  }

  makeSet(x) {
    this.parent[x] = x;
    this.rank[x] = 0;
  }

  find(x) {
    if (this.parent[x] !== x) {
      this.parent[x] = this.find(this.parent[x]);
    }
    return this.parent[x];
  }

  union(x, y) {
    const xRoot = this.find(x);
    const yRoot = this.find(y);

    if (xRoot === yRoot) return;

    if (this.rank[xRoot] < this.rank[yRoot]) {
      this.parent[xRoot] = yRoot;
    } else if (this.rank[xRoot] > this.rank[yRoot]) {
      this.parent[yRoot] = xRoot;
    } else {
      this.parent[yRoot] = xRoot;
      this.rank[xRoot]++;
    }
  }
}

function kruskal(graph) {
  const ds = new DisjointSet();
  const result = [];
  
  // 初始化不相交集合
  for (const vertex of graph.vertices) {
    ds.makeSet(vertex);
  }
  
  // 按权重排序边
  graph.edges.sort((a, b) => a.weight - b.weight);
  
  for (const edge of graph.edges) {
    const u = edge.u;
    const v = edge.v;
    
    if (ds.find(u) !== ds.find(v)) {
      result.push(edge);
      ds.union(u, v);
    }
  }
  
  return result;
}

// 使用示例
const graph = {
  vertices: ['A', 'B', 'C', 'D', 'E', 'F'],
  edges: [
    { u: 'A', v: 'B', weight: 4 },
    { u: 'A', v: 'C', weight: 2 },
    { u: 'B', v: 'E', weight: 3 },
    { u: 'C', v: 'D', weight: 2 },
    { u: 'C', v: 'F', weight: 4 },
    { u: 'D', v: 'E', weight: 3 },
    { u: 'D', v: 'F', weight: 1 },
    { u: 'E', v: 'F', weight: 1 }
  ]
};

console.log(kruskal(graph));
/*
[
  { u: 'D', v: 'F', weight: 1 },
  { u: 'E', v: 'F', weight: 1 },
  { u: 'A', v: 'C', weight: 2 },
  { u: 'C', v: 'D', weight: 2 },
  { u: 'B', v: 'E', weight: 3 }
]
*/

适用场景

  • 网络设计(如电缆、光纤布局)
  • 电路设计
  • 集群分析
  • 图像分割
  • 近似算法(如旅行商问题的近似解)

5. 拓扑排序(有向无环图)

实现代码

class DirectedGraph {
  constructor() {
    this.adjacencyList = new Map();
  }

  addVertex(vertex) {
    if (!this.adjacencyList.has(vertex)) {
      this.adjacencyList.set(vertex, []);
    }
  }

  addEdge(from, to) {
    this.adjacencyList.get(from).push(to);
  }

  topologicalSort() {
    const visited = new Set();
    const result = [];
    const vertices = Array.from(this.adjacencyList.keys());

    const dfs = (vertex) => {
      visited.add(vertex);
      
      const neighbors = this.adjacencyList.get(vertex);
      for (const neighbor of neighbors) {
        if (!visited.has(neighbor)) {
          dfs(neighbor);
        }
      }
      
      result.unshift(vertex);
    };

    for (const vertex of vertices) {
      if (!visited.has(vertex)) {
        dfs(vertex);
      }
    }

    return result;
  }
}

// 使用示例
const graph = new DirectedGraph();
graph.addVertex('A');
graph.addVertex('B');
graph.addVertex('C');
graph.addVertex('D');
graph.addVertex('E');
graph.addVertex('F');

graph.addEdge('A', 'D');
graph.addEdge('F', 'B');
graph.addEdge('B', 'D');
graph.addEdge('F', 'A');
graph.addEdge('D', 'C');

console.log(graph.topologicalSort()); // ['F', 'E', 'B', 'A', 'D', 'C']

适用场景

  • 任务调度
  • 课程安排
  • 构建系统(如Makefile)
  • 事件模拟
  • 依赖解析(如npm包依赖)

总结

  1. DFS vs BFS:

    • 需要最短路径或最少步骤 → BFS
    • 需要探索所有可能性或检测环 → DFS
  2. Dijkstra:

    • 带权图的最短路径(权重为正)
    • 比BFS更通用但更复杂
  3. Kruskal:

    • 寻找连接所有节点的最小成本方式
    • 适合稀疏图
  4. 拓扑排序:

    • 有依赖关系的任务排序
    • 只能用于有向无环图

根据具体问题需求选择合适的图算法可以大大提高程序效率。