Что лучше, списки смежности или матрицы смежности для задач графа в C ++?

Что лучше, списки смежности или матрица смежности для задач с графами в C ++? Каковы преимущества и недостатки каждого из них?


person magiix    schedule 07.02.2010    source источник
comment
Используемая вами структура зависит не от языка, а от проблемы, которую вы пытаетесь решить.   -  person avakar    schedule 08.02.2010
comment
Я имел в виду для общего использования, как алгоритм djikstra, я задал этот вопрос, потому что я не знаю, стоит ли попробовать реализацию связанного списка, потому что ее сложнее кодировать, чем матрица смежности.   -  person magiix    schedule 08.02.2010
comment
Списки в C ++ так же просто, как набрать std::list (или, еще лучше, std::vector).   -  person avakar    schedule 08.02.2010
comment
@avakar: или std::deque, или std::set. Это зависит от того, как график будет меняться со временем, и от того, какие алгоритмы вы собираетесь использовать на них.   -  person Alexandre C.    schedule 22.04.2011
comment
Прочтите подробности в ханской академии   -  person Adil Abbasi    schedule 06.06.2021


Ответы (11)


Это зависит от проблемы.

Матрица смежности

  • Использует память O (n ^ 2)
  • Быстро искать и проверять наличие или отсутствие определенного ребра
    между любыми двумя узлами O (1)
  • Медленно перебирать все ребра
  • Добавление / удаление узла происходит медленно; сложная операция O (n ^ 2)
  • Быстро добавить новое ребро O (1)

Список смежности

  • Использование памяти больше зависит от количества ребер (и меньше от количества узлов),
    что может сэкономить много памяти, если матрица смежности разреженная.
  • Определение наличия или отсутствия определенного ребра между любыми двумя узлами
    происходит немного медленнее, чем с матрицей O (k); где k - количество соседних узлов
  • Быстро перебирать все ребра, потому что вы можете получить доступ к любым соседям узла напрямую.
  • Добавление / удаление узла происходит быстро; проще, чем матричное представление
  • Быстро добавить новое ребро O (1)
person Mark Byers    schedule 07.02.2010
comment
связанные списки сложнее кодировать, как вы думаете, стоит ли потратить на их реализацию некоторое время на изучение? - person magiix; 08.02.2010
comment
@magiix: Да, я думаю, вы должны понимать, как кодировать связанные списки, если это необходимо, но также важно не изобретать велосипед: cplusplus.com/reference/stl/list - person Mark Byers; 08.02.2010
comment
может ли кто-нибудь предоставить ссылку с чистым кодом для, скажем, поиска в ширину в формате связанных списков ?? - person magiix; 08.02.2010
comment
Использование std :: list geeksforgeeks.org/breadth-first-traversal- для графика - person atif93; 13.11.2016

Этот ответ предназначен не только для C ++, поскольку все упомянутое касается самих структур данных, независимо от языка. И мой ответ предполагает, что вы знаете базовую структуру списков и матриц смежности.

объем памяти

Если память является вашей основной заботой, вы можете следовать этой формуле для простого графика, который допускает циклы:

Матрица смежности занимает n 2 / 8 байтов (один бит на запись).

Список смежности занимает пространство 8e, где e - количество ребер (32-битный компьютер).

Если мы определим плотность графа как d = e / n 2 (количество ребер, деленное на максимальное количество ребер), мы можем найти «точку останова», где список занимает больше памяти, чем матрица:

8e> n 2 / 8, когда d> 1/64

Таким образом, с этими числами (все еще 32-битными) точка останова находится на 1/64. Если плотность (e / n 2) больше 1/64, то предпочтительнее использовать матрицу, если вы хотите сэкономить память.

Вы можете прочитать об этом на wikipedia (статья о матрицах смежности) и на многих других сайтах. .

Дополнительное примечание: можно улучшить пространственную эффективность матрицы смежности, используя хеш-таблицу, в которой ключи представляют собой пары вершин (только неориентированные).

Итерация и поиск

Списки смежности - это компактный способ представления только существующих ребер. Однако это происходит за счет возможного медленного поиска определенных ребер. Поскольку длина каждого списка равна степени вершины, в худшем случае время поиска для проверки конкретного ребра может стать O (n), если список неупорядочен. Однако поиск соседей вершины становится тривиальным, а для разреженного или небольшого графа стоимость итерации по спискам смежности может быть незначительной.

