C语言邻接表实现全解析:轻松搞定图的DFS与BFS遍历(含完整代码)

C语言邻接表实现图遍历

第一章:C语言邻接表的图遍历

在图论中,图的遍历是访问图中所有顶点的基本操作,常用方法包括深度优先搜索(DFS)和广度优先搜索(BFS)。使用邻接表存储图结构可以高效地表示稀疏图,并节省存储空间。邻接表通过链表数组实现,每个数组元素指向一个链表,链表中存储与该顶点相邻的所有顶点。

邻接表的数据结构设计

采用结构体定义邻接表中的节点和图的基本结构。每个边节点包含目标顶点索引和指向下一个节点的指针。
// 定义边节点
struct Edge {
    int dest;
    struct Edge* next;
};

// 定义图结构
struct Graph {
    int V; // 顶点数量
    struct Edge** adjList; // 邻接表数组
};

图的创建与初始化

创建图时需为邻接表数组动态分配内存,并将每个链表头初始化为 NULL。
  1. 输入顶点数 V 和边数 E
  2. 为图结构分配内存
  3. 为 adjList 数组分配 V 个指针空间并初始化为 NULL

添加边的操作

无向图需在两个顶点的邻接链表中互相插入节点。
void addEdge(struct Graph* graph, int src, int dest) {
    // 添加 dest 到 src 的邻接表
    struct Edge* newNode = (struct Edge*)malloc(sizeof(struct Edge));
    newNode->dest = dest;
    newNode->next = graph->adjList[src];
    graph->adjList[src] = newNode;

    // 对于无向图,反向也添加
    newNode = (struct Edge*)malloc(sizeof(struct Edge));
    newNode->dest = src;
    newNode->next = graph->adjList[dest];
    graph->adjList[dest] = newNode;
}

遍历实现方式对比

遍历方式数据结构适用场景
DFS栈(递归)路径查找、连通分量
BFS队列最短路径(无权图)
graph TD A[Start] --> B{Visited?} B -- No --> C[Mark Visited] C --> D[Process Node] D --> E[Explore Neighbors] E --> B B -- Yes --> F[End]

第二章:邻接表的数据结构设计与实现

2.1 图的基本概念与邻接表原理

图是由顶点集合和边集合构成的非线性数据结构,广泛应用于社交网络、路径规划等场景。根据边是否有方向,图可分为有向图和无向图。
邻接表存储结构
邻接表通过为每个顶点维护一个链表,记录其所有邻接顶点,实现图的稀疏表示,节省空间。

struct Graph {
    int V; // 顶点数
    vector<list<int>> adjList;
    Graph(int V) : V(V), adjList(V) {}
    void addEdge(int u, int v) {
        adjList[u].push_back(v); // 添加有向边 u->v
    }
};
上述代码中, adjList 是大小为 V 的向量,每个元素是一个链表,存储从对应顶点出发的所有邻接点。添加边的时间复杂度为 O(1),适合稀疏图存储。
空间复杂度分析
  • 邻接表的空间复杂度为 O(V + E),其中 V 为顶点数,E 为边数
  • 相比邻接矩阵的 O(V²),在稀疏图中优势显著

2.2 结点与边的存储结构定义

在图数据结构中,结点(Vertex)和边(Edge)的存储方式直接影响算法效率与内存占用。常见的存储结构包括邻接表和邻接矩阵。
邻接表实现
使用链表或动态数组存储每个结点的相邻边,节省空间且适合稀疏图。
type Graph struct {
    vertices int
    adjList  map[int][]int
}

func NewGraph(v int) *Graph {
    return &Graph{
        vertices: v,
        adjList:  make(map[int][]int),
    }
}
该Go代码定义了一个基于哈希表的邻接表结构, adjList键为结点ID,值为相邻结点列表,插入边的时间复杂度为O(1),空间复杂度为O(V + E)。
邻接矩阵对比
使用二维数组表示结点间连接关系,适合稠密图。
结构类型空间复杂度查询效率
邻接表O(V + E)O(degree)
邻接矩阵O(V²)O(1)

2.3 图的创建与内存管理策略

在构建图结构时,合理的内存管理策略对性能至关重要。采用邻接表存储稀疏图可显著减少空间占用。
动态内存分配示例

typedef struct {
    int vertex;
    struct Node* next;
} Node;

Node* createNode(int v) {
    Node* newNode = (Node*)malloc(sizeof(Node));
    newNode->vertex = v;
    newNode->next = NULL;
    return newNode; // 动态申请节点内存
}
该代码通过 malloc 为图节点分配堆内存,避免栈溢出。每个节点仅存储邻接顶点索引,节省空间。
内存释放策略
  • 使用后及时调用 free() 释放节点
  • 建议维护全局句柄统一管理图内存生命周期
  • 循环遍历链表逐个释放,防止内存泄漏

