导航菜单

图(Graph)是一种复杂的非线性数据结构,由节点(顶点)和边组成。图广泛应用于社交网络、路由算法、依赖关系等领域。掌握图的算法对于解决许多实际问题至关重要。

图的基础

图的定义

图 G = (V, E) 由顶点集合 V 和边集合 E 组成。

图的类型

1. 有向图 vs 无向图

  • 有向图:边有方向,从 u 到 v
  • 无向图:边无方向,u 和 v 相互连接

2. 加权图 vs 无权图

  • 加权图:边有权重(距离、成本等)
  • 无权图:边没有权重

3. 连通图 vs 非连通图

  • 连通图:任意两个顶点之间都有路径
  • 非连通图:存在不连通的顶点

图的表示方法

1. 邻接矩阵(Adjacency Matrix)

class Graph:
    def __init__(self, num_vertices):
        self.num_vertices = num_vertices
        self.matrix = [[0] * num_vertices for _ in range(num_vertices)]
    
    def add_edge(self, u, v, weight=1):
        self.matrix[u][v] = weight
        # 如果是无向图,还需要:
        # self.matrix[v][u] = weight
    
    def has_edge(self, u, v):
        return self.matrix[u][v] != 0

优点

  • 查找边的时间复杂度 O(1)
  • 适合稠密图

缺点

  • 空间复杂度 O(V²)
  • 不适合稀疏图

2. 邻接表(Adjacency List)

from collections import defaultdict

class Graph:
    def __init__(self):
        self.graph = defaultdict(list)
    
    def add_edge(self, u, v, weight=1):
        self.graph[u].append((v, weight))
        # 如果是无向图,还需要:
        # self.graph[v].append((u, weight))
    
    def get_neighbors(self, u):
        return self.graph[u]

优点

  • 空间复杂度 O(V + E)
  • 适合稀疏图
  • 遍历邻接节点高效

缺点

  • 查找边的时间复杂度 O(degree)

图的遍历

1. 深度优先搜索(DFS)

def dfs(graph, start, visited=None):
    if visited is None:
        visited = set()
    
    visited.add(start)
    print(start)
    
    for neighbor, _ in graph.get_neighbors(start):
        if neighbor not in visited:
            dfs(graph, neighbor, visited)

# 迭代实现
def dfs_iterative(graph, start):
    stack = [start]
    visited = set()
    
    while stack:
        node = stack.pop()
        if node not in visited:
            visited.add(node)
            print(node)
            for neighbor, _ in graph.get_neighbors(node):
                if neighbor not in visited:
                    stack.append(neighbor)

2. 广度优先搜索(BFS)

from collections import deque

def bfs(graph, start):
    queue = deque([start])
    visited = {start}
    
    while queue:
        node = queue.popleft()
        print(node)
        
        for neighbor, _ in graph.get_neighbors(node):
            if neighbor not in visited:
                visited.add(neighbor)
                queue.append(neighbor)

最短路径算法

1. Dijkstra 算法(单源最短路径,非负权重)

import heapq

def dijkstra(graph, start):
    distances = {node: float('inf') for node in graph.graph}
    distances[start] = 0
    pq = [(0, start)]
    visited = set()
    
    while pq:
        dist, node = heapq.heappop(pq)
        if node in visited:
            continue
        visited.add(node)
        
        for neighbor, weight in graph.get_neighbors(node):
            if neighbor not in visited:
                new_dist = dist + weight
                if new_dist < distances[neighbor]:
                    distances[neighbor] = new_dist
                    heapq.heappush(pq, (new_dist, neighbor))
    
    return distances

2. Bellman-Ford 算法(单源最短路径,可处理负权重)

def bellman_ford(graph, start, num_vertices):
    distances = {i: float('inf') for i in range(num_vertices)}
    distances[start] = 0
    
    # 松弛操作
    for _ in range(num_vertices - 1):
        for u in graph.graph:
            for v, weight in graph.get_neighbors(u):
                if distances[u] + weight < distances[v]:
                    distances[v] = distances[u] + weight
    
    # 检测负权环
    for u in graph.graph:
        for v, weight in graph.get_neighbors(u):
            if distances[u] + weight < distances[v]:
                return None  # 存在负权环
    
    return distances

3. Floyd-Warshall 算法(全源最短路径)

