Три способи зберігати графік у пам'яті, переваги та недоліки


90

Існує три способи зберігати графік у пам'яті:

  1. Вузли як об'єкти, а ребра як вказівники
  2. Матриця, що містить всі вагові значення ребер між нумерованим вузлом x та вузлом y
  3. Список ребер між нумерованими вузлами

Я знаю, як писати всі три, але не впевнений, що продумав усі переваги та недоліки кожного.

Які переваги та недоліки кожного з цих способів зберігання графіка в пам’яті?


3
Я б розглядав матрицю лише в тому випадку, якщо графік був би дуже зв’язаний або дуже малий. Для малозв’язаних графіків підходи до об’єкта / покажчика або списку ребер забезпечують набагато краще використання пам’яті. Мені цікаво, що, крім сховища, я пропустив. ;)
sarnold

2
Вони також відрізняються за часовою складністю, матриця дорівнює O (1), а інші подання можуть сильно варіюватися залежно від того, що ви шукаєте.
msw

1
Я згадую, як я ще раз читав статтю, в якій описував апаратні переваги реалізації графа як матриці над списком покажчиків. Я не можу багато чого про це пам’ятати, крім того, що, оскільки ви маєте справу з суміжним блоком пам’яті, у будь-який момент більша частина вашого робочого набору цілком може бути в кеші L2. Список вузлів / покажчиків, з іншого боку, може бути здійснений через пам'ять, і, ймовірно, буде потрібно завантаження, яке не потрапляє в кеш. Я не впевнений, що згоден, але це цікава думка.
nerraga

1
@Dean J: просто запитання про "вузли як об'єкти та ребра як подання покажчиків". Яку структуру даних ви використовуєте для зберігання покажчиків в об’єкті? Це список?
Тимофій

4
Загальні назви такі: (1) еквівалент списку суміжності , (2) матриці суміжності , (3) списку країв .
Євген Сергєєв

Відповіді:


51

Один із способів проаналізувати це - з точки зору складності пам’яті та часу (що залежить від того, як ви хочете отримати доступ до графіка).

Зберігання вузлів як об’єктів із вказівниками один на одного

  • Складність пам'яті для цього підходу - O (n), оскільки у вас стільки об’єктів, скільки у вас є вузлів. Кількість необхідних покажчиків (на вузли) становить до O (n ^ 2), оскільки кожен об'єкт вузла може містити покажчики до n вузлів.
  • Складність часу для цієї структури даних становить O (n) для доступу до будь-якого даного вузла.

Зберігання матриці крайових ваг

  • Це буде складність пам'яті O (n ^ 2) для матриці.
  • Перевага цієї структури даних полягає в тому, що складність часу для доступу до будь-якого даного вузла становить O (1).

Залежно від того, який алгоритм ви запускаєте на графіку, і скільки вузлів є, вам доведеться вибрати відповідне подання.


