【图论】最短路算法总结

总分模式

  • Dijkstra:适用于权正的单源最短路径问题。
    • 稠密图:推荐使用临界矩阵存储图信息,朴素版 Dijkstra
    • 稀疏图:推荐使用邻接表存储图信息,pq 优化的 Dijkstra
  • Bellman-Ford:适用于权值有负值的图的单源最短路径,并且能够检测并输出负圈。
  • SPFA:适用于权值有负值,不要求输出负环的单源最短路径。
  • Floyd:每对节点之间的最短路径,可以处理带负权边的图(但不能有负权回路),虽然复杂度较高,但均摊到每一点对结点上,还不错/font>
Floyd Dijkstra bellman-ford SPFA
时间复杂度 O ( V ³ ) O(V³) 一般: O ( E + V )   l o g V O(E+V)\ logV

堆优化最坏: O ( V 2 l o g V ) O(V^2logV)
O ( E V ) O(EV) 最坏 O ( E V ) O(EV)
适用情况 稠密图和顶点关系密切 稠密图和顶点关系密切 稀疏图和边关系密切 稀疏图和边关系密切
负权 × ×
可否处理负权边 × ×
可否判负权回路 × × × ×

一、Dijkstra

朴素版 Dijkstra

private static int dijkstra() {
	dist[1] = 0;
	for (int i = 0; i < V; i++) {	  //跌打n次,表示从每一个结点开始一次查找
		int mini = -1;
		for (int j = 1; j <= V; j++) {//枚举每一个点
			if (vis[j] == false && (mini == -1 || dist[j] < dist[mini])) //从所有未访问的点钟找到dist最小的点
				mini = j;
		}
		vis[mini] = true;
		for (int j = 1; j <= V; j++) {
			dist[j] = Math.min(dist[j], dist[mini] + edges[mini][j]);
		}
	}
	return dist[V] == MAX_VALUE ? -1 : dist[V];
}

复杂度分析

  • 时间复杂度: O ( V 2 ) O(V^2)
  • 空间复杂度: O ( . . . ) O(...)

堆优化 Dijkstra

注:优先队列装载的结点信息有二:{编号 i,i 距离源点的最短路长度}

private static void dijkstra(int S) {
    dist[S] = 0;
    Queue<Node> q = new PriorityQueue<>((e1, e2) -> e1.cost - e2.cost);
    q.add(new Node(S, dist[S]));
    
    while (!q.isEmpty()) {
        Node now = q.poll();
		int v = now.to;
		if (vis[v])
			continue;
		vis[v] = true;
        for (int i = head[v]; i != 0; i = edges[i].next) {
			int to = edges[i].to, w = edges[i].w;
			if (dist[to] > dist[v] + w) {
				dist[to] = dist[v] + w;
				if (!vis[to]) {
					q.add(new Node(to, dist[to]));
					// vis[to] = true; 注
				}
			}
		}
    }
}

具体参考这里:https://blog.csdn.net/qq_43539599/article/details/105458608

复杂度分析

  • 时间复杂度: O ( E l o g V ) O(ElogV)
  • 空间复杂度: O ( . . . ) O(...)

二、Bellman-Ford

Dijkstra 算法处理负权图的最短路问题会失效,Bellman-Ford 算法有着效率较低,代码难度较小的特点。

private static void bellmanFord(int S) {
	Arrays.fill(dist, Double.POSITIVE_INFINITY);
	dist[S] = 0;
	//对每一个结点松弛第一遍
	for (int k = 0; k < V - 1; k++) {
		for (int i = 0; i < graph.length; i++)
		for (Edge edge : graph[i]) {
			if (dist[edge.to] > dist[i] + edge.cost)
				dist[edge.to] = dist[i] + edge.cost;
		}
	}
	//如果还能检测出最优路径,说明存在负环
	for (int k = 0; k < V - 1; k++) {
		for (int i = 0; i < graph.length; i++)
		for (Edge edge : graph[i]) {
			if (dist[edge.to] > dist[i] + edge.cost)
				dist[edge.to] = Double.NEGATIVE_INFINITY;
		}
	}
}

