К содержанию

Подтема 2 из 6

Лучший путь до цели

Почему нельзя перебрать все пути и как найти кратчайший, посчитав для каждой вершины одно число — f(v).

Почему не перебор

Почему бы не выписать все пути, как в учебном графе?

Путей бывает слишком много. В файле задачи темы всего 200 рёбер, а путей из 1 в 100 — почти 150 миллиардов. Каждая развилка умножает число путей: две стрелки на каждом из 30 шагов дают 2³⁰ — больше миллиарда путей. Перебрать их все не успеет ни человек, ни программа.

А если на каждом шаге брать самую дешёвую стрелку? Не сработает. Из 1 самая дешёвая стрелка — в 4, за 2.5. Но из 4 до 100 идти ещё 8.0, всего 10.5. А путь через 7 начинается дороже, с 5.5, и выходит 7.5.

Самая дешёвая стрелка из 1 ведёт в 4, но путь 1 → 4 → 100 стоит 10.5. Кратчайший путь 1 → 7 → 100 — 7.5.

Дешёвый первый шаг ничего не говорит о том, сколько стоит остаток пути. Нужен способ, который учитывает весь остаток, но не перебирает пути по одному.

Одно число на вершину

Что нужно знать о вершине, чтобы решить, идти ли через неё?

Только одно: сколько стоит лучший путь от неё до цели. Как мы в неё пришли, для остатка пути неважно. В вершину 7 можно прийти из 1 или из 6. Но дальше из 7 одна и та же дорога: 7 → 100 за 2.0. Каким бы путём мы ни пришли в 7, лучший остаток стоит 2.

Обозначим это число f(v) — длина кратчайшего пути из вершины v до цели. Здесь f(7) = 2; запись читается «эф от семи». Если знать f для всех вершин, ответ задачи — f(1): длина лучшего пути из 1 в 100.

Правило для f

Как найти f(v), если для соседей f уже известно?

Перебрать стрелки из v: к весу каждой прибавить f вершины, куда она ведёт, и взять наименьшую сумму. В самой цели f = 0 — идти уже некуда. Любой путь из v начинается с какой-то стрелки, а лучший остаток после неё стоит f её конца. Остаётся выбрать лучшую стрелку.

Считаем от цели назад — так у соседей f всегда уже есть:

  • f(100) = 0 — мы уже в цели;
  • f(7) = 2.0 + f(100) = 2;
  • f(4) = 8.0 + f(100) = 8;
  • f(1) = min(5.5 + f(7); 12.0 + f(100); 2.5 + f(4)) = min(7.5; 12; 10.5) = 7.5.

f(1) = 7.5 — тот же ответ, что дал перебор, хотя ни один путь целиком мы не выписывали. Посмотри расчёт по шагам: под вершинами появляются значения f, выделена стрелка, которая дала минимум.

  1. Значения f пока не известны. Начнём с вершин, после которых идти некуда или почти некуда, — с цели.
  2. Из 12 рёбер нет — до цели не дойти: f(12) = ∞.
  3. f(100) = 0: мы уже в цели, идти больше не нужно.
  4. f(7) = 2.0 + f(100) = 2.0 + 0 = 2. Ответы для соседей уже известны — они ближе к цели.
  5. f(4) = 8.0 + f(100) = 8.0 + 0 = 8. Ответы для соседей уже известны — они ближе к цели.
  6. f(1) = min(5.5 + f(7), 12.0 + f(100), 2.5 + f(4)) = min(5.5 + 2, 12.0 + 0, 2.5 + 8) = 7.5. Ответы для соседей уже известны — они ближе к цели.
  7. f(6) = min(7.0 + f(7), 1.0 + f(1)) = min(7.0 + 2, 1.0 + 7.5) = 8.5. Ответы для соседей уже известны — они ближе к цели.
  8. Готово: f(1) = 7.5. Если из каждой вершины идти по ребру, которое дало минимум, получится путь 1 → 7 → 100.

Правило работает для любой вершины, не только для нужной в ответе: f(6) = min(7.0 + 2; 1.0 + 7.5) = 8.5. А почему у вершины 12 стоит ∞ — в следующем разделе.

Заметь, как устроено правило: чтобы найти f вершины, нужны f её соседей. Функцию, которая для ответа вызывает саму себя, называют . Так мы и запишем f на Python в подтеме 3.

Тупик — бесконечность

Чему равно f(12)? Из 12 не выходит ни одной стрелки.

До цели из 12 не дойти, поэтому f(12) = ∞ — бесконечность, хуже любого настоящего пути. Ноль ставить нельзя: ноль значит «уже пришли».

Посмотрим, к чему приводит ноль. Добавим в учебный граф стрелку 4 → 12 весом 0.5. Если считать f(12) = 0, то f(4) = min(8.0 + 0; 0.5 + 0) = 0.5, а f(1) = 2.5 + 0.5 = 3. Но путь за 3 — это 1 → 4 → 12: он кончается в тупике, а не в 100.

