Dijkstra 算法和 Floyd-Warshall 算法

Dijkstra 算法:

定义概览:

Dijkstra(迪杰斯特拉)算法是典型的单源最短路径算法,用于计算一个节点到其他所有节点的最短路径。主要特点是以起始点为中心向外层层扩展,直到扩展到终点为止。Dijkstra算法是很有代表性的最短路径算法,在很多专业课程中都作为基本内容有详细的介绍,如数据结构,图论,运筹学等等。注意该算法要求图中不存在负权边。

问题描述:在无向图 G=(V,E) 中,假设每条边 E[i] 的长度为 w[i],找到由顶点 V0 到其余各点的最短路径。(单源最短路径)

Dijkstra 算法的思路可以类比成探路

假设你从某个地方出发,逐步前进,每次都选择当前可达范围内最短的路径,不断扩大已探明的区域,直到找到所有地方的最短路径。

理解方法:

        1. 贪心策略:每一步都选择当前看起来最优的路径(即当前距离最近的未访问节点),逐步构建出从起点到其他所有节点的最短路径。

        2. 单源出发:只关注从一个节点到其他节点的最短路径,不考虑其他起点。

关键点:

  1. 距离的动态更新:通过每个新访问的节点,更新与它相连的其他节点的最短距离。

  2. 已访问与未访问的分离:将已确定最短路径的节点标记为已访问,不再考虑。

  3. 只处理非负权值:因为贪心策略假设从当前节点扩展是最优的,这在有负权边时可能不成立。

举个例子来帮助理解:假设你在一个城市找公交路线,从某站出发(起点),要去其他站(终点)。你总是选择当前最短的路线,这样每次出发都离目标更近一步。 

 注:

为什么Dijkstra不能处理负权边?

在一个图中,我们有四个节点:A、B、C和D。节点之间的边有权重,这些权重表示从一个节点到另一个节点的距离或成本。具体来说,从节点A到节点B的边有一个正权重4,而从节点B到节点C的边有一个负权重-2。这意味着,虽然从A到B需要4个单位的成本,但是从B到C实际上会减少2个单位的成本。

在Dijkstra算法中,我们通常寻找从单一源点到所有其他节点的最短路径。这个算法的核心思想是贪心策略,即在每一步选择当前已知的最短路径。然而,当图中存在负权边时,这个贪心策略可能会失败。因为即使我们已经选择了从A到B的路径,如果我们后来发现通过B到C的负权边可以进一步减少总成本,那么之前选择的路径就不再是最短的了。

在这个例子中,虽然从A到B的路径成本是4,但通过B到C的路径可以使得总成本减少到2(4 - 2 = 2)。如果Dijkstra算法没有考虑到B到C的这条边,它可能会错过从A到C的最短路径。

代码实现: 

Dijkstra(Graph, source):
    初始化距离表dist[], 所有节点距离设为∞
    dist[source] = 0
    创建优先队列pq,存储 (节点, 最短路径值)
    pq.push((0, source))  # 起点距离为0
    
    while pq 非空:
        (current_dist, current_node) = pq.pop()
        如果 current_dist > dist[current_node]:
            跳过
        
        遍历 current_node 的邻居节点 neighbor:
            计算新距离 new_dist = current_dist + weight(current_node, neighbor)
            如果 new_dist < dist[neighbor]:
                更新 dist[neighbor] = new_dist
                pq.push((new_dist, neighbor))
    
    返回 dist[]
import heapq

def dijkstra(graph, start):
    # graph 是一个邻接表,表示图;start 是起点
    # 初始化最短路径为无穷大
    dist = {node: float('inf') for node in graph}
    dist[start] = 0
    # 优先队列(小根堆)
    pq = [(0, start)]  # (距离, 节点)

    while pq:
        current_dist, current_node = heapq.heappop(pq)

        # 如果当前节点的距离已经大于已知最短路径,跳过
        if current_dist > dist[current_node]:
            continue

        # 遍历邻接节点并更新最短路径
        for neighbor, weight in graph[current_node]:
            distance = current_dist + weight

            # 如果找到更短的路径,更新
            if distance < dist[neighbor]:
                dist[neighbor] = distance
                heapq.heappush(pq, (distance, neighbor))

    return dist

# 示例图(邻接表表示法)
graph = {
    'A': [('B', 1), ('C', 4)],
    'B': [('A', 1), ('C', 2), ('D', 5)],
    'C': [('A', 4), ('B', 2), ('D', 1)],
    'D': [('B', 5), ('C', 1)],
}

start_node = 'A'
shortest_paths = dijkstra(graph, start_node)
print(shortest_paths)

运行结果: 

结果分析: 

  • 这是从起点 A 出发,求得从 A 到其他所有节点的最短路径。

  • 输出字典中每个键值对的含义是:从起点 A 到相应节点的最短路径的距离。

    • AA 的最短路径是 0(因为起点到自己距离为 0)。

    • AB 的最短路径是 1

    • AC 的最短路径是 3

    • AD 的最短路径是 4

 Floyd-Warshall 算法:

定义概览:

Floyd-Warshall 算法是一种用于计算图中所有顶点对之间的最短路径的经典算法。它适用于有向图无向图,并且可以处理正权值负权值的图(但不支持有负权环的图)。Floyd-Warshall 算法通过动态规划的思想,逐步优化所有点对之间的最短路径。

理解方法 :

        1. 动态规划:通过不断尝试“中间节点”,将点对之间的路径逐步优化。

        2. 多源最短路径:计算每个点到每个点的最短路径,而不仅是单个源点。

关键点: 

        1. 路径的逐步优化:每次加入一个中间节点 k,更新所有点对 i,ji, ji,j 的最短路径。

        2. 完整性:无论从哪个节点出发,到任何节点都能得到最优路径。

        3. 支持负权值边:只要图中没有负权环,算法都能正确计算最短路径。

 举个例子来帮助理解:假设你要从一个城市飞往另一个城市,直达的票价可能贵,但通过一个中转城市可能更便宜。你会逐步检查所有可能的中转站,找到最便宜的路线。 

代码实现:

1. 初始化:dist[i][j]为顶点i到顶点j的直接边的权重,如果没有边则为∞,dist[i][i] = 0。
2. 对于每个中间顶点k(从1到V):
   1. 对于每对顶点(i, j):
      - 更新dist[i][j] = min(dist[i][j], dist[i][k] + dist[k][j])
def floyd_warshall(graph):
    # graph 是邻接矩阵表示的图,graph[i][j] 表示从 i 到 j 的边的权重,若无边则为无穷大
    V = len(graph)  # 顶点数
    dist = [row[:] for row in graph]  # 创建一个复制的矩阵来存储最短路径

    # 使用动态规划更新最短路径
    for k in range(V):
        for i in range(V):
            for j in range(V):
                if dist[i][j] > dist[i][k] + dist[k][j]:
                    dist[i][j] = dist[i][k] + dist[k][j]

    return dist


# 示例图(邻接矩阵表示法)
# 无边用无穷大表示
INF = float('inf')
graph = [
    [0, 1, 4, INF],
    [1, 0, 2, 5],
    [4, 2, 0, 1],
    [INF, 5, 1, 0]
]

shortest_paths = floyd_warshall(graph)
for row in shortest_paths:
    print(row)

 运行结果:

结果分析: 

      这是图中所有顶点对之间的最短路径。每行表示从某个顶点出发到其他顶点的最短路径。

  • 第一行表示从 A 到其他节点的最短路径:AA 的距离是 0AB 的距离是 1AC 的距离是 3AD 的距离是 4

  • 第二行表示从 B 到其他节点的最短路径:BA 的距离是 1BB 的距离是 0BC 的距离是 2BD 的距离是 3

  • 第三行表示从 C 到其他节点的最短路径:CA 的距离是 3CB 的距离是 2CC 的距离是 0CD 的距离是 1

  • 第四行表示从 D 到其他节点的最短路径:DA 的距离是 4DB 的距离是 3DC 的距离是 1DD 的距离是 0

两者对比 :

算法的视角:

  • Dijkstra
    • 专注于单个出发点,逐步扩展路径。
    • 每次只关注一个点,并通过贪心确定最优。
  • Floyd-Warshall
    • 通过引入中转节点,全面优化所有点对之间的路径。
    • 涉及所有点的动态调整。

核心公式比较:

  • Dijkstra
    更新规则:

    \text{dist}[v] = \min(\text{dist}[v], \text{dist}[u] + \text{weight}[u][v])

    (如果当前点 u 的最短路径确定,则检查是否可以通过 u 更快到达邻接点 v。)

  • Floyd-Warshall
    更新规则:

    \text{dist}[i][j] = \min(\text{dist}[i][j], \text{dist}[i][k] + \text{dist}[k][j])

    (尝试使用中转点 k 优化 i→ji \to ji→j 的路径。)

应用场景对比:

维度DijkstraFloyd-Warshall
问题类型单源最短路径多源最短路径
路径优化方式逐步扩展,从起点贪心出发中间点全局优化
适用图的类型非负权值支持负权值,但无负权环
复杂度O(V^2) or O((V+E) \log V)O(V^3)
优点对稀疏图(少量边)效率更高适合稠密图(大量边)和多源问题
例子计算一个城市到其他城市的最短路计算所有城市间的最短路

总结理解:

  • 如果你只关心从一个出发点到所有节点的最短路径,用 Dijkstra,它像逐步扩展的“探路者”。

  • 如果你关心所有节点之间的最短路径,用 Floyd-Warshall,它像尝试各种“中转”方式的“航线优化师”。

选择合适的算法取决于你的需求和图的特性:

  • 单点到所有点:Dijkstra。

  • 所有点到所有点:Floyd-Warshall。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值