具体参考这里:https://blog.csdn.net/qq_43539599/article/details/105472733

复杂度分析

  • 时间复杂度: O ( E V ) O(EV)
  • 空间复杂度: O ( E V ) O(EV)

二、SPFA

方法一:朴素版 spfa

SPFA 是队列优化的 bellman-ford 算法

private static void spfa(int S) {
    dist[S] = 0;
    Queue<Integer> q = new ArrayDeque<>();
    q.add(S);
    inq[S] = true;
    
    while (!q.isEmpty()) {
        int v = q.poll();
		inq[v] = false;
        for (int i = head[v]; i != 0; i = edges[i].next) {
			int to = edges[i].to, w = edges[i].w;
			if (dist[to] > dist[v] + w) {
				dist[to] = dist[v] + w;
				if (!inq[to]) {
					q.add(to);
					inq[to] = true;
				}
			}
		}
    }
}

复杂度分析

  • 时间复杂度: O ( k E ) O(kE)
  • 空间复杂度: O ( . . . ) O(...)

方法二:SLF 优化

双端队列优化…

void spfa(int S) {
	Arrays.fill(dist, INF);
	dist[S] = 0;
	ArrayDeque<Integer> q = new ArrayDeque<>();
	q.add(S);
	inq[S] = true;
	
	while (!q.isEmpty()) {
		int v = q.poll();
		inq[v] = false;
		for (int i = head[v]; i != 0; i = edges[i].next) {
			int to = edges[i].to, w = edges[i].w;
			if (dist[to] > dist[v] + w) {
				dist[to] = dist[v] + w;
				if (!inq[to]) {
					if (!q.isEmpty() && dist[to] < dist[q.peek()]) {
						q.addFirst(to);
					} else {
						q.addLast(to);
					}
					inq[to] = true;
				}
			}
		}
	}
}

复杂度分析

  • 时间复杂度: O ( k E ) O(kE)
  • 空间复杂度: O ( . . . ) O(...)

四、Floyd

朴素 floyd

floyd 算法通常用来应对求任意两点间的最短路,比如叫你求一下每对结点的最短路长度。

void init() {
	...
}
void floyd() {
	for (int k = 1; k <= V; k++)
	for (int i = 1; i <= V; i++)
	for (int j = 1; j <= V; j++) {
		dp[i][j] = Math.min(dp[i][j], dp[i][k] + dp[k][j]);
	}
}

复杂度分析

  • 时间复杂度: O ( V 3 ) O(V^3)
  • 空间复杂度: O ( V 3 ) O(V^3)

求最小环

void init() {
	...
}
void floyd() {
	minCycle = INF;
	for (int k = 1; k <= V; k++) {	  //枚举中间点
		for (int i = 1; i < k; i++)   
		for (int j = i+1; j < k; j++) {
			minCycle = Math.min(minCycle, e[i][k]+e[k][j] + dist[i][j]);
		}
		for (int i = 1; i <= V; i++)  //枚举起点
		for (int j = 1; j <= V; j++) {//枚举终点点
			dist[i][j] = Math.min(dist[i][j], dist[i][k] + dist[k][j]);
		}
	}
}

复杂度分析

  • 时间复杂度: O ( V 3 ) O(V^3) ,V 大于 300 就最好不要用。
  • 空间复杂度: O ( V 3 ) O(V^3)

稠密图:边数约为结点数的两倍的图,即 E = 2V。想想一下图是不是很乱!
稀疏图:节点的数量 >> 边的数量,这样的图好看多了是不!

  • 有关稠密图与稀疏图的鲜明对比的习题:做一下这题
发布了691 篇原创文章 · 获赞 151 · 访问量 4万+

猜你喜欢

转载自blog.csdn.net/qq_43539599/article/details/105535039
今日推荐