09-图

💡 核心结论

图的本质

  • 定义:节点(顶点)+ 边的集合,G = (V, E)

  • 分类:有向/无向、有权/无权、连通/非连通

  • 存储:邻接矩阵O(V²)、邻接表O(V+E)

  • 遍历:DFS深度优先、BFS广度优先

  • 应用:社交网络、地图导航、依赖关系

存储方式对比

方式

空间

查边

遍历邻居

适用

邻接矩阵

O(V²)

O(1)

O(V)

稠密图、需频繁查边

邻接表

O(V+E)

O(degree)

O(degree)

稀疏图、节省空间

图的遍历

算法

数据结构

空间

应用

DFS

栈/递归

O(V)

路径、连通性、拓扑排序

BFS

队列

O(V)

最短路径、层级关系

经典算法

  • 最短路径:Dijkstra、Bellman-Ford、Floyd

  • 最小生成树:Prim、Kruskal

  • 拓扑排序:DFS、Kahn算法

  • 强连通分量:Kosaraju、Tarjan

🎯 图的表示

邻接矩阵

# 无向图
graph = [
    [0, 1, 1, 0],  # 0连接1,2
    [1, 0, 0, 1],  # 1连接0,3
    [1, 0, 0, 1],  # 2连接0,3
    [0, 1, 1, 0]   # 3连接1,2
]

# 有向图
graph = [
    [0, 1, 0, 0],  # 0→1
    [0, 0, 1, 1],  # 1→2,3
    [0, 0, 0, 1],  # 2→3
    [0, 0, 0, 0]   # 3无出边
]

# 带权图
graph = [
    [0, 4, 2, 0],
    [4, 0, 0, 5],
    [2, 0, 0, 3],
    [0, 5, 3, 0]
]

邻接表

# 无向图
graph = {
    0: [1, 2],
    1: [0, 3],
    2: [0, 3],
    3: [1, 2]
}

# 有向图
graph = {
    0: [1],
    1: [2, 3],
    2: [3],
    3: []
}

# 带权图
graph = {
    0: [(1, 4), (2, 2)],
    1: [(0, 4), (3, 5)],
    2: [(0, 2), (3, 3)],
    3: [(1, 5), (2, 3)]
}

🌲 DFS深度优先

递归实现

def dfs(graph, node, visited):
    visited.add(node)
    print(node)
    
    for neighbor in graph[node]:
        if neighbor not in visited:
            dfs(graph, neighbor, visited)

应用:检测环

def has_cycle(graph):
    visited = set()
    rec_stack = set()
    
    def dfs(node):
        visited.add(node)
        rec_stack.add(node)
        
        for neighbor in graph[node]:
            if neighbor not in visited:
                if dfs(neighbor):
                    return True
            elif neighbor in rec_stack:
                return True  # 找到环
        
        rec_stack.remove(node)
        return False
    
    for node in graph:
        if node not in visited:
            if dfs(node):
                return True
    return False

🌊 BFS广度优先

最短路径

from collections import deque

def shortest_path(graph, start, end):
    queue = deque([(start, [start])])
    visited = {start}
    
    while queue:
        node, path = queue.popleft()
        
        if node == end:
            return path
        
        for neighbor in graph[node]:
            if neighbor not in visited:
                visited.add(neighbor)
                queue.append((neighbor, path + [neighbor]))
    
    return None

🛤️ 拓扑排序

Kahn算法(BFS)

def topological_sort(graph):
    # 计算入度
    in_degree = {node: 0 for node in graph}
    for node in graph:
        for neighbor in graph[node]:
            in_degree[neighbor] += 1
    
    # 入度为0的节点入队
    queue = [node for node in graph if in_degree[node] == 0]
    result = []
    
    while queue:
        node = queue.pop(0)
        result.append(node)
        
        for neighbor in graph[node]:
            in_degree[neighbor] -= 1
            if in_degree[neighbor] == 0:
                queue.append(neighbor)
    
    return result if len(result) == len(graph) else None

🎯 最短路径算法

Dijkstra算法(单源最短路径)

import heapq

def dijkstra(graph, start):
    distances = {node: float('inf') for node in graph}
    distances[start] = 0
    pq = [(0, start)]  # (distance, node)
    
    while pq:
        curr_dist, curr_node = heapq.heappop(pq)
        
        if curr_dist > distances[curr_node]:
            continue
        
        for neighbor, weight in graph[curr_node]:
            distance = curr_dist + weight
            if distance < distances[neighbor]:
                distances[neighbor] = distance
                heapq.heappush(pq, (distance, neighbor))
    
    return distances

