图
图(graph) 是一种非线性数据结构,由顶点(vertex) 和边 (Edge) 组成。他们之间的相关性没有那么高,链表的线性关系,树状图的派生关系。自由度较高就代表复杂度比较高。
顶点 (Vertex):图中的基本单位,通常用来表示对象一个节点
边 (Edge):连接两个顶点的线,可以是有向(Directed)或无向(Undirected)。
图论
图论是数学和计算机科学中的一个重要分支,专门研究图的性质和结构。图是一种非线性数据结构,由顶点(也称为节点)和边(连接顶点的线)组成。图论在许多实际应用中扮演着关键角色,如网络设计、社交网络分析和路线规划等。
- 社交关系:用户之间的朋友关系。
- 交通网络:城市之间的道路连接。
2. 图的类型
图可以根据不同的特性进行分类:
-
无向图:边没有方向,两个顶点之间的关系是双向的。例如:A和B是朋友关系。
-
有向图:边有方向,表示从一个顶点到另一个顶点的单向关系。例如:用户A关注用户B。
-
加权图:每条边都有一个权重,通常表示成本、距离或时间。
-
无环图(DAG):没有回路的图,通常在任务依赖关系中使用。
二、图的表示方法
图的表示方法主要有两种:邻接矩阵和邻接表。
1. 邻接矩阵
邻接矩阵是一个二维数组,其中的元素表示图中顶点之间的连接情况。对于稀疏图会浪费空间。
在图论中,边的添加和删除、顶点的添加和删除是基本的操作。使用邻接矩阵表示图时,这些操作的时间复杂度和具体实现方法如下:
(1)邻接矩阵的结构
假设我们有一个图,使用邻接矩阵 adjMat
来表示。该矩阵的大小为 ( V \times V ),其中 ( V ) 是图中顶点的数量。矩阵的元素 adjMat[i][j]
表示从顶点 ( i ) 到顶点 ( j ) 的边的权重(若无边则为0或无穷大)。
(2) 添加/删除边
-
添加边:在邻接矩阵中修改指定的边,只需更新
adjMat[i][j]
和adjMat[j][i]
。时间复杂度为 ( O(1) )。// 添加边 void addEdge(int[][] adjMat, int u, int v, int weight) { adjMat[u][v] = weight; adjMat[v][u] = weight; // 无向图 }
-
删除边:同样,只需将
adjMat[i][j]
和adjMat[j][i]
设为无穷大(或0)。时间复杂度为 ( O(1) )。// 删除边 void removeEdge(int[][] adjMat, int u, int v) { adjMat[u][v] = Integer.MAX_VALUE; // 或0 adjMat[v][u] = Integer.MAX_VALUE; // 无向图 }
(3)添加顶点
-
添加顶点:在邻接矩阵的尾部添加一行一列,并将所有新元素初始化为无穷大(或0)。时间复杂度为 ( O(V2) )。
// 添加顶点 int[][] addVertex(int[][] adjMat, int V) { int[][] newAdjMat = new int[V + 1][V + 1]; for (int i = 0; i < V; i++) { for (int j = 0; j < V; j++) { newAdjMat[i][j] = adjMat[i][j]; } } // 初始化新行和新列 for (int i = 0; i < V + 1; i++) { newAdjMat[V][i] = Integer.MAX_VALUE; // 或0 newAdjMat[i][V] = Integer.MAX_VALUE; // 或0 } return newAdjMat; }
(4) 删除顶点
-
删除顶点:在邻接矩阵中删除一行和一列。若删除的是首行首列,需要将其他元素向左上移动。时间复杂度为 ( O(V^2) )。
// 删除顶点 int[][] removeVertex(int[][] adjMat, int V, int vertex) { int[][] newAdjMat = new int[V - 1][V - 1]; int newRow = 0, newCol; for (int i = 0; i < V; i++) { if (i == vertex) continue; // 跳过删除的顶点 newCol = 0; for (int j = 0; j < V; j++) { if (j == vertex) continue; // 跳过删除的顶点 newAdjMat[newRow][newCol] = adjMat[i][j]; newCol++; } newRow++; } return newAdjMat; }
(5)初始化
-
初始化:传入 ( V ) 个顶点,初始化长度为 ( V ) 的顶点列表
vertices
,使用 ( O(V) ) 时间;初始化大小为 ( V \times V ) 的邻接矩阵adjMat
,使用 ( O(V^2) ) 时间。// 初始化 int[][] initializeGraph(int V) { int[][] adjMat = new int[V][V]; for (int i = 0; i < V; i++) { for (int j = 0; j < V; j++) { if (i == j) { adjMat[i][j] = 0; // 自环权重为0 } else { adjMat[i][j] = Integer.MAX_VALUE; // 或0 } } } return adjMat; }
2. 邻接表
邻接表为每个顶点维护一个列表,列出与其直接相连的顶点。对于大型稀疏图,这种表示方法更加节省空间。
三、图的基本算法
1. 深度优先搜索(DFS)
深度优先搜索是一种用于遍历或搜索图的算法。它从某个顶点开始,尽可能深入到每个分支,然后回溯。
Java实现:
import java.util.*;
class Graph {
private Map<String, List<String>> graph;
public Graph() {
graph = new HashMap<>();
}
public void addEdge(String u, String v) {
graph.putIfAbsent(u, new ArrayList<>());
graph.putIfAbsent(v, new ArrayList<>());
graph.get(u).add(v);
graph.get(v).add(u); // 无向图
}
public void dfs(String start) {
Set<String> visited = new HashSet<>();
dfsHelper(start, visited);
}
private void dfsHelper(String vertex, Set<String> visited) {
visited.add(vertex);
System.out.print(vertex + " ");
for (String neighbor : graph.get(vertex)) {
if (!visited.contains(neighbor)) {
dfsHelper(neighbor, visited);
}
}
}
}
// 使用示例
public class Main {
public static void main(String[] args) {
Graph g = new Graph();
g.addEdge("A", "B");
g.addEdge("A", "C");
g.addEdge("B", "D");
g.addEdge("C", "D");
System.out.println("DFS Traversal:");
g.dfs("A");
}
}
2. 广度优先搜索(BFS)
广度优先搜索是一种图的遍历方法,从某个顶点开始,逐层访问其邻接顶点。
Java实现:
import java.util.*;
class Graph {
private Map<String, List<String>> graph;
public Graph() {
graph = new HashMap<>();
}
public void addEdge(String u, String v) {
graph.putIfAbsent(u, new ArrayList<>());
graph.putIfAbsent(v, new ArrayList<>());
graph.get(u).add(v);
graph.get(v).add(u); // 无向图
}
public void bfs(String start) {
Set<String> visited = new HashSet<>();
Queue<String> queue = new LinkedList<>();
visited.add(start);
queue.add(start);
while (!queue.isEmpty()) {
String vertex = queue.poll();
System.out.print(vertex + " ");
for (String neighbor : graph.get(vertex)) {
if (!visited.contains(neighbor)) {
visited.add(neighbor);
queue.add(neighbor);
}
}
}
}
}
// 使用示例
public class Main {
public static void main(String[] args) {
Graph g = new Graph();
g.addEdge("A", "B");
g.addEdge("A", "C");
g.addEdge("B", "D");
g.addEdge("C", "D");
System.out.println("\nBFS Traversal:");
g.bfs("A");
}
}
3. Dijkstra算法
Dijkstra算法用于加权图,找到从源顶点到其他顶点的最短路径。
Java实现:
import java.util.*;
class Graph {
private Map<String, Map<String, Integer>> graph;
public Graph() {
graph = new HashMap<>();
}
public void addEdge(String u, String v, int weight) {
graph.putIfAbsent(u, new HashMap<>());
graph.putIfAbsent(v, new HashMap<>());
graph.get(u).put(v, weight);
graph.get(v).put(u, weight); // 无向图
}
public Map<String, Integer> dijkstra(String start) {
Map<String, Integer> distances = new HashMap<>();
PriorityQueue<Node> minHeap = new PriorityQueue<>(Comparator.comparingInt(node -> node.distance));
for (String vertex : graph.keySet()) {
distances.put(vertex, Integer.MAX_VALUE);
}
distances.put(start, 0);
minHeap.add(new Node(start, 0));
while (!minHeap.isEmpty()) {
Node current = minHeap.poll();
for (Map.Entry<String, Integer> entry : graph.get(current.vertex).entrySet()) {
String neighbor = entry.getKey();
int weight = entry.getValue();
int distance = current.distance + weight;
if (distance < distances.get(neighbor)) {
distances.put(neighbor, distance);
minHeap.add(new Node(neighbor, distance));
}
}
}
return distances;
}
private static class Node {
String vertex;
int distance;
Node(String vertex, int distance) {
this.vertex = vertex;
this.distance = distance;
}
}
}
// 使用示例
public class Main {
public static void main(String[] args) {
Graph g = new Graph();
g.addEdge("A", "B", 1);
g.addEdge("A", "C", 4);
g.addEdge("B", "C", 2);
g.addEdge("B", "D", 5);
g.addEdge("C", "D", 1);
Map<String, Integer> result = g.dijkstra("A");
System.out.println("\nDijkstra's shortest path from A:");
for (Map.Entry<String, Integer> entry : result.entrySet()) {
System.out.println("Distance to " + entry.getKey() + ": " + entry.getValue());
}
}
}
4. Kruskal算法
Kruskal算法用于求解最小生成树。
Java实现:
import java.util.*;
class UnionFind {
private Map<String, String> parent = new HashMap<>();
private Map<String, Integer> rank = new HashMap<>();
public void add(String u) {
parent.put(u, u);
rank.put(u, 0);
}
public String find(String u) {
if (!u.equals(parent.get(u))) {
parent.put(u, find(parent.get(u))); // 路径压缩
}
return parent.get(u);
}
public void union(String u, String v) {
String rootU = find(u);
String rootV = find(v);
if (!rootU.equals(rootV)) {
if (rank.get(rootU) > rank.get(rootV)) {
parent.put(rootV, rootU);
} else if (rank.get(rootU) < rank.get(rootV)) {
parent.put(rootU, rootV);
} else {
parent.put(rootV, rootU);
rank.put(rootU, rank.get(rootU) + 1);
}
}
}
}
class Graph {
private List<Edge> edges = new ArrayList<>();
public void addEdge(String u, String v, int weight) {
edges.add(new Edge(u, v, weight));
}
public List<Edge> kruskal() {
edges.sort(Comparator.comparingInt(edge -> edge.weight));
UnionFind uf = new UnionFind();
List<Edge> mst = new ArrayList<>();
for (Edge edge : edges) {
uf.add(edge.u);
uf.add(edge.v);
}
for (Edge edge : edges) {
if (uf.find(edge.u) != uf.find(edge.v)) {
uf.union(edge.u, edge.v);
mst.add(edge);
}
}
return mst;
}
private static class Edge {
String u;
String v;
int weight;
Edge(String u, String v, int weight) {
this.u = u;
this.v = v;
this.weight = weight;
}
}
}
// 使用示例
public class Main {
public static void main(String[] args) {
Graph g = new Graph();
g.addEdge("A", "B", 1);
g.addEdge("A", "C", 3);
g.addEdge("B", "C", 1);
g.addEdge("B", "D", 4);
g.addEdge("C", "D", 1);
List<Graph.Edge> mst = g.kruskal();
System.out.println("\nKruskal's Minimum Spanning Tree:");
for (Graph.Edge edge : mst) {
System.out.println(edge.u + " -- " + edge.v + " == " + edge.weight);
}
}
}
四、图的应用
图论在现实生活中的应用广泛,以下是一些例子:
-
社交网络:分析用户之间的关系,推荐朋友。
- 例如,Facebook使用图算法来分析用户之间的连接,提供好友推荐。
-
地图导航:计算城市之间的最短路径。
- Google Maps使用图算法来查找最优路线,考虑交通状况和距离。
-
网络流:优化网络中的数据流量。
- 如最大流算法用于找出网络中可传输的最大数据流量。
-
任务调度:管理项目任务之间的依赖关系。
- 在项目管理中,DAG用于表示任务先后顺序,帮助制定合理的调度计划。
持续更新中~~