Strona główna » Algorytmy » Struktury Danych » Przechowywanie Grafów
 

Przechowywanie Grafów

Wstęp

Struktura przechowująca grafy w komputerze różni się złożonością pamięciową. Należy również pamiętać, że stosowany sposób przechowywania może różnić się w zależności od przechowywanego typu grafu. Przedstawiony poniżej kod pozwala na przechowywanie grafu nieskierowanego bez wag krawędziowych.

Przechowywanie Grafu

Jednym z sposobów na przechowanie grafu w komputerze jest macierz sąsiedztwa. Jednak ta technika jest mało wydajna pamięciowo jeśli jest bardzo mało krawędzi, a ponadto każda krawędź jest przechowywana podwójnie. To powoduje kolejne komplikacje, że należy pamiętać o podwójnej aktualizacji danych (jeśli krawędź nie jest skierowana). Na podstawie tych powodów warto zastanowić się nad jak najprostszym i najszybszym sposobem.

Dobrym pomysłem jest, aby każdy wierzchołek posiadał listę wierzchołków z którymi jest połączony. Ponadto przyjmując, że graf nie jest skierowany to można ograniczyć liczbę przechowywanych informacji o połączeniach. Jeśli krawędź (x, y) istnieje to (y, x) też. Przypuśćmy, że x < y wtedy istnienie krawędzi (y, x) sprawdzimy szukając krawędzi (x, y). W ten sposób liczba przechowywanych danych spada o połowę.

Przykład

Dany jest następujący graf:

Można go zapisać w następującej postaci:

WierzchołekPołączenia
01, 2, 3
12
2-
3-

W celu sprawdzenia czy istnieje krawędź (1, 2) należy sprawdzić w liście połączeń wierzchołka 1 czy istnieje wartość 2. W przypadku szukania krawędzi (3, 2) należy zamienić wartościami miejscami tj. (2, 3) i dopiero sprawdzić czy wierzchołek 2 ma połącznie z wierzchołkiem 3.

Implementacja

Wierzcholek

Wierzchołek to elementarna część grafu. Będzie on przechowywał listę wierzchołków z którymi jest połączony na liście. Dodatkowo będzie pozwalał na wykonanie dwóch podstawowych operacji: dodania nowej krawędzi oraz sprawdzenia czy dane połączenie już istnieje.

C++
C#
  1. class Wierzcholek {
  2. private:
  3.   std::vector<int> polaczenia;
  4. public:
  5.   bool sprawdzPolaczenie(int cel) {
  6.     return std::find(polaczenia.begin(), polaczenia.end(), cel) != polaczenia.end();
  7.   }
  8.   bool dodajKrawedz(int cel) {
  9.     if (sprawdzPolaczenie(cel)) {
  10.       return false;
  11.     }
  12.     else {
  13.       polaczenia.push_back(cel);
  14.       return true;
  15.     }
  16.   }
  17. };

W klasie Wierzcholek informacja o połączonych wierzchołkach są przechowywane w zmiennej prywatnej polaczenia. W celu sprawdzenia czy istnieje połączenia tego wierzchałka z jakimś należy wywołać sprawdzenie funkcją sprawdzPolaczenie(), a jako argument podać wierzchołek docelowy. Wynikiem działania funkcji będzie wartość logiczna. Z kolei funkcja dodajKrawedz() pozwala dodać połączenie do innego wierzchołka. Funkcja zwraca informację logiczną czy została dodana. Połączenie może nie zostać dodane ze względu na oszczędzanie pamięci jeśli dane połąćzenie już istnieje.

Graf

Z kolei graf będzie przechowywał listę wszystkich wierzchołków. Ilość wierzchołków jest do ustalenia podczas inicjalizacji nowego wystąpienia klasy. (Zabezpiecza to klasę przed wystąpieniem błędu dodania nowego połączenia, ponieważ dany wierzchołek mógł nie zostać jeszcze dodany.)

C++
C#
  1. class Graf {
  2. private:
  3.   std::vector<Wierzcholek*> wierzcholki;
  4. public:
  5.   Graf(int ile) {
  6.     while (ile-- > 0)
  7.       wierzcholki.push_back(new Wierzcholek());
  8.   }
  9.   void dodajPolaczenia(int id, std::initializer_list<int> polaczenia) {
  10.     for (int krawedz : polaczenia)
  11.       wierzcholki[id]->dodajKrawedz(krawedz);
  12.   }
  13.   bool sprawdzKrawedz(int start, int koniec) {
  14.     if (start > koniec)
  15.       return sprawdzKrawedz(koniec, start);
  16.     return wierzcholki[start]->sprawdzPolaczenie(koniec);
  17.   }
  18. };

W klasie Graf informacja o połączonych wierzchołkach są przechowywane w zmiennej prywatnej wierzcholki. W celu dodania połączeń można skorzystać z funkcji dodajPolaczenia(). Jako pierwszy przyjmowane jest id wierzchołka, a jako drugi listę wierzchołków do których ma zostać utworzone połączenie. Po dodaniu krawędzi można sprawdzić je funkcją sprawdzKrawedz(), która sprawdzi czy połączenie (start, koniec) ostnieje. Wynikiem jest wartość logiczna.

Testowanie funkcji

Napisaną klasę Graf można przetestować przy pomocy poniższego kodu, który wprowadza graf podany wcześniej w przykładzie. Po utworzeniu grafu zostają wypisane wszystkie krawędzi pomiędzy różnymi wierzchołkami, które nie istnieją.

C++
C#
  1. int main () {
  2.   Graf* g = new Graf(4);
  3.   g->dodajPolaczenia(0, { 1, 2, 3 });
  4.   g->dodajPolaczenia(1, { 2 });
  5.  
  6.   cout << "Nie istnieja krawedzie:" << endl;
  7.   for (int i = 0; i < 4; i++)
  8.     for (int j = i + 1; j < 4; j++)
  9.       if (!g->sprawdzKrawedz(i, j))
  10.         cout << i << " z " << j << endl;
  11.   system("pause");
  12.   return 0;
  13. }

Uruchomienie programu dla przykładowo wprowadzonego grafu zwraca następujący wynik:

  1. Nie istnieją krawędzie:
  2. 1 z 3
  3. 2 z 3

Wynik ten zgadza się z wprowadzonym grafem.

Zadania
Zadanie 1
Kod źródłowy Zadanie 1Kod źródłowy Zadanie 1