1 added
1 removed
Original
2026-01-01
Modified
2026-02-26
1
-
<p>Вы уже знаете, что граф - это фигура, состоящая из вершин и соединяющих их ребер. С помощью графов решают многие важные классы задач, с которыми мы познакомимся далее в э��ом уроке.</p>
1
+
<p>Вы уже знаете, что граф - это фигура, состоящая из вершин и соединяющих их ребер. С помощью графов решают многие важные классы задач, с которыми мы познакомимся далее в этом уроке.</p>
2
<h2>Выбираем оптимальный путь на метро</h2>
2
<h2>Выбираем оптимальный путь на метро</h2>
3
<p>Представьте себе схему метро крупного города: скорее всего, в центре будут пересекаться несколько разных веток. Из-за этого получается, что проехать между станциями можно разными способами.</p>
3
<p>Представьте себе схему метро крупного города: скорее всего, в центре будут пересекаться несколько разных веток. Из-за этого получается, что проехать между станциями можно разными способами.</p>
4
<p>Например, в московском метро от станции "Фрунзенская" до станции "Полянка" можно доехать двумя способами:</p>
4
<p>Например, в московском метро от станции "Фрунзенская" до станции "Полянка" можно доехать двумя способами:</p>
5
<p>Попробуем определить, какой из этих маршрутов короче. Чтобы посчитать общее время в пути, нужно знать, сколько времени занимает проезд между соседними станциями и сколько времени занимает переход.</p>
5
<p>Попробуем определить, какой из этих маршрутов короче. Чтобы посчитать общее время в пути, нужно знать, сколько времени занимает проезд между соседними станциями и сколько времени занимает переход.</p>
6
<p>По сути, карта метро - это граф. Немного дорисуем его, чтобы обозначить переходы с ветки на ветку. На новом рисунке проставим время в минутах рядом с каждым ребром - перегоном между соседними станциями или переходом:</p>
6
<p>По сути, карта метро - это граф. Немного дорисуем его, чтобы обозначить переходы с ветки на ветку. На новом рисунке проставим время в минутах рядом с каждым ребром - перегоном между соседними станциями или переходом:</p>
7
<p>Теперь можно вычислить полное время на каждом маршруте и выбрать самый короткий. Решение выглядит обманчиво простым. Люди интуитивно отбрасывают заведомо плохие варианты и сразу видят два подходящих маршрута. У компьютера интуиции нет - он переберет все маршруты, которых в действительности гораздо больше двух.</p>
7
<p>Теперь можно вычислить полное время на каждом маршруте и выбрать самый короткий. Решение выглядит обманчиво простым. Люди интуитивно отбрасывают заведомо плохие варианты и сразу видят два подходящих маршрута. У компьютера интуиции нет - он переберет все маршруты, которых в действительности гораздо больше двух.</p>
8
<p>Задачи такого рода в программировании относят к классу<strong>"задачи о кратчайшем пути"</strong>. Число, приписанное каждому ребру, называют<strong>весом ребра</strong>, а сам граф называют<strong>взвешенным</strong>.</p>
8
<p>Задачи такого рода в программировании относят к классу<strong>"задачи о кратчайшем пути"</strong>. Число, приписанное каждому ребру, называют<strong>весом ребра</strong>, а сам граф называют<strong>взвешенным</strong>.</p>
9
<p>Неочевидно, почему используют слово "вес", а не "длительность пути". Этому есть объяснение. За названием "взвешенные графы" скрываются разные задачи, решаемые одним и тем же способом. Для некоторых задач числа обозначают время, для других - расстояния, для третьих - денежные суммы, для четвертых - вес.</p>
9
<p>Неочевидно, почему используют слово "вес", а не "длительность пути". Этому есть объяснение. За названием "взвешенные графы" скрываются разные задачи, решаемые одним и тем же способом. Для некоторых задач числа обозначают время, для других - расстояния, для третьих - денежные суммы, для четвертых - вес.</p>
10
<p>Этот термин широко используется в математике - там есть весовые коэффициенты или весовая функция. Так что программисты получили этот термин в наследство от математиков.</p>
10
<p>Этот термин широко используется в математике - там есть весовые коэффициенты или весовая функция. Так что программисты получили этот термин в наследство от математиков.</p>
11
<h2>Строим маршрут по автомобильным дорогам</h2>
11
<h2>Строим маршрут по автомобильным дорогам</h2>
12
<p>Рассмотрим еще одну задачу - построение автомобильного маршрута. Для начала нужно определиться с ребрами и вершинами:</p>
12
<p>Рассмотрим еще одну задачу - построение автомобильного маршрута. Для начала нужно определиться с ребрами и вершинами:</p>
13
<ul><li>Ребрами будут считаться сами дороги</li>
13
<ul><li>Ребрами будут считаться сами дороги</li>
14
<li>Вершинами - развилки и пересечения дорог</li>
14
<li>Вершинами - развилки и пересечения дорог</li>
15
</ul><p>Здесь мы сталкиваемся с одной сложностью, которой не было в задаче с метро. В отличие от метро, автомобильные дороги могут быть с односторонним движением. Рисуя граф, мы должны учитывать эту особенность.</p>
15
</ul><p>Здесь мы сталкиваемся с одной сложностью, которой не было в задаче с метро. В отличие от метро, автомобильные дороги могут быть с односторонним движением. Рисуя граф, мы должны учитывать эту особенность.</p>
16
<p>Граф движения по городу может выглядеть так, как показано на картинке ниже. Голубым цветом нарисованы проспекты, а зеленом - односторонняя часть пути во дворах:</p>
16
<p>Граф движения по городу может выглядеть так, как показано на картинке ниже. Голубым цветом нарисованы проспекты, а зеленом - односторонняя часть пути во дворах:</p>
17
<p>На этот граф мы добавили стрелки, которые показывают направление движения:</p>
17
<p>На этот граф мы добавили стрелки, которые показывают направление движения:</p>
18
<ul><li>Если дорога односторонняя, мы рисуем ребро со стрелкой</li>
18
<ul><li>Если дорога односторонняя, мы рисуем ребро со стрелкой</li>
19
<li>Если дорога двухсторонняя, мы рисуем два ребра с противоположными стрелками</li>
19
<li>Если дорога двухсторонняя, мы рисуем два ребра с противоположными стрелками</li>
20
</ul><p>Если у ребер графа задано направление, такой граф называют<strong>направленным</strong>или<strong>ориентированным</strong>. Часто название "ориентированный граф" сокращают до "орграфа".</p>
20
</ul><p>Если у ребер графа задано направление, такой граф называют<strong>направленным</strong>или<strong>ориентированным</strong>. Часто название "ориентированный граф" сокращают до "орграфа".</p>
21
<p>Обратите внимание, что граф автомобильных дорог не только ориентированный, но и взвешенный - ведь нам нужно находить по нему оптимальные маршруты. В отличие от метро, на автомобильных дорогах бывают пробки. Поэтому мы не можем заранее присвоить ребрам точный вес - придется обозначать его как время проезда без учета пробок.</p>
21
<p>Обратите внимание, что граф автомобильных дорог не только ориентированный, но и взвешенный - ведь нам нужно находить по нему оптимальные маршруты. В отличие от метро, на автомобильных дорогах бывают пробки. Поэтому мы не можем заранее присвоить ребрам точный вес - придется обозначать его как время проезда без учета пробок.</p>
22
<h2>Выбираемся из лабиринта</h2>
22
<h2>Выбираемся из лабиринта</h2>
23
<p>Иногда нам не нужно выискивать идеальный маршрут - достаточно выяснить, можно ли его построить. Для примера представьте, что нам надо выбраться из лабиринта:</p>
23
<p>Иногда нам не нужно выискивать идеальный маршрут - достаточно выяснить, можно ли его построить. Для примера представьте, что нам надо выбраться из лабиринта:</p>
24
<p>В таком случае мы согласимся на любой путь и не будем уточнять, если ли пути покороче.</p>
24
<p>В таком случае мы согласимся на любой путь и не будем уточнять, если ли пути покороче.</p>
25
<p>Есть несколько стратегий выхода из лабиринта. Например, есть<strong>правило правой руки</strong>, которое предлагает такую стратегию:</p>
25
<p>Есть несколько стратегий выхода из лабиринта. Например, есть<strong>правило правой руки</strong>, которое предлагает такую стратегию:</p>
26
<ul><li>Мы кладем правую руку на стену и начинаем идти по лабиринту</li>
26
<ul><li>Мы кладем правую руку на стену и начинаем идти по лабиринту</li>
27
<li>Если мы пришли к развилке, всегда выбираем правый путь</li>
27
<li>Если мы пришли к развилке, всегда выбираем правый путь</li>
28
<li>Если мы пришли в тупик, возвращаемся к последней развилке и идем в следующий по счету коридор</li>
28
<li>Если мы пришли в тупик, возвращаемся к последней развилке и идем в следующий по счету коридор</li>
29
<li>Если все коридоры закончились тупиком, возвращаемся к предпоследней развилке и продолжаем обход справа налево</li>
29
<li>Если все коридоры закончились тупиком, возвращаемся к предпоследней развилке и продолжаем обход справа налево</li>
30
</ul><p>Конечно, коридоры можно обходить и слева направо, тогда речь будет идти о<strong>правиле левой руки</strong>- оно работает абсолютно так же. Здесь важно придерживаться всегда одной стороны, не смешивая повороты направо и налево.</p>
30
</ul><p>Конечно, коридоры можно обходить и слева направо, тогда речь будет идти о<strong>правиле левой руки</strong>- оно работает абсолютно так же. Здесь важно придерживаться всегда одной стороны, не смешивая повороты направо и налево.</p>
31
<p>Как и в случае с автомобильной картой, вершинами графа будут только места развилок и тупики. Посмотрим, как будет выглядеть маршрут по лабиринту:</p>
31
<p>Как и в случае с автомобильной картой, вершинами графа будут только места развилок и тупики. Посмотрим, как будет выглядеть маршрут по лабиринту:</p>
32
<p>Рассмотрим этот рисунок подробнее:</p>
32
<p>Рассмотрим этот рисунок подробнее:</p>
33
<ul><li>Красными линиями обозначены ребра графа</li>
33
<ul><li>Красными линиями обозначены ребра графа</li>
34
<li>Кружками обозначены вершины</li>
34
<li>Кружками обозначены вершины</li>
35
<li>Направление обхода графа показано синим цветом</li>
35
<li>Направление обхода графа показано синим цветом</li>
36
</ul><p>Стратегия предполагает, что мы пытаемся пройти как можно глубже, а если попадаем в тупик, возвращаемся назад. На рисунке это тоже видно - синяя линия часто идет в двух направлениях.</p>
36
</ul><p>Стратегия предполагает, что мы пытаемся пройти как можно глубже, а если попадаем в тупик, возвращаемся назад. На рисунке это тоже видно - синяя линия часто идет в двух направлениях.</p>
37
<p>Такой способ движения по графу называется<strong>обходом в глубину</strong>*. Этот алгоритм применяется для поиска вершины с определенными свойствами, поэтому часто употребляют похожий термин - "поиск в глубину". В профессиональной литературе вы можете встретить аббревиатуру<strong>DFS</strong>- depth first search, то есть "поиск сначала в глубину".</p>
37
<p>Такой способ движения по графу называется<strong>обходом в глубину</strong>*. Этот алгоритм применяется для поиска вершины с определенными свойствами, поэтому часто употребляют похожий термин - "поиск в глубину". В профессиональной литературе вы можете встретить аббревиатуру<strong>DFS</strong>- depth first search, то есть "поиск сначала в глубину".</p>
38
<p>Этот алгоритм прекрасно работает, если в лабиринте нет замкнутых коридоров или петель. Если мы попадем на петлю, мы можем вечно ходить по одному и тому же коридору, как показано на рисунке:</p>
38
<p>Этот алгоритм прекрасно работает, если в лабиринте нет замкнутых коридоров или петель. Если мы попадем на петлю, мы можем вечно ходить по одному и тому же коридору, как показано на рисунке:</p>
39
<p>Если мы нарисуем граф для такого лабиринта, мы обнаружим такую же петлю. Графы с петлями называются<strong>циклическими</strong>и требуют осторожности при обходе:</p>
39
<p>Если мы нарисуем граф для такого лабиринта, мы обнаружим такую же петлю. Графы с петлями называются<strong>циклическими</strong>и требуют осторожности при обходе:</p>
40
<p>Обходя лабиринт с петлями, можно помечать посещенные развилки мелом. Встретив пометку, мы узнаем, что попали в цикл. Действовать в этом случае надо так же, как и в тупике - развернуться и идти назад.</p>
40
<p>Обходя лабиринт с петлями, можно помечать посещенные развилки мелом. Встретив пометку, мы узнаем, что попали в цикл. Действовать в этом случае надо так же, как и в тупике - развернуться и идти назад.</p>
41
<h2>Обходим препятствия</h2>
41
<h2>Обходим препятствия</h2>
42
<p>В ролевых и стратегических играх пользователь управляет игровыми юнитами. Например, он может отправить боевой или строительный юнит на другой конец карты. Как правило, на карте встречаются препятствия - непроходимые горы и озера.</p>
42
<p>В ролевых и стратегических играх пользователь управляет игровыми юнитами. Например, он может отправить боевой или строительный юнит на другой конец карты. Как правило, на карте встречаются препятствия - непроходимые горы и озера.</p>
43
<p>Игровая карта может выглядеть так:</p>
43
<p>Игровая карта может выглядеть так:</p>
44
<p>Двигаясь по карте, юниты уверенно огибают преграды и достигают цели за кратчайшее время. Разберемся, как это работает.</p>
44
<p>Двигаясь по карте, юниты уверенно огибают преграды и достигают цели за кратчайшее время. Разберемся, как это работает.</p>
45
<p>Как и в предыдущих задачах, мы сначала решаем, что будет считаться ребрами и вершинами графа. В компьютерных играх вершины размещают в клетках карты. Ребра связывают соседние пустые клетки, в которых нет гор или озер.</p>
45
<p>Как и в предыдущих задачах, мы сначала решаем, что будет считаться ребрами и вершинами графа. В компьютерных играх вершины размещают в клетках карты. Ребра связывают соседние пустые клетки, в которых нет гор или озер.</p>
46
<p>Представим, что нам надо добраться из вершины Н в вершину К:</p>
46
<p>Представим, что нам надо добраться из вершины Н в вершину К:</p>
47
<p>Поиск в глубину поможет найти дорогу, но если на карте много поворотов и узких проходов, эффективнее будет другой алгоритм. Он называется<strong>волновым</strong>, потому что напоминает волны, кругами расходящиеся на воде от брошенного камня:</p>
47
<p>Поиск в глубину поможет найти дорогу, но если на карте много поворотов и узких проходов, эффективнее будет другой алгоритм. Он называется<strong>волновым</strong>, потому что напоминает волны, кругами расходящиеся на воде от брошенного камня:</p>
48
<p>Сначала мы проверяем соседей начальной вершины, затем - соседей соседей, и так далее, каждый раз удаляясь от начальной вершины на один шаг. В какой-то момент очередной круг доберется до конечной вершины. Это будет означать, что путь найден:</p>
48
<p>Сначала мы проверяем соседей начальной вершины, затем - соседей соседей, и так далее, каждый раз удаляясь от начальной вершины на один шаг. В какой-то момент очередной круг доберется до конечной вершины. Это будет означать, что путь найден:</p>
49
<p>Другое название этого алгоритма -<strong>поиск в ширину</strong>или<strong>BFS</strong>, breadth first search. Он позволяет найти маршрут с наименьшим количеством ребер. Впрочем, у него гораздо больше применений. В частности, одна из его модификаций позволяет закрашивать на изображениях замкнутые области произвольной формы.</p>
49
<p>Другое название этого алгоритма -<strong>поиск в ширину</strong>или<strong>BFS</strong>, breadth first search. Он позволяет найти маршрут с наименьшим количеством ребер. Впрочем, у него гораздо больше применений. В частности, одна из его модификаций позволяет закрашивать на изображениях замкнутые области произвольной формы.</p>
50
<p>Может показаться, что задачи на графах касаются в первую очередь карт или каких-то картинок, но это не так. Далее мы узнаем, как графы применяют при решении экономических и логистических задач.</p>
50
<p>Может показаться, что задачи на графах касаются в первую очередь карт или каких-то картинок, но это не так. Далее мы узнаем, как графы применяют при решении экономических и логистических задач.</p>
51
<h2>Считаем сдачу</h2>
51
<h2>Считаем сдачу</h2>
52
<p>Одна из задач, которую решает программное обеспечение банкоматов и вендинговых аппаратов - выдача сдачи. Как правило, автоматы пытаются выдать сдачу наименьшим количеством банкнот или монет.</p>
52
<p>Одна из задач, которую решает программное обеспечение банкоматов и вендинговых аппаратов - выдача сдачи. Как правило, автоматы пытаются выдать сдачу наименьшим количеством банкнот или монет.</p>
53
<p>Номиналы монет подбираются так, чтобы ими можно было выдать любую сумму. Например, сдачу в 8 рублей можно выдать тремя монетами: 5 рублей, 2 рубля и 1 рубль.</p>
53
<p>Номиналы монет подбираются так, чтобы ими можно было выдать любую сумму. Например, сдачу в 8 рублей можно выдать тремя монетами: 5 рублей, 2 рубля и 1 рубль.</p>
54
<p>Чтобы посчитать сдачу, можно использовать простой алгоритм выбора монет:</p>
54
<p>Чтобы посчитать сдачу, можно использовать простой алгоритм выбора монет:</p>
55
<ul><li>Пока можем, набираем сумму самыми крупными монетами</li>
55
<ul><li>Пока можем, набираем сумму самыми крупными монетами</li>
56
<li>Затем переходим к следующим по номиналу монетам, и так далее</li>
56
<li>Затем переходим к следующим по номиналу монетам, и так далее</li>
57
</ul><p>Проблема в том, что иногда в автомате заканчиваются монеты определенного номинала. Отсутствие двухрублевых монет не скажется на работе алгоритма: он выберет одну пятирублевую монету и три однорублевых.</p>
57
</ul><p>Проблема в том, что иногда в автомате заканчиваются монеты определенного номинала. Отсутствие двухрублевых монет не скажется на работе алгоритма: он выберет одну пятирублевую монету и три однорублевых.</p>
58
<p>Но если в автомате закончатся рублевые монеты, алгоритм не сможет вернуть сдачу в 8 рублей. Сначала он выберет монету в 5 рублей, потом монету в 2 рубля, а дальше начинаются сложности - надо выбрать монету в 1 рубль, а они закончились.</p>
58
<p>Но если в автомате закончатся рублевые монеты, алгоритм не сможет вернуть сдачу в 8 рублей. Сначала он выберет монету в 5 рублей, потом монету в 2 рубля, а дальше начинаются сложности - надо выбрать монету в 1 рубль, а они закончились.</p>
59
<p>Даже в этом случае задача может быть решена - сдачу можно выдать четырьмя монетами по 2 рубля. Проблема в том, что простой алгоритм этот вариант не найдет.</p>
59
<p>Даже в этом случае задача может быть решена - сдачу можно выдать четырьмя монетами по 2 рубля. Проблема в том, что простой алгоритм этот вариант не найдет.</p>
60
<p>Более сложный алгоритм работает на графах. Это может показаться странным, потому что не совсем очевидно, как они сюда относятся:</p>
60
<p>Более сложный алгоритм работает на графах. Это может показаться странным, потому что не совсем очевидно, как они сюда относятся:</p>
61
<p>На рисунке мы видим, что каждый узел графа может содержать набор монет. Двигаясь по стрелкам, мы добавляем к набору одну новую монету. Мы начинаем с пустого узла, в котором нет ни одной монеты. Далее мы ищем узлы, где достигается нужная сумма в 8 рублей.</p>
61
<p>На рисунке мы видим, что каждый узел графа может содержать набор монет. Двигаясь по стрелкам, мы добавляем к набору одну новую монету. Мы начинаем с пустого узла, в котором нет ни одной монеты. Далее мы ищем узлы, где достигается нужная сумма в 8 рублей.</p>
62
<p>У нас нет карты, которая стала бы основой для графа, потому что мы не храним все узлы графа. Вместо этого, можно генерировать новые соседние узлы по мере надобности, следуя простым правилам.</p>
62
<p>У нас нет карты, которая стала бы основой для графа, потому что мы не храним все узлы графа. Вместо этого, можно генерировать новые соседние узлы по мере надобности, следуя простым правилам.</p>
63
<p>Подобные графы часто встречаются в программировании, они называются<strong>неявными</strong>. Неявный граф не требует хранения своих узлов - узлы можно вывести или вычислить.</p>
63
<p>Подобные графы часто встречаются в программировании, они называются<strong>неявными</strong>. Неявный граф не требует хранения своих узлов - узлы можно вывести или вычислить.</p>
64
<p>Хорошим кандидатом для решения задачи о монетах будет<strong>алгоритм поиска в ширину</strong>. Мы продвигаемся во всех направлениях от пустого узла, в поисках узлов, содержащих сумму 8 рублей.</p>
64
<p>Хорошим кандидатом для решения задачи о монетах будет<strong>алгоритм поиска в ширину</strong>. Мы продвигаемся во всех направлениях от пустого узла, в поисках узлов, содержащих сумму 8 рублей.</p>
65
<p>К сожалению, этот алгоритм не очень эффективен. Нам надо дать сдачу наименьшим числом монет, поэтому мы можем всегда начинать с монеты самого крупного номинала. Это решение похоже на то, которые мы рассматривали в начале. Разница только в том, что у нас остается возможность найти правильный ответ, даже если каких-то номиналов не хватает.</p>
65
<p>К сожалению, этот алгоритм не очень эффективен. Нам надо дать сдачу наименьшим числом монет, поэтому мы можем всегда начинать с монеты самого крупного номинала. Это решение похоже на то, которые мы рассматривали в начале. Разница только в том, что у нас остается возможность найти правильный ответ, даже если каких-то номиналов не хватает.</p>
66
<p>Такой поиск похож на поиск в ширину, у которого есть предпочтительное направление. Мы можем использовать его, потому что у нас есть дополнительная информация о задаче.</p>
66
<p>Такой поиск похож на поиск в ширину, у которого есть предпочтительное направление. Мы можем использовать его, потому что у нас есть дополнительная информация о задаче.</p>
67
<p>При работе с графами программисты часто используют подобную информацию для того, чтобы ускорить алгоритм. Такие модификации называются<strong>информированными алгоритмами</strong>*. Классические алгоритмы без дополнительной информации называют<strong>неинформированными</strong>.</p>
67
<p>При работе с графами программисты часто используют подобную информацию для того, чтобы ускорить алгоритм. Такие модификации называются<strong>информированными алгоритмами</strong>*. Классические алгоритмы без дополнительной информации называют<strong>неинформированными</strong>.</p>
68
<h2>Выводы</h2>
68
<h2>Выводы</h2>
69
<p>Повторим ключевую мысль из этого урока - графы решают множество практических задач, с которыми мы сталкиваемся каждый день. Изучив эту тему, вы сможете:</p>
69
<p>Повторим ключевую мысль из этого урока - графы решают множество практических задач, с которыми мы сталкиваемся каждый день. Изучив эту тему, вы сможете:</p>
70
<ul><li>Находить кратчайшие маршруты на карте метро - решать задачи о кратчайшем пути с помощью взвешенных графов</li>
70
<ul><li>Находить кратчайшие маршруты на карте метро - решать задачи о кратчайшем пути с помощью взвешенных графов</li>
71
<li>Строить пути на автомобильных картах с помощью ориентированных графов</li>
71
<li>Строить пути на автомобильных картах с помощью ориентированных графов</li>
72
<li>Искать выход из лабиринта по правилу правой руки, применять обход в глубину и работать с циклическими графами</li>
72
<li>Искать выход из лабиринта по правилу правой руки, применять обход в глубину и работать с циклическими графами</li>
73
<li>Задавать маршруты для юнитов в компьютерных играх с помощью волновых графов</li>
73
<li>Задавать маршруты для юнитов в компьютерных играх с помощью волновых графов</li>
74
<li>Правильно давать сдачу в вендинговых автоматах с помощью поиска в ширину</li>
74
<li>Правильно давать сдачу в вендинговых автоматах с помощью поиска в ширину</li>
75
</ul>
75
</ul>