def floyd_warshall(graph, num_vertices):
    dist = [[float('inf')] * num_vertices for _ in range(num_vertices)]
    
    # 初始化
    for i in range(num_vertices):
        dist[i][i] = 0
    
    for u in graph.graph:
        for v, weight in graph.get_neighbors(u):
            dist[u][v] = weight
    
    # 动态规划
    for k in range(num_vertices):
        for i in range(num_vertices):
            for j in range(num_vertices):
                dist[i][j] = min(dist[i][j], dist[i][k] + dist[k][j])
    
    return dist

拓扑排序

拓扑排序用于有向无环图(DAG),常用于任务调度、依赖关系等。

def topological_sort(graph, num_vertices):
    in_degree = [0] * num_vertices
    
    # 计算入度
    for u in graph.graph:
        for v, _ in graph.get_neighbors(u):
            in_degree[v] += 1
    
    # 找到所有入度为 0 的节点
    queue = deque([i for i in range(num_vertices) if in_degree[i] == 0])
    result = []
    
    while queue:
        node = queue.popleft()
        result.append(node)
        
        for neighbor, _ in graph.get_neighbors(node):
            in_degree[neighbor] -= 1
            if in_degree[neighbor] == 0:
                queue.append(neighbor)
    
    if len(result) != num_vertices:
        return None  # 存在环
    
    return result

最小生成树

1. Kruskal 算法

class UnionFind:
    def __init__(self, n):
        self.parent = list(range(n))
        self.rank = [0] * 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):
        root_x = self.find(x)
        root_y = self.find(y)
        if root_x == root_y:
            return False
        if self.rank[root_x] < self.rank[root_y]:
            self.parent[root_x] = root_y
        elif self.rank[root_x] > self.rank[root_y]:
            self.parent[root_y] = root_x
        else:
            self.parent[root_y] = root_x
            self.rank[root_x] += 1
        return True

def kruskal(edges, num_vertices):
    edges.sort(key=lambda x: x[2])  # 按权重排序
    uf = UnionFind(num_vertices)
    mst = []
    
    for u, v, weight in edges:
        if uf.union(u, v):
            mst.append((u, v, weight))
            if len(mst) == num_vertices - 1:
                break
    
    return mst

2. Prim 算法

import heapq

def prim(graph, start, num_vertices):
    mst = []
    visited = {start}
    pq = []
    
    for neighbor, weight in graph.get_neighbors(start):
        heapq.heappush(pq, (weight, start, neighbor))
    
    while pq and len(visited) < num_vertices:
        weight, u, v = heapq.heappop(pq)
        if v in visited:
            continue
        visited.add(v)
        mst.append((u, v, weight))
        
        for neighbor, w in graph.get_neighbors(v):
            if neighbor not in visited:
                heapq.heappush(pq, (w, v, neighbor))
    
    return mst

常见题目

1. 课程表(拓扑排序)

def can_finish(num_courses, prerequisites):
    graph = defaultdict(list)
    in_degree = [0] * num_courses
    
    for course, prereq in prerequisites:
        graph[prereq].append(course)
        in_degree[course] += 1
    
    queue = deque([i for i in range(num_courses) if in_degree[i] == 0])
    count = 0
    
    while queue:
        node = queue.popleft()
        count += 1
        for neighbor in graph[node]:
            in_degree[neighbor] -= 1
            if in_degree[neighbor] == 0:
                queue.append(neighbor)
    
    return count == num_courses

2. 岛屿数量(DFS/BFS)

def num_islands(grid):
    if not grid:
        return 0
    
    rows, cols = len(grid), len(grid[0])
    count = 0
    
    def dfs(r, c):
        if r < 0 or r >= rows or c < 0 or c >= cols or grid[r][c] == '0':
            return
        grid[r][c] = '0'
        dfs(r + 1, c)
        dfs(r - 1, c)
        dfs(r, c + 1)
        dfs(r, c - 1)
    
    for r in range(rows):
        for c in range(cols):
            if grid[r][c] == '1':
                count += 1
                dfs(r, c)
    
    return count

总结

图是算法面试中的高级数据结构:

  1. 表示方法:邻接矩阵、邻接表
  2. 遍历算法:DFS、BFS
  3. 最短路径:Dijkstra、Bellman-Ford、Floyd-Warshall
  4. 拓扑排序:用于 DAG 的排序
  5. 最小生成树:Kruskal、Prim

掌握图的算法能够帮助你解决许多复杂的实际问题。


数据结构部分的学习就到这里了!接下来,让我们进入算法部分,学习各种高效的算法技巧。

搜索