(3-5-2)Bellman-Ford算法:路径算法性能分析与可视化(2)

(6)下面的代码实现了Bellman-Ford,用于在带有权重的图中找到从指定源节点到所有其他节点的最短路径或检测是否存在负权重环。该算法首先初始化距离数组为无穷大,然后通过多次松弛操作逐步更新距离数组,直到找到所有最短路径或检测到负权重环。最后,它返回结果数组,其中包含了从源节点到其他节点的最短路径或相应的提示信息,以及可选地绘制了图形化的结果。

# 4.2 Bellman-Ford算法的编码
def Bellman_Ford(M, d):
    n = len(M)  # 图中的顶点数
    distances = [float('inf')] * n  # 初始距离数组为无穷大
    distances[d] = 0  # 源点到自身的距离为0
    predecesseurs = [None] * n  # 初始前驱数组为None

    # 通过松弛边来更新距离
    for i in range(n - 1):  # 重复(n - 1)次
        for u in range(n):
            for v in range(n):
                poids_uv = M[u][v]  # 边(u, v)的权重
                if poids_uv is not None:
                    if distances[u] + poids_uv < distances[v]:
                        distances[v] = distances[u] + poids_uv
                        predecesseurs[v] = u

    # 检查是否存在负权重环路
    for u in range(n):
        for v in range(n):
            poids_uv = M[u][v]  # 边(u, v)的权重
            if poids_uv is not None:
                if distances[u] + poids_uv < distances[v]:
                    return "存在从d到达的路径,但存在负权重环路."

    # 构建路径或标记不可达
    resultats = []
    for s in range(n):
        if s != d:
            if distances[s] == float('inf'):
                resultats.append(f"顶点{s}在图中无法通过路径到达d.")
            else:
                chemin = []
                sommet_courant = s
                while sommet_courant is not None:
                    chemin.insert(0, sommet_courant)
                    sommet_courant = predecesseurs[sommet_courant]
                resultats.append((distances[s], chemin))

    return resultats


# 示例
matrice = graphe2(5, 0.5, 0, 10)
print(matrice)

source = 1
resultats = Bellman_Ford(matrice, source)

path = None
for resultat in resultats:
    print(resultat)
    if isinstance(resultat, str):
        print(resultat)
    else:
        _, path = resultat
        break

dessiner_graphe(matrice, chemins={source: (None, path)} if path is not None else None)

(7)下面的这段代码使用广度优先搜索(BFS)算法从给定起始节点出发,遍历有向图并返回遍历的节点列表。在遍历过程中将节点标记为已访问,将其加入结果列表,并将其未访问的邻居节点加入队列中,以实现广度优先的遍历方式。

def pl(M, s):
    n = len(M)
    couleur = {}     # 将所有顶点都标记为白色,并将起始顶点 s 标记为绿色
    for i in range(n):
        couleur[i] = 'blanc'
    couleur[s] = 'vert'
    file = [s]
    Resultat = [s]
    while file != []:
        i = file[0]           # 取出队列的第一个元素
        for j in range(n):  # 将顶点 i 的白色后继顶点加入队列:
            if M[file[0]][j] == 1 and couleur[j] == 'blanc':
                file.append(j)
                couleur[j] = 'vert'  # 将它们标记为绿色(已访问顶点)
                Resultat.append(j)  # 将它们加入 Resultat 列表中
        file.pop(0)  # 出队 i(移除队列的第一个元素)
    return Resultat

(8)函数pp(M, s)实现了深度优先搜索算法,用于从图中的特定起始顶点开始,按照深度优先的方式遍历图的所有节点,并返回遍历结果的顺序。

