图论基础知识--最小生成树算法kruskal(克鲁斯克尔)和普里姆算法(Prim算法);最短路径算法Dijkstra(迪杰斯特拉)和Floyd(弗洛伊德)

一.基础知识

 

         有向图   无向图

以无向图为例:

邻接矩阵:

度矩阵(对角矩阵):

二.最小生成树

应用:将网络顶点看着城市,边看着城市之间通讯网,边的权重看着成本,根据最小生成树可以构建城市之间成本最低的通讯网.

1.kruskal(克鲁斯克尔)算法

2.普里姆算法(Prim算法)

求点与点之间的最小生成树

代码:

#coding:utf-8
"""
最小生成树
"""
import networkx as nx
import matplotlib.pyplot as plt
import numpy as np
from numpy import random

G = nx.Graph()
# Matrix = np.array(random.randint((5), size=(5, 5)))
# print('==Matrix:', Matrix)
# print(maps)
Matrix = np.array(  [[3, 4, 0, 2, 2],
                     [4, 1, 0, 3, 4],
                     [0, 0, 0, 4, 4],
                     [2, 3, 4, 0, 3],
                     [2, 4, 4, 3, 1]])
#实际在用的时候,只用了下三角矩阵
#构建无向图
for i in range(len(Matrix)):
    for j in range(len(Matrix)):
        if Matrix[i, j] != 0:
            G.add_edge(i, j)
nx.draw_networkx(G)
plt.title("G")
plt.show()


class Graph(object):
    def __init__(self, Matrix):
        self.Matrix = Matrix
        self.nodenum = self.get_nodenum()
        self.edgenum = self.get_edgenum()

    def get_nodenum(self):
        return len(self.Matrix)

    def get_edgenum(self):
        count = 0
        for i in range(self.nodenum):  # 获取除去对角的下三角矩阵
            for j in range(i):
                # print('i,j', i, j)
                if self.Matrix[i][j] > 0 and self.Matrix[i][j] < 9999:
                    count += 1
        return count

    def kruskal(self):
        list = []
        if self.nodenum <= 0 or self.edgenum < self.nodenum - 1:
            return list

        edge_list = []
        for i in range(self.nodenum):  # 获取除去对角的下三角矩阵
            for j in range(i):
                if self.Matrix[i][j] < 9999:
                    edge_list.append([i, j, self.Matrix[i][j]])
        print('==排序之前边的集合 edge_list:', edge_list)
        edge_list.sort(key=lambda a: a[2])  # 已经排好序的边集合
        print('==排序以后边的集合 edge_list:', edge_list)
        group = [[i] for i in range(self.nodenum)]  # 存储代表元列表
        print('存储代表元列表', group)
        for edge in edge_list:
            for i in range(len(group)):
                if edge[0] in group[i]:
                    m = i#开始节点
                if edge[1] in group[i]:
                    n = i#终止节点
            if m != n:# 合并联通分量 进行存储元列表更新
                list.append(edge)
                print('开始节点m,终止节点n:', m, n)
                group[m] = group[m] + group[n]
                group[n] = []
                print('==更新后的代表元列表:', group)
        return list

    def prim(self):
        list = []
        if self.nodenum <= 0 or self.edgenum < self.nodenum - 1:
            return list

        selected_node = [0]
        candidate_node = [i for i in range(1, self.nodenum)]#候选节点
        # print('==candidate_node:', candidate_node)
        while len(candidate_node) > 0:
            begin, end, minweight = 0, 0, 9999
            for i in selected_node:
                for j in candidate_node:
                    if self.Matrix[i][j] < minweight:
                        minweight = self.Matrix[i][j]
                        begin = i#存储开始节点
                        end = j#存储终止节点
            list.append([begin, end, minweight])
            selected_node.append(end)#找到权重最小的边 加入可选节点
            candidate_node.remove(end)#候选节点被找到 进行移除
        return list
#
#
G = Graph(Matrix)
print('邻接矩阵为\n%s' % G.Matrix)
print('节点数据为%d,边数为%d\n' % (G.nodenum, G.edgenum))
print('------最小生成树kruskal算法------')
print(G.kruskal())

print('------最小生成树prim算法')
print(G.prim())



三.最短路径:

1.Dijkstra(迪杰斯特拉)算法

Dijkstra算法用于解决单源最短路问题,所谓单源最短路就是指定一个起点,求出这个起点到其它所有点的最短路。本质就是依次让每个顶点作为起点,更新最短路的过程。

示例:求M到各个顶点的最短路径

             

先构建邻接矩阵Adjacent, 初始状态dist[1~n] = inf, dist[M]=0,顶点更新状态vst[1~n] = 0

以点M为起点:

dist[M] = 0    vst[M] = 0
dist[W] = inf  vst[W] = 0
dist[E] = inf  vst[E] = 0
dist[D] = inf  vst[D] = 0
dist[X] = inf  vst[X] = 0

