给定一个带权重的有向图和一个源顶点,找出从源顶点到图中所有其他顶点的最短路径。图中的边权重可以表示距离、时间或费用等,并且假设所有的权重都是非负数。
考虑以下有向图,顶点用数字表示,边的权重标在箭头旁边。
1 --(1)--> 2 --(3)--> 3
| | ^
|(4) |(2) |
| v |
+------> 4 ----(7)---+
输入:源顶点为1。
输出:从顶点1到其他顶点的最短路径长度。
Dijkstra算法是解决加权有向图中单源最短路径问题的一个非常有效的算法。算法的基本思想是维护两个集合:已确定最短路径的顶点集合S和未确定最短路径的顶点集合Q。算法从源顶点开始,逐步将距离最小的顶点从Q移动到S,并更新其它顶点到源顶点的距离。
import java.util.Arrays;
public class DijkstraAlgorithm {
// 图的顶点个数
private static final int V = 4;
// 找到未处理的距离最小的顶点
int minDistance(int[] dist, Boolean[] sptSet) {
int min = Integer.MAX_VALUE, minIndex = -1;
for (int v = 0; v < V; v++)
if (!sptSet[v] && dist[v] <= min) {
min = dist[v];
minIndex = v;
}
return minIndex;
}
// 打印最短路径数组
void printSolution(int[] dist) {
System.out.println("Vertex \t\t Distance from Source");
for (int i = 0; i < V; i++)
System.out.println(i + " \t\t " + dist[i]);
}
// 实现Dijkstra算法
void dijkstra(int[][] graph, int src) {
int[] dist = new int[V]; // 输出数组,dist[i]会保存从源到i的最短路径长度
Boolean[] sptSet = new Boolean[V]; // sptSet[i]为true表示顶点i已处理
Arrays.fill(dist, Integer.MAX_VALUE);
Arrays.fill(sptSet, false);
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, 1, 0, 4},
{0, 0, 3, 2},
{0, 0, 0, 0},
{0, 0, 7, 0}
};
DijkstraAlgorithm t = new DijkstraAlgorithm();
t.dijkstra(graph, 0);
}
}
请注意,上述代码假设图用邻接矩阵表示,且图中没有负权边。Dijkstra算法在有负权边的图中可能不会得到正确的结果,在这种情况下,可以使用Bellman-Ford算法。
Bellman-Ford算法是一种用于在加权图中找到单源最短路径的算法,它可以处理图中包含负权边的情况。与Dijkstra算法相比,Bellman-Ford算法更为通用,但在没有负权边的情况下效率较低。
Bellman-Ford算法通过对所有边进行多次松弛操作(relaxation)来逐步减少到每个顶点的估计距离,直到这些估计值反映出从源顶点到所有其他顶点的最短路径长度。松弛操作指的是更新当前的最短路径估计,如果通过某条边到达其终点的路径比已知的最短路径更短,则进行更新。
O(VE)
,其中V
是顶点数,E
是边数。class Graph {
class Edge {
int src, dest, weight;
Edge() {
src = dest = weight = 0;
}
}
int V, E;
Edge edge[];
Graph(int v, int e) {
V = v;
E = e;
edge = new Edge[e];
for (int i = 0; i < e; ++i)
edge[i] = new Edge();
}
void BellmanFord(Graph graph, int src) {
int V = graph.V, E = graph.E;
int dist[] = new int[V];
// Step 1: Initialize distances from src to all other vertices as INFINITE
for (int i = 0; i < V; ++i)
dist[i] = Integer.MAX_VALUE;
dist[src] = 0;
// Step 2: Relax all edges |V| - 1 times.
for (int i = 1; i < V; ++i) {
for (int j = 0; j < E; ++j) {
int u = graph.edge[j].src;
int v = graph.edge[j].dest;
int weight = graph.edge[j].weight;
if (dist[u] != Integer.MAX_VALUE && dist[u] + weight < dist[v])
dist[v] = dist[u] + weight;
}
}
// Step 3: Check for negative-weight cycles.
for (int j = 0; j < E; ++j) {
int u = graph.edge[j].src;
int v = graph.edge[j].dest;
int weight = graph.edge[j].weight;
if (dist[u] != Integer.MAX_VALUE && dist[u] + weight < dist[v]) {
System.out.println("Graph contains negative weight cycle");
return;
}
}
printArr(dist, V);
}
// A utility function to print the solution
void printArr(int dist[], int V) {
System.out.println("Vertex Distance from Source");
for (int i = 0; i < V; ++i)
System.out.println(i + "\t\t" + dist[i]);
}
}
Bellman-Ford算法广泛应用于各种领域,特别是在网络路由协议如距离向量路由协议中,它可以帮助找到最优的路径,并且有效地处理负权边的情况。此外,它还能够检测图中是否存在负权回路,这在某些应用中非常重要。