2.4 边的插入操作与无向图处理

在图数据结构中,边的插入是构建图的核心操作之一。对于无向图,每条边连接两个顶点,且具有对称性,因此插入时需双向建立关联。
邻接表表示法下的边插入
使用邻接表存储图时,每个顶点维护一个相邻顶点列表。插入一条无向边 (u, v) 需同时将 v 加入 u 的邻接表,并将 u 加入 v 的邻接表。

func (g *Graph) AddEdge(u, v int) {
    g.adj[u] = append(g.adj[u], v)
    g.adj[v] = append(g.adj[v], u) // 无向图需双向插入
}
上述代码中,`AddEdge` 方法在顶点 u 和 v 之间添加一条边。由于是无向图,必须在两个方向上更新邻接关系,以保证连通性的一致表达。
时间与空间复杂度分析
  • 时间复杂度:单次插入操作为 O(1),假设使用切片或链表实现邻接表;
  • 空间复杂度:整体为 O(V + E),其中 V 是顶点数,E 是边数。

2.5 完整数据结构代码实现与测试

核心结构体定义

type LinkedList struct {
    Head *Node
}

type Node struct {
    Data int
    Next *Node
}
该结构体定义了链表的头部指针和节点元素。Node 中 Data 存储整型值,Next 指向下一个节点,Head 为链表入口。
基础操作实现
  • Insert:在链表尾部插入新节点
  • Delete:根据值删除指定节点
  • Display:遍历并打印所有节点值
单元测试验证
测试用例输入预期输出
插入后遍历1→2→31 2 3
删除中间节点删除21 3

第三章:深度优先搜索(DFS)遍历实现

3.1 DFS算法思想与递归实现

核心思想
深度优先搜索(DFS)是一种用于遍历或搜索图和树的算法。其核心思想是沿着一条路径尽可能深入地访问节点,直到无法继续为止,然后回溯到上一节点,尝试其他分支。
递归实现方式
使用递归实现DFS简洁直观,系统调用栈自动保存访问路径。

def dfs(graph, start, visited):
    visited.add(start)
    print(start)
    for neighbor in graph[start]:
        if neighbor not in visited:
            dfs(graph, neighbor, visited)
上述代码中,`graph` 以邻接表形式存储图结构,`start` 为当前访问节点,`visited` 集合记录已访问节点,防止重复访问。每次递归调用处理一个未访问的邻接节点,实现深度优先遍历。
  • 时间复杂度:O(V + E),V为节点数,E为边数
  • 空间复杂度:O(V),主要消耗在递归栈和visited集合

3.2 访问标记与连通性判断

在图结构处理中,访问标记是实现连通性判断的核心机制。通过为每个节点设置布尔标记,可有效避免遍历过程中的重复访问。
基本实现逻辑
使用深度优先搜索(DFS)结合访问数组进行连通性检测:

visited := make([]bool, n)
var dfs func(int)
dfs = func(u int) {
    visited[u] = true
    for _, v := range graph[u] {
        if !visited[v] {
            dfs(v)
        }
    }
}
上述代码中, visited 数组记录节点是否已被访问,防止无限递归。函数 dfs 从起始节点出发,递归访问所有可达节点。
应用场景对比
  • 无向图连通分量计数
  • 有向图强连通分量检测
  • 网络拓扑中路径可达性分析

3.3 非递归DFS:栈的应用实践

栈与深度优先搜索的结合
非递归实现DFS依赖栈结构模拟函数调用过程,避免递归带来的栈溢出风险。通过显式管理节点访问顺序,提升程序稳定性。
核心代码实现
def dfs_iterative(graph, start):
    stack = [start]
    visited = set()
    while stack:
        node = stack.pop()
        if node not in visited:
            visited.add(node)
            # 逆序入栈保证正确访问顺序
            for neighbor in reversed(graph[node]):
                if neighbor not in visited:
                    stack.append(neighbor)
    return visited
该函数以起始节点开始,利用列表模拟栈行为。每次弹出栈顶节点并标记为已访问,未访问的邻接节点逆序压入栈,确保按深度优先顺序遍历。
算法执行流程对比
步骤当前栈已访问
1[A]{}
2[B,C]{A}
3[B,D,E]{A,C}

第四章:广度优先搜索(BFS)遍历实现

4.1 BFS算法原理与队列机制

广度优先搜索的核心思想
BFS(Breadth-First Search)是一种图的遍历算法,其核心是按层次逐层扩展,优先访问起始节点的所有邻接点,再向深层推进。该过程依赖队列的“先进先出”特性,确保节点按访问顺序依次处理。
队列在BFS中的作用
队列用于存储待访问的节点,初始时将起点入队。每次从队首取出一个节点,访问其所有未访问的邻接节点并依次入队,直到队列为空。