С другой стороны, матрицы смежности используют больше места для обеспечения постоянного времени поиска. Поскольку существует каждая возможная запись, вы можете проверить наличие края в постоянное время с помощью индексов. Однако поиск соседей занимает O (n), так как вам нужно проверить всех возможных соседей. Очевидный недостаток места в том, что для разреженных графов добавляется много отступов. См. Обсуждение памяти выше для получения дополнительной информации об этом.

Если вы все еще не знаете, что использовать: большинство реальных проблем создают разреженные и / или большие графы, которые лучше подходят для представления списков смежности. Может показаться, что их сложнее реализовать, но я уверяю вас, что это не так, и когда вы пишете BFS или DFS и хотите получить всех соседей узла, они находятся всего в одной строке кода. Однако обратите внимание, что я не продвигаю списки смежности в целом.

person keyser    schedule 24.03.2011
comment
+1 для понимания, но это должно быть исправлено фактической структурой данных, используемой для хранения списков смежности. Вы можете сохранить для каждой вершины ее список смежности в виде карты или вектора, и в этом случае необходимо обновить фактические числа в ваших формулах. Кроме того, аналогичные вычисления могут использоваться для оценки точек безубыточности для временной сложности конкретных алгоритмов. - person Alexandre C.; 22.04.2011
comment
Да, это формула для конкретного сценария. Если вам нужен приблизительный ответ, воспользуйтесь этой формулой или при необходимости измените ее в соответствии с вашими требованиями (например, у большинства людей сейчас 64-битный компьютер :)) - person keyser; 22.04.2011
comment
Для тех, кто интересуется, формула для точки разрыва (максимальное количество средних ребер в графе из n узлов) будет e = n / s, где s - размер указателя. - person deceleratedcaviar; 23.01.2012

Хорошо, я скомпилировал временные и пространственные сложности основных операций на графах.
Изображение ниже должно быть самоочевидным.
Обратите внимание, насколько предпочтительна матрица смежности, когда мы ожидаем, что граф будет плотным, и как Список предпочтительнее, когда мы ожидаем, что график будет разреженным.
Я сделал некоторые предположения. Спросите меня, нуждается ли в пояснении сложность (время или пространство). (Например, для разреженного графа я взял En как небольшую константу, так как я предполагал, что добавление новой вершины добавит только несколько ребер, потому что мы ожидаем, что граф останется разреженным даже после добавления этого вершина.)

Подскажите пожалуйста, есть ли ошибки.

введите описание изображения здесь

person John Red    schedule 18.07.2015
comment
В случае, если неизвестно, является ли граф плотным или разреженным, будет ли правильным сказать, что сложность пространства для списка смежности будет O (v + e)? - person ; 26.10.2015
comment
Для большинства практических алгоритмов одной из наиболее важных операций является перебор всех ребер, выходящих из данной вершины. Вы можете добавить его в свой список - это O (степень) для AL и O (V) для AM. - person max; 24.11.2016
comment
@johnred, не лучше ли сказать, что Добавление вершины (время) для AL - это O (1), потому что вместо O (en), потому что мы действительно не добавляем ребра при добавлении вершины. Добавление ребра можно рассматривать как отдельную операцию. Для AM имеет смысл учитывать, но даже здесь нам просто нужно инициализировать соответствующие строки и столбец новой вершины до нуля. Добавление ребер даже для AM можно учесть отдельно. - person Usman; 04.01.2017
comment
Как добавить вершину в AL O (V)? Нам нужно создать новую матрицу, скопировать в нее предыдущие значения. Это должно быть O (v ^ 2). - person Alex_ban; 25.01.2017

Это зависит от того, что вы ищете.

С помощью матриц смежности вы можете быстро ответить на вопросы о том, принадлежит ли определенное ребро между двумя вершинами графу, а также можете быстро вставлять и удалять ребра. Обратной стороной является то, что вам приходится использовать чрезмерное пространство, особенно для графов с большим количеством вершин, что очень неэффективно, особенно если ваш граф разреженный.

С другой стороны, с списками смежности сложнее проверить, находится ли данное ребро в графе, потому что вам нужно выполнить поиск по соответствующему списку, чтобы найти ребро, но они занимают больше места.

Как правило, списки смежности являются правильной структурой данных для большинства приложений графов.

person Alex Ntousias    schedule 07.02.2010
comment
что, если вы используете словари для хранения списка смежности, это даст вам наличие края в амортизированном времени O (1). - person Rohith Yeravothula; 27.10.2018

