基础图论的一点理解

本文详细介绍了图的两种存储方式——邻接表和邻接矩阵,以及如何使用它们来存储有向图和无向图。接着讨论了拓扑排序的实现方法和最短路问题,包括Floyd算法和Dijkstra算法的实现细节,同时提供了代码示例和相关题目链接,帮助理解这些算法的工作原理和应用场景。
摘要由CSDN通过智能技术生成

有向图

一般g存图、dis存边权、u起点、v终点、w边权、s是包含定点的集合

无向图

无向图就是特殊的有向图,一遍存图存 " 存两遍 "

邻接表

存出边和边权的二维列表或者字典(一般是列表,因为大部分题目以从1开始的编号作为结点,如果用字典存要浪费一部分空间,而列表这部分空间用索引实现即可),如果是无向图一对就要添加两次

用二维列表存code

n, m = map(int, input().split())
g = [[] for _ in range(n+1)]
for _ in range(m):
    u, v, w = map(int, input().split())
    g[u].append((v, w))
    g[v].append((u, w))  # 有向图这句不要

特点

优:节省空间,在只要遍历某点的出边时很方便

缺:无法快速查找两个点之间的边权

邻接矩阵

用二维列表存边权

code

dis = [[float('inf')]*(n+1) for _ in range(n+1)]
for _ in range(m):
    u, v, w = map(int, input().split())
    dis[u][v] = w
    dis[v][u] = w  # 有向图这句不要

特点

优:可以很方便的查找两个顶点间的边权,可以O(1)时间添加和删除边

缺:会浪费空间(要提前占满二维空间,即提前把空间开好)

拓扑排序

拓扑排序主要用来解决有向图中的依赖解析(dependency resolution)问题

也可以用来找出(先拓扑排序,因为环上的入度都比较大,拓扑排序在后面,dfs或bfs剩下来的就是环)

实现方法

  1. 把入度为0的挑出来,把他们的出边的入度-1
  2. 不断把入度为0的挑出来,就是拓扑排序的结果

题目传送门

P4017 最大食物链计数 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

code

n, m = map(int, input().split())
d = dict(zip(range(1, n+1), ([0, []] for _ in range(n))))
for _ in range(m):
    a, b = map(int, input().split())
    d[b][0] += 1  # 入度+1
    d[a][1].append(b)  # 出边+1
in1 = [(i, j) for i, j in d.items() if j[0] == 0]
out = [i for i, j in d.items() if j[1] == []]
c = []
dp = [0]*(n+1)
for i, j in in1:
    dp[i] = 1
    for k in j[1]:
        d[k][0] -= 1  # 入度-1
        dp[k] = (dp[k] + dp[i]) % 80112002
        if not d[k][0]:
            c.append((k, d[k]))
for i, j in c:
    for k in j[1]:
        d[k][0] -= 1  # 入度-1
        dp[k] = (dp[k]+dp[i]) % 80112002
        if not d[k][0]:
            c.append((k, d[k]))
ans = 0
for i in out:
    ans = (ans+dp[i]) % 80112002
print(ans)

最短路

在这里插入图片描述

Floyd算法

适用于多源最短路问题

初始化

code
f = [[0 if i == j else float('inf') for j in range(n)] for i in range(n)]

主体

标准code
for k in range(1, n+1):
    for i in range(1, n+1):
        for j in range(1, n+1):
            if f[i][j] > f[i][k]+f[k][j]:  # 避免多次调用函数
                f[i][j] = f[i][k]+f[k][j]
细节
  1. k必须在最外层:一是因为基本的逻辑是先只考虑直接连通问题,再把k一个一个添加进去算子图,所以为了保证每个k都是在遍历全图,k得在外循环;二是因为得确保

    f[i][k]和f[k][j]都是已经和前面的k更新过的,如果k在内循环会导致它们可能还没被前面的k松弛过
    

    可以说在计算时都是以k为基点的,把一个一个"中转站"加入去更新

    一句话,Floyd算法的本质是DP,而k是DP的阶段,因此要写最外面。

  2. i 和 j 的顺序无所谓,随意调换

