Во многих вычислительных приложениях естественным образом используется не только набор элементов (item), но и набор связей (connection) между парами этих элементов. Отношения, которые вытекают из этих связей, немедленно вызывают множество естественных вопросов. Существует ли путь, состоящий из связей, от одного такого элемента к другому? В какие другие элементы можно перейти из заданного элемента? Каков наилучший путь от одного элемента к другому?
Для моделирования таких ситуаций мы будем пользоваться объектами, которые называются графами (graph). В данной главе мы подробно рассмотрим основные свойства графов и заложим основу для изучения всевозможных алгоритмов, которые помогут ответить на вопросы, подобные сформулированным выше. Эти алгоритмы часто используют различные вычислительные средства, рассмотренные в частях I—IV. Они также служат тем фундаментом, без которого невозможно подступиться ко многим важным задачам, и решение которых нельзя представить без привлечения солидной алгоритмической технологии.
Теория графов, будучи крупной ветвью комбинаторной математики, интенсивно изучалась в течение не одной сотни лет. Было выявлено много важных и полезных свойств графов, однако многие задачи еще ждут своего решения. В этой книге мы извлечем из разнообразных знаний о графах лишь небольшую часть -все то, что нам необходимо знать -и изучим множество полезных фундаментальных алгоритмов.
Как и многие другие задачи, которые нам уже довелось рассмотреть, алгоритмическое изучение графов -в основном молодая отрасль знаний. Хотя некоторые фундаментальные алгоритмы открыты давно, все же большинство интереснейших алгоритмов получено в течение нескольких последних десятилетий. Даже простейшие алгоритмы на графах позволяют получить полезные компьютерные программы, а нетривиальные алгоритмы, которые нам предстоит разобрать, относятся к числу наиболее элегантных и интересных из известных алгоритмов.
Чтобы продемонстрировать все разнообразие приложений, использующих графы для обработки данных, начнем изучение алгоритмов в этой области с рассмотрения нескольких примеров.
Эти примеры демонстрируют диапазон приложений, для которых граф служит подходящей абстракцией, и, соответственно, диапазон вычислительных задач, с которыми доведется столкнуться при работе с графами. Такие задачи и являются главной темой настоящей книги. Во многих подобных приложениях, которые могут встретиться на практике, объем данных просто огромен, поэтому эффективность алгоритма означает, будет ли вообще найдено решение.
Нам уже приходилось сталкиваться с графами в части I. Ведь самые первые рассмотренные нами алгоритмы -алгоритмы объединения-поиска, описанные в "Введение" -представляют собой простейшие алгоритмы на графах. В "Элементарные структуры данных" графы послужили иллюстрацией применений двумерных массивов и связных списков, а в "Рекурсия и деревья" графы применялись для демонстрации связи между рекурсивными программами и фундаментальными структурами данных. Любую связную структуру данных можно представить в виде графа, а некоторые известные алгоритмы обработки деревьев и других связных структур представляют собой частные случаи алгоритмов на графах. Данная глава обеспечит контекст для изучения алгоритмов на графах -от простейших, приведенных в части I, до очень сложных, описываемых в лекциях 18—22.
Как всегда, мы хотим знать, какой из возможных алгоритмов решения конкретной задачи наиболее эффективен. Изучение характеристик производительности алгоритмов на графах представляет собой довольно сложную задачу, поскольку
Мы часто оцениваем границы производительности алгоритмов на графах в худшем случае, даже если они весьма пессимистичны. К счастью, как мы увидим, многие алгоритмы оптимальны и не требуют больших излишних затрат. Другая группа алгоритмов затрачивает одинаковый объем ресурсов на обработку всех графов заданного размера. Мы можем с достаточной степенью точности предсказать, как поведут себя такие алгоритмы в конкретных ситуациях. В тех случаях, когда такие предсказания невозможны, придется уделить особое внимание свойствам различных видов графов, которые могут появиться на практике, и оценить, как эти свойства могут повлиять на производительность алгоритмов.
Мы начнем с изучения основных определений графов и свойств графов, а также со стандартной терминологии, которая используется для их описания. Потом мы определим интерфейсы АТД (абстрактных типов данных), которыми будем пользоваться при изучении алгоритмов на графах, и две наиболее важные структуры данных, применяемых для представления графов -матрицы смежности и списки смежности, а также различные способы реализации базовых функций АТД. Затем мы рассмотрим клиентские программы для генерации случайных графов, которые могут использоваться для тестирования алгоритмов и для изучения свойств графов. Используя весь этот материал в качестве базы, мы познакомимся с алгоритмами для решения трех классических задач поиска путей в графах, которые продемонстрируют, что задачи обработки графов существенно отличаются друг от друга по сложности даже в тех случаях, когда внешне они довольно похожи. Глава завершается обзором наиболее важных задач обработки графов, которые будут рассмотрены в этой книге в порядке сложности их решений.