3
Я вважаю, що складність часу для пошуку в моделі об’єкта / покажчика становить лише O (n), якщо ви також зберігаєте вузли в окремому масиві. В іншому випадку вам потрібно буде пройти графік, шукаючи потрібний вузол, ні? Обхід кожного вузла (але не обов'язково кожного ребра) у довільному графіку не може бути виконаний в O (n), чи не так?
Barry Fruitman

@BarryFruitman Я майже впевнений, що ти маєш рацію. BFS - це O (V + E). Крім того, якщо ви шукаєте вузол, який не підключений до інших вузлів, ви ніколи його не знайдете.
WilderField

10

Ще кілька речей, які слід врахувати:

  1. Матрична модель легше піддається графікам із зваженими краями, зберігаючи ваги в матриці. Модель об'єкта / вказівника повинна зберігати ваги країв у паралельному масиві, що вимагає синхронізації з масивом вказівника.

  2. Модель об’єкта / вказівника працює краще з направленими графіками, ніж з неорієнтованими графіками, оскільки вказівники повинні підтримуватися парами, які можуть стати несинхронізованими.


1
Ви маєте на увазі, що покажчики повинні підтримуватися в парі з неорієнтованими графіками, правильно? Якщо воно спрямоване, ви просто додаєте вершину до списку суміжностей певної вершини, але якщо вона не спрямована, вам потрібно додати одну до списку суміжностей обох вершин?
FrostyStraw

@FrostyStraw Так, саме так.
Barry Fruitman

8

Як зазначають деякі, метод "об'єкти та вказівники" страждає від труднощів пошуку, але цілком природний для таких речей, як побудова бінарних дерев пошуку, де є багато зайвої структури.

Я особисто люблю матриці суміжності, оскільки вони значно полегшують всілякі задачі, використовуючи інструменти з алгебраїчної теорії графів. (K-та ступінь матриці суміжності дає кількість шляхів довжиною k від вершини i до вершини j, наприклад. Додайте матрицю ідентичності, перш ніж приймати k-ту степінь, щоб отримати кількість шляхів довжиною <= k. Візьміть ранг n-1 мінор лапласіанця, щоб отримати кількість розкритих дерев ... І так далі.)

Але всі кажуть, що матриці суміжності дорого пам’яті! Вони лише наполовину праві: Ви можете обійти це за допомогою розріджених матриць, коли у вашого графіка мало ребер. Розріджені матричні структури даних роблять саме роботу, просто зберігаючи список суміжностей, але все ще мають повну гаму стандартних матричних операцій, що дає вам найкраще з обох світів.


7

Я думаю, що ваш перший приклад трохи неоднозначний - вузли як об’єкти, а ребра як вказівники. Ви можете відстежувати їх, зберігаючи лише вказівник на якийсь кореневий вузол, і в цьому випадку доступ до даного вузла може бути неефективним (скажімо, ви хочете вузол 4 - якщо об’єкт вузла не наданий, можливо, доведеться його шукати) . У цьому випадку ви також втратите частини графіку, недоступні з кореневого вузла. Я думаю, що це той випадок, коли припускає f64 rainbow, коли він каже, що складність часу для доступу до даного вузла становить O (n).

В іншому випадку ви також можете зберегти масив (або хеш-карту), повний покажчиків на кожен вузол. Це дозволяє O (1) отримати доступ до даного вузла, але трохи збільшує використання пам'яті. Якщо n - кількість вузлів, а e - кількість ребер, просторова складність цього підходу буде O (n + e).

Складність простору для матричного підходу буде уздовж лінії O (n ^ 2) (припускаючи, що ребра односпрямовані). Якщо ваш графік розріджений, у вашій матриці буде багато порожніх комірок. Але якщо ваш графік повністю зв’язаний (e = n ^ 2), це вигідно порівняно з першим підходом. Як каже RG, у вас також може бути менше помилок кеш-пам’яті при такому підході, якщо ви виділите матрицю як один шматок пам’яті, що може зробити швидше дотримання багатьох ребер навколо графіка.

Третій підхід є, мабуть, найефективнішим у більшості випадків - O (e) - але робить пошук усіх країв даного вузла клопотом O (e). Я не можу подумати про випадок, коли це було б дуже корисно.


Список ребер є природним для алгоритму Крускала ("для кожного ребра шукайте в union-find"). Також Скіена (2-е видання, стор. 157) розповідає про списки країв як основну структуру даних для графіків у своїй бібліотеці Combinatorica (яка є загальною бібліотекою багатьох алгоритмів). Він згадує, що однією з причин цього є обмеження, накладені обчислювальною моделлю Mathematica, яка є середовищем, в якому живе Combinatorica.
Євген Сергєєв


4

Існує ще один варіант: вузли як об’єкти, ребра як об’єкти теж, при цьому кожне ребро одночасно знаходиться у двох подвійно пов’язаних списках: список усіх ребер, що виходять з одного вузла, і список усіх ребер, що йдуть в один вузол .

struct Node {
    ... node payload ...
    Edge *first_in;    // All incoming edges
    Edge *first_out;   // All outgoing edges
};

struct Edge {
    ... edge payload ...
    Node *from, *to;
    Edge *prev_in_from, *next_in_from; // dlist of same "from"
    Edge *prev_in_to, *next_in_to;     // dlist of same "to"
};

Накладні витрати на пам’ять великі (2 вказівники на вузол і 6 вказівників на ребро), але ви отримуєте

  • O (1) вставка вузла
  • O (1) вставка краю (задані вказівники на вузли "від" та "до")
  • O (1) видалення краю (з урахуванням покажчика)
  • Видалення вузла O (deg (n)) (з урахуванням покажчика)
  • O (deg (n)) пошук сусідів вузла

Структура також може представляти досить загальний графік: орієнтований мультиграф з циклами (тобто ви можете мати кілька різних ребер між однаковими двома вузлами, включаючи безліч різних петель - ребра, що йдуть від x до x).

Більш детальне пояснення цього підходу доступне тут .


3

Добре, так що якщо ребра не мають ваг, матриця може бути двійковим масивом, і використання двійкових операторів може змусити справи йти дуже, дуже швидко в такому випадку.

Якщо графік розріджений, метод об’єкта / покажчика здається набагато ефективнішим. Утримання об’єкта / покажчиків у структурі даних, спеціально для того, щоб переконувати їх в один шматок пам'яті, також може бути хорошим планом або будь-яким іншим способом змусити їх залишатися разом.

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

Повернути спрямований графік легко за допомогою представлення матриці та легко за допомогою списку суміжностей, але не так добре з поданням об’єкта / покажчика.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.