标准优化code
for k in range(1, n+1):
    for i in range(1, n+1):
        if k != i:  # 中转站是自己是毫无意义的
            if f[i][k] == float('inf'):  # 跳过不可能的中转站
                continue
            for j in range(1, n+1):
                if f[i][j] > f[i][k]+f[k][j]:  # 避免多次调用函数
                    f[i][j] = f[i][k]+f[k][j]
无向图的优化code

因为无向图是对称的,所以可以优化。注意有向图千万不要只计算上三角或下三角

for k in range(1, n+1):
    for i in range(2, n+1):  # 给j留空间(实际f[1][1]为0已经初始化过了)
        if k != i:  # 中转站是自己是毫无意义的
            if f[i][k] == float('inf'):  # 跳过不可能的中转站
                continue
            for j in range(1, i):  # 到i就是对角线了,f[i][i]是0,初始化的时候就算过了
                if f[i][j] > f[i][k]+f[k][j]:  # 避免多次调用函数
                    f[i][j] = f[j][i] = f[i][k]+f[k][j]  # 初始就是相等的,如果有替换就一起换

题目传送门

B3647 【模板】Floyd 算法 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

code
n, m = map(int, input().split())
f = [[0 if i == j else float('inf') for j in range(n+1)] for i in range(n+1)]
for _ in range(m):
    u, v, w = map(int, input().split())
    f[u][v] = f[v][u] = w
for k in range(1, n+1):
    for i in range(2, n+1):
        if k != i:
            if f[i][k] != float('inf'):
                for j in range(1, i):
                    if f[i][j] > f[i][k]+f[k][j]:
                        f[i][j] = f[j][i] = f[i][k]+f[k][j]
for i in range(1, n+1):
    for j in range(1, n+1):
        print(f[i][j], end=' ')
    print()

迪杰斯特拉算法(Dijkstra)

适用于单源最短路问题,即一个点到所有点

优点:高效稳定。缺点:边权不能为负

思想:贪心+优先队列
在这里插入图片描述

如图,原本考虑s到u最短直连通路径就是8,但是因为负边权的存在最短路径变成了5,所以迪杰斯特拉算法(Dijkstra)算法不支持负边权

思维过程(类似bfs,也就是为什么不能出现负边权)

  1. 从源点u1开始下一个一定是源点的最短直联通点u2
  2. 再下一个一定是u2的最短直联通点或u1剩下的最短直联通点
  3. 每次加入的点将自己的直连通点加入排序,每次弹出最小的那个作为下一个源点

实现过程

  1. 从源点开始,将所有直联通点压入最小堆hp中
  2. 弹出里面路程最短的作为下一个源点
  3. 遍历下一个源点的还没加入路径的直联通点
  4. 如果能产生更优方案就压入最小堆,更高的优先级让最优的方案优先被弹出(原本的方案还在,不会消失)
  5. 因为一个点只要一个最优方案,所以加入路径后就直接用done标记,这样下次遍历时就会跳过这个

标准code

题目传送门

P4779 【模板】单源最短路径(标准版) - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

import heapq


def dij(s, d):
    done = [0]*(n+1)
    hp = []
    heapq.heappush(hp, (0, s))  # (堆,(优先级,元素))
    while hp:
        u = heapq.heappop(hp)[1]  # 因为弹出的是元组,但是现在只要下一个顶点
        if done[u]:
            continue
        done[u] = 1
        for v, w in g[u]:
            if done[v]:
                continue
            if d[v] > d[u]+w:  # 第一次替换就是第一种能到的情况,会压入hp
                d[v] = d[u]+w
                heapq.heappush(hp, (d[v], v))  # 如果不换就说明到v的仍然是直通或之前留下来的方案快,而hp只留下一个点最快的方案,等这个点最快的方案最快了就弹出,如果没有发生替换


n, m, s0 = map(int, input().split())
g = [[] for _ in range(n+1)]
dis = [float("inf")]*(n+1)  # 存到源点的距离
dis[s0] = 0
for _ in range(m):
    u, v, w = map(int, input().split())
    g[u].append((v, w))
dij(s0, dis)
for i in range(1, n+1):
    print(dis[i], end = ' ')

