SorryToPerson logo
返回
算法2026-05-06·10 分钟

算法知识库:图论算法实现

JavaScript/TypeScript 实现图论基础算法,如最小生成树、图着色、最短路径等。

图论算法实现

1. 图的表示

ts
class Graph {
  private adjacencyList: Map<string, Map<string, number>> = new Map();

  addVertex(vertex: string): void {
    if (!this.adjacencyList.has(vertex)) {
      this.adjacencyList.set(vertex, new Map());
    }
  }

  addEdge(vertex1: string, vertex2: string, weight: number = 1): void {
    this.addVertex(vertex1);
    this.addVertex(vertex2);

    this.adjacencyList.get(vertex1)!.set(vertex2, weight);
    this.adjacencyList.get(vertex2)!.set(vertex1, weight);
  }

  getVertices(): string[] {
    return Array.from(this.adjacencyList.keys());
  }

  getNeighbors(vertex: string): Map<string, number> {
    return this.adjacencyList.get(vertex) || new Map();
  }

  getWeight(vertex1: string, vertex2: string): number {
    return this.adjacencyList.get(vertex1)?.get(vertex2) || Infinity;
  }
}

2. 深度优先搜索 (DFS)

ts
function dfs(graph: Graph, start: string, visited: Set<string> = new Set()): string[] {
  const result: string[] = [];
  const stack: string[] = [start];

  while (stack.length > 0) {
    const vertex = stack.pop()!;

    if (!visited.has(vertex)) {
      visited.add(vertex);
      result.push(vertex);

      const neighbors = graph.getNeighbors(vertex);
      for (const neighbor of neighbors.keys()) {
        if (!visited.has(neighbor)) {
          stack.push(neighbor);
        }
      }
    }
  }

  return result;
}

function dfsRecursive(graph: Graph, vertex: string, visited: Set<string> = new Set(), result: string[] = []): string[] {
  visited.add(vertex);
  result.push(vertex);

  const neighbors = graph.getNeighbors(vertex);
  for (const neighbor of neighbors.keys()) {
    if (!visited.has(neighbor)) {
      dfsRecursive(graph, neighbor, visited, result);
    }
  }

  return result;
}

3. 广度优先搜索 (BFS)

ts
function bfs(graph: Graph, start: string): string[] {
  const result: string[] = [];
  const visited = new Set<string>();
  const queue: string[] = [start];

  visited.add(start);

  while (queue.length > 0) {
    const vertex = queue.shift()!;
    result.push(vertex);

    const neighbors = graph.getNeighbors(vertex);
    for (const neighbor of neighbors.keys()) {
      if (!visited.has(neighbor)) {
        visited.add(neighbor);
        queue.push(neighbor);
      }
    }
  }

  return result;
}

4. Dijkstra 最短路径

ts
function dijkstra(graph: Graph, start: string): Map<string, { distance: number; path: string[] }> {
  const distances = new Map<string, number>();
  const previous = new Map<string, string>();
  const unvisited = new Set<string>();

  // 初始化
  for (const vertex of graph.getVertices()) {
    distances.set(vertex, vertex === start ? 0 : Infinity);
    unvisited.add(vertex);
  }

  while (unvisited.size > 0) {
    // 找到距离最小的顶点
    let minDistance = Infinity;
    let minVertex: string | null = null;

    for (const vertex of unvisited) {
      const distance = distances.get(vertex)!;
      if (distance < minDistance) {
        minDistance = distance;
        minVertex = vertex;
      }
    }

    if (minVertex === null || minDistance === Infinity) break;

    unvisited.delete(minVertex);

    // 更新邻居距离
    const neighbors = graph.getNeighbors(minVertex);
    for (const [neighbor, weight] of neighbors) {
      if (unvisited.has(neighbor)) {
        const alt = distances.get(minVertex)! + weight;
        if (alt < distances.get(neighbor)!) {
          distances.set(neighbor, alt);
          previous.set(neighbor, minVertex);
        }
      }
    }
  }

  // 构建结果
  const result = new Map<string, { distance: number; path: string[] }>();
  for (const vertex of graph.getVertices()) {
    const path = reconstructPath(previous, vertex);
    result.set(vertex, {
      distance: distances.get(vertex)!,
      path,
    });
  }

  return result;
}

function reconstructPath(previous: Map<string, string>, target: string): string[] {
  const path: string[] = [];
  let current: string | undefined = target;

  while (current) {
    path.unshift(current);
    current = previous.get(current);
  }

  return path;
}

5. Prim 最小生成树