Предположим, у нас есть граф с n количеством узлов и m количеством ребер,

Пример графика
 введите описание изображения здесь

Матрица смежности: мы создаем матрицу с n количеством строк и столбцов, поэтому в памяти будет занимать место, пропорциональное n 2 . Проверка наличия ребра между двумя узлами с именами u и v займет Θ (1) времени. Например, проверка на (1, 2) - это ребро в коде будет выглядеть следующим образом:

if(matrix[1][2] == 1)

Если вы хотите идентифицировать все ребра, вам нужно перебрать матрицу, для этого потребуется два вложенных цикла, и это займет Θ (n 2). (Вы можете просто использовать верхнюю треугольную часть матрицы для определения всех ребер, но это снова будет Θ (n 2))

Список смежности: мы создаем список, в котором каждый узел также указывает на другой список. В вашем списке будет n элементов, и каждый элемент будет указывать на список, в котором количество элементов равно количеству соседей этого узла (см. Изображение для лучшей визуализации). Таким образом, это займет место в памяти, пропорциональное n + m. Проверка того, является ли (u, v) ребром, займет O (deg (u)) времени, в течение которого deg (u) равно количеству соседей u. Потому что самое большее вам придется перебирать список, на который указывает u. Для идентификации всех ребер потребуется Θ (n + m).

Список смежности примера графа

введите здесь описание изображения
Вы должны сделать свой выбор в соответствии с вашими потребностями. По репутации я не мог поставить изображение матрицы, извините за это

person Muhammed Kadir    schedule 19.03.2017

Если вы изучаете анализ графов на C ++, вероятно, первым делом стоит начать с библиотеки ускоренных графов, которая реализует ряд алгоритмов, включая BFS.

ИЗМЕНИТЬ

Этот предыдущий вопрос о SO, вероятно, поможет:

как сделать- create-ac-boost-unirected-graph-and-traverse-it-in-depth-first-searchc h

person Binary Nerd    schedule 07.02.2010
comment
Спасибо, я проверю эту библиотеку - person magiix; 08.02.2010
comment
+1 для графика повышения. Это путь (за исключением, конечно, в образовательных целях) - person Tristram Gräbener; 24.03.2011

Лучше всего ответить на этот вопрос с помощью примеров.

Подумайте, например, о Floyd-Warshall. Мы должны использовать матрицу смежности, иначе алгоритм будет асимптотически медленнее.

Или что, если это плотный граф на 30 000 вершин? Тогда матрица смежности может иметь смысл, поскольку вы будете хранить 1 бит на пару вершин, а не 16 бит на ребро (минимум, который вам понадобится для списка смежности): это 107 МБ, а не 1,7 ГБ.

Но для таких алгоритмов, как DFS, BFS (и тех, которые его используют, например Эдмондса-Карпа), приоритетного поиска (Dijkstra, Prim, A *) и т. Д., Список смежности ничем не хуже матрицы. Что ж, матрица может иметь небольшое ребро, когда граф плотный, но только с незначительным постоянным множителем. (Сколько? Это вопрос экспериментов.)

person Evgeni Sergeev    schedule 25.11.2016
comment
Для таких алгоритмов, как DFS и BFS, если вы используете матрицу, вам нужно проверять всю строку каждый раз, когда вы хотите найти соседние узлы, тогда как у вас уже есть соседние узлы в соседнем списке. Как вы думаете, почему an adjacency list is as good as a matrix в таких случаях? - person realUser404; 24.09.2018
comment
@ realUser404 Точно, сканирование всей строки матрицы - это операция O (n). Списки смежности лучше подходят для разреженных графов, когда вам нужно пройти все исходящие ребра, они могут сделать это за O (d) (d: степень узла). Однако матрицы имеют лучшую производительность кеша, чем списки смежности, из-за последовательного доступа, поэтому для несколько плотных графов сканирование матриц может иметь больше смысла. - person Jochem Kuijpers; 15.10.2018

Чтобы добавить к keyser5053 ответ об использовании памяти.

Для любого ориентированного графа матрица смежности (с 1 битом на ребро) потребляет n^2 * (1) бит памяти.

Для полного графа список смежности (с 64-битными указателями) потребляет n * (n * 64) бит памяти, без учета накладных расходов на список.

Для неполного графа список смежности потребляет 0 бит памяти, исключая служебные данные списка.


Для списка смежности вы можете использовать следующую формулу, чтобы определить максимальное количество ребер (e), прежде чем матрица смежности станет оптимальной для памяти.

