182. 跟着三叶学最短路径问题(存图方式)

三叶最短路径问题博客 "跟着大哥混有饭吃"

1.图1(跟着三叶学图论最短路算法)

这是 LeetCode 上的「743. 网络延迟时间」,难度为「中等」    

有 n 个网络节点,标记为1到 n。
给你一个列表times,表示信号经过 有向 边的传递时间。times[i] = (ui, vi, wi),其中ui是源节点,
vi是目标节点, wi是一个信号从源节点传递到目标节点的时间。
现在,从某个节点K发出一个信号。需要多久才能使所有节点都收到信号?如果不能使所有节点收到信号,返回-1 。

1 <= k <= n <= 100
1 <= times.length <= 6000
times[i].length == 3
1 <= ui, vi <= n
ui != vi
0 <= wi <= 100
所有 (ui, vi) 对都 互不相同(即,不含重复边)

times = [[2,1,1],[2,3,1],[3,4,1]], n = 4, k = 2
times = [[1,2,1]], n = 2, k = 1
times = [[1,2,1]], n = 2, k = 2

没办法自己不会分析,只能学三叶的分析,假装我会了!!!
做题首先: 看题目给出的条件,条件k < n <= 100 也就是节点>= 100, times.length <= 6000  边>= 6000
[从某个节点K发出一个信号。需要多久才能使所有节点都收到信号?] 这句话什么意思呢? 
举个例子:
比如太阳到地球需要10s, 到月球需要5s, 到火星需要8s, 那么太阳光最短几秒可以照射到这三个星球,
直接取太阳光带最远的星球花的时间是不是就好了,也就是到地球的10s.这个题是一个意思,我们只需要求出k点发出的信号到最远的地方所花的时间就好了.

# 存图方式: 重点*****  m 表示节点数, n表示边数
1. 邻接矩阵(二维数组存图,很方便理解和观看,但是消耗很大,耗时很长O(m**2))
chart = [[0 for _ in range(n)] for _ in range(n)]
def add(src, dest, weight):
    chart[src][dest] = weight
2.邻接表(链表形式存图, 非常适合稀疏图, 消耗很小O(m+n))
    下面我举了一个列子方便理解:
        n = 6  # 节点
        m = 10  # 边 一般会设置大点
        edges = [[0, 1, 100], [1, 2, 100], [0, 2, 500]]
        edges = [[4, 1, 1], [1, 2, 3], [0, 3, 2], [0, 4, 10], [3, 1, 1], [1, 4, 3]]
        idx = 0  # 边编号
        he = [-1 for _ in range(n)]

        e = [0 for _ in range(m)]
        ne = [0 for _ in range(m)]
        w = [0 for _ in range(m)]

        def add(src, dest, weight):
            nonlocal idx
            e[idx] = dest  # 当前边指向的节点dest
            ne[idx] = he[src]  # 链表的上一个边对应的idx
            he[src] = idx  # 当前起始节点指向的边
            w[idx] = weight  # 当前边的权重
            idx += 1
            """ 先看代码,熟悉一下看下面解释,看不懂继续看while循环代码
            同一个起点, 对应的边的链表:比如从9->3 9 ->4
            idx=0:  
            e[0] = 3   0号边指向3号节点
            ne[0] = he[9] = -1   -1表示结束
            he[9] = 0  9号节点指向0号边
            w[0] = x   0号边的权重
            
            idx = 1
            e[1] = 4   1号边指向4号节点
            ne[1] = he[9] = 0   1号边的上一个边是0号边
            he[9] = 1  9号节点执行1号边
            w[1] = x   1号边的权重
            
            最后发现每he[n]  n一直都表示的其实当前节点, 只不过he[n]的值一直在变换( 相当于链表头结点插入值), he[n]一直更换头结点
            idx = ne[he[n]] 指向的是他的下一个边的编号, 通过这个编号, 我们可以通过dest = e[idx], dest就是n指向的一个节点
            之后在通过ne[idx]拿到下一个idx的编号,一直到节点为-1
            9 -> 4 -> 3 -> -1    看不懂下面有代码while循环中的代码
            """

        for edge in edges:
            add(edge[0], edge[1], edge[2])

        # 上面解释不是很清楚,直接上代码
        i = he[0]  # 以0号节点为例, i = he[0]取出0号节点的指向的头节点(也就是一个边的编号)(链表里面存储的是边的编号)
        while i != -1:
            b = e[i]  # e中存储的i号边指向的节点
            c = w[i]  # w中存储的是i号边的权重
            print(b, c)
            i = ne[i]  # ne存储0号节点的下一个边(这些边都是从0号节点出发的,只不是使用数组代替链表存储了他们)

3.类(这个方式很少用, O(m))
class Edge(object):
    def __init__(self, src, dest, weight):
        self.src = src
        self.dest = dest
        self.weight = weight

1.Floyd 算法(邻接矩阵)

Floyd 算法主要分三步:
1.初始化图
distance = [[INF for _ in range(M)] for _ in range(M)]
distance[i][j] = distance[j][i] = 0 if i == j else INF  # i,j 相等就是同有个节点, 所以为0