找出dist中值最小且未被使用的点,发现dist[M]=0最小,且vst[M]=0,未被使用,故将M作为新的起点.
找出所有M可达的顶点,为X、W、E
dist[X]=inf > 0+10 ,更新dist[X]=10
dist[W]=inf > 0+5 ,更新dist[W]=5
dist[E]=inf > 0+8 ,更新dist[E]=8
M已被使用,vst[M]=1

dist[M] = 0    vst[M] = 1
dist[W] = 5    vst[W] = 0
dist[E] = 8    vst[E] = 0
dist[D] = inf  vst[D] = 0
dist[X] = 10   vst[X] = 0

依次遍历每个顶点........ 

Inf = float('inf')
# Dijkstra算法,就是依次让每个顶点作为起点,更新最短路的过程。
Adjacent = [[0, 10, 5, Inf, 8],
            [10, 0, 3, 1, Inf],
            [5, 3, 0, 9, 2],
            [Inf, 1, 9, 0, 6],
            [8, Inf, 2, 6, 0]]

Src, Dst, N = 0, 4, 5


def dijstra(adj, src, dst, n):
    dist = [Inf] * n  # 初始化为inf无穷大
    dist[src] = 0  # 起始点到起始点距离为0
    vst = [0] * n  # 记录已经确定的顶点

    prev = [0] * n
    while True:
        now = -1
        for u in range(n):  # 找到dist最小且vst=0的点作为起点
            if not vst[u] and (now == -1 or dist[u] < dist[now]):
                now = u
        print('====now:', now)
        if now == -1:  # now未被更新,即表示所有顶点都被使用过,算法结束
            break
        for v in range(n):  # 遍历当前起点now能到达的所有点
            if dist[v] > dist[now] + adj[now][v]:  # 如果dist[v]大于dist[now] + adj[now][v] 则更新
                dist[v] = dist[now] + adj[now][v]
                prev[v] = now
            print('==dist:', dist)

        # assert 1==0
        vst[now] = 1  # 当前起点now已被使用过,vst[now]=1
    # print('===dist:', dist)
    # print('==prev:', prev)
    #
    # print('==dist[dst]:', dist[dst])
    return dist, prev

dist, prev = dijstra(Adjacent, Src, Dst, N)

print('==dist,prev:', dist, prev)

def construct_path(prev, index, path=[]):
    path = path + [prev[index]]
    if prev[index] == 0:#终止条件
        return path
    new_path = construct_path(prev, prev[index], path)
    return new_path
# res = construct_path(prev, 4,[4])
# print('==res:', res)
for i in range(len(prev)):
    path = construct_path(prev, i, [i])
    print('{}节点路径为:{}'.format(i, path))

2.Floyd(弗洛伊德)

本质任意两个节点最短路径是否经过此节点,用dp的思想来存储中间结果.

示例:

#Floyd(弗洛伊德)找最短路径 本质任意两个节点最短路径是否经过此节点
import numpy as np
Inf = float('inf')
DIS = [[0, 3, 8, Inf, -4],
            [Inf, 0, Inf, 1, 7],
            [Inf, 4, 0, Inf, Inf],
            [2, Inf, -5, 0, Inf],
            [Inf, Inf, Inf, 6, 0]]

Direction = [[0, 1, 2, 3, 4],
     [0, 1, 2, 3, 4],
     [0, 1, 2, 3, 4],
     [0, 1, 2, 3, 4],
     [0, 1, 2, 3, 4]]
V = 5
#k就是是否要经过的节点,i就是开始节点,j就是终止节点
for k in range(V):
    for i in range(V):
        for j in range(V):
            if DIS[i][k] + DIS[k][j] < DIS[i][j]:
                DIS[i][j] = DIS[i][k] + DIS[k][j]
                Direction[i][j] = Direction[i][k]

print('最终的距离矩阵{}'.format(np.array(DIS)))
print('方向矩阵{}'.format(np.array(Direction)))


def construct_path(Direction, i, j, path=[]):
    path = path + [Direction[i][j]]
    if Direction[i][j] == j:#终止条件
        return path
    new_path = construct_path(Direction, Direction[i][j], j, path)
    return new_path

#找到路径
for i in range(V):
    for j in range(V):
        path = construct_path(Direction, i, j, [i])
        print('{}--{}节点路径为:{}'.format(i, j, path))

 最终的距离矩阵和方向矩阵,其中方向矩阵可以用来还原开始节点到终止节点的路径:

参考:

https://blog.csdn.net/weixin_43093481/article/details/82702176

https://www.bilibili.com/video/BV1q4411M7r9?from=search&seid=4042347737055062965

猜你喜欢

转载自blog.csdn.net/fanzonghao/article/details/107556540