Dijkstras 最短路径算法

在继续之前,建议你简要介绍一下 Adjacency Matrix 和 BFS

Dijkstra 算法被称为单源最短路径算法。它用于查找图中节点之间的最短路径,其可以表示例如道路网络。它由 Edsger W. Dijkstra于 1956 年构思,三年后出版。

我们可以使用广度优先搜索(BFS)搜索算法找到最短路径。该算法工作正常,但问题是,它假设遍历每条路径的成本相同,这意味着每条边的成本是相同的。Dijkstra 算法帮助我们找到每条路径成本不同的最短路径。

首先我们将看到,如何修改 BFS 以编写 Dijkstra 算法,然后我们将添加优先级队列以使其成为完整的 Dijkstra 算法。

假设每个节点与源的距离保持在 d [] 数组中。如同, d [3] 表示 d [3] 时间从到达节点 3 。如果我们不知道距离,我们将在 d [3]中存储无穷大。另外,让 [u] [v] 代表 uv 的成本。这意味着从 u 节点到 v 节点需要花费[u] [v] 。 ** **** **** **** **** **** ****

StackOverflow 文档

我们需要了解 Edge Relaxation。比方说,从你的房子,即源头,到 A 的位置需要 10 分钟。而且要花 B 分钟需要 25 分钟。我们有, **** ** ****

d[A] = 10
d[B] = 25

现在让我们说从 A 地B 处需要 7 分钟,这意味着: **** ****

cost[A][B] = 7

然后,我们可以去放置B通过进入放置A,然后从一个地方A,要去的地方B,这将需要 10 + 7 = 17 分钟,而不是 25 分钟。所以,

d[A] + cost[A][B] < d[B]

然后我们更新,

d[B] = d[A] + cost[A][B]

这叫做放松。我们将从节点 u 到节点 v ,如果 **d [u] + cost [u] [v] <d [v],**那么我们将更新 d [v] = d [u] + cost [u] [v]

在 BFS 中,我们不需要两次访问任何节点。我们只检查了是否访问过某个节点。如果没有访问它,我们将节点推入队列,将其标记为已访问并将距离增加 1.在 Dijkstra 中,我们可以推送队列中的节点,而不是使用访问更新它,我们放松或更新新边缘。我们来看一个例子:

StackOverflow 文档

我们假设,节点 1。然后,

d[1] = 0
d[2] = d[3] = d[4] = infinity (or a large value)

我们将 d [2],d [3]d [4]设置为*无穷大,*因为我们还不知道距离。而的距离当然是 0 。现在,我们从源代码转到其他节点,如果我们可以更新它们,那么我们将它们推入队列中。比方说,我们将遍历边缘 1-2 。当 d [1] + 2 <d [2]时d [2] = 2 。类似地,我们将遍历边缘 1-3 ,这使得 d [3] = 5

我们可以清楚地看到 5 不是我们可以跨越到节点 3 的最短距离。因此,像 BFS 一样只遍历一个节点在这里不起作用。如果我们使用边缘 2-3节点 2节点 3 ,我们可以更新 d [3] = d [2] + 1 = 3 。所以我们可以看到一个节点可以多次更新。你问多少次?节点可以更新的最大次数是节点的度数。 **** ****

让我们看一下多次访问任何节点的伪代码。我们将简单地修改 BFS:

procedure BFSmodified(G, source):
Q = queue()
distance[] = infinity
Q.enqueue(source)
distance[source]=0
while Q is not empty
    u <- Q.pop()
    for all edges from u to v in G.adjacentEdges(v) do
        if distance[u] + cost[u][v] < distance[v]
            distance[v] = distance[u] + cost[u][v]
        end if
    end for
end while
Return distance

这可用于从源中查找所有节点的最短路径。这段代码的复杂性不太好。这就是原因,

在 BFS 中,当我们从节点 1 到所有其他节点时,我们遵循先到先服务方法。例如,我们在处理节点 2 之前从节点 3 。如果我们从转到节点 3 ,我们将节点 4 更新为 5 + 3 = 8 。当我们再次从节点 2 更新节点 3 时,我们需要再次将节点 4 更新为 3 + 3 = 6 ! 因此节点 4 更新两次。 **** **** **** **** **** **** **** **** **** **** ****

Dijkstra 建议,如果我们先更新最近的节点,而不是先到先服务方法,那么它将需要更少的更新。如果我们之前处理过节点 2 ,那么节点 3 之前就已经更新,并且在相应地更新节点 4 之后,我们很容易得到最短的距离! 我们的想法是从最靠近的队列中选择节点。所以我们将在这里使用 Priority Queue ,这样当我们弹出队列时,它将为我们带来离最近的节点 u 。它会怎么做?它会用它来检查 d [u] 的值。 **** ****

我们来看看伪代码:

procedure dijkstra(G, source):
Q = priority_queue()
distance[] = infinity
Q.enqueue(source)
distance[source] = 0
while Q is not empty
    u <- nodes in Q with minimum distance[]
    remove u from the Q
    for all edges from u to v in G.adjacentEdges(v) do
        if distance[u] + cost[u][v] < distance[v]
            distance[v] = distance[u] + cost[u][v]
            Q.enqueue(v)
        end if
    end for
end while
Return distance

伪代码返回所有其他节点与源的距离。如果我们想知道单个节点 v 的距离,我们可以简单地在从队列中弹出 v 时返回该值。

现在,Dijkstra 算法在出现负边缘时是否有效?如果存在负循环,则会出现无限循环,因为它会每次都降低成本。即使存在负边缘,Dijkstra 也无法工作,除非我们在弹出目标后立即返回。但是,它不会是 Dijkstra 算法。我们需要 Bellman-Ford 算法来处理负边缘/周期。

复杂:

BFS 的复杂度为 O(log(V + E)) ,其中 V 是节点数, E 是边数。对于 Dijkstra,复杂性类似,但优先级队列的排序需要 O(logV) 。总复杂度为: O(Vlog(V)+ E)

下面是使用邻接矩阵求解 Dijkstra 最短路径算法的 Java 示例

import java.util.*;
import java.lang.*;
import java.io.*;

class ShortestPath
{
    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] == false && dist[v] <= min)
            {
                min = dist[v];
                min_index = v;
            }

        return min_index;
    }

    void printSolution(int dist[], int n)
    {
        System.out.println("Vertex Distance from Source");
        for (int i = 0; i < V; i++)
            System.out.println(i+" \t\t "+dist[i]);
    }

    void dijkstra(int graph[][], int src)
    {

        Boolean sptSet[] = new Boolean[V];

        for (int i = 0; i < V; i++)
        {
            dist[i] = Integer.MAX_VALUE;
            sptSet[i] = 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, V);
    }

    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}
                                };
        ShortestPath t = new ShortestPath();
        t.dijkstra(graph, 0);
    }
}

该计划的预期产出是

Vertex   Distance from Source
0                0
1                4
2                12
3                19
4                21
5                11
6                9
7                8
8                14