总分模式
- Dijkstra:适用于权正的单源最短路径问题。
- 稠密图:推荐使用临界矩阵存储图信息,朴素版 Dijkstra
- 稀疏图:推荐使用邻接表存储图信息,pq 优化的 Dijkstra
- Bellman-Ford:适用于权值有负值的图的单源最短路径,并且能够检测并输出负圈。
- SPFA:适用于权值有负值,不要求输出负环的单源最短路径。
- Floyd:每对节点之间的最短路径,可以处理带负权边的图(但不能有负权回路),虽然复杂度较高,但均摊到每一点对结点上,还不错/font>
Floyd | Dijkstra | bellman-ford | SPFA | |
---|---|---|---|---|
时间复杂度 | 一般:
堆优化最坏: |
最坏 | ||
适用情况 | 稠密图和顶点关系密切 | 稠密图和顶点关系密切 | 稀疏图和边关系密切 | 稀疏图和边关系密切 |
负权 | √ | √ | √ | |
可否处理负权边 | √ | √ | √ | |
可否判负权回路 | √ | √ |
一、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];
}
复杂度分析
- 时间复杂度: ,
- 空间复杂度: ,
堆优化 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
复杂度分析
- 时间复杂度: ,
- 空间复杂度: ,
二、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
复杂度分析
- 时间复杂度: ,
- 空间复杂度: ,
二、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;
}
}
}
}
}
复杂度分析
- 时间复杂度: ,
- 空间复杂度: ,
方法二: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;
}
}
}
}
}
复杂度分析
- 时间复杂度: ,
- 空间复杂度: ,
四、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]);
}
}
复杂度分析
- 时间复杂度: ,
- 空间复杂度: ,
求最小环
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]);
}
}
}
复杂度分析
- 时间复杂度: ,V 大于 300 就最好不要用。
- 空间复杂度: ,
稠密图:边数约为结点数的两倍的图,即 E = 2V。想想一下图是不是很乱!
稀疏图:节点的数量 >> 边的数量,这样的图好看多了是不!
- 有关稠密图与稀疏图的鲜明对比的习题:做一下这题