def Bellman_Ford(M, d, ordre):
    n = len(M)  # 图中顶点的数量
    distances = [float('inf')] * n  # 距离数组初始为无穷大
    distances[d] = 0  # 源点到自身的距离为0
    predecesseurs = [None] * n  # 前驱数组初始为None

    if ordre == 'aleatoire':  # 随机顺序
        fleches = []
        for u in range(n):
            for v in range(n):
                poids_uv = M[u][v]  # 边(u, v)的权重
                if poids_uv is not None:
                    fleches.append((u, v))
        random.shuffle(fleches)
    elif ordre == 'largeur':  # 宽度优先顺序
        fleches = []
        parcours = pl(M, d)
        for u in parcours:
            for v in range(n):
                poids_uv = M[u][v]  # 边(u, v)的权重
                if poids_uv is not None:
                    fleches.append((u, v))
    elif ordre == 'profondeur':  # 深度优先顺序
        fleches = []
        parcours = pp(M, d)
        for u in parcours:
            for v in range(n):
                poids_uv = M[u][v]  # 边(u, v)的权重
                if poids_uv is not None:
                    fleches.append((u, v))

    # 松弛操作以更新距离
    tours = 0
    for _ in range(n - 1):  # 重复(n - 1)次
        modifie = False
        for u, v in fleches:
            poids_uv = M[u][v]  # 边(u, v)的权重
            if distances[u] + poids_uv < distances[v]:
                distances[v] = distances[u] + poids_uv
                predecesseurs[v] = u
                modifie = True
        if not modifie:
            break
        tours += 1

    # 检查是否存在负权重环
    for u, v in fleches:
        poids_uv = M[u][v]  # 边(u, v)的权重
        if distances[u] + poids_uv < distances[v]:
            return "Sommet joignable depuis d par un chemin, mais présence d'un cycle négatif."

    # 构建最短路径
    resultats = []
    for s in range(n):
        if s != d:
            if distances[s] == float('inf'):
                resultats.append(f"Sommet {s} non joignable depuis d par un chemin dans le graphe.")
            else:
                chemin = []
                sommet_courant = s
                while sommet_courant is not None:
                    chemin.insert(0, sommet_courant)
                    sommet_courant = predecesseurs[sommet_courant]
                resultats.append((distances[s], chemin))

    return resultats, tours

(9)下面的代码实现了Bellman-Ford算法的三种变体,根据构建箭头列表的不同方式进行。根据指定的顺序(随机、广度优先或深度优先),确定了遍历图中节点的顺序,以计算最短路径。然后,对图中的边进行松弛操作,更新顶点的距离。最后,检查是否存在负权重环,并构建最短路径。

def Bellman_Ford(M, d, ordre):
    n = len(M)  # 图中顶点的数量
    distances = [float('inf')] * n  # 距离数组初始为无穷大
    distances[d] = 0  # 源点到自身的距离为0
    predecesseurs = [None] * n  # 前驱数组初始为None

    if ordre == 'aleatoire':  # 随机顺序
        fleches = []
        for u in range(n):
            for v in range(n):
                poids_uv = M[u][v]  # 边(u, v)的权重
                if poids_uv is not None:
                    fleches.append((u, v))
        random.shuffle(fleches)
    elif ordre == 'largeur':  # 宽度优先顺序
        fleches = []
        parcours = pl(M, d)
        for u in parcours:
            for v in range(n):
                poids_uv = M[u][v]  # 边(u, v)的权重
                if poids_uv is not None:
                    fleches.append((u, v))
    elif ordre == 'profondeur':  # 深度优先顺序
        fleches = []
        parcours = pp(M, d)
        for u in parcours:
            for v in range(n):
                poids_uv = M[u][v]  # 边(u, v)的权重
                if poids_uv is not None:
                    fleches.append((u, v))

    # 松弛操作以更新距离
    tours = 0
    for _ in range(n - 1):  # 重复(n - 1)次
        modifie = False
        for u, v in fleches:
            poids_uv = M[u][v]  # 边(u, v)的权重
            if distances[u] + poids_uv < distances[v]:
                distances[v] = distances[u] + poids_uv
                predecesseurs[v] = u
                modifie = True
        if not modifie:
            break
        tours += 1

    # 检查是否存在负权重环
    for u, v in fleches:
        poids_uv = M[u][v]  # 边(u, v)的权重
        if distances[u] + poids_uv < distances[v]:
            return "Sommet joignable depuis d par un chemin, mais présence d'un cycle négatif."

    # 构建最短路径
    resultats = []
    for s in range(n):
        if s != d:
            if distances[s] == float('inf'):
                resultats.append(f"Sommet {s} non joignable depuis d par un chemin dans le graphe.")
            else:
                chemin = []
                sommet_courant = s
                while sommet_courant is not None:
                    chemin.insert(0, sommet_courant)
                    sommet_courant = predecesseurs[sommet_courant]
                resultats.append((distances[s], chemin))

    return resultats, tours

(10)下面的代码首先生成了一个大小为n的随机加权图的邻接矩阵,然后使用三种不同的方式(随机顺序、广度优先顺序、深度优先顺序)对Bellman-Ford算法进行了测试,并输出了各种顺序下的结果和迭代次数。

# 生成大小为n的随机加权图的邻接矩阵
n = 50
matrice = np.random.randint(1, 10, size=(n, n)).astype('float64')

# Bellman-Ford算法函数
resultats_aleatoire, tours_aleatoire = Bellman_Ford(matrice, 0, 'aleatoire')
resultats_largeur, tours_largeur = Bellman_Ford(matrice, 0, 'largeur')
resultats_profondeur, tours_profondeur = Bellman_Ford(matrice, 0, 'profondeur')