Floyd算法(所有点对最短路径)

def floyd(graph):
    n = len(graph)
    dist = [[float('inf')] * n for _ in range(n)]
    
    # 初始化
    for i in range(n):
        dist[i][i] = 0
        for j in range(n):
            if graph[i][j] != 0:
                dist[i][j] = graph[i][j]
    
    # Floyd
    for k in range(n):
        for i in range(n):
            for j in range(n):
                dist[i][j] = min(dist[i][j], dist[i][k] + dist[k][j])
    
    return dist

🌳 最小生成树

Kruskal算法(并查集)

class UnionFind:
    def __init__(self, n):
        self.parent = list(range(n))
    
    def find(self, x):
        if self.parent[x] != x:
            self.parent[x] = self.find(self.parent[x])
        return self.parent[x]
    
    def union(self, x, y):
        px, py = self.find(x), self.find(y)
        if px != py:
            self.parent[px] = py
            return True
        return False

def kruskal(n, edges):
    # edges: [(weight, u, v)]
    edges.sort()  # 按权重排序
    uf = UnionFind(n)
    mst = []
    total_weight = 0
    
    for weight, u, v in edges:
        if uf.union(u, v):
            mst.append((u, v, weight))
            total_weight += weight
    
    return mst, total_weight

📚 LeetCode练习

基础

进阶

💻 完整代码实现

Python 实现

  1"""
  2图的实现和基本算法
  3"""
  4
  5from collections import deque, defaultdict
  6
  7
  8class Graph:
  9    """图的邻接表实现"""
 10    
 11    def __init__(self, directed=False):
 12        self.graph = defaultdict(list)
 13        self.directed = directed
 14    
 15    def add_edge(self, u, v, weight=1):
 16        """添加边"""
 17        self.graph[u].append((v, weight))
 18        if not self.directed:
 19            self.graph[v].append((u, weight))
 20    
 21    def dfs(self, start):
 22        """深度优先遍历"""
 23        visited = set()
 24        result = []
 25        
 26        def dfs_helper(node):
 27            visited.add(node)
 28            result.append(node)
 29            for neighbor, _ in self.graph[node]:
 30                if neighbor not in visited:
 31                    dfs_helper(neighbor)
 32        
 33        dfs_helper(start)
 34        return result
 35    
 36    def bfs(self, start):
 37        """广度优先遍历"""
 38        visited = {start}
 39        queue = deque([start])
 40        result = []
 41        
 42        while queue:
 43            node = queue.popleft()
 44            result.append(node)
 45            
 46            for neighbor, _ in self.graph[node]:
 47                if neighbor not in visited:
 48                    visited.add(neighbor)
 49                    queue.append(neighbor)
 50        
 51        return result
 52    
 53    def has_cycle(self):
 54        """检测有向图是否有环"""
 55        visited = set()
 56        rec_stack = set()
 57        
 58        def dfs(node):
 59            visited.add(node)
 60            rec_stack.add(node)
 61            
 62            for neighbor, _ in self.graph[node]:
 63                if neighbor not in visited:
 64                    if dfs(neighbor):
 65                        return True
 66                elif neighbor in rec_stack:
 67                    return True
 68            
 69            rec_stack.remove(node)
 70            return False
 71        
 72        for node in self.graph:
 73            if node not in visited:
 74                if dfs(node):
 75                    return True
 76        return False
 77    
 78    def topological_sort(self):
 79        """拓扑排序(Kahn算法)"""
 80        in_degree = {node: 0 for node in self.graph}
 81        for node in self.graph:
 82            for neighbor, _ in self.graph[node]:
 83                in_degree[neighbor] += 1
 84        
 85        queue = [node for node in self.graph if in_degree[node] == 0]
 86        result = []
 87        
 88        while queue:
 89            node = queue.pop(0)
 90            result.append(node)
 91            
 92            for neighbor, _ in self.graph[node]:
 93                in_degree[neighbor] -= 1
 94                if in_degree[neighbor] == 0:
 95                    queue.append(neighbor)
 96        
 97        return result if len(result) == len(self.graph) else None
 98
 99
