Floyd算法求最短路径

floyd算法用于求图中各个点到其它点的最短路径,无论其中经过多少个中间点。该算法的核心理念是基于动态规划,

不断更新最短距离,遍历所有的点。

知识基础:图的邻接矩阵表示:

如图是一个简单图,从A开始,按照ABCDEFG的顺序来制定一个方阵,该方阵每一行代表一个点到所有点的直达距离,

到它本身的距离是0,如果两点之间没有直接相连(非邻接)的,那么这两点的距离就定位无穷或者-1,例如图中的A点到

其它所有点的距离为 0 7 ∞ 5 ∞ ∞ ∞ 按照ABCDEFG的顺序排列,方阵的每一行从上到下按照ABCDEFG的顺

序排列出各点到各点的距离,这样的方阵就叫做图的邻接矩阵,例如该图的邻接矩阵data为:

由于该图是无向图,所以它的邻接矩阵是先对角线对称的。如果是有向图的话则要根据边的方向来确定点与点间的距离。

编程中,我们一般用二维数组表示邻接矩阵。

算法核心:遍历图中的每一个点,通过该点的入读和出度来计算以该点作为中间点连接另外两点的距离,来与原来的距

离作比较,存最小的值,不断刷新。例如以k为中间点,计算i到j的最短距离,则比较data[i][j] 和data[i][k]+data[k][j]的

大小,如果后者更小,则刷新数组,令data[i][j] = data[i][k]+data[k][j]。由于k值是不断变化的,所以遍历完整个数

组,data[i][j]所存的值就是i到j的最小值。

需要注意的是,由i到j中间可能会经过多个点,所以我们要理解data[i][k]也并非表示i到k的直达距离,一开始data[i][k]

确实是i到k的直达距离,但是随着数组data的不断刷新,点到点的距离不再单是直达距离而是经过0个或多个点的最短距

离。所以data[i][k]中也可能经过多个点。而data[k][j]表示从k到j的直达距离因为后面的距离还没刷新(遍历数组是从上到

下,从左到右)。

记录路径 :定义一个二维数组path来记录各点到各点所经过的中间点,如果两点之间没有中间点的话就以它的起点作为

中间点,这样做的好处是能够通过反推找到完整的路径

代码

f = float('inf')  # float('inf')表示无穷大

# 准备数据
data = [
    [0, 7, f, 5, f, f, f],
    [7, 0, 8, 9, 7, f, f],
    [f, 8, 0, f, 5, f, f],
    [5, 9, f, 0, 15, 6, f],
    [f, 7, 5, 15, 0, 8, 9],
    [f, f, f, 6, 8, 0, 11],
    [f, f, f, f, 9, 11, 0],
]
path = [[i] * 7 for i in range(7)]

for k in range(7):
    for i in range(7):
        for j in range(7):
            if data[i][j] > data[i][k] + data[k][j]:  # 比较距离的大小
                data[i][j] = data[i][k] + data[k][j]  # 每次都存最小的值
                path[i][j] = k  # 记录中间点


# 定义函数找出x到y的具体路径
def show_trace(x,y):
    trace = []
    def add_trace(x, y):
        global mm
        if x != y:
            add_trace(x, path[x][y])
        return trace.append(y)
    add_trace(x,y)
    trace_str = str(trace)
    trace_str = trace_str.replace(',','-->')
    print(f"从 {x} 到 {y} 的最短路径为: {trace_str}")

for i in data:
    print(i)

show_trace(0,4)  # 求A到E的最短路径
show_trace(0,6)	 # 求A到G的最短路径

#[0, 7, 15, 5, 14, 11, 22]
#[7, 0, 8, 9, 7, 15, 16]
#[15, 8, 0, 17, 5, 13, 14]
#[5, 9, 17, 0, 14, 6, 17]
#[14, 7, 5, 14, 0, 8, 9]
#[11, 15, 13, 6, 8, 0, 11]
#[22, 16, 14, 17, 9, 11, 0]
#从 0 到 4 的最短路径为: [0--> 1--> 4]
#从 0 到 6 的最短路径为: [0--> 3--> 5--> 6]

接下再用2021蓝桥杯pythonA组的题目来深入理解

【问题描述】

小蓝学习了最短路径之后特别高兴,他定义了一个特别的图,希望找到图中的最短路径。

小蓝的图由2021个结点组成,依次编号1至2021

对于两个不同的结点a,b,如果a和b的差的绝对值大于21,则两个结点之间没有边相连;如果a和b的差的绝对值小于等于21,则两个点之间有一条长度为a和b的最小公倍数的无向边相连。

例如:结点1和结点23之间没有边相连;结点3和结点24之间有一条无向边,长度为24;结点15和结点25之间有一条无向边,长度为75.

请计算,结点1和结点2021之间的最短路径长度是多少。

题目分析:该题点与点之间是否直连受到二者差值的约束,线段的距离也是通过计算才能得出,因为是求1到2021的最短距离,所以

只需要1行的矩阵来记录1点到其它所有点的最短距离,同样的,1到2021的通过的中间点也只需要一行矩阵来存储。因此上面代码的

循环在这里可以减少一层。

解题代码:

def fn(x, y):
    x1, y1 = x, y
    while y1 != 0:
        x1, y1 = y1, x1 % y1
    return x*y//x1
n = 2021
# 由于一开始不知道1到各点的距离所以全设为无穷大,反正后面会计算求出
data = [float('inf')]*(n+1) # 这里数组向前进一位是为了后面计算最小公倍数更方便
path = [1]*(n+1) #相应地路径数组也要进一位
data[1] = 0 # 1到1的距离是0
for i in range(1, n+1):
    for j in range(i+1, i+22):
        if j > 2021:
            break
        if data[j] > fn(i,j) + data[i]:
            data[j] = fn(i,j) + data[i]
            path[j] = i # 记录中间点
# 显示路径
def show_trace(y):
    trace = []
    def add_trace(y):
        global mm
        if 1 != y:
            add_trace(path[y])
        return trace.append(y)
    add_trace(y)
    trace_str = str(trace)
    trace_str = trace_str.replace(',','-->')
    print(f"从 1 到 {y} 的最短路径为: {trace_str}")
print(data[n])
# 10266837
show_trace(25)# 路径太长了这里就不展示了

posted @ 2022-03-18 16:11  草帽小子路飞  阅读(2233)  评论(0编辑  收藏  举报