Что лучше, списки смежности или матрица смежности для задач с графами в C ++? Каковы преимущества и недостатки каждого?
129
Что лучше, списки смежности или матрица смежности для задач с графами в C ++? Каковы преимущества и недостатки каждого?
std::list
(или еще лучшеstd::vector
).std::deque
илиstd::set
. Это зависит от того, как график будет меняться со временем и какие алгоритмы вы собираетесь использовать на них.Ответы:
Это зависит от проблемы.
Матрица смежности
между любыми двумя узлами O (1)
Список смежности
что может сэкономить много памяти, если матрица смежности разреженная.
происходит немного медленнее, чем с матрицей O (k); где k - количество соседних узлов
источник
Этот ответ предназначен не только для C ++, поскольку все упомянутое касается самих структур данных, независимо от языка. И мой ответ предполагает, что вы знаете базовую структуру списков и матриц смежности.
объем памяти
Если память - ваша главная забота, вы можете следовать этой формуле для простого графика, который допускает циклы:
Матрица смежности занимает п 2 /8 байт пространства (один бит на входе).
Список смежности занимает пространство 8e, где e - количество ребер (32-битный компьютер).
Если мы определим плотность графа как d = e / n 2 (количество ребер, деленное на максимальное количество ребер), мы можем найти «точку останова», где список занимает больше памяти, чем матрица:
8e> п 2 /8 при д> 1/64
Таким образом, с этими числами (по-прежнему 32-битными) точка останова достигает 1/64 . Если плотность (e / n 2 ) больше 1/64, то матрица предпочтительнее, если вы хотите сэкономить память.
Вы можете прочитать об этом в Википедии (статья о матрицах смежности) и на многих других сайтах.
Боковое примечание : можно улучшить пространственную эффективность матрицы смежности, используя хеш-таблицу, в которой ключи представляют собой пары вершин (только неориентированные).
Итерация и поиск
Списки смежности - это компактный способ представления только существующих ребер. Однако это происходит за счет возможного медленного поиска определенных ребер. Поскольку каждый список имеет длину, равную степени вершины, в худшем случае время поиска для проверки конкретного ребра может стать O (n), если список неупорядочен. Однако поиск соседей вершины становится тривиальным, а для разреженного или маленького графа стоимость итерации по спискам смежности может быть незначительной.
С другой стороны, матрицы смежности используют больше места для обеспечения постоянного времени поиска. Поскольку существует каждая возможная запись, вы можете проверить наличие края в постоянное время с помощью индексов. Однако поиск соседей занимает O (n), поскольку вам нужно проверить всех возможных соседей. Очевидный недостаток места в том, что для разреженных графов добавляется много отступов. См. Обсуждение памяти выше для получения дополнительной информации об этом.
Если вы все еще не уверены, что использовать : большинство реальных проблем создают разреженные и / или большие графы, которые лучше подходят для представлений списков смежности. Может показаться, что их сложнее реализовать, но я уверяю вас, что это не так, и когда вы пишете BFS или DFS и хотите получить всех соседей узла, они находятся всего в одной строке кода. Однако обратите внимание, что я не продвигаю списки смежности в целом.
источник
e = n / s
гдеs
- размер указателя.Хорошо, я собрал временные и пространственные сложности основных операций на графах.
Изображение ниже не требует пояснений.
Обратите внимание, насколько предпочтительна матрица смежности, когда мы ожидаем, что граф будет плотным, и как предпочтительнее использовать список смежности, когда мы ожидаем, что граф будет разреженным.
Я сделал некоторые предположения. Спросите меня, нуждается ли в пояснении сложность (время или пространство). (Например, для разреженного графа я взял En как небольшую константу, так как я предполагал, что добавление новой вершины добавит только несколько ребер, потому что мы ожидаем, что граф останется разреженным даже после добавления этого вершина) .
Подскажите пожалуйста, есть ли ошибки.
источник
Это зависит от того, что вы ищете.
С помощью матриц смежности вы можете быстро ответить на вопросы о том, принадлежит ли определенное ребро между двумя вершинами графу, а также можете быстро вставлять и удалять ребра. Недостатком является то , что вы должны использовать избыточное пространство, особенно для графов с большим числом вершин, что очень неэффективно , особенно если ваш график разрежен.
С другой стороны, со списками смежности сложнее проверить, находится ли данное ребро в графе, потому что вам нужно искать по соответствующему списку, чтобы найти ребро, но они более эффективны.
Как правило, списки смежности являются правильной структурой данных для большинства приложений графов.
источник
Предположим, что у нас есть граф, который имеет n узлов и m ребер,
Пример графика
Матрица смежности : мы создаем матрицу, содержащую n строк и столбцов, поэтому в памяти потребуется место, пропорциональное n 2 . Проверка наличия ребра между двумя узлами с именами u и v займет Θ (1) времени. Например, проверка на (1, 2) - это край будет выглядеть в коде следующим образом:
Если вы хотите идентифицировать все ребра, вам нужно перебрать матрицу, для этого потребуется два вложенных цикла, и это займет Θ (n 2 ). (Вы можете просто использовать верхнюю треугольную часть матрицы для определения всех ребер, но это снова будет Θ (n 2 ))
Список смежности: мы создаем список, в котором каждый узел также указывает на другой список. В вашем списке будет n элементов, и каждый элемент будет указывать на список, в котором количество элементов равно количеству соседей этого узла (см. Изображение для лучшей визуализации). Таким образом, это займет место в памяти, пропорциональное n + m . Проверка того, является ли (u, v) ребром, займет O (deg (u)) времени, в течение которого deg (u) равно количеству соседей u. Потому что, самое большее, вам придется перебирать список, на который указывает u. Для идентификации всех ребер потребуется Θ (n + m).
Список смежности примера графа
Вы должны сделать свой выбор в соответствии с вашими потребностями. Из-за своей репутации я не мог поставить изображение матрицы, извините за это
источник
Если вы изучаете анализ графов на C ++, вероятно, первым делом стоит начать с библиотеки ускоряющих графов , которая реализует ряд алгоритмов, включая BFS.
РЕДАКТИРОВАТЬ
Этот предыдущий вопрос о SO, вероятно, поможет:
как-создать-ac-boost-unirected-graph-and-traverse-it-in-depth-first-search h
источник
Лучше всего ответить на этот вопрос с помощью примеров.
Подумайте, например, о Флойд-Уоршалле . Мы должны использовать матрицу смежности, иначе алгоритм будет асимптотически медленнее.
Или что, если это плотный граф на 30 000 вершин? Тогда матрица смежности может иметь смысл, поскольку вы будете хранить 1 бит на пару вершин, а не 16 бит на ребро (минимум, который вам понадобится для списка смежности): это 107 МБ, а не 1,7 ГБ.
Но для таких алгоритмов, как DFS, BFS (и тех, которые его используют, например Эдмондс-Карп), приоритетного поиска (Dijkstra, Prim, A *) и т. Д., Список смежности ничем не хуже матрицы. Что ж, матрица может иметь небольшое ребро, когда граф плотный, но только с незначительным постоянным множителем. (Сколько? Это вопрос экспериментов.)
источник
an adjacency list is as good as a matrix
в таких случаях?Чтобы добавить к keyser5053 ответ об использовании памяти.
Для любого ориентированного графа матрица смежности (с 1 битом на ребро) потребляет
n^2 * (1)
биты памяти.Для полного графа список смежности (с 64-битными указателями) потребляет
n * (n * 64)
биты памяти, исключая служебные данные списка.Для неполного графа список смежности потребляет
0
биты памяти, исключая служебные данные списка.Для списка смежности вы можете использовать следующую формулу, чтобы определить максимальное количество ребер (
e
), прежде чем матрица смежности станет оптимальной для памяти.edges = n^2 / s
для определения максимального количества ребер, гдеs
- размер указателя платформы.Если ваш график динамически обновляется, вы можете поддерживать эту эффективность со средним числом ребер (на узел) равным
n / s
.Некоторые примеры с 64-битными указателями и динамическим графом (динамический граф эффективно обновляет решение проблемы после изменений, а не пересчитывает его с нуля каждый раз после того, как изменение было внесено).
Для ориентированного графа, где
n
равно 300, оптимальное количество ребер на узел с использованием списка смежности:Если мы вставим это в формулу keyser5053
d = e / n^2
(гдеe
- общее количество ребер), мы увидим, что находимся ниже точки останова (1 / s
):Однако 64 бита для указателя могут быть излишними. Если вместо этого вы используете 16-битные целые числа в качестве смещения указателя, мы можем уместить до 18 ребер до точки разрыва.
Каждый из этих примеров игнорирует накладные расходы самих списков смежности (
64*2
для векторных и 64-битных указателей).источник
d = (4 * 300) / (300 * 300)
, не так лиd = 4 / (300 * 300)
? Поскольку формула естьd = e / n^2
.В зависимости от реализации матрицы смежности число n графа должно быть известно заранее для эффективной реализации. Если график слишком динамичный и требует то и дело расширения матрицы, это тоже можно считать недостатком?
источник
Если вы используете хеш-таблицу вместо матрицы или списка смежности, вы получите лучшее время выполнения и пространство для всех операций (проверка на наличие ребра
O(1)
, получение всех смежных реберO(degree)
и т. Д.).Однако есть некоторые накладные расходы на постоянный коэффициент как для времени выполнения, так и для пространства (хеш-таблица не так быстро, как связанный список или поиск в массиве, и требует приличного дополнительного места для уменьшения коллизий).
источник
Я просто собираюсь коснуться преодоления компромисса с обычным представлением списка смежности, поскольку другие ответы охватывают другие аспекты.
Можно представить граф в списке смежности с запросом EdgeExists за амортизированное постоянное время, воспользовавшись преимуществами структур данных Dictionary и HashSet . Идея состоит в том, чтобы хранить вершины в словаре, и для каждой вершины мы сохраняем хэш-набор, ссылающийся на другие вершины, с которыми у нее есть ребра.
Один незначительный компромисс в этой реализации заключается в том, что она будет иметь пространственную сложность O (V + 2E) вместо O (V + E), как в обычном списке смежности, поскольку ребра представлены здесь дважды (поскольку каждая вершина имеет свой собственный хэш-набор ребер). Но такие операции, как AddVertex , AddEdge , RemoveEdge, могут быть выполнены за амортизированное время O (1) с этой реализацией, за исключением RemoveVertex, который принимает O (V) как матрицу смежности. Это означало бы, что кроме простоты реализации, матрица смежности не имеет особых преимуществ. Мы можем сэкономить место на разреженном графе с почти такой же производительностью в этой реализации списка смежности.
Взгляните на реализации ниже в репозитории Github C # для получения подробной информации. Обратите внимание, что для взвешенного графа он использует вложенный словарь вместо комбинации словаря и хеш-набора, чтобы учесть значение веса. Аналогично для ориентированного графа есть отдельные хеш-наборы для входных и выходных ребер.
Продвинутая алгоритмы
Примечание: я считаю, что с помощью ленивого удаления мы можем дополнительно оптимизировать операцию RemoveVertex до амортизации O (1), хотя я не тестировал эту идею. Например, при удалении просто отметьте вершину как удаленную в словаре, а затем лениво очистите потерянные ребра во время других операций.
источник