输出源点到每点的路径code

import heapq


def dij(s, d, r):
    done = [0]*(n+1)
    hp = []
    r[1] = '1 '
    heapq.heappush(hp, (0, s))  # (堆,(优先级,元素))
    while hp:
        u = heapq.heappop(hp)[1]  # 因为弹出的是元组,但是现在只要下一个顶点
        if done[u]:
            continue
        done[u] = 1
        for v, w in g[u]:
            if done[v]:
                continue
            if d[v] > d[u]+w:  # 第一次替换就是第一种能到的情况,会压入hp
                d[v] = d[u]+w
                r[v] = r[u]+f"{v} "
                heapq.heappush(hp, (d[v], v))  # 如果不换就说明到v的仍然是直通或之前留下来的方案快,而hp只留下一个点最快的方案,等这个点最快的方案最快了就弹出,如果没有发生替换


n, m, s0 = map(int, input().split())
g = [[] for _ in range(n+1)]
dis = [float("inf")]*(n+1)  # 存到源点的距离
dis[s0] = 0
road = ["-1"]*(n+1)  # 不能到达输出-1
for _ in range(m):
    u, v, w = map(int, input().split())
    g[u].append((v, w))
    g[v].append((u, w))  # 做题一定注意单双
dij(s0, dis, road)
for i in range(1, n+1):
    print(road[i])

输出源点到某一点的最短路径

题目传送门

Dijkstra? - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

import heapq


def dij(s, d, r, k):
    done = [0]*(n+1)
    hp = []
    r[1] = '1 '
    heapq.heappush(hp, (0, s))  # (堆,(优先级,元素))
    while hp:
        u = heapq.heappop(hp)[1]  # 因为弹出的是元组,但是现在只要下一个顶点
        if u == k:
            print(r[k])
            return
        if done[u]:
            continue
        done[u] = 1
        for v, w in g[u]:
            if done[v]:
                continue
            if d[v] > d[u]+w:  # 第一次替换就是第一种能到的情况,会压入hp
                d[v] = d[u]+w
                r[v] = r[u]+f"{v} "
                heapq.heappush(hp, (d[v], v))  # 如果不换就说明到v的仍然是直通或之前留下来的方案快,而hp只留下一个点最快的方案,等这个点最快的方案最快了就弹出,如果没有发生替换


n, m, s0, key = map(int, input().split())
g = [[] for _ in range(n+1)]
dis = [float("inf")]*(n+1)  # 存到源点的距离
dis[s0] = 0
road = [""]*(n+1)
for _ in range(m):
    u, v, w = map(int, input().split())
    g[u].append((v, w))
    g[v].append((u, w))
dij(s0, dis, road)
if dis[k] == float('inf'):
    print(-1)

变式

题目传送门

P1576 最小花费 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

import heapq


def dij(s, d, k):
    done = [0]*(n+1)
    hp = []
    heapq.heappush(hp, (1, s))  # (堆,(优先级,元素))
    while hp:
        u = heapq.heappop(hp)[1]  # 因为弹出的是元组,但是现在只要下一个顶点
        if u == k:
            print('%.8f' % (100*d[k]))
            return
        if done[u]:
            continue
        done[u] = 1
        for v, w in g[u]:
            if done[v]:
                continue
            if d[v] > d[u]/w:  # 第一次替换就是第一种能到的情况,会压入hp
                d[v] = d[u]/w
                heapq.heappush(hp, (d[v], v))  # 如果不换就说明到v的仍然是直通或之前留下来的方案快,而hp只留下一个点最快的方案,等这个点最快的方案最快了就弹出,如果没有发生替换


n, m = map(int, input().split())
g = [[] for _ in range(n+1)]
dis = [float("inf")]*(n+1)  # 存到源点的距离
for _ in range(m):
    u, v, w = map(int, input().split())
    w = 1-w/100
    g[u].append((v, w))
    g[v].append((u, w))
a, b = map(int, input().split())
dis[a] = 1
dij(a, dis, b)
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

陈子昂-北工大

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

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

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

打赏作者

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

抵扣说明:

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

余额充值