from collections import deque

def bfs(graph, start):
    visited = set()
    queue = deque([start])
    visited.add(start)
    
    while queue:
        node = queue.popleft()          # 取出队首节点
        for neighbor in graph[node]:    # 遍历邻接节点
            if neighbor not in visited:
                visited.add(neighbor)
                queue.append(neighbor)  # 新节点入队
上述代码中, deque 提供高效的两端操作, popleft() 保证按访问顺序处理节点, visited 集合避免重复访问,确保算法正确性与效率。

4.2 层序遍历与路径探索应用

层序遍历是二叉树操作中的基础算法,广泛应用于路径探索、最短路径判定等场景。通过队列实现广度优先搜索(BFS),可逐层访问节点,确保访问顺序的层次性。
层序遍历基础实现
func levelOrder(root *TreeNode) []int {
    if root == nil { return nil }
    var result []int
    queue := []*TreeNode{root}
    
    for len(queue) > 0 {
        node := queue[0]
        queue = queue[1:]
        result = append(result, node.Val)
        
        if node.Left != nil {
            queue = append(queue, node.Left)
        }
        if node.Right != nil {
            queue = append(queue, node.Right)
        }
    }
    return result
}
该函数使用切片模拟队列,依次处理每层节点。每次从队首取出节点,将其值存入结果,并将左右子节点加入队尾,保证层次顺序。
路径追踪扩展
在路径探索中,可维护每个节点的父节点映射,反向重构从根到目标节点的完整路径,适用于文件系统导航或决策树回溯等场景。

4.3 非递归BFS代码实现与优化

基础非递归BFS实现
使用队列实现广度优先搜索,避免递归带来的栈溢出问题。以下为Python示例:

from collections import deque

def bfs(graph, start):
    visited = set()
    queue = deque([start])
    result = []
    
    while queue:
        node = queue.popleft()
        if node not in visited:
            visited.add(node)
            result.append(node)
            for neighbor in graph[node]:
                if neighbor not in visited:
                    queue.append(neighbor)
    return result
该实现中, deque 提供高效的队头出队操作, visited 集合避免重复访问,确保每个节点仅处理一次。
性能优化策略
  • 预判目标节点,提前终止搜索
  • 使用数组替代哈希集合(当节点编号连续时)以降低常数时间开销
  • 批量处理同一层节点,便于统计层级信息

4.4 BFS与DFS性能对比分析

在图遍历算法中,BFS与DFS在时间与空间复杂度上表现出显著差异。通常情况下,两者的访问时间复杂度均为 O(V + E),其中 V 为顶点数,E 为边数,但在实际运行中性能表现受结构影响较大。
空间开销对比
  • BFS 使用队列存储层级节点,最坏情况下需存储整层节点,空间复杂度为 O(w),w 为最大宽度;
  • DFS 依赖递归栈,深度为 h 时空间复杂度为 O(h),适用于深度较大的稀疏图。
典型代码实现片段

# BFS 实现
from collections import deque
def bfs(graph, start):
    visited = set()
    queue = deque([start])
    while queue:
        node = queue.popleft()
        if node not in visited:
            visited.add(node)
            for neighbor in graph[node]:
                queue.append(neighbor)
该实现使用双端队列确保先进先出,适合最短路径搜索。相比之下,DFS 使用递归或栈可减少广度扩展带来的内存压力。

第五章:总结与展望

技术演进的持续驱动
现代后端架构正加速向云原生与服务网格演进。以 Istio 为代表的控制平面,已能通过 Sidecar 模式实现细粒度流量管理。实际案例中,某金融平台在 K8s 集群中部署了基于 Envoy 的网关层,结合 JWT 认证与 mTLS 加密,显著提升了跨服务调用的安全性。
  • 微服务间通信默认启用双向 TLS
  • 通过 VirtualService 实现灰度发布
  • 使用 DestinationRule 定义负载均衡策略
可观测性的关键实践
在生产环境中,仅依赖日志已无法满足故障排查需求。某电商平台集成 OpenTelemetry 后,实现了从用户请求到数据库调用的全链路追踪。其核心指标通过 Prometheus 抓取,并由 Grafana 动态展示。
指标类型采集工具告警阈值
HTTP 延迟(P99)Prometheus>500ms
错误率OpenTelemetry Collector>1%
代码层面的弹性设计
为应对瞬时高并发,需在应用层实现熔断与重试机制。以下 Go 示例展示了使用 hystrix-go 的典型配置:

hystrix.ConfigureCommand("queryUser", hystrix.CommandConfig{
    Timeout:                1000,
    MaxConcurrentRequests:  100,
    ErrorPercentThreshold:  25,
})

var result string
err := hystrix.Do("queryUser", func() error {
    return callUserService(&result)
}, nil)
Client API Gateway
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值