ts
function primMST(graph: Graph): { mst: Array<{ from: string; to: string; weight: number }>; totalWeight: number } {
  const vertices = graph.getVertices();
  if (vertices.length === 0) return { mst: [], totalWeight: 0 };

  const mst: Array<{ from: string; to: string; weight: number }> = [];
  const visited = new Set<string>();
  const minHeap: Array<{ vertex: string; weight: number; from: string }> = [];

  // 从第一个顶点开始
  visited.add(vertices[0]);

  // 添加初始边到堆
  const neighbors = graph.getNeighbors(vertices[0]);
  for (const [neighbor, weight] of neighbors) {
    minHeap.push({ vertex: neighbor, weight, from: vertices[0] });
  }

  while (visited.size < vertices.length && minHeap.length > 0) {
    // 找到权重最小的边
    minHeap.sort((a, b) => a.weight - b.weight);
    const { vertex, weight, from } = minHeap.shift()!;

    if (visited.has(vertex)) continue;

    visited.add(vertex);
    mst.push({ from, to: vertex, weight });

    // 添加新顶点的边到堆
    const newNeighbors = graph.getNeighbors(vertex);
    for (const [neighbor, neighborWeight] of newNeighbors) {
      if (!visited.has(neighbor)) {
        minHeap.push({ vertex: neighbor, weight: neighborWeight, from: vertex });
      }
    }
  }

  const totalWeight = mst.reduce((sum, edge) => sum + edge.weight, 0);
  return { mst, totalWeight };
}

6. Kruskal 最小生成树

ts
function kruskalMST(graph: Graph): { mst: Array<{ from: string; to: string; weight: number }>; totalWeight: number } {
  const edges: Array<{ from: string; to: string; weight: number }> = [];
  const vertices = graph.getVertices();

  // 收集所有边
  for (const vertex of vertices) {
    const neighbors = graph.getNeighbors(vertex);
    for (const [neighbor, weight] of neighbors) {
      // 避免重复添加边
      if (vertex < neighbor) {
        edges.push({ from: vertex, to: neighbor, weight });
      }
    }
  }

  // 按权重排序
  edges.sort((a, b) => a.weight - b.weight);

  const unionFind = new UnionFind(vertices);
  const mst: Array<{ from: string; to: string; weight: number }> = [];

  for (const edge of edges) {
    if (!unionFind.connected(edge.from, edge.to)) {
      unionFind.union(edge.from, edge.to);
      mst.push(edge);
    }
  }

  const totalWeight = mst.reduce((sum, edge) => sum + edge.weight, 0);
  return { mst, totalWeight };
}

class UnionFind {
  private parent: Map<string, string> = new Map();
  private rank: Map<string, number> = new Map();

  constructor(vertices: string[]) {
    for (const vertex of vertices) {
      this.parent.set(vertex, vertex);
      this.rank.set(vertex, 0);
    }
  }

  find(vertex: string): string {
    if (this.parent.get(vertex) !== vertex) {
      this.parent.set(vertex, this.find(this.parent.get(vertex)!));
    }
    return this.parent.get(vertex)!;
  }

  union(vertex1: string, vertex2: string): void {
    const root1 = this.find(vertex1);
    const root2 = this.find(vertex2);

    if (root1 !== root2) {
      const rank1 = this.rank.get(root1)!;
      const rank2 = this.rank.get(root2)!;

      if (rank1 > rank2) {
        this.parent.set(root2, root1);
      } else if (rank1 < rank2) {
        this.parent.set(root1, root2);
      } else {
        this.parent.set(root2, root1);
        this.rank.set(root1, rank1 + 1);
      }
    }
  }

  connected(vertex1: string, vertex2: string): boolean {
    return this.find(vertex1) === this.find(vertex2);
  }
}

7. 图着色 (贪婪算法)

ts
function graphColoring(graph: Graph): Map<string, number> {
  const vertices = graph.getVertices();
  const colors = new Map<string, number>();
  const availableColors = new Set<number>();

  // 为每个顶点分配颜色
  for (const vertex of vertices) {
    // 找到邻居使用的颜色
    const neighborColors = new Set<number>();
    const neighbors = graph.getNeighbors(vertex);

    for (const neighbor of neighbors.keys()) {
      const neighborColor = colors.get(neighbor);
      if (neighborColor !== undefined) {
        neighborColors.add(neighborColor);
      }
    }

    // 找到第一个可用的颜色
    let color = 0;
    while (neighborColors.has(color)) {
      color += 1;
    }

    colors.set(vertex, color);
  }

  return colors;
}

8. 拓扑排序

ts
function topologicalSort(graph: Graph): string[] {
  const result: string[] = [];
  const visited = new Set<string>();
  const visiting = new Set<string>();

  const dfs = (vertex: string): boolean => {
    if (visiting.has(vertex)) return false; // 发现环
    if (visited.has(vertex)) return true;

    visiting.add(vertex);

    const neighbors = graph.getNeighbors(vertex);
    for (const neighbor of neighbors.keys()) {
      if (!dfs(neighbor)) return false;
    }

    visiting.delete(vertex);
    visited.add(vertex);
    result.unshift(vertex); // 添加到结果开头

    return true;
  };

  for (const vertex of graph.getVertices()) {
    if (!visited.has(vertex)) {
      if (!dfs(vertex)) {
        throw new Error('Graph contains a cycle');
      }
    }
  }

  return result;
}

9. 实现要点

  • 图可以用邻接表或邻接矩阵表示。
  • DFS 和 BFS 用于图遍历。
  • Dijkstra 求单源最短路径。
  • Prim 和 Kruskal 构建最小生成树。
  • 图着色解决冲突避免问题。
  • 拓扑排序用于有向无环图。
算法图论JavaScript