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包依赖)
总结
-
DFS vs BFS:
- 需要最短路径或最少步骤 → BFS
- 需要探索所有可能性或检测环 → DFS
-
Dijkstra:
- 带权图的最短路径(权重为正)
- 比BFS更通用但更复杂
-
Kruskal:
- 寻找连接所有节点的最小成本方式
- 适合稀疏图
-
拓扑排序:
- 有依赖关系的任务排序
- 只能用于有向无环图
根据具体问题需求选择合适的图算法可以大大提高程序效率。