201812-4-数据中心
问题
思路
求最优树结构的流水线最大耗时。
⚠️:使用了==最小堆==做==优先队列==的,==Prim(普里姆)==求解最小生成树问题,最小生成树的最大边长即为最优树结构的流水线最大耗时。
利用一个==最小堆==来维护所有从起始节点出发能够到达的边,每次从堆中取出一条最小边并更新访问节点集合,直到所有节点都被访问或者堆为空为止。
代码中使用了==最小堆==和==集合==两种数据结构,具体流程如下:
- 读入图的节点数、边数以及起始节点编号;
- 创建一个字典用于存储图中的边信息,将每条边的起点、终点和边长加入到字典中;
- 创建一个集合用于存储已经访问过的节点,创建一个堆用于存储从起始节点可达的边;
- 循环直到所有节点都被访问过或者堆为空:
- 从堆中取出一条最小的边;
- 如果该边的终点不在已访问的节点集合中,则将其加入已访问节点集合中,并更新最大边长的值;
- 将该边终点能到达的所有边加入堆中;
- 输出图中的最大边长。
实现
# 导入必要的库
from heapq import heapify, heappop, heappush
from collections import defaultdict
# 从用户输入中读取图的节点数、边数以及起始节点编号。
n = int(input()) # 图中的节点数
m = int(input()) # 图中的边数
root = int(input()) - 1 # 起始节点的编号
# 创建一个字典用于存储图中的边信息,将每条边的起点、终点和边长加入到字典中。
road_set = defaultdict(list) # 字典,用于存储图中的边信息
max_length = -1 # 初始化图中的最大边长为 -1
# 将每条边的起点、终点和边长加入到字典中
for i in range(m):
src, dst, length = map(int, file.readline().split())
road_set[src - 1].append((length, dst - 1))
road_set[dst - 1].append((length, src - 1))
# 创建一个集合用于存储已经访问过的节点,创建一个堆用于存储从起始节点可达的边。
in_set = {root} # 已访问节点的集合
heap = road_set[root][:] # 从起始节点可达的边的堆
heapify(heap) # 堆化
# 循环直到所有节点都被访问过或者堆为空。
while len(in_set) < n and len(heap) != 0:
# 从堆中取出一条最小的边
length, dst = heappop(heap)
# 如果该边的终点不在已访问的节点集合中,则将其加入已访问节点集合中,并更新最大边长的值
if dst not in in_set:
in_set.add(dst)
max_length = max(max_length, length)
# 将该边终点能到达的所有边加入堆中
for ln, dt in road_set[dst]:
if dt not in in_set:
heappush(heap, (ln, dt))
# 输出图中的最大边长。
print(max_length)
Appendix
1、defaultdict和dict的区别
defaultdict 和 dict 都是字典(dictionary)类型
| dict | defaultdict |
|---|---|
访问一个不存在的键值,会触发一个 KeyError 异常 | 访问一个不存在的键值时,它不会触发 KeyError 异常,而是会返回一个默认值 |
defaultdict在访问一个不存在的键值时,会自动创建这个键值,并将其值设置为默认值。因此,如果我们不想要这个自动创建的行为,可以使用dict来代替。- 创建时需要传递一个函数作为参数来设置默认值,这个函数会在访问一个不存在的键值时被调用。常见的设置默认值的函数包括
int、list、set等,它们分别用于设置默认值为整数 0、空列表、空集合等。
from collections import defaultdict
# 创建 defaultdict,设置默认值为一个空列表
my_dict = defaultdict(list)
# 往字典中添加数据
my_dict['fruit'].append('apple')
my_dict['fruit'].append('banana')
my_dict['color'].append('red')
# 打印字典
print(my_dict)
2、Dijkstra——单源最短路径算法
迪杰斯特拉算法:解决带权有向图或者无向图中单源最短路径问题的算法。它可以找到从给定起点到所有其他节点的最短路径,前提是图中==不能有负权边==。
- 初始化:设置起点的距离为0,其它节点的距离为无穷大,将所有节点加入“待确定最短路径的节点集合”;
- 循环直到所有节点都已经被加入“已确定最短路径的节点集合”:
- 从“待确定最短路径的节点集合”中选取距离起点最近的节点,加入“已确定最短路径的节点集合”;
- 更新该节点到其它节点的距离,如果更新后的距离比原来的距离更短,则更新该节点的距离;
- 输出起点到每个节点的最短距离。
3、Prim算法——求解最小生成树算法
普里姆算法:是一种常用的求解最小生成树的算法,它的基本思想是从一个初始节点开始,依次加入与当前生成树相邻的最短边,直到所有节点都被加入为止。
- 选定一个起始节点,并将其加入生成树中。
- 对于所有与生成树相邻的节点,计算与它们相连的边的权值,并选择权值最小的边所连接的节点。
- 将权值最小的边所连接的节点加入生成树中。
- 重复步骤2和步骤3,直到所有节点都被加入为止。
⚠️:在Prim算法中,每次加入一个新节点时,需要同时更新生成树和候选边集。同时,为了避免重复计算,一般使用==优先队列==来维护候选边集,以便快速地找到当前生成树所连接的所有节点中,与其距离最短的边。
4、最小堆实现优先队列
优先队列是一种特殊的队列,其中每个元素都有与之关联的优先级。当从队列中取出元素时,具有最高优先级的元素将首先被取出。
最小堆(堆中每个节点的值都小于等于其子节点的值。)是一种数据结构,它是一棵二叉树,其中每个节点的值都小于或等于其子节点的值。最小堆通常用于实现优先队列,其中堆顶元素是具有最高优先级的元素。
最小堆的性质保证了堆顶元素具有最小值,因此在实现优先队列时,我们可以使用最小堆来确保具有最高优先级的元素在队列的最前面。当我们向优先队列中添加元素时,我们可以将其插入到最小堆中,并在需要时执行堆化操作以保持堆的性质。当我们从队列中取出元素时,我们只需要从堆顶取出元素即可。
Python实现最小堆
heapify(iterable):该函数将一个可迭代对象转换为堆。也就是说,它会重新排列可迭代对象的元素,使其满足堆的性质。时间复杂度为 O(n),其中 n 是可迭代对象中的元素数量。heappop(heap):该函数弹出并返回堆中最小的元素。由于堆的性质,最小的元素总是位于堆的根部。时间复杂度为 O(log n),其中 n 是堆中的元素数量。heappush(heap, item):该函数将一个元素插入到堆中,并维持堆的性质。
from heapq import heapify, heappop, heappush
a = [(3, 2), (4, 1), (1, 10)]
heapify(a) # 堆化[(1, 10), (3, 2), (4, 1)],之后heappop,heappush会自动堆化
heappop(a) # pop出(1, 10),堆a变为[(4, 1), (3, 2)]
heappush(a,(2,1)) # push入(2,1),堆a变为[(2, 1), (4, 1), (3, 2)]