23. Алгоритмические задачи, связанные с анализом графов: #313008
В текстовом файле содержится описание ациклического ориентированного взвешенного графа.
В каждой строке файла записаны два натуральных числа ($L, M$) и одно положительное вещественное число ($W$). $L, M$ — номера вершин графа, $W$ — вес ребра, ведущего из вершины $L$ в вершину $M$. Таким образом, количество строк в файле равно количеству рёбер в графе. Две вершины графа не могут быть соединены более чем одним ребром.
Найдите и запишите в ответе целую часть длины кратчайшего пути из вершины с номером $1$ в вершину с номером $100$. Существование хотя бы одного такого пути гарантируется. Под длиной кратчайшего пути понимается минимальная сумма весов рёбер, составляющих путь.
Для выполнения этого задания следует написать программу.
Вершины графа могут быть пронумерованы не подряд. $L ≤ 1000, M ≤ 1000; W ≤ 10 000$. Количество строк в файле не превосходит $200$. Числа в строках разделены произвольным ненулевым количеством пробелов и/или табуляций.
Типовой пример организации данных во входном файле для графа на рисунке

| 100 6 6 1 7 4 1 1 | 12 7 1 7 100 100 100 4 | 1.0 7.0 1.0 5.5 2.0 8.0 12.0 2.5 |
Для приведенного примера верным ответом будет $7$.
Граф ациклический, поэтому функция $f(v)$ — «длина кратчайшего пути из $v$ в $100$» — определяется рекуррентно: $f(100) = 0$, а для остальных вершин $f(v) = min(w + f(u))$ по всем рёбрам $v → u$. Если из $v$ рёбер нет и $v ≠ 100$, пути не существует — возвращаем $+∞$. Запоминание результатов (lru_cache) делает решение мгновенным.
from functools import lru_cache
import sys
sys.setrecursionlimit(10000)
g = {} # вершина -> список (сосед, вес)
for line in open('23_1.txt'):
s = line.split() # любые пробелы и табуляции
if len(s) < 3: # пустая строка
continue
a, b, w = int(s[0]), int(s[1]), float(s[2])
g.setdefault(a, []).append((b, w))
@lru_cache(None)
def f(v): # кратчайший путь из v в 100
if v == 100:
return 0
best = float('inf') # тупик: пути нет
for u, w in g.get(v, []):
best = min(best, w + f(u))
return best
print(int(f(1)))
Второй способ — алгоритм Дейкстры (веса положительные, поэтому он применим):
import heapq
g = {}
for line in open('23_1.txt'):
s = line.split()
if len(s) < 3:
continue
a, b, w = int(s[0]), int(s[1]), float(s[2])
g.setdefault(a, []).append((b, w))
g.setdefault(b, [])
d = {v: float('inf') for v in g}
d[1] = 0
q = [(0, 1)]
while q:
dv, v = heapq.heappop(q)
if dv > d[v]:
continue
for u, w in g[v]:
if dv + w < d[u]:
d[u] = dv + w
heapq.heappush(q, (d[u], u))
print(int(d[100]))
Длина кратчайшего пути $20809,6$; путь $1 → 603 → 693 → 809 → 100$ (четыре ребра).