2.填充值  (将给出的节点边权关系填充进去)
for item in times:
    s, t , c = item[0], item[1], item[2]
    distance[s][t] = c
3.dis[i][j]存储从i->j的最小距离
他有一个更新条件 if dis[i][j] > dis[i][p] + dis:  dis[i][j] = dis[i][p] + dis, 其实就是根据三角形边推出来的,其中p是中间过渡节点, 所以需要遍历

Floyd 算法会将所有节点之间的最短路径都求出来, 之后只要遍历dist[k][x], 找出k点到其他x节点(需要遍历)的最大值就好了
def floyd(dis):
    for p in range(1, n + 1):
        for i in range(1, n + 1):
            for j in range(1, n + 1):
                dis[i][j] = min(dis[i][j], dis[i][p] + dis[p][j])

class Solution(object):
    def networkDelayTime(self, times, n, k):
        """
        :type times: List[List[int]]
        :type n: int
        :type k: int
        :rtype: int
        https://zhuanlan.zhihu.com/p/339542626  Floyd 算法
        """
        M = 110
        INF = float("inf")
        distance = [[INF for _ in range(M)] for _ in range(M)]
        # 初始化图
        for i in range(1, n + 1):
            for j in range(1, n + 1):
                distance[i][j] = distance[j][i] = 0 if i == j else INF

        # 邻接矩阵图
        for item in times:
            s, t , c = item[0], item[1], item[2]
            distance[s][t] = c

        def floyd(dis):
            for p in range(1, n + 1):
                for i in range(1, n + 1):
                    for j in range(1, n + 1):
                        dis[i][j] = min(dis[i][j], dis[i][p] + dis[p][j])
        floyd(distance)
        ans = 0
        for i in range(1, n + 1):
            ans = max(ans, distance[k][i])
        return ans if ans != INF else -1

1.Dijkstra(邻接矩阵)

https://leetcode-cn.com/problems/network-delay-time/solution/gtalgorithm-dan-yuan-zui-duan-lu-chi-tou-w3zc/

"""743. 网络延迟时间"""
class Solution(object):
    def networkDelayTime(self, times, n, k):
        INF = float("inf")
        w = [[0 for _ in range(n)] for _ in range(n)]  # 节点之间的权重
        dist = [INF for _ in range(n)]  # 从初始节点到n节点的最多距离

        # 初始化图
        for i in range(n):
            for j in range(n):
                w[i][j] = 0 if i == j else INF

        # 添加边权关系
        for item in times:
            s, t, c = item[0] - 1, item[1] - 1, item[2]  # 以为题目是1->n, 需要-1变成0->n-1
            w[s][t] = c
        dist[k-1] = 0

        vis = [False for _ in range(n)]
        for i in range(n-1):  # 遍历次数,为节点次数, 每个节点都需要
            x = -1  # 找没被表示的节点,以该节点更新其他节点与初始节点的距离
            for p in range(n):
                if not vis[p] and (x == -1 or dist[p] < dist[x]): x = p
            vis[x] = True
            for p in range(n):
                dist[p] = min(dist[p], dist[x] + w[x][p])

        ans = max(dist)
        return -1 if ans >= INF / 2 else ans

1.Dijkstra(邻接表)

class Node(object):
    def __init__(self, x, val):
        self.x = x
        self.v = val

    def __gt__(self, other):
        return self.v > other.v

"""743. 网络延迟时间"""
class Solution(object):
    def __init__(self):
        self.n = 110
        self.m = 6010
        self.INF = float("inf")

        self.e = [0 for _ in range(self.m)]
        self.ne = [0 for _ in range(self.m)]
        self.he = [-1 for _ in range(self.n)]
        self.w = [0 for _ in range(self.m)]
        self.dist = [self.INF for _ in range(self.n)]  # 从初始节点到n节点的最多距离
        self.idx = 0

    def add(self, src, dest, weight):
        self.e[self.idx] = dest
        self.ne[self.idx] = self.he[src]
        self.he[src] = self.idx
        self.w[self.idx] = weight
        self.idx += 1

    def networkDelayTime(self, times, n, k):
        """堆优化 Dijkstra(邻接表)"""
        import heapq
        heap = []
        # 存图
        for item in times:
            self.add(item[0], item[1], item[2])

        vis = [False for _ in range(self.n)]
        heapq.heappush(heap, Node(k, 0))
        self.dist[k] = 0

        while heap:
            t: Node = heapq.heappop(heap)
            node, _ = t.x
            if vis[node]: continue
            vis[node] = True

            i = self.he[node]
            while i != -1:
                j = self.e[i]  # 下一个节点
                if self.dist[j] > self.dist[node] + self.w[i]:
                    self.dist[j] = self.dist[node] + self.w[i]
                    heapq.heappush(heap, Node(j, self.dist[j]))
                i = self.ne[i]  # 获取下一个边

        ans = max(self.dist[1:n+1])
        return -1 if ans >= self.INF / 2 else ans


s1 = Solution()
times = [[2,1,1],[2,3,1],[3,4,1]]; n = 4; k = 2
ret = s1.networkDelayTime(times, n, k)
print(ret)