edges = n^2 / s, чтобы определить максимальное количество ребер, где s - размер указателя платформы.

Если ваш график динамически обновляется, вы можете поддерживать эту эффективность со средним числом ребер (на узел) n / s.


Некоторые примеры с 64-битными указателями и динамическим графом (динамический граф эффективно обновляет решение проблемы после изменений, а не пересчитывает его с нуля каждый раз после внесения изменений.)

Для ориентированного графа, где n равно 300, оптимальное количество ребер на узел с использованием списка смежности:

= 300 / 64
= 4

Если мы вставим это в формулу keyser5053, d = e / n^2 (где e - общее количество ребер), мы увидим, что мы находимся ниже точки останова (1 / s):

d = (4 * 300) / (300 * 300)
d < 1/64
aka 0.0133 < 0.0156

Однако 64 бита для указателя может быть излишним. Если вместо этого вы используете 16-битные целые числа в качестве смещения указателя, мы можем уместить до 18 ребер до точки разрыва.

= 300 / 16
= 18

d = ((18 * 300) / (300^2))
d < 1/16
aka 0.06 < 0.0625

Каждый из этих примеров игнорирует накладные расходы самих списков смежности (64*2 для векторных и 64-битных указателей).

person deceleratedcaviar    schedule 23.01.2012
comment
Я не понимаю часть d = (4 * 300) / (300 * 300), разве не d = 4 / (300 * 300)? Поскольку формула d = e / n^2. - person Saurabh; 06.06.2020

В зависимости от реализации матрицы смежности «n» графа должно быть известно раньше для эффективной реализации. Если график слишком динамичный и требует время от времени расширять матрицу, это тоже можно считать недостатком?

person ChrisOdney    schedule 08.05.2014

Если вы используете хэш-таблицу вместо матрицы или списка смежности, вы получите лучшее или такое же время выполнения и пространство большого O для всех операций (проверка ребра - O(1), получение всех смежных ребер - O(degree) и т. Д.) .

Однако есть некоторые накладные расходы на постоянный коэффициент как для времени выполнения, так и для пространства (хеш-таблица не так быстро, как связанный список или поиск в массиве, и требует приличного количества дополнительного места для уменьшения коллизий).

person max    schedule 24.11.2016

Я просто коснусь преодоления компромисса с регулярным представлением списка смежности, поскольку другие ответы охватывают эти аспекты.

Можно представить граф в списке смежности с помощью запроса EdgeExists за амортизированное постоянное время, воспользовавшись преимуществами структур данных Dictionary и HashSet. Идея состоит в том, чтобы хранить вершины в словаре, и для каждой вершины мы сохраняем хэш-набор, ссылающийся на другие вершины, с которыми у нее есть ребра.

Один незначительный компромисс в этой реализации заключается в том, что она будет иметь пространственную сложность O (V + 2E) вместо O (V + E), как в обычном списке смежности, поскольку ребра представлены здесь дважды (потому что каждая вершина имеет свой собственный хэш-набор ребер). Но такие операции, как AddVertex, AddEdge, RemoveEdge, могут быть выполнены с амортизированным временем O (1) с этой реализацией, за исключением RemoveVertex , который будет амортизирован за O (V), как в матрице смежности с поисковым словарем индекса массива. Это означало бы, что кроме простоты реализации, матрица смежности не имеет особых преимуществ. Мы можем сэкономить место на разреженном графе с почти такой же производительностью в этой реализации списка смежности.

Взгляните на реализации ниже в репозитории Github C # для получения подробной информации. Обратите внимание, что для взвешенного графа он использует вложенный словарь вместо комбинации словаря и хеш-набора, чтобы учесть значение веса. Аналогично для ориентированного графа существуют отдельные хеш-наборы для входных и выходных ребер.

Расширенные алгоритмы

Примечание: я считаю, что с помощью ленивого удаления мы можем дополнительно оптимизировать операцию RemoveVertex до амортизации O (1), хотя я не тестировал эту идею. Например, при удалении просто отметьте вершину как удаленную в словаре, а затем лениво очистите потерянные ребра во время других операций.

person justcoding121    schedule 13.10.2017
comment
Для матрицы смежности удаление вершины принимает O (V ^ 2), а не O (V) - person Saurabh; 04.06.2020
comment
да. Но если вы используете словарь для отслеживания индексов массива, он опустится до O (V). Взгляните на этот реализация RemoveVertex. - person justcoding121; 06.06.2020