外观
图算法
什么是图算法
图算法是专门用于处理图结构数据的计算方法。图由顶点和边组成,能够表示各种复杂关系网络,如社交网络、交通系统、知识图谱等。图算法解决路径查找、连通性分析、网络流优化等核心问题。
特点:
- 关系建模:表达实体间的复杂关联
- 网络分析:揭示系统结构和动态特性
- 算法多样:从简单遍历到复杂优化问题
- 应用广泛:社交网络、推荐系统、路由规划
示意图 (基本图结构):
A —— B
| |
C —— D图的基本表示
邻接矩阵
使用二维数组表示图的连接关系,矩阵元素表示顶点间的边。
特点:
- 直接访问:快速判断任意两顶点是否相邻
- 空间开销:需要 O(V²) 空间,适合稠密图
- 权值表示:可直接存储边的权重信息
javascript
// 邻接矩阵实现
export class AdjacencyMatrix {
constructor(vertexCount, directed = false) {
this.vertexCount = vertexCount;
this.directed = directed;
this.matrix = Array.from({ length: vertexCount }, () =>
new Array(vertexCount).fill(0)
);
}
addEdge(source, target, weight = 1) {
this.matrix[source][target] = weight;
if (!this.directed) {
this.matrix[target][source] = weight;
}
}
removeEdge(source, target) {
this.matrix[source][target] = 0;
if (!this.directed) {
this.matrix[target][source] = 0;
}
}
hasEdge(source, target) {
return this.matrix[source][target] !== 0;
}
getNeighbors(vertex) {
const neighbors = [];
for (let i = 0; i < this.vertexCount; i++) {
if (this.matrix[vertex][i] !== 0) {
neighbors.push({ vertex: i, weight: this.matrix[vertex][i] });
}
}
return neighbors;
}
getWeight(source, target) {
return this.matrix[source][target];
}
}示意图 (邻接矩阵):
图结构:
0 -- 1
| |
2 -- 3
邻接矩阵:
0 1 2 3
0 0 1 1 0
1 1 0 0 1
2 1 0 0 1
3 0 1 1 0邻接表
为每个顶点维护一个邻居列表,存储相连的顶点及边信息。
特点:
- 空间高效:仅存储实际存在的边,适合稀疏图
- 遍历优化:直接获取顶点的所有邻居
- 灵活扩展:易于动态添加删除顶点和边
javascript
// 邻接表实现
export class AdjacencyList {
constructor(directed = false) {
this.directed = directed;
this.adjacencyList = new Map();
}
addVertex(vertex) {
if (!this.adjacencyList.has(vertex)) {
this.adjacencyList.set(vertex, new Map());
}
}
addEdge(source, target, weight = 1) {
this.addVertex(source);
this.addVertex(target);
this.adjacencyList.get(source).set(target, weight);
if (!this.directed) {
this.adjacencyList.get(target).set(source, weight);
}
}
removeEdge(source, target) {
if (this.adjacencyList.has(source)) {
this.adjacencyList.get(source).delete(target);
}
if (!this.directed && this.adjacencyList.has(target)) {
this.adjacencyList.get(target).delete(source);
}
}
hasEdge(source, target) {
return this.adjacencyList.has(source) &&
this.adjacencyList.get(source).has(target);
}
getNeighbors(vertex) {
if (this.adjacencyList.has(vertex)) {
return Array.from(this.adjacencyList.get(vertex).entries())
.map(([neighbor, weight]) => ({ vertex: neighbor, weight }));
}
return [];
}
getWeight(source, target) {
if (this.adjacencyList.has(source) &&
this.adjacencyList.get(source).has(target)) {
return this.adjacencyList.get(source).get(target);
}
return 0;
}
getVertices() {
return Array.from(this.adjacencyList.keys());
}
}示意图 (邻接表):
图结构:
0 -- 1
| |
2 -- 3
邻接表:
0: {1: 1, 2: 1}
1: {0: 1, 3: 1}
2: {0: 1, 3: 1}
3: {1: 1, 2: 1}图遍历算法
深度优先搜索 (DFS)
沿着图的深度方向遍历,尽可能深地探索每个分支。
特点:
- 递归深度:使用栈结构 (显式或隐式) 管理遍历顺序
- 路径探索:适合寻找路径和连通分量
- 回溯机制:自然支持回溯搜索
javascript
// DFS递归实现
export function depthFirstSearch(graph, startVertex) {
const visited = new Set();
const discoveryOrder = [];
const finishOrder = [];
const parent = new Map();
let time = 0;
function dfs(vertex) {
time++;
visited.add(vertex);
discoveryOrder.push(vertex);
const neighbors = graph.getNeighbors(vertex);
for (const neighbor of neighbors) {
if (!visited.has(neighbor.vertex)) {
parent.set(neighbor.vertex, vertex);
dfs(neighbor.vertex);
}
}
time++;
finishOrder.push(vertex);
}
parent.set(startVertex, null);
dfs(startVertex);
return {
discoveryOrder,
finishOrder,
parent,
visited: Array.from(visited)
};
}
// DFS迭代实现
export function depthFirstSearchIterative(graph, startVertex) {
const visited = new Set();
const stack = [startVertex];
const discoveryOrder = [];
const parent = new Map();
visited.add(startVertex);
parent.set(startVertex, null);
while (stack.length > 0) {
const currentVertex = stack.pop();
discoveryOrder.push(currentVertex);
const neighbors = graph.getNeighbors(currentVertex);
// 逆序入栈以保证与递归版本顺序一致
for (let i = neighbors.length - 1; i >= 0; i--) {
const neighbor = neighbors[i];
if (!visited.has(neighbor.vertex)) {
visited.add(neighbor.vertex);
parent.set(neighbor.vertex, currentVertex);
stack.push(neighbor.vertex);
}
}
}
return {
discoveryOrder,
parent,
visited: Array.from(visited)
};
}
// DFS应用:检测环
export function hasCycle(graph) {
const visited = new Set();
const recursionStack = new Set();
function dfs(vertex) {
if (recursionStack.has(vertex)) {
return true; // 发现环
}
if (visited.has(vertex)) {
return false;
}
visited.add(vertex);
recursionStack.add(vertex);
const neighbors = graph.getNeighbors(vertex);
for (const neighbor of neighbors) {
if (dfs(neighbor.vertex)) {
return true;
}
}
recursionStack.delete(vertex);
return false;
}
for (const vertex of graph.getVertices()) {
if (!visited.has(vertex) && dfs(vertex)) {
return true;
}
}
return false;
}示意图 (DFS 遍历过程):
图结构:
A — B — C
| | |
D — E — F
DFS遍历顺序(从A开始):
A → B → C → F → E → D
栈状态变化:
初始: [A]
弹出A → 访问A → 推入D, B → [D, B]
弹出B → 访问B → 推入E, C → [D, E, C]
弹出C → 访问C → 推入F → [D, E, F]
弹出F → 访问F → 推入E(已访问) → [D, E]
弹出E → 访问E → 推入D(已访问) → [D]
弹出D → 访问D → 栈空广度优先搜索 (BFS)
按层次遍历图,先访问所有相邻顶点再深入下一层。
特点:
- 层次遍历:使用队列管理遍历顺序
- 最短路径:在无权图中能找到最短路径
- 系统扩展:均匀探索所有方向
javascript
// BFS实现
export function breadthFirstSearch(graph, startVertex) {
const visited = new Set();
const queue = [startVertex];
const discoveryOrder = [];
const parent = new Map();
const distance = new Map();
visited.add(startVertex);
parent.set(startVertex, null);
distance.set(startVertex, 0);
while (queue.length > 0) {
const currentVertex = queue.shift();
discoveryOrder.push(currentVertex);
const neighbors = graph.getNeighbors(currentVertex);
for (const neighbor of neighbors) {
if (!visited.has(neighbor.vertex)) {
visited.add(neighbor.vertex);
parent.set(neighbor.vertex, currentVertex);
distance.set(neighbor.vertex, distance.get(currentVertex) + 1);
queue.push(neighbor.vertex);
}
}
}
return {
discoveryOrder,
parent,
distance,
visited: Array.from(visited)
};
}
// BFS应用:最短路径(无权图)
export function shortestPathBFS(graph, startVertex, targetVertex) {
const visited = new Set();
const queue = [startVertex];
const parent = new Map();
const distance = new Map();
visited.add(startVertex);
parent.set(startVertex, null);
distance.set(startVertex, 0);
while (queue.length > 0) {
const currentVertex = queue.shift();
if (currentVertex === targetVertex) {
// 重建路径
const path = [];
let node = targetVertex;
while (node !== null) {
path.unshift(node);
node = parent.get(node);
}
return {
path,
distance: distance.get(targetVertex)
};
}
const neighbors = graph.getNeighbors(currentVertex);
for (const neighbor of neighbors) {
if (!visited.has(neighbor.vertex)) {
visited.add(neighbor.vertex);
parent.set(neighbor.vertex, currentVertex);
distance.set(neighbor.vertex, distance.get(currentVertex) + 1);
queue.push(neighbor.vertex);
}
}
}
return null; // 没有路径
}示意图 (BFS 遍历过程):
图结构:
A — B — C
| | |
D — E — F
BFS遍历顺序(从A开始):
A → B → D → C → E → F
队列状态变化:
初始: [A]
处理A → 加入B, D → [B, D]
处理B → 加入C, E → [D, C, E]
处理D → 加入E(已加入) → [C, E]
处理C → 加入F → [E, F]
处理E → 加入F(已加入) → [F]
处理F → 队列空
层次结构:
层级0: A
层级1: B, D
层级2: C, E
层级3: F最短路径算法
Dijkstra 算法
解决带权图的单源最短路径问题,要求权重非负。
特点:
- 贪心策略:每次选择当前距离最小的顶点
- 非负权重:不能处理负权边
- 最优子结构:最短路径的子路径也是最短的
javascript
// Dijkstra算法实现
export function dijkstra(graph, startVertex) {
const distances = new Map();
const visited = new Set();
const previous = new Map();
const priorityQueue = new MinPriorityQueue();
// 初始化距离
for (const vertex of graph.getVertices()) {
distances.set(vertex, Infinity);
}
distances.set(startVertex, 0);
priorityQueue.enqueue(startVertex, 0);
while (!priorityQueue.isEmpty()) {
const { element: currentVertex, priority: currentDistance } = priorityQueue.dequeue();
if (visited.has(currentVertex)) {
continue;
}
visited.add(currentVertex);
const neighbors = graph.getNeighbors(currentVertex);
for (const neighbor of neighbors) {
if (visited.has(neighbor.vertex)) {
continue;
}
const newDistance = currentDistance + neighbor.weight;
if (newDistance < distances.get(neighbor.vertex)) {
distances.set(neighbor.vertex, newDistance);
previous.set(neighbor.vertex, currentVertex);
priorityQueue.enqueue(neighbor.vertex, newDistance);
}
}
}
return { distances, previous };
}
// 从Dijkstra结果重建路径
export function reconstructPath(previous, startVertex, targetVertex) {
const path = [];
let current = targetVertex;
while (current !== undefined) {
path.unshift(current);
current = previous.get(current);
}
// 检查路径是否有效
if (path[0] === startVertex) {
return path;
}
return null; // 没有路径
}
// 最小优先队列实现
class MinPriorityQueue {
constructor() {
this.heap = [];
this.elementToIndex = new Map();
}
enqueue(element, priority) {
this.heap.push({ element, priority });
this.elementToIndex.set(element, this.heap.length - 1);
this.bubbleUp(this.heap.length - 1);
}
dequeue() {
if (this.heap.length === 0) return null;
const min = this.heap[0];
const end = this.heap.pop();
this.elementToIndex.delete(min.element);
if (this.heap.length > 0) {
this.heap[0] = end;
this.elementToIndex.set(end.element, 0);
this.sinkDown(0);
}
return min;
}
isEmpty() {
return this.heap.length === 0;
}
bubbleUp(idx) {
const element = this.heap[idx];
while (idx > 0) {
const parentIdx = Math.floor((idx - 1) / 2);
const parent = this.heap[parentIdx];
if (element.priority >= parent.priority) break;
this.heap[parentIdx] = element;
this.heap[idx] = parent;
this.elementToIndex.set(element.element, parentIdx);
this.elementToIndex.set(parent.element, idx);
idx = parentIdx;
}
}
sinkDown(idx) {
const length = this.heap.length;
const element = this.heap[idx];
while (true) {
let leftChildIdx = 2 * idx + 1;
let rightChildIdx = 2 * idx + 2;
let swap = null;
let leftChild, rightChild;
if (leftChildIdx < length) {
leftChild = this.heap[leftChildIdx];
if (leftChild.priority < element.priority) {
swap = leftChildIdx;
}
}
if (rightChildIdx < length) {
rightChild = this.heap[rightChildIdx];
if (
(swap === null && rightChild.priority < element.priority) ||
(swap !== null && rightChild.priority < leftChild.priority)
) {
swap = rightChildIdx;
}
}
if (swap === null) break;
this.heap[idx] = this.heap[swap];
this.heap[swap] = element;
this.elementToIndex.set(element.element, swap);
this.elementToIndex.set(this.heap[idx].element, idx);
idx = swap;
}
}
}示意图 (Dijkstra 算法过程):
带权图:
A
1/ \4
B — C
2\ /3
D
从A到各顶点的最短路径:
A: 0
B: 1 (A→B)
C: 4 (A→C) 或 3 (A→B→D→C)
D: 3 (A→B→D)
Dijkstra步骤:
初始: 距离{A:0, B:∞, C:∞, D:∞}
处理A → 更新B:1, C:4 → 距离{B:1, C:4, D:∞}
处理B → 更新D:1+2=3 → 距离{C:4, D:3}
处理D → 更新C:3+3=6(但4<6,不更新) → 距离{C:4}
处理C → 完成Bellman-Ford 算法
处理带负权边的单源最短路径,并能检测负权环。
特点:
- 负权处理:能够处理负权边
- 负环检测:检测图中是否存在负权环
- 动态规划:通过松弛操作逐步逼近最优解
javascript
// Bellman-Ford算法实现
export function bellmanFord(graph, startVertex) {
const distances = new Map();
const previous = new Map();
// 初始化
for (const vertex of graph.getVertices()) {
distances.set(vertex, Infinity);
}
distances.set(startVertex, 0);
// 获取所有边
const edges = [];
for (const vertex of graph.getVertices()) {
const neighbors = graph.getNeighbors(vertex);
for (const neighbor of neighbors) {
edges.push({
source: vertex,
target: neighbor.vertex,
weight: neighbor.weight
});
}
}
// 松弛操作 |V| - 1 次
for (let i = 0; i < graph.getVertices().length - 1; i++) {
let updated = false;
for (const edge of edges) {
const newDistance = distances.get(edge.source) + edge.weight;
if (newDistance < distances.get(edge.target)) {
distances.set(edge.target, newDistance);
previous.set(edge.target, edge.source);
updated = true;
}
}
// 如果没有更新,提前终止
if (!updated) break;
}
// 检查负权环
for (const edge of edges) {
if (distances.get(edge.source) + edge.weight < distances.get(edge.target)) {
throw new Error('图中存在负权环');
}
}
return { distances, previous };
}示意图 (Bellman-Ford 松弛过程):
带负权图:
A --2--> B --1--> C
\ | /
\ -3 4 -2
\ | /
---- D ----
从A开始:
初始: dist[A]=0, 其他∞
第1轮松弛:
A→B: 0+2=2 < ∞ → dist[B]=2
A→D: 0+(-3)=-3 < ∞ → dist[D]=-3
B→C: 2+1=3 < ∞ → dist[C]=3
B→D: 2+4=6 > -3 → 不更新
D→C: -3+(-2)=-5 < 3 → dist[C]=-5
第2轮松弛:
B→C: 2+1=3 > -5 → 不更新
D→C: -3+(-2)=-5 = -5 → 不更新
完成最小生成树算法
Prim 算法
通过逐步添加最小权重边来构建最小生成树。
特点:
- 贪心策略:每次选择连接树和非树节点的最小边
- 稠密图适用:在稠密图中效率较高
- 类似 Dijkstra:使用优先队列管理候选边
javascript
// Prim算法实现
export function prim(graph) {
if (graph.getVertices().length === 0) return [];
const mst = [];
const visited = new Set();
const minHeap = new MinPriorityQueue();
// 从任意顶点开始
const startVertex = graph.getVertices()[0];
visited.add(startVertex);
// 将起始顶点的边加入堆
const startNeighbors = graph.getNeighbors(startVertex);
for (const neighbor of startNeighbors) {
minHeap.enqueue(
{ from: startVertex, to: neighbor.vertex, weight: neighbor.weight },
neighbor.weight
);
}
while (!minHeap.isEmpty() && visited.size < graph.getVertices().length) {
const { element: edge } = minHeap.dequeue();
if (visited.has(edge.to)) {
continue;
}
// 添加边到MST
mst.push(edge);
visited.add(edge.to);
// 将新顶点的边加入堆
const newNeighbors = graph.getNeighbors(edge.to);
for (const neighbor of newNeighbors) {
if (!visited.has(neighbor.vertex)) {
minHeap.enqueue(
{ from: edge.to, to: neighbor.vertex, weight: neighbor.weight },
neighbor.weight
);
}
}
}
return mst;
}
// 计算MST总权重
export function calculateMSTWeight(mst) {
return mst.reduce((total, edge) => total + edge.weight, 0);
}示意图 (Prim 算法过程):
带权图:
A
1/ \4
B — C
2\ /3
D
Prim构建过程:
从A开始:
选择最小边AB(1) → MST: [AB]
从{A,B}出发:
候选边: AC(4), BD(2) → 选择BD(2) → MST: [AB, BD]
从{A,B,D}出发:
候选边: AC(4), DC(3) → 选择DC(3) → MST: [AB, BD, DC]
完成,总权重: 1+2+3=6Kruskal 算法
通过排序所有边并逐步添加不形成环的边来构建最小生成树。
特点:
- 边排序:先对所有边按权重排序
- 并查集:使用并查集高效检测环
- 稀疏图适用:在稀疏图中效率较高
javascript
// Kruskal算法实现
export function kruskal(graph) {
const mst = [];
const edges = [];
// 收集所有边
for (const vertex of graph.getVertices()) {
const neighbors = graph.getNeighbors(vertex);
for (const neighbor of neighbors) {
// 避免重复添加无向图的边
if (graph.directed || vertex <= neighbor.vertex) {
edges.push({
source: vertex,
target: neighbor.vertex,
weight: neighbor.weight
});
}
}
}
// 按权重排序边
edges.sort((a, b) => a.weight - b.weight);
const unionFind = new UnionFind(graph.getVertices());
for (const edge of edges) {
if (!unionFind.connected(edge.source, edge.target)) {
mst.push(edge);
unionFind.union(edge.source, edge.target);
// 如果已经找到V-1条边,提前终止
if (mst.length === graph.getVertices().length - 1) {
break;
}
}
}
return mst;
}
// 并查集实现
class UnionFind {
constructor(vertices) {
this.parent = new Map();
this.rank = new Map();
for (const vertex of vertices) {
this.parent.set(vertex, vertex);
this.rank.set(vertex, 0);
}
}
find(x) {
if (this.parent.get(x) !== x) {
this.parent.set(x, this.find(this.parent.get(x))); // 路径压缩
}
return this.parent.get(x);
}
union(x, y) {
const rootX = this.find(x);
const rootY = this.find(y);
if (rootX === rootY) return;
// 按秩合并
if (this.rank.get(rootX) < this.rank.get(rootY)) {
this.parent.set(rootX, rootY);
} else if (this.rank.get(rootX) > this.rank.get(rootY)) {
this.parent.set(rootY, rootX);
} else {
this.parent.set(rootY, rootX);
this.rank.set(rootX, this.rank.get(rootX) + 1);
}
}
connected(x, y) {
return this.find(x) === this.find(y);
}
}示意图 (Kruskal 算法过程):
边按权重排序:AB(1), BD(2), CD(3), AC(4), BC(5)
Kruskal步骤:
选择AB(1) → 不形成环 → MST: [AB]
选择BD(2) → 不形成环 → MST: [AB, BD]
选择CD(3) → 不形成环 → MST: [AB, BD, CD]
选择AC(4) → 形成环(A-B-D-C-A) → 跳过
选择BC(5) → 形成环 → 跳过
完成,总权重: 1+2+3=6拓扑排序
对有向无环图进行线性排序,使得对于每条边 (u→v),u 在 v 之前。
特点:
- DAG 专用:只能应用于有向无环图
- 依赖解析:表示任务间的前置关系
- 多解可能:一个 DAG 可能有多个有效排序
javascript
// Kahn算法(基于入度)
export function topologicalSortKahn(graph) {
if (!graph.directed) {
throw new Error('拓扑排序只适用于有向图');
}
const inDegree = new Map();
const queue = [];
const result = [];
// 计算入度
for (const vertex of graph.getVertices()) {
inDegree.set(vertex, 0);
}
for (const vertex of graph.getVertices()) {
const neighbors = graph.getNeighbors(vertex);
for (const neighbor of neighbors) {
inDegree.set(neighbor.vertex, inDegree.get(neighbor.vertex) + 1);
}
}
// 入度为0的顶点入队
for (const [vertex, degree] of inDegree) {
if (degree === 0) {
queue.push(vertex);
}
}
while (queue.length > 0) {
const current = queue.shift();
result.push(current);
const neighbors = graph.getNeighbors(current);
for (const neighbor of neighbors) {
inDegree.set(neighbor.vertex, inDegree.get(neighbor.vertex) - 1);
if (inDegree.get(neighbor.vertex) === 0) {
queue.push(neighbor.vertex);
}
}
}
// 检查是否有环
if (result.length !== graph.getVertices().length) {
throw new Error('图中存在环,无法进行拓扑排序');
}
return result;
}
// DFS方法拓扑排序
export function topologicalSortDFS(graph) {
if (!graph.directed) {
throw new Error('拓扑排序只适用于有向图');
}
const visited = new Set();
const temp = new Set(); // 临时标记,用于检测环
const result = [];
function dfs(vertex) {
if (temp.has(vertex)) {
throw new Error('图中存在环');
}
if (visited.has(vertex)) {
return;
}
temp.add(vertex);
const neighbors = graph.getNeighbors(vertex);
for (const neighbor of neighbors) {
dfs(neighbor.vertex);
}
temp.delete(vertex);
visited.add(vertex);
result.unshift(vertex); // 在开头添加,实现逆后序
}
for (const vertex of graph.getVertices()) {
if (!visited.has(vertex)) {
dfs(vertex);
}
}
return result;
}示意图 (拓扑排序):
有向无环图:
A → B → C
↓ ↓
D → E → F
拓扑排序结果:
有效排序1: A, B, D, C, E, F
有效排序2: A, D, B, E, C, F
无效排序: C, A, ... (违反C必须在A之后)
Kahn算法过程:
初始入度: A:0, B:1, C:1, D:1, E:2, F:2
队列: [A]
处理A → 更新B:0, D:0 → 队列: [B, D]
处理B → 更新C:0, E:1 → 队列: [D, C]
处理D → 更新E:0 → 队列: [C, E]
处理C → 更新F:1 → 队列: [E, F]
处理E → 更新F:0 → 队列: [F]
处理F → 完成连通分量算法
无向图连通分量
查找无向图中的连通分量,识别相互连接的顶点集合。
特点:
- 连通性分析:识别图中的独立组件
- DFS/BFS 应用:使用遍历算法标记连通分量
- 网络分析:用于社交网络、图像分割等
javascript
// 使用DFS查找连通分量
export function connectedComponents(graph) {
if (graph.directed) {
throw new Error('连通分量算法适用于无向图');
}
const visited = new Set();
const components = [];
for (const vertex of graph.getVertices()) {
if (!visited.has(vertex)) {
const component = [];
dfsComponent(graph, vertex, visited, component);
components.push(component);
}
}
return components;
}
function dfsComponent(graph, vertex, visited, component) {
visited.add(vertex);
component.push(vertex);
const neighbors = graph.getNeighbors(vertex);
for (const neighbor of neighbors) {
if (!visited.has(neighbor.vertex)) {
dfsComponent(graph, neighbor.vertex, visited, component);
}
}
}
// 使用并查集查找连通分量
export function unionFindComponents(graph) {
if (graph.directed) {
throw new Error('连通分量算法适用于无向图');
}
const unionFind = new UnionFind(graph.getVertices());
// 合并所有边连接的顶点
for (const vertex of graph.getVertices()) {
const neighbors = graph.getNeighbors(vertex);
for (const neighbor of neighbors) {
unionFind.union(vertex, neighbor.vertex);
}
}
// 收集连通分量
const componentsMap = new Map();
for (const vertex of graph.getVertices()) {
const root = unionFind.find(vertex);
if (!componentsMap.has(root)) {
componentsMap.set(root, []);
}
componentsMap.get(root).push(vertex);
}
return Array.from(componentsMap.values());
}示意图 (连通分量):
无向图:
A — B C — D
| | | |
E — F G — H
连通分量:
组件1: [A, B, E, F] (通过边连接)
组件2: [C, D, G, H] (通过边连接)
DFS过程:
从A开始DFS → 访问A,B,E,F → 组件1完成
从C开始DFS → 访问C,D,G,H → 组件2完成强连通分量 (Kosaraju 算法)
查找有向图中的强连通分量,其中任意两顶点相互可达。
特点:
- 有向图分析:识别有向图中的紧密连接组件
- 两次 DFS:使用正向和反向图进行遍历
- 应用广泛:用于编译器优化、电路设计等
javascript
// Kosaraju算法查找强连通分量
export function kosarajuSCC(graph) {
if (!graph.directed) {
throw new Error('强连通分量算法适用于有向图');
}
const visited = new Set();
const stack = [];
// 第一次DFS:记录完成时间
for (const vertex of graph.getVertices()) {
if (!visited.has(vertex)) {
dfsFirstPass(graph, vertex, visited, stack);
}
}
// 构建反向图
const reversedGraph = reverseGraph(graph);
// 第二次DFS:按完成时间逆序遍历
visited.clear();
const scc = [];
while (stack.length > 0) {
const vertex = stack.pop();
if (!visited.has(vertex)) {
const component = [];
dfsSecondPass(reversedGraph, vertex, visited, component);
scc.push(component);
}
}
return scc;
}
function dfsFirstPass(graph, vertex, visited, stack) {
visited.add(vertex);
const neighbors = graph.getNeighbors(vertex);
for (const neighbor of neighbors) {
if (!visited.has(neighbor.vertex)) {
dfsFirstPass(graph, neighbor.vertex, visited, stack);
}
}
stack.push(vertex);
}
function dfsSecondPass(graph, vertex, visited, component) {
visited.add(vertex);
component.push(vertex);
const neighbors = graph.getNeighbors(vertex);
for (const neighbor of neighbors) {
if (!visited.has(neighbor.vertex)) {
dfsSecondPass(graph, neighbor.vertex, visited, component);
}
}
}
function reverseGraph(graph) {
const reversed = new AdjacencyList(true);
// 添加所有顶点
for (const vertex of graph.getVertices()) {
reversed.addVertex(vertex);
}
// 反转所有边
for (const vertex of graph.getVertices()) {
const neighbors = graph.getNeighbors(vertex);
for (const neighbor of neighbors) {
reversed.addEdge(neighbor.vertex, vertex, neighbor.weight);
}
}
return reversed;
}示意图 (强连通分量):
有向图:
A → B → C → D
↑ ↓ ↑ ↓
E ← F G → H
强连通分量:
SCC1: [A, E, F, B] (相互可达)
SCC2: [C, G] (相互可达)
SCC3: [D] (单独顶点)
SCC4: [H] (单独顶点)
Kosaraju过程:
第一次DFS(完成顺序): D, H, C, G, B, F, E, A
反向图DFS:
从A开始 → 找到SCC1: [A, B, E, F]
从C开始 → 找到SCC2: [C, G]
从D开始 → 找到SCC3: [D]
从H开始 → 找到SCC4: [H]