С f(12) = ∞ дешёвая стрелка 4 → 12 ничего не даёт: 0.5 + ∞ = ∞. Значения остальных вершин и ответ 7.5 не меняются.

С бесконечностью всё складывается само: ∞ плюс любое число — снова ∞, а любое настоящее число меньше ∞. Поэтому стрелка в тупик никогда не даёт минимум. Бесконечность бывает и у вершины со стрелками — если все они ведут в тупики.

Итого

  • Перебор путейпутей бывают миллиарды — не успеть
  • Жадный выбордешёвый первый шаг не гарантирует лучший путь
  • f(v)длина кратчайшего пути из v до цели
  • f(цели)0
  • Правилоf(v) = min(вес + f(конца)) по стрелкам из v
  • Тупикf = ∞, а не 0
  • Учебный примерf(1) = 7.5

Проверьте себя

Ответь сначала сам, потом открой ответ.

Вопрос 1
Иди из 6 по самой дешёвой стрелке, пока не дойдёшь до 100. Сколько стоит такой путь? А кратчайший?
ОтветСкрыть ответ
Ответ
По дешёвым стрелкам: 6 → 1 (1.0), 1 → 4 (2.5), 4 → 100 (8.0) — всего 11.5. Кратчайший — 6 → 1 → 7 → 100 за 8.5.
Вопрос 2
Почему для f(1) неважно, каким путём мы попали в вершину 1?
ОтветСкрыть ответ
Ответ
f(1) — длина пути от 1 до цели. Всё, что было до 1, на остаток пути не влияет: дальше из 1 одни и те же стрелки.
Вопрос 3
Найди f(6) для цели 100 в учебном графе.
ОтветСкрыть ответ
Ответ
Из 6 две стрелки: f(6) = min(7.0 + f(7); 1.0 + f(1)) = min(9; 8.5) = 8.5.
Вопрос 4
Рёбра 1 → 100 (12) и 1 → 81 (2), из 81 рёбер нет. Найди f(100), f(81) и f(1).
ОтветСкрыть ответ
Ответ
f(100) = 0, f(81) = ∞, f(1) = min(12 + 0; 2 + ∞) = 12. Дешёвая стрелка в тупик ничего не даёт.
Вопрос 5
Граф: 1 → 5 (3), 1 → 9 (4), 5 → 8 (1), 5 → 100 (6), 9 → 100 (2), из 8 рёбер нет. Найди f(1). Что получится, если ошибочно считать f(8) = 0?
ОтветСкрыть ответ
Ответ
f(100) = 0, f(8) = ∞, f(9) = 2, f(5) = 6, f(1) = min(3 + 6; 4 + 2) = 6. С ошибкой f(8) = 0 вышло бы f(5) = 1 и f(1) = 4 — длина пути в тупик 8, который до 100 не доходит.

Практика

Для проверки ответов нужен JavaScript. Условия, подсказки и решения доступны ниже.

Жадный путь из 6

Учебный граф темы — файл из восьми строк:

100 12 1.06 7 7.06 1 1.01 7 5.57 100 2.04 100 8.01 100 12.01 4 2.5

Иди из вершины 6, каждый раз выбирая самую дешёвую стрелку, пока не придёшь в 100. На сколько такой путь длиннее кратчайшего пути из 6 в 100?

Введите число.

Через вершину 7

Учебный граф темы — файл из восьми строк:

100 12 1.06 7 7.06 1 1.01 7 5.57 100 2.04 100 8.01 100 12.01 4 2.5

Найди длину кратчайшего пути из 6 в 100, проходящего через вершину 7. В вершину 7 из 6 можно попасть напрямую или через 1.

Введите длину — десятичную дробь через точку.

f(1) и f(6) учебного графа

Учебный граф темы — файл из восьми строк:

100 12 1.06 7 7.06 1 1.01 7 5.57 100 2.04 100 8.01 100 12.01 4 2.5

Найди f(1) и f(6) — длины кратчайших путей из 1 и из 6 в вершину 100.

Введите f(1) и f(6) через пробел — десятичные дроби через точку.

f от цели назад с тупиком

Граф задан рёбрами:

1 4 2.51 6 4.14 6 1.24 15 0.66 15 2.96 100 7.415 100 5.815 22 0.3

Из вершины 22 рёбер нет. Найди длину кратчайшего пути из 1 в 100. В ответ запиши её целую часть.

Введите целую часть найденной длины.

В учебный граф добавили ребро 4 → 12 весом 0.5:

100 12 1.06 7 7.06 1 1.01 7 5.57 100 2.04 100 8.01 100 12.01 4 2.54 12 0.5

Из 12 стрелок нет. Какое значение f(1) получится, если ошибочно считать f(12) = 0?

Введите число.