【视频讲解】bilibili
Dijkstra
Prim
【手动可视化】Algorithm Visualizer (https://algorithm-visualizer.org/)
【手动可视化】Data Structure Visualizations (https://www.cs.usfca.edu/~galles/visualization/Algorithms.html)
期末考试选择题中考察了连通分量和子图的区别。
图(Graph)是由节点(Vertex)和边(Edge)组成的一种数据结构,表示节点之间的关系。图分为有向图和无向图,边可以有权重。若图的顶点数为n,则它得生成树含有n-1条边。
无向图:几条边就是几个度(全部顶点的度的和等于边数的两倍)d=2e
有向图:某顶点的度=出度数+入度数(全部顶点的度=所有顶点出度+入度)
子图:就是一个整图的一部分,但是必须子图也是图。且也就是说边和顶点需要一起出现。
图可以使用邻接矩阵和邻接表两种方式进行存储,它们各有优缺点,适用于不同的场景。
期末考试有考到把给出的无向图的邻接矩阵写出来
邻接矩阵是使用二维数组来表示图的连接关系。对于有向图,矩阵中的元素 a[i][j]
表示从节点 i 到节点 j 是否存在边;对于无向图,矩阵是对称的,a[i][j] = a[j][i]
。
优点:
缺点:
邻接表是通过链表来表示图的连接关系。对于每个节点,用一个链表存储与它相邻的节点。
优点:
缺点:
class GraphMatrix {
private int[][] matrix;
public GraphMatrix(int n) {
matrix = new int[n][n];
}
public void addEdge(int i, int j) {
matrix[i][j] = 1;
matrix[j][i] = 1; // 对于无向图,设置对称位置的元素
}
// 其他操作...
}
import java.util.LinkedList;
class GraphList {
private int V;
private LinkedList<Integer>[] adjList;
public GraphList(int V) {
this.V = V;
adjList = new LinkedList[V];
for (int i = 0; i < V; ++i) {
adjList[i] = new LinkedList<>();
}
}
public void addEdge(int i, int j) {
adjList[i].add(j);
adjList[j].add(i); // 对于无向图,添加到两个节点的邻接表中
}
// 其他操作...
}
广度优先遍历是一种逐层访问图中节点的遍历方法。从起始节点开始,依次访问其所有相邻节点,然后再逐层访问下一层的节点。
import java.util.LinkedList;
import java.util.Queue;
class Graph {
private int V; // 节点数量
private LinkedList<Integer>[] adjList;
public Graph(int V) {
this.V = V;
adjList = new LinkedList[V];
for (int i = 0; i < V; ++i) {
adjList[i] = new LinkedList<>();
}
}
public void addEdge(int i, int j) {
adjList[i].add(j);
adjList[j].add(i); // 对于无向图,添加到两个节点的邻接表中
}
public void bfs(int start) {
boolean[] visited = new boolean[V];
Queue<Integer> queue = new LinkedList<>();
visited[start] = true;
queue.offer(start);
while (!queue.isEmpty()) {
int current = queue.poll();
System.out.print(current + " ");
for (int neighbor : adjList[current]) {
if (!visited[neighbor]) {
visited[neighbor] = true;
queue.offer(neighbor);
}
}
}
}
}
深度优先遍历是一种递归或栈的方式遍历图的方法。从起始节点开始,访问一个相邻节点,然后递归或压栈访问该节点的未访问相邻节点,直到没有未访问的相邻节点为止,然后回溯到上一层继续。
import java.util.LinkedList;
import java.util.Stack;
class Graph {
private int V; // 节点数量
private LinkedList<Integer>[] adjList;
public Graph(int V) {
this.V = V;
adjList = new LinkedList[V];
for (int i = 0; i < V; ++i) {
adjList[i] = new LinkedList<>();
}
}
public void addEdge(int i, int j) {
adjList[i].add(j);
adjList[j].add(i); // 对于无向图,添加到两个节点的邻接表中
}
public void dfs(int start) {
boolean[] visited = new boolean[V];
dfsRecursive(start, visited);
}
private void dfsRecursive(int current, boolean[] visited) {
visited[current] = true;
System.out.print(current + " ");
for (int neighbor : adjList[current]) {
if (!visited[neighbor]) {
dfsRecursive(neighbor, visited);
}
}
}
}
期末考试中,选择题考察了拓扑排序(哪种算法能判断一个图中有没有环),大题中考察了Dijkstra算法(给了有向图写出最短路径,需有过程)
最小生成树(Minimum Spanning Tree,简称 MST)是一个连通图的生成树,其中包含了图中所有的顶点,但是只包含足够的边以使得生成树的总权重最小。两个经典的算法用于找到最小生成树:Prim 算法和 Kruskal 算法。Prim 算法更注重顶点的选择,而 Kruskal 算法更注重边的选择。
动态演示在GreedAlgorithm-prim,kruskal,dijkstra
Prim 算法通过逐步选择连接两棵独立树的最小权重边来构建最小生成树。它始终在当前已选取的顶点集合和未选取的顶点集合之间找到权重最小的边。
注:在 Prim 算法中,通常规定图的边的权值不能为0。这是因为 Prim 算法的核心思想是选择具有最小权值的边,然后逐步构建最小生成树。如果存在权值为0的边,它们可能在选择过程中引起混淆。
import java.util.Arrays;
class PrimAlgorithm {
static final int V = 5;
int minKey(int key[], boolean mstSet[]) {
int min = Integer.MAX_VALUE, minIndex = -1;
for (int v = 0; v < V; v++) {
if (!mstSet[v] && key[v] < min) {
min = key[v];
minIndex = v;
}
}
return minIndex;
}
void primMST(int graph[][]) {
int parent[] = new int[V];
int key[] = new int[V];
boolean mstSet[] = new boolean[V];
Arrays.fill(key, Integer.MAX_VALUE);
key[0] = 0;
parent[0] = -1;
for (int count = 0; count < V - 1; count++) {
int u = minKey(key, mstSet);
mstSet[u] = true;
for (int v = 0; v < V; v++) {
if (graph[u][v] != 0 && !mstSet[v] && graph[u][v] < key[v]) {
parent[v] = u;
key[v] = graph[u][v];
}
}
}
printMST(parent, graph);
}
void printMST(int parent[], int graph[][]) {
System.out.println("Edge \tWeight");
for (int i = 1; i < V; i++) {
System.out.println(parent[i] + " - " + i + "\t" + graph[i][parent[i]]);
}
}
public static void main(String[] args) {
PrimAlgorithm t = new PrimAlgorithm();
int graph[][] = new int[][]{{0, 2, 0, 6, 0},
{2, 0, 3, 8, 5},
{0, 3, 0, 0, 7},
{6, 8, 0, 0, 9},
{0, 5, 7, 9, 0}};
t.primMST(graph);
}
}
Kruskal 算法通过按权重递增的顺序选择边来构建最小生成树。它始终选择不形成环路的边,直到构建完整的最小生成树。
import java.util.Arrays;
class KruskalAlgorithm {
class Edge implements Comparable<Edge> {
int src, dest, weight;
public int compareTo(Edge compareEdge) {
return this.weight - compareEdge.weight;
}
}
int V, E;
Edge edge[];
KruskalAlgorithm(int v, int e) {
V = v;
E = e;
edge = new Edge[E];
for (int i = 0; i < e; ++i)
edge[i] = new Edge();
}
int find(int parent[], int i) {
if (parent[i] == -1)
return i;
return find(parent, parent[i]);
}
void union(int parent[], int x, int y) {
int xset = find(parent, x);
int yset = find(parent, y);
parent[xset] = yset;
}
void kruskalMST() {
Edge result[] = new Edge[V];
int e = 0;
int i = 0;
for (i = 0; i < V; ++i)
result[i] = new Edge();
Arrays.sort(edge);
int parent[] = new int[V];
Arrays.fill(parent, -1);
i = 0;
while (e < V - 1) {
Edge nextEdge = edge[i++];
int x = find(parent, nextEdge.src);
int y = find(parent, nextEdge.dest);
if (x != y) {
result[e++] = nextEdge;
union(parent, x, y);
}
}
System.out.println("Edge \tWeight");
for (i = 0; i < e; ++i)
System.out.println(result[i].src + " - " + result[i].dest + "\t" + result[i].weight);
}
public static void main(String[] args) {
int V = 4;
int E = 5;
KruskalAlgorithm graph = new KruskalAlgorithm(V, E);
graph.edge[0].src = 0;
graph.edge[0].dest = 1;
graph.edge[0].weight = 10;
graph.edge[1].src = 0;
graph.edge[1].dest = 2;
graph.edge[1].weight = 6;
graph.edge[2].src = 0;
graph.edge[2].dest = 3;
graph.edge[2].weight = 5;
graph.edge[3].src = 1;
graph.edge[3].dest = 3;
graph.edge[3].weight = 15;
graph.edge[4].src = 2;
graph.edge[4].dest = 3;
graph.edge[4].weight = 4;
graph.kruskalMST();
}
}
Dijkstra 算法用于找到图中单源最短路径。它从起始节点开始,逐步选择距离最近的节点,并更新到其他节点的最短距离。
import java.util.Arrays;
class DijkstraAlgorithm {
static final int V = 9;
int minDistance(int dist[], boolean sptSet[]) {
int min = Integer.MAX_VALUE, min_index = -1;
for (int v = 0; v < V; v++) {
if (!sptSet[v] && dist[v] <= min) {
min = dist[v];
min_index = v;
}
}
return min_index;
}
void printSolution(int dist[]) {
System.out.println("Vertex \tDistance from Source");
for (int i = 0;
i < V; i++)
System.out.println(i + " \t" + dist[i]);
}
void dijkstra(int graph[][], int src) {
int dist[] = new int[V];
boolean sptSet[] = new boolean[V];
Arrays.fill(dist, Integer.MAX_VALUE);
dist[src] = 0;
for (int count = 0; count < V - 1; count++) {
int u = minDistance(dist, sptSet);
sptSet[u] = true;
for (int v = 0; v < V; v++) {
if (!sptSet[v] && graph[u][v] != 0 && dist[u] != Integer.MAX_VALUE &&
dist[u] + graph[u][v] < dist[v]) {
dist[v] = dist[u] + graph[u][v];
}
}
}
printSolution(dist);
}
public static void main(String[] args) {
int graph[][] = new int[][]{{0, 4, 0, 0, 0, 0, 0, 8, 0},
{4, 0, 8, 0, 0, 0, 0, 11, 0},
{0, 8, 0, 7, 0, 4, 0, 0, 2},
{0, 0, 7, 0, 9, 14, 0, 0, 0},
{0, 0, 0, 9, 0, 10, 0, 0, 0},
{0, 0, 4, 14, 10, 0, 2, 0, 0},
{0, 0, 0, 0, 0, 2, 0, 1, 6},
{8, 11, 0, 0, 0, 0, 1, 0, 7},
{0, 0, 2, 0, 0, 0, 6, 7, 0}};
DijkstraAlgorithm t = new DijkstraAlgorithm();
t.dijkstra(graph, 0);
}
}
拓扑排序用于有向无环图(DAG),它确定图中节点的线性顺序,使得对于每一条有向边 (u, v)
,节点 u
在拓扑排序中都出现在节点 v
的前面。
import java.util.*;
class TopologicalSort {
private int V;
private LinkedList<Integer> adj[];
TopologicalSort(int v) {
V = v;
adj = new LinkedList[v];
for (int i = 0; i < v; ++i)
adj[i] = new LinkedList();
}
void addEdge(int v, int w) {
adj[v].add(w);
}
void topologicalSortUtil(int v, boolean visited[], Stack<Integer> stack) {
visited[v] = true;
Integer i;
Iterator<Integer> it = adj[v].iterator();
while (it.hasNext()) {
i = it.next();
if (!visited[i])
topologicalSortUtil(i, visited, stack);
}
stack.push(v);
}
void topologicalSort() {
Stack<Integer> stack = new Stack<>();
boolean visited[] = new boolean[V];
for (int i = 0; i < V; i++)
visited[i] = false;
for (int i = 0; i < V; i++)
if (!visited[i])
topologicalSortUtil(i, visited, stack);
System.out.println("Topological Sort:");
while (!stack.empty())
System.out.print(stack.pop() + " ");
}
public static void main(String args[]) {
TopologicalSort g = new TopologicalSort(6);
g.addEdge(5, 2);
g.addEdge(5, 0);
g.addEdge(4, 0);
g.addEdge(4, 1);
g.addEdge(2, 3);
g.addEdge(3, 1);
g.topologicalSort();
}
}