# 显示结果
print("Results (random order):", resultats_aleatoire)
print("Number of iterations (random order):", tours_aleatoire)
print()
print("Results (breadth-first order):", resultats_largeur)
print("Number of iterations (breadth-first order):", tours_largeur)
print()
print("Results (depth-first order):", resultats_profondeur)
print("Number of iterations (depth-first order):", tours_profondeur)

(11)函数TempsDij用于计算执行Dijkstra算法的时间,该函数的输入参数是一个正整数n(矩阵的大小)。在函数内部,首先生成一个大小为n的随机整数矩阵,然后记录算法执行的开始时间。接着调用Dijkstra算法进行计算,并记录算法执行结束后的时间。最后返回算法执行的时间。在示例中,调用了TempsDij函数,并打印了Dijkstra算法执行的时间。

def TempsDij(n):
    # 生成一个大小为n的随机整数矩阵
    matrice = np.random.randint(1, 10, size=(n, n)).astype('float64')

    # 初始化
    debut = time.time()

    # 调用Dijkstra算法
    Dijkstra(matrice, 0)

    # 计算执行时间
    temps_calcul = time.time() - debut

    return temps_calcul

temps = TempsDij(1000)
print("Temps de calcul TempsDij :", temps, "secondes")

(12)函数TempsDij用于计算执行 Dijkstra 算法所需的时间。该函数接受一个正整数 n 作为输入参数,表示矩阵的大小。在函数内部,首先生成一个大小为 n 的随机整数矩阵,然后调用 Dijkstra 算法对该矩阵进行计算。最后,记录执行算法所需的时间,并将结果返回。在示例中,该函数被调用并打印出执行 Dijkstra 算法所需的时间。

def TempsDij(n):
    # 生成一个大小为n的随机整数矩阵
    matrice = np.random.randint(1, 10, size=(n, n)).astype('float64')

    # 初始化
    debut = time.time()

    # 调用Dijkstra算法
    Dijkstra(matrice, 0)

    # 计算执行时间
    temps_calcul = time.time() - debut

    return temps_calcul

temps = TempsDij(1000)
print("Temps de calcul TempsDij :", temps, "secondes")

(13)下面这段代码的功能是评估 Bellman-Ford 算法在不同输入条件下的执行时间。首先,通过调用 TempsBF 函数来获取三种不同顺序(随机顺序、宽度优先顺序和深度优先顺序)下的算法执行时间。然后,通过使用 graphe2 函数生成不同大小的矩阵,并将每个矩阵的比例设置为 1/n,其中 n 是矩阵的大小。接着,使用 tempsBF_alternatif 函数计算 Bellman-Ford 算法在这些特定条件下的执行时间。最后,打印出算法的执行时间以及与输入大小的关系。

n = 50
temps_aleatoire, temps_largeur, temps_profondeur = TempsBF(n)

print("时间计算(随机顺序):", temps_aleatoire)
print("时间计算(宽度优先顺序):", temps_largeur)
print("时间计算(深度优先顺序):", temps_profondeur)


temps_aleatoire, temps_largeur, temps_profondeur = TempsBF(200)
print("指数 a 为", (math.log(temps_aleatoire) - math.log(TempsBF(100)[0])) / (math.log(200) - math.log(100)))

# 为指定大小的 n 计算贝尔曼-福特算法的执行时间,其中比例随 n 减小而减小
def tempsBF_alternatif(n):
    M = graphe2(n, 1/n, 1, 10) # 创建一个比例为 1/n 的矩阵
    start = time.perf_counter() # 用 start 变量初始化时间
    Bellman_Ford(M, 0, "aleatoire") # 启动
    stop = time.perf_counter() # 在第二个变量中保留结束时间
    return stop-start # 返回两个变量的差异以获取执行时间

# 例子
print("替代时间:", tempsBF_alternatif(50))

(14)函数 Trans2 用于计算传递闭包。最后,通过示例测试了这个函数,给定了一个图的邻接矩阵,然后确定了该图是否是强连通的。

# 使用Trans2(M) 函数创建强连通矩阵
def Trans2(M):
    k = len(M)
    for s in range(k):
        for r in range(k):
            if M[r, s] == 1:
                for t in range(k):
                    if M[s, t] == 1:
                        M[r, t] = 1
    return M

(15)函数 fc用于检查给定的有向图是否是强连通的,它通过计算给定图的传递闭包来实现这一功能,并检查每对顶点之间是否存在路径,以确定图是否是强连通的。

def fc(M):
    # 计算矩阵的传递闭包
    T = Trans2(M)

    # 检查强连通性
    k = len(M)
    for i in range(k):
        for j in range(k):
            if T[i, j] != 1:
                # 如果顶点 i 和 j 之间没有路径,则图不是强连通的
                return False

    return True

# 示例
M = np.array([[0, 1, 0, 0, 0],
              [0, 0, 1, 0, 0],
              [0, 0, 0, 0, 1],
              [0, 0, 1, 0, 0],
              [0, 1, 0, 0, 0]])