100def dijkstra(graph, start):
101    """Dijkstra最短路径算法"""
102    import heapq
103    
104    distances = {node: float('inf') for node in graph}
105    distances[start] = 0
106    pq = [(0, start)]
107    
108    while pq:
109        curr_dist, curr_node = heapq.heappop(pq)
110        
111        if curr_dist > distances[curr_node]:
112            continue
113        
114        for neighbor, weight in graph[curr_node]:
115            distance = curr_dist + weight
116            if distance < distances[neighbor]:
117                distances[neighbor] = distance
118                heapq.heappush(pq, (distance, neighbor))
119    
120    return distances
121
122
123def demo():
124    """演示图操作"""
125    print("=== 图演示 ===\n")
126    
127    # 创建无向图
128    g = Graph(directed=False)
129    g.add_edge(0, 1)
130    g.add_edge(0, 2)
131    g.add_edge(1, 3)
132    g.add_edge(2, 3)
133    
134    print("DFS遍历:", g.dfs(0))
135    print("BFS遍历:", g.bfs(0))
136    
137    # 有向图
138    print("\n=== 拓扑排序 ===\n")
139    dag = Graph(directed=True)
140    dag.add_edge(5, 2)
141    dag.add_edge(5, 0)
142    dag.add_edge(4, 0)
143    dag.add_edge(4, 1)
144    dag.add_edge(2, 3)
145    dag.add_edge(3, 1)
146    
147    print("拓扑排序:", dag.topological_sort())
148    
149    # Dijkstra
150    print("\n=== Dijkstra最短路径 ===\n")
151    graph_dict = {
152        0: [(1, 4), (2, 2)],
153        1: [(2, 1), (3, 5)],
154        2: [(3, 3)],
155        3: []
156    }
157    distances = dijkstra(graph_dict, 0)
158    print("从节点0到各节点的最短距离:", distances)
159
160
161if __name__ == '__main__':
162    demo()
163

