🕸️ 图结构深度解析

从基础概念到经典算法 - 数据结构可视化学习平台

📚

什么是图结构?

📖 基本定义

图(Graph)是由顶点(Vertex)边(Edge)组成的非线性数据结构,表示为 G = (V, E)。

与树不同,图可以有环,任意两个顶点之间可以有边相连,是描述复杂关系的最通用数据结构。

✨ 核心特点

• 顶点之间可以有多条路径

• 可以存在环(循环路径)

• 边可以有方向(有向图)或无方向(无向图)

• 边可以带权重,表示距离、成本等

🎯 图 vs 树

树:特殊的图,无环且连通

图:可以有环,可以不连通

• 树有 n-1 条边(n个节点)

• 图的边数可以是 0 到 n(n-1)/2

💡 核心理解: 图是最灵活的数据结构,能够表示现实世界中几乎所有的关系网络。社交网络中的好友关系、城市间的道路连接、网页间的超链接、任务间的依赖关系等,都可以用图来建模和分析。
📝

核心术语解析

顶点 (Vertex/Node)
图的基本元素,代表实体。在社交网络中代表用户,在地图中代表城市。
边 (Edge)
连接两个顶点的线段,表示顶点间的关系。可以是有向或无向的。
度 (Degree)
与某顶点相连的边数。有向图中分为入度(指向该点的边数)和出度(从该点出发的边数)。
权重 (Weight)
边上的数值,表示两顶点间的距离、成本、时间等量化关系。
路径 (Path)
从一个顶点到另一个顶点经过的顶点序列。路径长度是经过的边数或边权重之和。
环 (Cycle)
起点和终点相同的路径。一个没有环的图称为无环图(DAG:有向无环图)。
连通 (Connected)
无向图中任意两点间都有路径。有向图中为强连通(双向可达)或弱连通。
邻接 (Adjacent)
如果两个顶点之间有边直接相连,则称这两个顶点是邻接的。
稀疏图 (Sparse)
边数远小于顶点数平方的图,E << V²。适合用邻接表存储。
稠密图 (Dense)
边数接近顶点数平方的图,E ≈ V²。适合用邻接矩阵存储。
子图 (Subgraph)
由原图的部分顶点和边组成的图,这些边的端点都在选取的顶点中。
连通分量 (Component)
无向图的极大连通子图。一个图可能由多个互不相连的连通分量组成。
🕸️

常见图的类型

无向图 (Undirected Graph)

边没有方向的图

📌 特点

  • 边连接两个顶点,没有起点终点之分
  • 如果A连接B,则B也连接A
  • 顶点的度等于与之相连的边数

💼 应用

  • 社交网络的好友关系
  • 无向道路网络
  • 计算机网络拓扑

有向图 (Directed Graph)

边有方向的图

📌 特点

  • 边有起点和终点,用箭头表示
  • A→B 不等于 B→A
  • 顶点有入度(进入的边)和出度(出去的边)

💼 应用

  • 网页链接关系(PageRank)
  • 任务依赖关系
  • 微博/Twitter关注关系

加权图 (Weighted Graph)

边带有权值的图

📌 特点

  • 每条边都有一个数值(权重)
  • 权重可表示距离、成本、时间等
  • 可以是有向或无向的

💼 应用

  • 地图导航(道路距离)
  • 航班票价查询
  • 网络带宽/延迟

有向无环图 (DAG)

没有环的有向图

📌 特点

  • 有向图且不存在环
  • 可以进行拓扑排序
  • 有明确的依赖层次

💼 应用

  • 任务调度系统
  • 编译器依赖分析
  • Git版本控制
  • 区块链结构

完全图 (Complete Graph)

任意两点都直接相连

📌 特点

  • n个顶点有 n(n-1)/2 条边
  • 任意两点距离为1
  • 是最稠密的简单图

🔧 性质

  • 每个顶点的度为 n-1
  • 包含所有可能的子图
  • 常用于算法分析的极端情况

二部图 (Bipartite Graph)

顶点可分两组,边只在组间

📌 特点

  • 顶点分为两个互斥的集合
  • 边只连接不同集合的顶点
  • 没有奇数长度的环

💼 应用

  • 任务分配问题
  • 推荐系统(用户-商品)
  • 匹配问题
🔍

图的遍历算法

📖 遍历概念: 图的遍历是从某个顶点出发,按照某种规则访问图中所有顶点且每个顶点仅被访问一次。BFS和DFS是两种基本的遍历策略,它们是许多图算法的基础。

🎮 遍历可视化演示

访问顺序

广度优先搜索 (BFS)

策略:先访问所有邻居,再访问邻居的邻居

数据结构:队列 (FIFO)

应用:最短路径、层级遍历、社交网络度数

