图的基础知识

图 (Graph) 是由顶点 (Vertex) 和边 (Edge) 组成的数据结构,用于表示对象之间的关系。

图的分类

↔️
无向图
边没有方向,关系是双向的
➡️
有向图
边有方向,关系是单向的
🔢
加权图
边带有权重/代价数值
🔄
有环图
存在从某点回到自身的路径

图的存储方式

存储方式 空间复杂度 查询边 遍历邻居 适用场景
邻接矩阵 O(V²) O(1) O(V) 稠密图
邻接表 O(V+E) O(degree) O(degree) 稀疏图
边列表 O(E) O(E) O(E) Kruskal 等算法
graph TD A[图算法] --> B[遍历算法] A --> C[最短路径] A --> D[最小生成树] A --> E[网络流] A --> F[拓扑排序] B --> B1[DFS 深度优先] B --> B2[BFS 广度优先] C --> C1[Dijkstra] C --> C2[Bellman-Ford] C --> C3[Floyd-Warshall] C --> C4[A*搜索] D --> D1[Prim] D --> D2[Kruskal] E --> E1[Ford-Fulkerson] E --> E2[Edmonds-Karp] E --> E3[Dinic] style A fill:#2c5282,color:#fff style B fill:#4299e1,color:#fff style C fill:#48bb78,color:#fff style D fill:#ed8936,color:#fff style E fill:#f56565,color:#fff style F fill:#9f7aea,color:#fff

Dijkstra 算法 - 单源最短路径

Dijkstra 算法用于在带权图中找到从起点到所有其他节点的最短路径,适用于权值为非负数的情况。

💡 核心思想
贪心策略 —— 每次选择距离起点最近且未访问的节点,更新其邻居的距离。

Dijkstra 算法可视化

准备开始 Dijkstra 算法演示
时间复杂度
O((V+E) log V)
空间复杂度
O(V)
适用条件
非负权边

🎯 Dijkstra

单源最短路径,贪心策略

时间: O((V+E) log V)
空间: O(V)

适用于非负权图,使用优先队列优化

🔄 Bellman-Ford

单源最短路径,可处理负权

时间: O(VE)
空间: O(V)

可检测负权环,适用于有负权边的图

📊 Floyd-Warshall

多源最短路径,动态规划

时间: O(V³)
空间: O(V²)

计算所有点对之间的最短路径

// Dijkstra 算法实现
function dijkstra(graph, start) {
    const n = graph.length;
    const dist = Array(n).fill(Infinity);
    const visited = Array(n).fill(false);
    const prev = Array(n).fill(-1);
    
    dist[start] = 0;
    
    for (let i = 0; i < n; i++) {
        // 找到未访问的最小距离节点
        let u = -1;
        for (let v = 0; v < n; v++) {
            if (!visited[v] && (u === -1 || dist[v] < dist[u])) {
                u = v;
            }
        }
        
        if (dist[u] === Infinity) break;
        visited[u] = true;
        
        // 更新邻居节点
        for (const [v, weight] of graph[u]) {
            if (!visited[v] && dist[u] + weight < dist[v]) {
                dist[v] = dist[u] + weight;
                prev[v] = u;
            }
        }
    }
    
    return { dist, prev };
}

// 使用优先队列优化的版本
function dijkstraPQ(graph, start) {
    const n = graph.length;
    const dist = Array(n).fill(Infinity);
    const prev = Array(n).fill(-1);
    const pq = new PriorityQueue();
    
    dist[start] = 0;
    pq.push(start, 0);
    
    while (!pq.isEmpty()) {
        const u = pq.pop();
        
        for (const [v, weight] of graph[u]) {
            if (dist[u] + weight < dist[v]) {
                dist[v] = dist[u] + weight;
                prev[v] = u;
                pq.push(v, dist[v]);
            }
        }
    }
    
    return { dist, prev };
}

// 简单优先队列实现
class PriorityQueue {
    constructor() {
        this.items = [];
    }
    
    push(element, priority) {
        this.items.push({ element, priority });
        this.items.sort((a, b) => a.priority - b.priority);
    }
    
    pop() {
        return this.items.shift().element;
    }
    
    isEmpty() {
        return this.items.length === 0;
    }
}

// 测试
const graph = [
    [[1, 4], [2, 2]],      // 节点 0
    [[0, 4], [2, 5], [3, 10]],  // 节点 1
    [[0, 2], [1, 5], [3, 3]],   // 节点 2
    [[1, 10], [2, 3]]      // 节点 3
];
console.log(dijkstra(graph, 0));
// dist: [0, 4, 2, 5]
import heapq

def dijkstra(graph, start):
    """
    Dijkstra 算法 - 优先队列优化版本
    graph: 邻接表,graph[u] = [(v, weight), ...]
    """
    n = len(graph)
    dist = [float('inf')] * n
    prev = [-1] * n
    dist[start] = 0
    
    # 优先队列:(distance, node)
    pq = [(0, start)]
    
    while pq:
        d, u = heapq.heappop(pq)
        
        if d > dist[u]:
            continue
            
        for v, weight in graph[u]:
            new_dist = dist[u] + weight
            if new_dist < dist[v]:
                dist[v] = new_dist
                prev[v] = u
                heapq.heappush(pq, (new_dist, v))
    
    return dist, prev

# 测试
graph = [
    [(1, 4), (2, 2)],           # 节点 0
    [(0, 4), (2, 5), (3, 10)],  # 节点 1
    [(0, 2), (1, 5), (3, 3)],   # 节点 2
    [(1, 10), (2, 3)]           # 节点 3
]