C++ 实现

  1/**
  2 * 图的实现和基本算法
  3 */
  4
  5#include <iostream>
  6#include <vector>
  7#include <queue>
  8#include <unordered_map>
  9#include <unordered_set>
 10#include <algorithm>
 11#include <climits>
 12
 13using namespace std;
 14
 15class Graph {
 16private:
 17    unordered_map<int, vector<pair<int, int>>> graph;  // node -> [(neighbor, weight)]
 18    bool directed;
 19
 20public:
 21    Graph(bool dir = false) : directed(dir) {}
 22    
 23    void addEdge(int u, int v, int weight = 1) {
 24        graph[u].push_back({v, weight});
 25        if (!directed) {
 26            graph[v].push_back({u, weight});
 27        }
 28    }
 29    
 30    // DFS递归
 31    void dfsHelper(int node, unordered_set<int>& visited, vector<int>& result) {
 32        visited.insert(node);
 33        result.push_back(node);
 34        
 35        for (auto& [neighbor, weight] : graph[node]) {
 36            if (!visited.count(neighbor)) {
 37                dfsHelper(neighbor, visited, result);
 38            }
 39        }
 40    }
 41    
 42    vector<int> dfs(int start) {
 43        unordered_set<int> visited;
 44        vector<int> result;
 45        dfsHelper(start, visited, result);
 46        return result;
 47    }
 48    
 49    // BFS
 50    vector<int> bfs(int start) {
 51        unordered_set<int> visited;
 52        queue<int> q;
 53        vector<int> result;
 54        
 55        visited.insert(start);
 56        q.push(start);
 57        
 58        while (!q.empty()) {
 59            int node = q.front();
 60            q.pop();
 61            result.push_back(node);
 62            
 63            for (auto& [neighbor, weight] : graph[node]) {
 64                if (!visited.count(neighbor)) {
 65                    visited.insert(neighbor);
 66                    q.push(neighbor);
 67                }
 68            }
 69        }
 70        
 71        return result;
 72    }
 73    
 74    // 检测环(有向图)
 75    bool hasCycleHelper(int node, unordered_set<int>& visited, 
 76                       unordered_set<int>& recStack) {
 77        visited.insert(node);
 78        recStack.insert(node);
 79        
 80        for (auto& [neighbor, weight] : graph[node]) {
 81            if (!visited.count(neighbor)) {
 82                if (hasCycleHelper(neighbor, visited, recStack)) {
 83                    return true;
 84                }
 85            } else if (recStack.count(neighbor)) {
 86                return true;
 87            }
 88        }
 89        
 90        recStack.erase(node);
 91        return false;
 92    }
 93    
 94    bool hasCycle() {
 95        unordered_set<int> visited;
 96        unordered_set<int> recStack;
 97        
 98        for (auto& [node, edges] : graph) {
 99            if (!visited.count(node)) {
100                if (hasCycleHelper(node, visited, recStack)) {
101                    return true;
102                }
103            }
104        }
105        return false;
106    }
107    
108    // 拓扑排序(Kahn算法)
109    vector<int> topologicalSort() {
110        unordered_map<int, int> inDegree;
111        
112        for (auto& [node, edges] : graph) {
113            if (!inDegree.count(node)) {
114                inDegree[node] = 0;
115            }
116            for (auto& [neighbor, weight] : edges) {
117                inDegree[neighbor]++;
118            }
119        }
120        
121        queue<int> q;
122        for (auto& [node, degree] : inDegree) {
123            if (degree == 0) {
124                q.push(node);
125            }
126        }
127        
128        vector<int> result;
129        while (!q.empty()) {
130            int node = q.front();
131            q.pop();
132            result.push_back(node);
133            
134            for (auto& [neighbor, weight] : graph[node]) {
135                inDegree[neighbor]--;
136                if (inDegree[neighbor] == 0) {
137                    q.push(neighbor);
138                }
139            }
140        }
141        
142        return result.size() == inDegree.size() ? result : vector<int>();
143    }
144};
145
146// Dijkstra最短路径
147unordered_map<int, int> dijkstra(
148    const unordered_map<int, vector<pair<int, int>>>& graph, 
149    int start) {
150    
151    unordered_map<int, int> distances;
152    for (auto& [node, edges] : graph) {
153        distances[node] = INT_MAX;
154    }
155    distances[start] = 0;
156    
157    // 优先队列:(distance, node)
158    priority_queue<pair<int, int>, vector<pair<int, int>>, greater<>> pq;
159    pq.push({0, start});
160    
161    while (!pq.empty()) {
162        auto [currDist, currNode] = pq.top();
163        pq.pop();
164        
165        if (currDist > distances[currNode]) {
166            continue;
167        }
168        
169        if (graph.count(currNode)) {
170            for (auto& [neighbor, weight] : graph.at(currNode)) {
171                int distance = currDist + weight;
172                if (distance < distances[neighbor]) {
173                    distances[neighbor] = distance;
174                    pq.push({distance, neighbor});
175                }
176            }
177        }
178    }
179    
180    return distances;
181}
182
183int main() {
184    cout << "=== 图算法演示 ===" << endl << endl;
185    
186    // 创建无向图
187    Graph g(false);
188    g.addEdge(0, 1);
189    g.addEdge(0, 2);
190    g.addEdge(1, 3);
191    g.addEdge(2, 3);
192    
193    cout << "DFS遍历: ";
194    vector<int> dfs_result = g.dfs(0);
195    for (int node : dfs_result) {
196        cout << node << " ";
197    }
198    cout << endl;
199    
200    cout << "BFS遍历: ";
201    vector<int> bfs_result = g.bfs(0);
202    for (int node : bfs_result) {
203        cout << node << " ";
204    }
205    cout << endl << endl;
206    
207    // 拓扑排序
208    cout << "拓扑排序:" << endl;
209    Graph dag(true);
210    dag.addEdge(5, 2);
211    dag.addEdge(5, 0);
212    dag.addEdge(4, 0);
213    dag.addEdge(4, 1);
214    dag.addEdge(2, 3);
215    dag.addEdge(3, 1);
216    
217    vector<int> topo = dag.topologicalSort();
218    cout << "  ";
219    for (int node : topo) {
220        cout << node << " ";
221    }
222    cout << endl << endl;
223    
224    // Dijkstra
225    cout << "Dijkstra最短路径:" << endl;
226    unordered_map<int, vector<pair<int, int>>> weighted_graph;
227    weighted_graph[0] = {{1, 4}, {2, 2}};
228    weighted_graph[1] = {{2, 1}, {3, 5}};
229    weighted_graph[2] = {{3, 3}};
230    weighted_graph[3] = {};
231    
232    auto distances = dijkstra(weighted_graph, 0);
233    for (auto& [node, dist] : distances) {
234        cout << "  到节点" << node << "的距离: " << dist << endl;
235    }
236    
237    return 0;
238}