JavaScript - BFS 与 DFS 实现
// 广度优先搜索 (BFS) - 使用队列
function bfs(graph, start) {
    const visited = new Set();
    const queue = [start];
    const result = [];
    
    while (queue.length > 0) {
        const vertex = queue.shift();
        if (!visited.has(vertex)) {
            visited.add(vertex);
            result.push(vertex);
            for (const neighbor of graph[vertex]) {
                if (!visited.has(neighbor)) {
                    queue.push(neighbor);
                }
            }
        }
    }
    return result;
}

// 深度优先搜索 (DFS) - 使用递归/栈
function dfs(graph, start, visited = new Set()) {
    const result = [];
    
    function traverse(vertex) {
        if (visited.has(vertex)) return;
        visited.add(vertex);
        result.push(vertex);
        for (const neighbor of graph[vertex]) {
            traverse(neighbor);
        }
    }
    
    traverse(start);
    return result;
}
🛤️

最短路径算法

🔷 Dijkstra算法

适用场景:单源最短路径,非负权重

策略:贪心,每次选择当前距离最小的未访问顶点

时间复杂度:O((V+E)logV) 使用优先队列

应用:地图导航、网络路由

🔶 Bellman-Ford算法

适用场景:单源最短路径,可处理负权边

策略:动态规划,松弛所有边V-1次

时间复杂度:O(VE)

特点:能检测负权环

🔴 Floyd-Warshall算法

适用场景:多源最短路径(所有点对)

策略:动态规划,考虑所有中间节点

时间复杂度:O(V³)

应用:距离矩阵计算、传递闭包

⚠️ 算法选择指南:
• 单源、无负边 → Dijkstra(最高效)
• 单源、有负边 → Bellman-Ford(能检测负环)
• 所有点对最短路 → Floyd-Warshall(代码简洁)
• 无权图最短路 → BFS(最简单)
JavaScript - Dijkstra算法实现
// Dijkstra算法 - 使用优先队列优化
function dijkstra(graph, start) {
    const distances = {};
    const visited = new Set();
    const pq = [[0, start]]; // [距离, 顶点]
    
    // 初始化距离为无穷大
    for (const vertex in graph) {
        distances[vertex] = vertex === start ? 0 : Infinity;
    }
    
    while (pq.length > 0) {
        // 取出当前最小距离的顶点
        pq.sort((a, b) => a[0] - b[0]);
        const [dist, vertex] = pq.shift();
        
        if (visited.has(vertex)) continue;
        visited.add(vertex);
        
        // 更新邻居的距离
        for (const [neighbor, weight] of graph[vertex]) {
            const newDist = dist + weight;
            if (newDist < distances[neighbor]) {
                distances[neighbor] = newDist;
                pq.push([newDist, neighbor]);
            }
        }
    }
    return distances;
}
🎯

交互式图结构演示

动态图可视化

输入边来构建图,支持有向图和无向图。可以执行BFS/DFS遍历并查看结果。拖拽节点可调整布局。

📊

算法复杂度对比

算法 时间复杂度 空间复杂度 适用场景
BFS O(V + E) O(V) 无权图最短路、层级遍历
DFS O(V + E) O(V) 连通性检测、拓扑排序、路径搜索
Dijkstra O((V+E)log V) O(V) 单源最短路径(非负权)
Bellman-Ford O(VE) O(V) 单源最短路径(可负权)
Floyd-Warshall O(V³) O(V²) 全源最短路径
Prim (MST) O((V+E)log V) O(V) 最小生成树(稠密图)
Kruskal (MST) O(E log E) O(V) 最小生成树(稀疏图)
拓扑排序 O(V + E) O(V) DAG依赖排序

* V = 顶点数, E = 边数

💼

实际应用场景

🗺️

地图导航

城市道路网络建模,计算最短/最快路线

👥

社交网络

好友关系分析、推荐系统、社群发现

🌐

网络路由

数据包传输路径选择、负载均衡

📦

依赖管理

包管理器的依赖解析、构建顺序

🔗

网页排名

PageRank算法、搜索引擎优化

🧬

生物信息

蛋白质相互作用网络、基因调控网络

🏭

供应链

物流网络优化、最优配送路径

🎮

游戏AI

寻路算法(A*)、状态空间搜索

💾

图的存储结构

📐 邻接矩阵

结构:V×V 的二维数组,matrix[i][j] 表示 i 到 j 的边

优点:

• 判断两点是否相邻:O(1)

• 实现简单直观

缺点:

• 空间 O(V²),稀疏图浪费空间

• 遍历所有边需要 O(V²)

📋 邻接表

结构:每个顶点维护一个邻居列表

优点:

• 空间 O(V+E),适合稀疏图

• 遍历顶点的邻居效率高

缺点:

• 判断两点是否相邻需要 O(度)

• 实现相对复杂

🔗 边集数组

结构:存储所有边的数组,每条边包含起点、终点、权重

优点:

• 适合按边操作的算法

• 如 Kruskal 最小生成树算法

缺点:

• 查找顶点的邻居效率低 O(E)

• 不适合频繁查询邻接关系