1.Bellman Ford(类)

"""743. 网络延迟时间"""
class Edge(object):
    def __init__(self, src, dest, weight):
        self.src = src
        self.dest = dest
        self.w = weight

class Solution(object):
    def networkDelayTime(self, times, n, k):
        """Bellman Ford(类 & 邻接表)"""
        INF = float("inf")
        w = [[0 for _ in range(n)] for _ in range(n)]  # 节点之间的权重
        dist = [INF for _ in range(n+1)]  # 从初始节点到n节点的最多距离

        # 初始化图
        for i in range(n):
            for j in range(n):
                w[i][j] = 0 if i == j else INF

        # 添加边权关系
        for item in times:
            s, t, c = item[0] - 1, item[1] - 1, item[2]  # 以为题目是1->n, 需要-1变成0->n-1
            w[s][t] = c
        dist[k] = 0

        for i in range(n):
            new_dist = dist[:]  # 确保本次操作不影响之前的dist数据
            for edge in times:
                src, dest, weight = edge
                dist[dest] = min(dist[dest], new_dist[src] + weight)

        ans = max(dist[1:])
        return -1 if ans >= INF / 2 else ans


s1 = Solution()
times = [[2,1,1],[2,3,1],[3,4,1]]; n = 4; k = 2
ret = s1.networkDelayTime(times, n, k)
print(ret)

1. Bellman Ford 邻接表

class Solution(object):
    def __init__(self):
        self.n = 110
        self.m = 6010
        self.INF = float("inf")

        self.e = [0 for _ in range(self.m)]
        self.ne = [0 for _ in range(self.m)]
        self.he = [-1 for _ in range(self.n)]
        self.w = [0 for _ in range(self.m)]
        self.dist = [self.INF for _ in range(self.n)]  # 从初始节点到n节点的最多距离
        self.idx = 0

    def add(self, src, dest, weight):
        self.e[self.idx] = dest
        self.ne[self.idx] = self.he[src]
        self.he[src] = self.idx
        self.w[self.idx] = weight
        self.idx += 1

    def networkDelayTime(self, times, n, k):
        # 存图
        for item in times:
            self.add(item[0], item[1], item[2])
        self.dist[k] = 0

        for p in range(1, n+1):
            new_dist = self.dist[:]
            for q in range(1, n+1):
                i = self.he[q]
                while i != -1:
                    b = self.e[i]
                    self.dist[b] = min(self.dist[b], new_dist[q] + self.w[i])
                    i = self.ne[i]
        ans = max(self.dist[1:n+1])
        return -1 if ans >= self.INF / 2 else ans


s1 = Solution()
times = [[2,1,1],[2,3,1],[3,4,1]]; n = 4; k = 2
ret = s1.networkDelayTime(times, n, k)
print(ret)

1.SPFA(邻接表)

https://blog.csdn.net/m15738518751/article/details/47805003  这个细节讲得很好,但是没图
https://www.cnblogs.com/shadowland/p/5870640.html   这个图很好,但是没讲细节
class Solution(object):
    def __init__(self):
        self.n = 110
        self.m = 6010
        self.INF = float("inf")

        self.e = [0 for _ in range(self.m)]  # 某一条边指向的节点
        self.ne = [0 for _ in range(self.m)]  # 当前边指向的下一个边索引
        self.he = [-1 for _ in range(self.n)]  # 头结点对应的边
        self.w = [0 for _ in range(self.m)]  # 权重
        self.dist = [self.INF for _ in range(self.n)]  # 从初始节点到n节点的最多距离
        self.idx = 0
        self.vis = [False for _ in range(self.n)]

    def add(self, src, dest, weight):
        self.e[self.idx] = dest
        self.ne[self.idx] = self.he[src]
        self.he[src] = self.idx
        self.w[self.idx] = weight
        self.idx += 1

    def networkDelayTime(self, times, n, k):
        # 存图
        import queue
        for item in times:
            self.add(item[0], item[1], item[2])
        self.dist[k] = 0

        heap_q = queue.SimpleQueue()
        heap_q.put(k)
        self.vis[k] = True
        while not heap_q.empty():
            cur = heap_q.get()
            self.vis[cur] = False  # 当前节点拿出来之后,就可以把self.vis置位False了
            new_dist = self.dist[:]
            i = self.he[cur]  # 当前节点指向的边的头idx
            while i != -1:
                b = self.e[i]  # 指向的节点
                if self.dist[b] > new_dist[cur] + self.w[i]:
                    if not self.vis[b]:  # 如果下一个节点没在则加入heap_q, 否则只更新距离即可
                        heap_q.put(b)
                        self.vis[b] = True
                    self.dist[b] = new_dist[cur] + self.w[i]
                i = self.ne[i]
        ans = max(self.dist[1:n+1])
        return -1 if ans >= self.INF / 2 else ans


s1 = Solution()
times = [[2,1,1],[2,3,1],[3,4,1]]; n = 4; k = 2
ret = s1.networkDelayTime(times, n, k)
print(ret)
原文地址:https://www.cnblogs.com/liuzhanghao/p/15210001.html