is_connexe = fc(M)
print(is_connexe)

(16)函数 test_stat_fc用于测试具有50%箭头的有向图是否是强连通的。该函数随机生成多个具有50%箭头的有向图,并使用 fc 函数检查它们是否是强连通的。然后,它计算满足条件的图所占的百分比,并将结果打印出来。

#强连通性测试
def test_stat_fc(n):
    nb_tests = 100  # 要执行的测试次数
    nb_fortement_connexes = 0  # 强连通图的数量

    for i in range(nb_tests):
        M = np.random.binomial(1, 0.5, size=(n, n))  # 生成具有1和10之间的随机值的随机矩阵
        if fc(M):
            nb_fortement_connexes += 1

    pourcentage_fortement_connexes = (nb_fortement_connexes / nb_tests) * 100

    return pourcentage_fortement_connexes

# 示例
n = 50
pourcentage = test_stat_fc(n)
print(f"具有50%箭头的图强连通的百分比,n={n} :{pourcentage}%")

(17)下面这段代码包含两个函数,test_stat_fc2 和 seuil,函数test_stat_fc2用于确定给定大小和连接概率下,强连通图的百分比。它通过生成多个随机图并检查它们是否是强连通图来估计强连通性的概率。函数seuil用于确定给定大小的图中的连接概率阈值,以使强连通图的概率大于或等于 99%。

#确定强连通性阈值
def test_stat_fc2(n, p):
    nb_graphes_fortement_connexes = 0
    nb_tests = 100  # 要执行的测试次数
    
    for i in range(nb_tests):
        matrice = np.random.binomial(1, p, size=(n, n))  # 生成具有1和10之间的随机值的图矩阵
        
        if fc(matrice):
            nb_graphes_fortement_connexes += 1
    
    pourcentage_fortement_connexes = (nb_graphes_fortement_connexes / nb_tests) * 100
    return  pourcentage_fortement_connexes

# 示例
n = 50
p = 0.5
pourcentage = test_stat_fc2(n, p)
print(f"对于 n={n},p={p} 的强连通图的百分比 : {pourcentage}%")

for i in range(2,20):
    print("对于矩阵大小为 ", i , "-->", test_stat_fc2(i,0.5))

def seuil(n):
    p = 0.5  
    while test_stat_fc2(n, p) >= 0.99:
        p -=1/100
    return p

8)下面的代码首先使用函数seuil计算了一系列不同大小图形的连接阈值,然后通过散点图可视化了图形大小和连接阈值之间的关系。接下来,对于散点图上的数据,进行了对数-对数尺度下的线性回归,并在对数-对数图上绘制了拟合线。最后,打印输出了拟合线的方程,以描述连接阈值与图形大小之间的关系。

n_values = range(10, 40)
seuil_values = [seuil(n) for n in n_values]

plt.scatter(n_values, seuil_values)
plt.xlabel('Taille du graphe (n)')
plt.ylabel('Seuil de forte connexité (p)')
plt.title('Représentation graphique de seuil(n)')
plt.show()

# 在对数尺度上进行线性回归
log_n_values = np.log(n_values)
log_seuil_values = np.log(seuil_values)
coefficients = np.polyfit(log_n_values, log_seuil_values, 1)
a, c = coefficients[0], np.exp(coefficients[1])

# 图形表示
plt.scatter(log_n_values, log_seuil_values)
plt.plot(log_n_values, np.polyval(coefficients, log_n_values), color='red', label='Régression linéaire')
plt.xlabel('log(Taille du graphe)')
plt.ylabel('log(Seuil de forte connexité)')
plt.title('Régression linéaire sur le graphe log-log')
plt.legend()
plt.show()

print(f"La fonction seuil(n) est approximativement de la forme p = {c} * n^{a}")

测试后会发现带有图形的阈值序列应当是递减的,这符合预期,如图3-11所示。这意味着随着 n 的增加,强连通性的阈值 p 会减小。阈值序列的递减通常是预期的,因为随着图的大小 n 增加,获得强连通性变得更加困难。因此,达到强连通性所需的概率 p 逐渐降低。通过检查阈值序列的图形表示,您可以确认在 [10, 40] 或更大的范围内,该序列是否确实递减,具体取决于计算能力。

图3-11  阈值序列的可视化图

在上面的可视化图中,我们观察到红色曲线具有渐近性质。这表明随着图中节点数量 n 的增加,强连通性阈值 p 的变化逐渐趋于稳定。因此,我们可以将红色曲线视为表示阈值 p 的函数 f(n)。通过观察此函数的形式,我们可以更好地了解在给定图大小 n 下实现强连通性所需的概率阈值。

  • 24
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

码农三叔

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值