Java图论算法竞赛应用详解

一、图的基本概念

1.1 什么是图

图(Graph)是一种非线性数据结构,由顶点(Vertex)的集合和边(Edge)的集合组成。在计算机科学中,图被广泛应用于表示各种实际问题,如社交网络、地图导航、网络拓扑等。

1.2 图的基本术语

  • 顶点(Vertex):图中的节点
  • 边(Edge):连接两个顶点的线段
  • 有向图:边有方向
  • 无向图:边无方向
  • 权重:边上的数值
  • 度:与顶点相连的边的数量

1.3 图的存储方式

1.3.1 邻接矩阵

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class AdjacencyMatrix {
private int V; // 顶点数
private int[][] matrix; // 邻接矩阵

public AdjacencyMatrix(int v) {
V = v;
matrix = new int[v][v];
}

// 添加边
public void addEdge(int source, int dest) {
matrix[source][dest] = 1;
matrix[dest][source] = 1; // 无向图需要这行
}
}

1.3.2 邻接表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class AdjacencyList {
private int V;
private ArrayList<ArrayList<Integer>> adj;

public AdjacencyList(int v) {
V = v;
adj = new ArrayList<>(v);
for (int i = 0; i < v; i++) {
adj.add(new ArrayList<>());
}
}

public void addEdge(int source, int dest) {
adj.get(source).add(dest);
adj.get(dest).add(source); // 无向图需要这行
}
}

二、图的基本算法

2.1 深度优先搜索(DFS)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class DFS {
private boolean[] visited;
private ArrayList<ArrayList<Integer>> adj;

public DFS(ArrayList<ArrayList<Integer>> adj) {
this.adj = adj;
visited = new boolean[adj.size()];
}

public void dfs(int v) {
visited[v] = true;
System.out.print(v + " ");

for (int n : adj.get(v)) {
if (!visited[n]) {
dfs(n);
}
}
}
}

2.2 广度优先搜索(BFS)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class BFS {
private boolean[] visited;
private ArrayList<ArrayList<Integer>> adj;

public BFS(ArrayList<ArrayList<Integer>> adj) {
this.adj = adj;
visited = new boolean[adj.size()];
}

public void bfs(int start) {
Queue<Integer> queue = new LinkedList<>();
visited[start] = true;
queue.add(start);

while (!queue.isEmpty()) {
int v = queue.poll();
System.out.print(v + " ");

for (int n : adj.get(v)) {
if (!visited[n]) {
visited[n] = true;
queue.add(n);
}
}
}
}
}

2.3 最短路径算法

2.3.1 Dijkstra算法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public class Dijkstra {
private static final int INF = Integer.MAX_VALUE;

public static int[] shortestPath(int[][] graph, int src) {
int V = graph.length;
int[] dist = new int[V];
boolean[] visited = new boolean[V];

Arrays.fill(dist, INF);
dist[src] = 0;

for (int count = 0; count < V-1; count++) {
int u = minDistance(dist, visited);
visited[u] = true;

for (int v = 0; v < V; v++) {
if (!visited[v] && graph[u][v] != 0 &&
dist[u] != INF &&
dist[u] + graph[u][v] < dist[v]) {
dist[v] = dist[u] + graph[u][v];
}
}
}
return dist;
}

private static int minDistance(int[] dist, boolean[] visited) {
int min = INF, minIndex = -1;
for (int v = 0; v < dist.length; v++) {
if (!visited[v] && dist[v] <= min) {
min = dist[v];
minIndex = v;
}
}
return minIndex;
}
}

2.4 最小生成树

