在技术面试中,图算法相关问题因其对数据结构理解、算法设计能力和问题拆解能力的综合考察,成为区分候选人技术水平的关键环节。无论是社交网络中的好友推荐、导航系统中的路径规划,还是电商平台的商品关联分析,图算法的应用场景几乎覆盖所有技术领域。本文将从图的表示与存储、基础遍历算法(DFS/BFS)及典型应用问题三方面展开,结合代码实现与优化思路,帮助读者系统掌握图算法的核心解题框架。
一、图的表示与存储:选择合适的底层结构
图的存储方式直接影响算法的实现效率与代码简洁度,常见的表示方法包括邻接矩阵与邻接表。
1. 邻接矩阵:适合稠密图,空间复杂度O(n²)
邻接矩阵通过二维数组存储顶点间的连接关系,矩阵元素matrix[i][j]表示顶点i到j的边权重(无权图中可用0/1表示)。其优势在于快速判断两点是否连通(O(1)时间复杂度),但空间浪费严重,尤其当图为稀疏图时(边数远小于顶点数的平方)。
代码示例(无权图):
class GraphMatrix:def __init__(self, vertices):self.vertices = verticesself.matrix = [[0] * vertices for _ in range(vertices)]def add_edge(self, u, v):self.matrix[u][v] = 1self.matrix[v][u] = 1 # 无向图需对称设置
2. 邻接表:适合稀疏图,空间复杂度O(V+E)
邻接表通过链表或数组存储每个顶点的邻居,空间效率更高。实现时可使用字典(Python)或哈希表,键为顶点,值为邻居列表。其缺点是判断两点是否连通需遍历链表(O(degree(u))时间复杂度)。
代码示例(无权图):
from collections import defaultdictclass GraphAdjList:def __init__(self):self.graph = defaultdict(list)def add_edge(self, u, v):self.graph[u].append(v)self.graph[v].append(u) # 无向图需双向添加
选择建议:面试中若题目未明确图的稀疏性,可优先询问面试官图的规模与边数比例,再决定存储方式。例如,当顶点数N>10⁴且边数E<<N²时,邻接表更优。
二、图的遍历:DFS与BFS的核心逻辑与代码实现
图的遍历是解决连通性、路径查找等问题的基础,分为深度优先搜索(DFS)与广度优先搜索(BFS)。
1. 深度优先搜索(DFS):递归与非递归实现
DFS通过递归或栈模拟“深入到底,回溯再探”的过程,适用于检测环、拓扑排序等问题。
递归实现:
def dfs_recursive(graph, start, visited=None):if visited is None:visited = set()visited.add(start)print(start, end=" ")for neighbor in graph[start]:if neighbor not in visited:dfs_recursive(graph, neighbor, visited)
非递归实现(显式栈):
def dfs_iterative(graph, start):visited = set()stack = [start]while stack:vertex = stack.pop()if vertex not in visited:visited.add(vertex)print(vertex, end=" ")# 逆序压栈以保证顺序正确(针对无向图)for neighbor in reversed(graph[vertex]):if neighbor not in visited:stack.append(neighbor)
2. 广度优先搜索(BFS):队列与层级遍历
BFS通过队列实现“先访问的顶点,其邻居先被探索”,适用于最短路径(无权图)、连通分量检测等问题。
代码实现:
from collections import dequedef bfs(graph, start):visited = set()queue = deque([start])visited.add(start)while queue:vertex = queue.popleft()print(vertex, end=" ")for neighbor in graph[vertex]:if neighbor not in visited:visited.add(neighbor)queue.append(neighbor)
关键区别:DFS适合寻找所有解(如迷宫问题),BFS适合寻找最优解(如最短路径);DFS空间复杂度O(h)(h为树高),BFS空间复杂度O(w)(w为树宽)。
三、典型面试问题:连通性与路径查找
1. 判断图是否为二分图(BFS/DFS染色法)
二分图要求顶点可被分成两组,组内无边。通过BFS/DFS染色,若发现相邻顶点同色则非二分图。
BFS实现:
def is_bipartite(graph):color = {}for node in graph:if node not in color:queue = deque([node])color[node] = 0while queue:current = queue.popleft()for neighbor in graph[current]:if neighbor in color:if color[neighbor] == color[current]:return Falseelse:color[neighbor] = 1 - color[current]queue.append(neighbor)return True
2. 检测图中的环(DFS回溯法)
通过DFS遍历时记录访问路径,若遇到已访问且非父节点的顶点,则存在环。
代码框架:
def has_cycle(graph):visited = set()recursion_stack = set() # 记录当前递归路径上的顶点def dfs(node, parent):visited.add(node)recursion_stack.add(node)for neighbor in graph[node]:if neighbor not in visited:if dfs(neighbor, node):return Trueelif neighbor in recursion_stack and neighbor != parent:return Truerecursion_stack.remove(node)return Falsefor node in graph:if node not in visited:if dfs(node, -1): # -1表示无父节点return Truereturn False
四、性能优化与面试技巧
- 时间复杂度分析:DFS/BFS的时间复杂度均为O(V+E),需在面试中明确说明。
- 空间优化:对于大规模图,可使用位运算压缩邻接矩阵,或选择更紧凑的邻接表实现(如C++中的
vector<vector<int>>)。 - 边界条件:处理空图、自环边、重复边等特殊情况,体现代码鲁棒性。
- 沟通技巧:在编码前与面试官确认图的表示方式(有向/无向、有权/无权),避免假设错误。
结语
图算法问题的核心在于理解图的存储结构、遍历逻辑及问题转化能力。掌握DFS/BFS的实现细节与典型应用场景,结合代码优化技巧,可有效提升面试表现。下一篇将深入探讨最短路径、最小生成树等高级图算法问题,敬请期待。