图(Graph) 由顶点集 V 和边集 E 组成,记为 G = (V, E)。图中的数据元素称为顶点(Vertex),顶点之间的关系称为边(Edge)。图比树更一般:树是一种特殊的图(连通且无环),而图中任意两个顶点之间都可以相连。
按边是否有方向,图分为两类:
<u, v> 表示,代表从 u 指向 v 的弧,u 称为弧尾,v 称为弧头。若边上带有数值(权值),则称为带权图,也叫网(Network)。权值常表示距离、花费、时间等。
顶点的度(Degree):无向图中顶点的度是与它相连的边数;有向图中分为入度(指向该顶点的边数)和出度(从该顶点发出的边数)。
连通与连通分量:无向图中,若两个顶点之间存在路径则称它们连通;若任意两个顶点都连通,则该图为连通图。无向图的极大连通子图称为连通分量(连通图只有一个连通分量,即它本身)。有向图中,若任意两个顶点相互可达(双向都有路径),则称为强连通图,其极大强连通子图称为强连通分量。
1. 邻接矩阵(Adjacency Matrix)
用二维数组 g[n][n] 存储:g[i][j] = 1 表示顶点 i 到 j 有边(无向图中对称,g[i][j] = g[j][i]);带权图存权值、无边存无穷大 ∞、对角线存 0。
0 1 2 3 4
0 | 0 1 1 0 0
1 | 1 0 0 1 1
2 | 1 0 0 0 1
3 | 0 1 0 0 0
4 | 0 1 1 0 0
2. 邻接表(Adjacency List)
用一个顶点表数组,每个顶点挂一条单链表,链表中的每个结点存储一个邻接顶点的编号(无向图每条边出现两次,两个方向各一次;有向图通常只存出边)。
邻接矩阵 vs 邻接表对比:
| 对比项 | 邻接矩阵 | 邻接表 |
|---|---|---|
| 存储空间 | O(n²),与边数无关 | O(n+e),稀疏图省空间 |
| 判断两点是否相邻 | O(1) | O(deg(v)),需扫描链表 |
| 找出某点的所有邻点 | O(n) | O(deg(v)) |
| 求顶点的度 | 无向图 O(n),有向图需统计行列 | 无向图 O(deg),有向图还须另建入度表 |
| 适用场景 | 稠密图(边数接近 n²) | 稀疏图(边数接近 n) |
| 操作 | 邻接矩阵 | 邻接表 |
|---|---|---|
| 判断两点是否相邻 | O(1) | O(deg(v)) |
| BFS 遍历 | O(n²) | O(n+e) |
| DFS 遍历 | O(n²) | O(n+e) |
| 存储空间 | O(n²) | O(n+e) |
为什么:
下面 4 种语言的实现演示相同的操作:用邻接表存储同一个无向图(5 个顶点、5 条边),从顶点 0 出发分别做 BFS 和 DFS 并打印遍历序列。
#include <stdio.h>
#include <stdlib.h>
#define MAX_VERTICES 20
// 邻接表边结点:存储一个邻接顶点
typedef struct ArcNode {
int adjvex; // 邻接顶点的编号
struct ArcNode *next; // 指向下一条边的结点
} ArcNode;
// 顶点表结点
typedef struct VNode {
int data; // 顶点信息
ArcNode *first; // 指向第一条边
} VNode;
// 图结构:顶点表 + 顶点数
typedef struct {
VNode vertices[MAX_VERTICES];
int n;
} ALGraph;
// 初始化图:顶点编号 0..n-1 作为顶点数据
void initGraph(ALGraph *g, int n) {
g->n = n;
for (int i = 0; i < n; i++) {
g->vertices[i].data = i;
g->vertices[i].first = NULL;
}
}
// 向链表中尾部追加一个边结点(保持与其它语言 push_back 一致的顺序)
void appendArc(VNode *vnode, int v) {
ArcNode *p = (ArcNode *)malloc(sizeof(ArcNode));
p->adjvex = v;
p->next = NULL;
if (vnode->first == NULL) {
vnode->first = p;
return;
}
ArcNode *t = vnode->first;
while (t->next != NULL) t = t->next;
t->next = p;
}
// 无向图加边 (u,v):两个方向都要加
void addEdge(ALGraph *g, int u, int v) {
appendArc(&g->vertices[u], v);
appendArc(&g->vertices[v], u);
}
// BFS:借助队列,从 start 出发广度优先遍历
void bfs(ALGraph *g, int start) {
int visited[MAX_VERTICES] = {0};
int queue[MAX_VERTICES], front = 0, rear = 0;
queue[rear++] = start;
visited[start] = 1;
while (front < rear) {
int u = queue[front++];
printf("%d ", u);
for (ArcNode *p = g->vertices[u].first; p != NULL; p = p->next) {
int v = p->adjvex;
if (!visited[v]) {
visited[v] = 1;
queue[rear++] = v;
}
}
}
printf("\n");
}
// DFS 辅助:递归访问顶点 u
void dfsVisit(ALGraph *g, int u, int visited[]) {
printf("%d ", u);
visited[u] = 1;
for (ArcNode *p = g->vertices[u].first; p != NULL; p = p->next) {
int v = p->adjvex;
if (!visited[v])
dfsVisit(g, v, visited);
}
}
// DFS:从 start 出发深度优先遍历(递归)
void dfs(ALGraph *g, int start) {
int visited[MAX_VERTICES] = {0};
dfsVisit(g, start, visited);
printf("\n");
}
int main() {
ALGraph g;
initGraph(&g, 5);
// 无向图的边 (u, v)
int edges[][2] = {{0,1}, {0,2}, {1,3}, {1,4}, {2,4}};
for (int i = 0; i < 5; i++)
addEdge(&g, edges[i][0], edges[i][1]);
printf("BFS from 0: ");
bfs(&g, 0);
printf("DFS from 0: ");
dfs(&g, 0);
return 0;
}
#include <iostream>
#include <vector>
#include <queue>
using namespace std;
// 邻接表:vector<int> 的数组,每个元素存邻接顶点编号
class Graph {
public:
int n;
vector<vector<int>> adj;
Graph(int n) : n(n), adj(n) {}
// 无向图加边 (u,v)
void addEdge(int u, int v) {
adj[u].push_back(v);
adj[v].push_back(u);
}
// BFS:借助队列,从 start 出发广度优先遍历
void bfs(int start) {
vector<bool> visited(n, false);
queue<int> q;
q.push(start);
visited[start] = true;
while (!q.empty()) {
int u = q.front();
q.pop();
cout << u << " ";
for (int v : adj[u]) {
if (!visited[v]) {
visited[v] = true;
q.push(v);
}
}
}
cout << endl;
}
// DFS 辅助:递归访问顶点 u
void dfsVisit(int u, vector<bool>& visited) {
cout << u << " ";
visited[u] = true;
for (int v : adj[u])
if (!visited[v])
dfsVisit(v, visited);
}
// DFS:从 start 出发深度优先遍历
void dfs(int start) {
vector<bool> visited(n, false);
dfsVisit(start, visited);
cout << endl;
}
};
int main() {
Graph g(5);
g.addEdge(0, 1);
g.addEdge(0, 2);
g.addEdge(1, 3);
g.addEdge(1, 4);
g.addEdge(2, 4);
cout << "BFS from 0: ";
g.bfs(0);
cout << "DFS from 0: ";
g.dfs(0);
return 0;
}
import java.util.*;
public class GraphTraversal {
private int n;
private List<List<Integer>> adj;
public GraphTraversal(int n) {
this.n = n;
adj = new ArrayList<>();
for (int i = 0; i < n; i++) adj.add(new ArrayList<>());
}
// 无向图加边 (u,v)
public void addEdge(int u, int v) {
adj.get(u).add(v);
adj.get(v).add(u);
}
// BFS:借助队列
public void bfs(int start) {
boolean[] visited = new boolean[n];
Queue<Integer> q = new LinkedList<>();
q.offer(start);
visited[start] = true;
while (!q.isEmpty()) {
int u = q.poll();
System.out.print(u + " ");
for (int v : adj.get(u)) {
if (!visited[v]) {
visited[v] = true;
q.offer(v);
}
}
}
System.out.println();
}
// DFS:递归
public void dfs(int start) {
boolean[] visited = new boolean[n];
dfsVisit(start, visited);
System.out.println();
}
private void dfsVisit(int u, boolean[] visited) {
System.out.print(u + " ");
visited[u] = true;
for (int v : adj.get(u))
if (!visited[v])
dfsVisit(v, visited);
}
public static void main(String[] args) {
GraphTraversal g = new GraphTraversal(5);
int[][] edges = {{0,1}, {0,2}, {1,3}, {1,4}, {2,4}};
for (int[] e : edges) g.addEdge(e[0], e[1]);
System.out.print("BFS from 0: ");
g.bfs(0);
System.out.print("DFS from 0: ");
g.dfs(0);
}
}
from collections import deque
class Graph:
"""邻接表实现的图(无向)"""
def __init__(self, n):
self.n = n
self.adj = [[] for _ in range(n)] # 邻接表:每个顶点一个列表
def add_edge(self, u, v):
"""无向图加边"""
self.adj[u].append(v)
self.adj[v].append(u)
def bfs(self, start):
"""BFS:借助队列"""
visited = [False] * self.n
q = deque([start])
visited[start] = True
order = []
while q:
u = q.popleft()
order.append(u)
for v in self.adj[u]:
if not visited[v]:
visited[v] = True
q.append(v)
return order
def dfs(self, start):
"""DFS:递归"""
visited = [False] * self.n
order = []
self._dfs(start, visited, order)
return order
def _dfs(self, u, visited, order):
order.append(u)
visited[u] = True
for v in self.adj[u]:
if not visited[v]:
self._dfs(v, visited, order)
if __name__ == "__main__":
g = Graph(5)
edges = [(0, 1), (0, 2), (1, 3), (1, 4), (2, 4)]
for u, v in edges:
g.add_edge(u, v)
print("BFS from 0:", g.bfs(0))
print("DFS from 0:", g.dfs(0))