Floyd 算法,“哈利波特的考试”

Floyd 算法:

Floyd 算法利用一个三重循环计算一个存储边长度的图中任意两点间最短的路径,其逻辑可以简单地被表示为:

def floyd(weight_list, vertex_num):
    for k in range(vertex_num):
        for i in range(vertex_num):
            for j in range(vertex_num):
                if weight_list[i][k] + weight_list[k][j] < weight_list[i][j]:
                    weight_list[i][j] = weight_list[i][k] + weight_list[k][j]
                    if i == j and weight_list[i][j] < 0:
                        return False
    return True

如同上述函数表示的,weight_list 作为一个长和宽都为 vertex_num 的二维数组 (在保存边长度之前其中的值被初始化为一个很大的数。或者如果你愿意,可以称呼其为 邻接矩阵),对在图中的两点 v 和 w,weight_list[v][w] 保存点 v 到点 w 的距离(或者从 v 到 w 的开销,或者其他有意义的值)。算法完成后 weight_list 保存在图中任意联通的两点 v 和 w 之间最短的距离。

因为算法的特性,Floyd 算法不适用于计算存在边长为负值的图 (这会在图中形成有矛盾的闭环),循环内的判断条件会使函数被传入含有负值边的图后返回 False。

函数 floyd() 会改变传入其中的图的内容,所以传入的图应当是输入的“原图”的一个副本。


扩展:

可以向上述的 floyd() 函数中再传入一个二维数组来记录循环中对 weight_list 产生的变动,比如命名为 path。

path 的初始值可以设为全是很大的正数 (比如 INFINITY),或者全为负数。

在每次 weight_list 中的值发生改变时,让 path 记录下新的路径:

def floyd_with_path(weight_list, path, vertex_num):
    for k in range(vertex_num):
        for i in range(vertex_num):
            for j in range(vertex_num):
                if weight_list[i][k] + weight_list[k][j] < weight_list[i][j]:
                    weight_list[i][j] = weight_list[i][k] + weight_list[k][j]    # 这一步更新路径长度
                    path[i][j] = k    # 这一步记录发生的改变,等式的含义表示为 i -> k -> j
                    if i == j and weight_list[i][j] < 0:
                        return False
    return True
这样在读取 path 时会得到从某一点 i 到某一点 j 的最短路径上需要经过的中间点 path[i][j],把这一点记为 k,那么还会有从 i 到 k 需要经过的中间点 path[i][k],…,以此类推,最终到某个点 t 上 path[i][t] 是初始值 (创建 path 时填充的负数或者 INFINITY),那么最短路径就表达为 i -> t -> ... -> k -> j。

需要注意的一点是,单从 path 里无法辨别图中的两个点 v 和 w 是不连通或者没有中间点的联通,因为这两种情况下 path[v][w] 都是初始值。所以关于连通性需要结合 weight_list 一起来看。


哈利波特的考试:

题面如下:


哈利·波特要考试了,他需要你的帮助。这门课学的是用魔咒将一种动物变成另一种动物的本事。
例如将猫变成老鼠的魔咒是haha,将老鼠变成鱼的魔咒是hehe等等。
反方向变化的魔咒就是简单地将原来的魔咒倒过来念,例如ahah可以将老鼠变成猫。
另外,如果想把猫变成鱼,可以通过念一个直接魔咒lalala,也可以将猫变老鼠、老鼠变鱼的魔咒连起来念:hahahehe。


现在哈利·波特的手里有一本教材,里面列出了所有的变形魔咒和能变的动物。
老师允许他自己带一只动物去考场,要考察他把这只动物变成任意一只指定动物的本事。
于是他来问你:带什么动物去可以让最难变的那种动物(即该动物变为哈利·波特自己带去的动物所需要的魔咒最长)需要的魔咒最短?
例如:如果只有猫、鼠、鱼,则显然哈利·波特应该带鼠去,因为鼠变成另外两种动物都只需要念4个字符;
而如果带猫去,则至少需要念6个字符才能把猫变成鱼;同理,带鱼去也不是最好的选择。


输入格式:


输入说明:输入第1行给出两个正整数N (≤100) 和M,其中N是考试涉及的动物总数,M是用于直接变形的魔咒条数。
为简单起见,我们将动物按1~N编号。
随后M行,每行给出了3个正整数,分别是两种动物的编号、以及它们之间变形需要的魔咒的长度(≤100),数字之间用空格分隔。


输出格式:


输出哈利·波特应该带去考场的动物的编号、以及最长的变形魔咒的长度,中间以空格分隔。
如果只带1只动物是不可能完成所有变形要求的,则输出0。如果有若干只动物都可以备选,则输出编号最小的那只。


输入样例:


6 11
3 4 70
1 2 1
5 4 50
2 6 50
5 6 60
1 3 70
4 6 60
3 6 80
5 1 100
2 4 60
5 2 80
输出样例:


4 70

题面完


这题要求的是求出“某一点,使这个点到其它任意一点的最大开销是所有点中同样的开销里最小的”。(好绕口)

通过构造一个图存储关于点和边长的信息,然后使用上述的 floyd() 函数处理,再返回结果:

class Graph:    # 在这里建立一个二维数组作为图。PTA 疑似不支持 numpy 库
    def __init__(self, vertexnum=100):
        self.graph = []
        self.INFINITY = 65535
        for i in range(vertexnum):
            self.graph.append([self.INFINITY]*vertexnum)

    def insert_edge(self, edge):    # 向无向图中插入一条边,传入的 edge 是一个列表
        start, end, weight = edge[0]-1, edge[1]-1, edge[2]
        self.graph[start][end] = weight
        self.graph[end][start] = weight


def floyd(weight_list, vertex_num):    # 分别传入二维数组和点的个数
    for k in range(vertex_num):
        for i in range(vertex_num):
            for j in range(vertex_num):
                if weight_list[i][k] + weight_list[k][j] < weight_list[i][j]:
                    weight_list[i][j] = weight_list[i][k] + weight_list[k][j]
                    if i == j and weight_list[i][j] < 0:
                        return False
    return True


def find_max_dist(weight_list, p_start, vertex_num):    # 返回最长的一条边,如果图不连通会返回 INFINITY
    max_dist = 0
    for j in range(vertex_num):
        if p_start != j and weight_list[p_start][j] > max_dist:
            max_dist = weight_list[p_start][j]

    return max_dist


def find_animal(graph, vertex_num, INFINITY):    # 由 main() 调用,在这里复制二维数组并调用 floyd()
    animal = -404
    need_single = True

    weight_list = graph.copy()    #这里传入的不是 graph 对象,而是 graph.graph

    floyd_succeed = floyd(weight_list, vertex_num)

    min_dist = INFINITY
    for p_animal in range(vertex_num):
        longest = find_max_dist(weight_list, p_animal, vertex_num)

        if longest == INFINITY:
            need_single = False
        if min_dist > longest:
            min_dist = longest
            animal = p_animal+1

    if need_single and floyd_succeed:
        print(animal, min_dist)    # 总之最后显示的是“所有最大值中的最小值”
    else:
        print(0)


def main():
    n, m = (int(x) for x in input().split())
    # graph = np.full((n, n), INFINITY)    # 如果使用 numpy 库就不需要构造图
    graph = Graph(n)

    for i in range(m):
        edge = list(map(int, input().split()))
        graph.insert_edge(edge)

    find_animal(graph.graph, n, graph.INFINITY)


main()


猜你喜欢

转载自blog.csdn.net/asura319/article/details/79340104
今日推荐