2.4.1 Kruskal算法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
public class Kruskal {
class Edge implements Comparable<Edge> {
int src, dest, weight;

public int compareTo(Edge other) {
return this.weight - other.weight;
}
}

class DisjointSet {
int[] parent, rank;

DisjointSet(int n) {
parent = new int[n];
rank = new int[n];
for (int i = 0; i < n; i++) {
parent[i] = i;
}
}

int find(int x) {
if (parent[x] != x) {
parent[x] = find(parent[x]);
}
return parent[x];
}

void union(int x, int y) {
int xRoot = find(x);
int yRoot = find(y);

if (rank[xRoot] < rank[yRoot]) {
parent[xRoot] = yRoot;
} else if (rank[xRoot] > rank[yRoot]) {
parent[yRoot] = xRoot;
} else {
parent[yRoot] = xRoot;
rank[xRoot]++;
}
}
}

public Edge[] kruskalMST(Edge[] edges, int V) {
Arrays.sort(edges);
Edge[] result = new Edge[V-1];
DisjointSet ds = new DisjointSet(V);

int e = 0, i = 0;
while (e < V-1) {
Edge nextEdge = edges[i++];
int x = ds.find(nextEdge.src);
int y = ds.find(nextEdge.dest);

if (x != y) {
result[e++] = nextEdge;
ds.union(x, y);
}
}
return result;
}
}

三、实际应用案例

3.1 图的连通性判断

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public class ConnectivityCheck {
private int V;
private ArrayList<ArrayList<Integer>> adj;

public ConnectivityCheck(int v) {
V = v;
adj = new ArrayList<>(V);
for (int i = 0; i < V; i++) {
adj.add(new ArrayList<>());
}
}

public void addEdge(int v, int w) {
adj.get(v).add(w);
adj.get(w).add(v);
}

private void DFSUtil(int v, boolean[] visited) {
visited[v] = true;
for (int n : adj.get(v)) {
if (!visited[n]) {
DFSUtil(n, visited);
}
}
}

public boolean isConnected() {
boolean[] visited = new boolean[V];
DFSUtil(0, visited);

for (boolean v : visited) {
if (!v) return false;
}
return true;
}
}

3.2 拓扑排序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
public class TopologicalSort {
private int V;
private ArrayList<ArrayList<Integer>> adj;

public TopologicalSort(int v) {
V = v;
adj = new ArrayList<>(V);
for (int i = 0; i < V; i++) {
adj.add(new ArrayList<>());
}
}

public void addEdge(int v, int w) {
adj.get(v).add(w);
}

private void topologicalSortUtil(int v, boolean[] visited,
Stack<Integer> stack) {
visited[v] = true;

for (int n : adj.get(v)) {
if (!visited[n]) {
topologicalSortUtil(n, visited, stack);
}
}

stack.push(v);
}

public int[] topologicalSort() {
Stack<Integer> stack = new Stack<>();
boolean[] visited = new boolean[V];

for (int i = 0; i < V; i++) {
if (!visited[i]) {
topologicalSortUtil(i, visited, stack);
}
}

int[] result = new int[V];
for (int i = 0; i < V; i++) {
result[i] = stack.pop();
}
return result;
}
}

四、优化技巧与注意事项

4.1 数据结构选择

  • 稀疏图(边较少):使用邻接表,空间复杂度O(V+E)
  • 稠密图(边较多):使用邻接矩阵,空间复杂度O(V²)
  • 需要快速判断两点是否相连:使用邻接矩阵
  • 需要遍历某个顶点的所有邻接点:使用邻接表

4.2 时间复杂度分析

  • DFS/BFS:O(V+E)
  • Dijkstra:O(V²),使用优先队列可优化到O((V+E)logV)
  • Kruskal:O(ElogE)
  • 拓扑排序:O(V+E)

4.3 常见优化方法

  1. 使用优先队列优化Dijkstra算法
  2. 路径压缩优化并查集
  3. 使用BitSet代替boolean数组标记访问状态
  4. 预处理技术(如Floyd-Warshall算法)

4.4 竞赛中的注意事项

  1. 初始化:注意数组大小,避免越界
  2. 边界条件:处理特殊情况(如空图、单个节点)
  3. 数据范围:注意整型溢出
  4. 输入输出:使用BufferedReader/BufferedWriter加速IO
  5. 代码复用:封装常用操作为工具方法

五、总结

图论算法在竞赛中是一个重要的知识点,掌握好基本概念和常用算法的实现是关键。在实际应用中,需要根据具体问题选择合适的算法和数据结构,同时注意代码的效率和正确性。通过不断练习和总结,才能在竞赛中游刃有余地运用图论算法解决各种问题。