Strona główna » Algorytmy » Artykuły » Algorytm Prima
 

Algorytm Prima

Opis

Algorytma Prima pozwala znaleźć w grafie nieskierowanym minimalne drzewo rozpinające. Każda z krawędzi musi mieć określoną wagę. To na podstawie tych wartości algorytm będzie dopasowywał je do drzewa. Punktem początkowym może być dowolny wierzchołek grafu. Innymi słowy minimalne drzewo rozpinające uzyskane na końcu zawsze będzie takie samo dla tego samego grafu.

Algorytm

Dla każdego wierzchołka określamy, że jego koszt wynosi nieskończoność, a poprzednik jest nieokreślony. Wybranemu wierzchołkowi przypisujemy wartość 0, a następnie tworzymy kolejkę wierzchołków do rozpatrzenia. W kolejce dane są posortowane po koszcie elementu. Następnie, dopóki kolejka nie jest pusta, wybieramy wierzchołek o najniższym koszcie i aktualizujemy jego sąsiadów, którzy występują w kolejce. Jeśli krawędź z wybranego wierzchołka z kolejki do sąsiada ma niższą wartość niż sąsiad ma koszt to aktualizujemy informacje.

Przykład

Rozpatrzmy następujący graf:

Przykładowy graf

Zgodnie z algorytmem najpierw utwórzmy dla każdego wierzchołka parę danych: koszt i poprzednika. Szukamy drzewa rozpoczynając od wierzchołka 0.

Kolejka012345WybranyKomentarz
0, 1, 2, 3, 4, 50 | -1∞ | -1∞ | -1∞ | -1∞ | -1∞ | -10Usuwamy 0 z kolejki i aktualizujemy sąsiadów
1, 2, 3, 4, 50 | -14 | 05 | 0∞ | -1∞ | -1∞ | -11Najmniejszy koszt wierzchołek 1
2, 3, 4, 50 | -14 | 03 | 14 | 1∞ | -1∞ | -12Najmniejszy koszt wierzchołek 2
3, 4, 50 | -14 | 03 | 12 | 26 | 21 | 25Najmniejszy koszt wierzchołek 5
3, 40 | -14 | 03 | 12 | 25 | 51 | 23Najmniejszy koszt wierzchołek 3
30 | -14 | 03 | 12 | 24 | 31 | 24Najmniejszy koszt wierzchołek 4
-0 | -14 | 03 | 12 | 24 | 31 | 2-Kolejka pusta, koniec algorytmu

Dla każdego wierzchołka zostały otrzymane dwie wartości. Pierwsza z nich określa koszt wierzchołka. Suma tej wartości dla wszystkich wierzchołków da łączną wartość minimalnego drzewa rozpinającego. W tym przypadku wynosi ona 14. Z kolei druga wartość poprzednik pozwala określić, które węzły są ze sobą połączone. W tym przypadku warto pamiętać, że graf nie jest skierowany. Na podstawie otrzymanych danych można narysować następujące minimalne drzewo rozpinające:

Minimalne drzewo rozpinające graf

Analiza złożoności

Złożoność algorytmu jest zależna od implementacji kolejki. Niemniej wewnątrz algorytmu przechodzimy po wszystkich n elementach kolejki i za każdym razem aktualizujemy n sąsiadów. Oznacza to, że sam algorytm ma złożoność O(n2). Jednak należy pamiętać, że dodatkowo w każdej pętli należy uaktualnić kolejkę priorytetową, albo przeszukiwać listę w celu znalezienia minimalnego elementu.

Implementacja

Założenia

Algorytm otrzymuje jako argumeny macierz sąsiedztwa grafu, a w wyniku zwraca parę danych: koszt i poprzednik dla każdego wierzchołka. Program nie sprawdza poprawności danych wejściowych. W celu wczytania przykładowego grafu należy podać go w następujący sposób:

  1. 0 4 5 0 0 0
  2. 4 0 3 4 0 0
  3. 5 3 0 2 6 1
  4. 0 4 2 0 4 2
  5. 0 0 6 4 0 5
  6. 0 0 1 2 5 0

Algorytm

Funkcja AlgorytmPrima() spełnia opisane wyżej założenia. W celu wyszukiwania minimalnego elementu w kolejce wykorzystuje wyszukiwanie.

  1. dane* AlgorytmPrima(int** macierz, int n, int s) {
  2.   dane* wynik = new dane[n];
  3.   wynik[s].koszt = 0;
  4.   vector<int> kolejka;
  5.   for (int i = 0; i < n; i++)
  6.     kolejka.push_back(i);
  7.   while (kolejka.size() != 0) {
  8.     int u = -1;
  9.     for each (int q in kolejka)
  10.       if (wynik[q].koszt != INT_MAX &&
  11.         (u == -1 || wynik[q].koszt < wynik[u].koszt))
  12.         u = q;
  13.     vector<int>::iterator poz;
  14.     for (int j = 0; j < n; j++) {
  15.       if (macierz[u][j] == 0)
  16.         continue;
  17.       poz = find(kolejka.begin(), kolejka.end(), j);
  18.       if (poz != kolejka.end() && wynik[j].koszt > macierz[u][j]) {
  19.         wynik[j].koszt = macierz[u][j];
  20.         wynik[j].poprzednik = u;
  21.       }
  22.     }
  23.     poz = find(kolejka.begin(), kolejka.end(), u);
  24.     kolejka.erase(poz, poz + 1);
  25.   }
  26.   return wynik;
  27. }

(2.) Utwórz miejsce do zapisu wyniku oraz (3.) przypisz pierwszemu wierzchołkowi zerowy koszt. Następnie (4. - 6.) utwórz kolejkę i (7.) operuj na niej, aż zostanie pusta. (8. - 12.) Znajdź element w kolejce o najniższym koszcie. Potem (14.) dla każdego jego sąsiada: (15. - 16.) sprawdź czy istnieje pomiędzy nimi krawędź oraz (17.) czy sąsiad jest w kolejce, a koszt trzeba zaktualizować. Jeśli tak to (18. - 19.) należy to wykonać. Na koniec pętli (22. - 23.) należy pamiętać o usunięciu wybranego z kolejki elementu! Na koniec zwracana jest wyznaczona tablica wynik.

Testowanie funkcji

W celu przetestowania działania napisanej funkcji można skorzystać z poniższego fragmentu kodu:

  1. int main() {
  2.   int n, s;
  3.   cout << "Ile wierzcholkow ma graf?\n n = ";
  4.   cin >> n;
  5.   cout << "Od ktorego wierzcholka zaczac?\n s = ";
  6.   cin >> s;
  7.   cout << "Podaj macierz:" << endl;
  8.   int** macierz = new int*[n];
  9.   for (int i = 0; i < n; i++) {
  10.     macierz[i] = new int[n];
  11.     for (int j = 0; j < n; j++)
  12.       cin >> macierz[i][j];
  13.   }
  14.   dane* wynik = AlgorytmPrima(macierz, n, s);
  15.   int suma = 0;
  16.   cout << "ID\tPoprz.\tKoszt\n";
  17.   for (int i = 0; i < n; i++) {
  18.     cout << i << "\t" << wynik[i].poprzednik;
  19.     cout << "\t" << wynik[i].koszt << "\n";
  20.     suma += wynik[i].koszt;
  21.   }
  22.   cout << "Laczny koszt wyonsi: " << suma;
  23.   system("pause");
  24.   return 0;
  25. }