文章目录
1.最短路径算法(如Dijkstra算法、Floyd-Warshall算法)
Dijkstra算法
import java.util.PriorityQueue;
public class Dijkstra {
private static final int INF = Integer.MAX_VALUE;
public static void dijkstra(int[][] graph, int start) {
int V = graph.length;
int[] dist = new int[V];
boolean[] visited = new boolean[V];
// 初始化距离和访问状态
for (int i = 0; i < V; i++) {
dist[i] = (i == start) ? 0 : INF;
visited[i] = false;
}
// 使用优先队列来保存待处理的节点
PriorityQueue<int[]> pq = new PriorityQueue<>((a, b) -> Integer.compare(a[1], b[1]));
pq.offer(new int[]{start, 0});
while (!pq.isEmpty()) {
int[] current = pq.poll();
int u = current[0];
int weight = current[1];
if (visited[u]) {
continue;
}
visited[u] = true;
for (int v = 0; v < V; v++) {
if (!visited[v] && graph[u][v] != INF && weight + graph[u][v] < dist[v]) {
dist[v] = weight + graph[u][v];
pq.offer(new int[]{v, dist[v]});
}
}
}
// 打印结果
for (int i = 0; i < V; i++) {
System.out.println("Distance from " + start + " to " + i + ": " + (dist[i] == INF ? "INF" : dist[i]));
}
}
public static void main(String[] args) {
int[][] graph = {
{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}
};
dijkstra(graph, 0);
}
}
Bellman-Ford算法
要检查图中是否存在负权重的环,我们需要在执行完Bellman-Ford算法的主要部分(即V-1次松弛操作)之后,再进行一次额外的松弛操作。如果在这次额外的操作中,还有边的距离被更新,那么说明图中存在负权重的环。
以下是完整的Bellman-Ford算法实现,包括负权重环的检查:
public class BellmanFord {
private static final int INF = Integer.MAX_VALUE;
public static boolean bellmanFord(int[][] graph, int start) {
int V = graph.length;
int[] dist = new int[V];
// 初始化距离
for (int i = 0; i < V; i++) {
dist[i] = INF;
}
dist[start] = 0;
// 对图中的每一条边进行V-1次松弛操作
for (int i = 0; i < V - 1; i++) {
for (int u = 0; u < V; u++) {
for (int v = 0; v < V; v++) {
if (graph[u][v] != INF && dist[u] != INF && dist[u] + graph[u][v] < dist[v]) {
dist[v] = dist[u] + graph[u][v];
}
}
}
}
// 检查负权重环
for (int u = 0; u < V; u++) {
for (int v = 0; v < V; v++) {
if (graph[u][v] != INF && dist[u] != INF && dist[u] + graph[u][v] < dist[v]) {
// 如果在V-1次松弛操作后还有距离被更新,说明存在负权重环
return false; // 返回false表示存在负权重环
}
}
}
// 如果没有发现负权重环,返回true
return true;
}
public static void main(String[] args) {
int[][] graph = {
// ... 图的邻接矩阵表示
};
boolean hasNegativeCycle = bellmanFord(graph, 0);
if (hasNegativeCycle) {
System.out.println("图中不存在负权重的环");
} else {
System.out.println("图中存在负权重的环");
}
// 如果需要,还可以打印出每个节点到起点的最短距离
// ...
}
}
在这个实现中,如果bellmanFord
方法返回false
,则表示图中存在负权重的环;如果返回true
,则表示图中不存在负权重的环。此外,在检查负权重环的循环中,我们并没有更新dist
数组,只是检查是否还有距离可以被更新。如果有,则说明存在负权重的环。
2.最小生成树算法(如Prim算法、Kruskal算法)
Prim算法
Prim算法是一种贪心算法,用于求解最小生成树。它从任意一个顶点开始,每次选择权值最小的边,并且这条边连接的另一个顶点必须不在已选择的顶点集合中。
import java.util.*;
class Graph {
int V; // 顶点数量
int[][] adjMatrix; // 邻接矩阵
boolean[] inMST; // 标记顶点是否已在最小生成树中
Graph(int v) {
V = v;
adjMatrix = new int[v][v];
inMST = new boolean[v];
}
void addEdge(int v, int w, int weight) {
adjMatrix[v][w] = weight;
adjMatrix[w][v] = weight;
}
void primMST() {
// 初始化,选择第一个顶点作为起始点
int startVertex = 0;
inMST[startVertex] = true;
// 用于存储已选择的边的集合
PriorityQueue<int[]> pq = new PriorityQueue<>((a, b) -> a[2] - b[2]);
// 将起始点相邻的边加入优先队列
for (int i = 0; i < V; i++) {
if (adjMatrix[startVertex][i] != 0 && !inMST[i]) {
pq.offer(new int[]{startVertex, i, adjMatrix[startVertex][i]});
}
}
while (!pq.isEmpty()) {
int[] edge = pq.poll();
int u = edge[0];
int v = edge[1];
int weight = edge[2];
// 如果顶点v已经在MST中,则跳过这条边
if (inMST[v]) continue;
// 将顶点v添加到MST中
inMST[v] = true;
// 打印选择的边
System.out.println("Edge " + u + " -- " + v + " == " + weight);
// 将与顶点v相邻且不在MST中的边加入优先队列
for (int i = 0; i < V; i++) {
if (adjMatrix[v][i] != 0 && !inMST[i]) {
pq.offer(new int[]{v, i, adjMatrix[v][i]});
}
}
}
}
// 主方法,用于测试Prim算法
public static void main(String[] args) {
Graph g = new Graph(4);
g.addEdge(0, 1, 10);
g.addEdge(0, 2, 6);
g.addEdge(0, 3, 5);
g.addEdge(1, 3, 15);
g.addEdge(2, 3, 4);
g.primMST();
}
}
Kruskal算法
Kruskal算法是一种基于并查集的算法,用于求解最小生成树。它首先将所有边按权重从小到大排序,然后依次选择边,如果选择的边不构成环,则将其加入最小生成树中。
import java.util.*;
class UnionFind {
int[] parent;
UnionFind(int n) {
parent = 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 rootX = find(x);
int rootY = find(y);
if (rootX != rootY) {
parent[rootX] = rootY;
}
}
boolean connected(int x, int y) {
return find(x) == find(y);
}
}
class Edge implements Comparable<Edge> {
int src, dest, weight;
Edge(int src, int dest, int weight) {
this.src = src
this.dest = dest;
this.weight = weight;
}
@Override
public int compareTo(Edge other) {
return Integer.compare(this.weight, other.weight);
}
}
class KruskalMST {
List<Edge> edges;
int V; // 顶点数量
KruskalMST(int v) {
V = v;
edges = new ArrayList<>();
}
void addEdge(int src, int dest, int weight) {
edges.add(new Edge(src, dest, weight));
}
void kruskalMST() {
Collections.sort(edges); // 对边按权重排序
UnionFind uf = new UnionFind(V);
int e = 0; // 已选择的边数
for (Edge edge : edges) {
int src = edge.src;
int dest = edge.dest;
// 如果src和dest不在同一个集合中,则将它们合并,并加入最小生成树
if (!uf.connected(src, dest)) {
uf.union(src, dest);
e++;
// 打印选择的边
System.out.println("Edge " + src + " -- " + dest + " == " + edge.weight);
// 如果选择的边数等于顶点数减一,则最小生成树已完成
if (e == V - 1) {
break;
}
}
}
}
// 主方法,用于测试Kruskal算法
public static void main(String[] args) {
KruskalMST g = new KruskalMST(4);
g.addEdge(0, 1, 10);
g.addEdge(0, 2, 6);
g.addEdge(0, 3, 5);
g.addEdge(1, 3, 15);
g.addEdge(2, 3, 4);
g.kruskalMST();
}
}
// 将UnionFind和Edge类放在这里,以便它们可以互相访问
// UnionFind类保持不变
// Edge类也已给出,现在它包含了完整的实现
// 当你运行KruskalMST的main方法时,它将打印出使用Kruskal算法找到的最小生成树的边
在这个示例中,KruskalMST
类表示一个使用Kruskal算法来找到最小生成树的图。Edge
类表示图中的边,并实现了Comparable
接口以便可以对边进行排序。UnionFind
类用于实现并查集数据结构,以便在Kruskal算法中检查两个顶点是否属于同一个集合(即是否在同一棵树中)。
3.拓扑排序(Topological Sort)
拓扑排序是对有向无环图(DAG, Directed Acyclic Graph)的顶点进行排序,使得对每一条有向边 (u, v),均有 u(在排序记录中)比 v 先出现。拓扑排序通常用于有向无环图中安排任务顺序的场景。
以下是使用Java实现拓扑排序的一个例子:
import java.util.*;
public class TopologicalSort {
private List<List<Integer>> adjList; // 邻接表表示图
private boolean[] visited; // 标记节点是否已访问
private Stack<Integer> stack; // 存储拓扑排序结果的栈
public TopologicalSort(int V) {
adjList = new ArrayList<>(V);
for (int i = 0; i < V; i++) {
adjList.add(new ArrayList<>());
}
visited = new boolean[V];
stack = new Stack<>();
}
// 添加边
public void addEdge(int v, int w) {
adjList.get(v).add(w);
}
// 拓扑排序的递归实现
public void topologicalSortUtil(int v) {
// 标记当前节点为已访问
visited[v] = true;
// 遍历所有相邻节点
for (int i : adjList.get(v)) {
if (!visited[i]) {
topologicalSortUtil(i);
}
}
// 将当前节点压入栈中,注意这里使用栈是为了逆序输出,如果要正序输出则可以用队列
stack.push(v);
}
// 拓扑排序的主方法
public List<Integer> topologicalSort() {
// 遍历所有节点
for (int i = 0; i < adjList.size(); i++) {
if (!visited[i]) {
topologicalSortUtil(i);
}
}
// 检查图中是否有环,如果有环则无法进行拓扑排序
for (boolean isVisited : visited) {
if (!isVisited) {
return null; // 或者抛出异常,表示图中存在环
}
}
// 将栈中的元素转为列表并返回
List<Integer> result = new ArrayList<>();
while (!stack.isEmpty()) {
result.add(stack.pop());
}
// 注意:由于我们使用栈来存储结果,这里返回的是拓扑排序的逆序结果
// 如果需要正序结果,则可以在添加元素到栈时改为添加到列表的头部
return result;
}
// 主方法,用于测试
public static void main(String[] args) {
TopologicalSort ts = new TopologicalSort(6);
// 添加边
ts.addEdge(5, 2);
ts.addEdge(5, 0);
ts.addEdge(4, 0);
ts.addEdge(4, 1);
ts.addEdge(2, 3);
ts.addEdge(3, 1);
// 执行拓扑排序
List<Integer> result = ts.topologicalSort();
// 输出结果
if (result != null) {
for (int vertex : result) {
System.out.print(vertex + " ");
}
} else {
System.out.println("图中存在环,无法进行拓扑排序。");
}
}
}
在这个实现中,我们首先创建了一个TopologicalSort
类,它包含了一个邻接表adjList
来存储图,一个布尔数组visited
来标记节点是否已访问,以及一个栈stack
来存储拓扑排序的结果。我们使用了递归的topologicalSortUtil
方法来遍历图的节点,并在遍历完成后将节点压入栈中。最后,在topologicalSort
方法中,我们遍历所有节点并调用递归方法。如果图中存在未访问的节点,则图中存在环,无法进行拓扑排序。否则,我们返回栈中的元素作为拓扑排序的结果。
在main
方法中,我们创建了一个TopologicalSort
对象,并向图中添加了一些边。然后,我们调用topologicalSort
方法来获取拓扑排序的结果,并输出结果。如果图中存在环,则输出相应的提示信息。