dist, prev = dijkstra(graph, 0)
print(f"最短距离:{dist}")  # [0, 4, 2, 5]
print(f"前驱节点:{prev}")  # [-1, 0, 0, 2]
package main

import (
    "container/heap"
    "fmt"
)

type Item struct {
    node     int
    distance int
    index    int
}

type PriorityQueue []*Item

func (pq PriorityQueue) Len() int { return len(pq) }

func (pq PriorityQueue) Less(i, j int) bool {
    return pq[i].distance < pq[j].distance
}

func (pq PriorityQueue) Swap(i, j int) {
    pq[i], pq[j] = pq[j], pq[i]
    pq[i].index = i
    pq[j].index = j
}

func (pq *PriorityQueue) Push(x interface{}) {
    item := x.(*Item)
    item.index = len(*pq)
    *pq = append(*pq, item)
}

func (pq *PriorityQueue) Pop() interface{} {
    old := *pq
    n := len(old)
    item := old[n-1]
    *pq = old[0 : n-1]
    return item
}

func dijkstra(graph [][][2]int, start int) []int {
    n := len(graph)
    dist := make([]int, n)
    for i := range dist {
        dist[i] = 1e9
    }
    dist[start] = 0

    pq := &PriorityQueue{}
    heap.Init(pq)
    heap.Push(pq, &Item{node: start, distance: 0})

    for pq.Len() > 0 {
        item := heap.Pop(pq).(*Item)
        u := item.node
        d := item.distance

        if d > dist[u] {
            continue
        }

        for _, edge := range graph[u] {
            v, weight := edge[0], edge[1]
            if dist[u]+weight < dist[v] {
                dist[v] = dist[u] + weight
                heap.Push(pq, &Item{node: v, distance: dist[v]})
            }
        }
    }

    return dist
}

func main() {
    graph := [][][2]int{
        {{1, 4}, {2, 2}},
        {{0, 4}, {2, 5}, {3, 10}},
        {{0, 2}, {1, 5}, {3, 3}},
        {{1, 10}, {2, 3}},
    }
    fmt.Println(dijkstra(graph, 0)) // [0 4 2 5]
}

Floyd-Warshall 算法 - 多源最短路径

Floyd-Warshall 算法用于计算图中所有节点对之间的最短路径,采用动态规划思想。

状态转移方程
dist[k][i][j] = min(dist[k-1][i][j], dist[k-1][i][k] + dist[k-1][k][j])
空间优化后:dist[i][j] = min(dist[i][j], dist[i][k] + dist[k][j])
function floydWarshall(graph) {
    const n = graph.length;
    const dist = Array(n).fill(null).map(() => Array(n).fill(Infinity));

    // 初始化
    for (let i = 0; i < n; i++) {
        dist[i][i] = 0;
    }

    // 填充直接边权重
    for (let u = 0; u < n; u++) {
        for (const [v, w] of graph[u]) {
            dist[u][v] = w;
        }
    }

    // Floyd-Warshall 核心
    for (let k = 0; k < n; k++) {
        for (let i = 0; i < n; i++) {
            for (let j = 0; j < n; j++) {
                if (dist[i][k] + dist[k][j] < dist[i][j]) {
                    dist[i][j] = dist[i][k] + dist[k][j];
                }
            }
        }
    }

    return dist;
}

// 测试
const graph = [
    [[1, 3], [3, 7]],
    [[2, 2]],
    [[3, 1]],
    [[0, 2]]
];
console.log(floydWarshall(graph));
def floyd_warshall(graph):
    """
    Floyd-Warshall 算法
    graph: 邻接矩阵,graph[i][j] 表示 i 到 j 的距离
    """
    n = len(graph)
    dist = [row[:] for row in graph]  # 深拷贝

    # 对角线为 0
    for i in range(n):
        dist[i][i] = 0

    # 核心算法
    for k in range(n):
        for i in range(n):
            for j in range(n):
                if dist[i][k] + dist[k][j] < dist[i][j]:
                    dist[i][j] = dist[i][k] + dist[k][j]

    return dist

# 测试
INF = float('inf')
graph = [
    [0, 3, INF, 7],
    [INF, 0, 2, INF],
    [INF, INF, 0, 1],
    [2, INF, INF, 0]
]

result = floyd_warshall(graph)
for row in result:
    print(row)
package main

import (
    "fmt"
    "math"
)

const INF = math.MaxInt32

func floydWarshall(graph [][]int) [][]int {
    n := len(graph)
    dist := make([][]int, n)
    for i := range dist {
        dist[i] = make([]int, n)
        copy(dist[i], graph[i])
    }

    // Floyd-Warshall 核心
    for k := 0; k < n; k++ {
        for i := 0; i < n; i++ {
            for j := 0; j < n; j++ {
                if dist[i][k] != INF && dist[k][j] != INF {
                    if dist[i][k]+dist[k][j] < dist[i][j] {
                        dist[i][j] = dist[i][k] + dist[k][j]
                    }
                }
            }
        }
    }

    return dist
}

func main() {
    graph := [][]int{
        {0, 3, INF, 7},
        {INF, 0, 2, INF},
        {INF, INF, 0, 1},
        {2, INF, INF, 0},
    }

    result := floydWarshall(graph)
    for _, row := range result {
        for _, val := range row {
            if val == INF {
                fmt.Print("INF ")
            } else {
                fmt.Printf("%d ", val)
            }
        }
        fmt.Println()
    }
}