Kolejka priorytetowa Kolejka priorytetowa Kolejka priorytetowa ang priority








![Przesiewanie w górę kopca heap[1…n] sieve-up(int i) { if i=1 then return; h : Przesiewanie w górę kopca heap[1…n] sieve-up(int i) { if i=1 then return; h :](https://slidetodoc.com/presentation_image_h2/6eb50b803e39a1a177d36cb2b5f3b1d4/image-9.jpg)
![Przesiewanie w dół kopca heap[1…n] sieve-down(int i) { if i > n/2 then return; Przesiewanie w dół kopca heap[1…n] sieve-down(int i) { if i > n/2 then return;](https://slidetodoc.com/presentation_image_h2/6eb50b803e39a1a177d36cb2b5f3b1d4/image-10.jpg)
![Implementacja operacji kopcowych � � � Wstawienie nowego elementu do kopca heap[1…n] dopisujemy element Implementacja operacji kopcowych � � � Wstawienie nowego elementu do kopca heap[1…n] dopisujemy element](https://slidetodoc.com/presentation_image_h2/6eb50b803e39a1a177d36cb2b5f3b1d4/image-11.jpg)












- Slides: 23
Kolejka priorytetowa
Kolejka priorytetowa �Kolejka priorytetowa (ang. priority queue) to struktura danych pozwalająca efektywnie realizować następujące operacje na zbiorze dynamicznym, którego elementy pochodzą z określonego uniwersum z porządkiem liniowym: ◦ insert(x) – dodanie nowego elementu x do zbioru, ◦ max() – odczytanie wartości największego elementu w zbiorze, ◦ extract-max(x) – usunięcie ze zbioru największego elementu. ◦ init() – utworzenie kolejki priorytetowej z zadanego zbioru dnych �Często rozważa się kolejki priorytetowe, w których poszukuje się elementu minimalnego zamiast maksymalnego.
Implementacja listowa kolejki priorytetowej �Lista uporządkowana od elementu największego w głowie do najmniejszego w ogonie może być implementacją kolejki priorytetowej. �Złożoność czasowa operacji kolejkowych: ◦ ◦ insert(x) – O(n) max() – O(1) extract-max() – O(1) init() – O(n log(n)) �Złożoność pamięciowa operacji kolejkowych: O(1) (za wyjątkiem operacji init).
Implementacja listowa kolejki priorytetowej �Lista nieuporządkowana może być implementacją kolejki priorytetowej (ewentualnie z elementem maksymalnym w głowie listy). �Złożoność czasowa operacji kolejkowych: ◦ insert(x) – O(1) ◦ max() – O(n) (ewentualnie O(1) gdy na początku znajduje się element maksymalny) ◦ extract-max() – O(n) ◦ init() – O(n) �Złożoność pamięciowa operacji kolejkowych: O(1) (za wyjątkiem operacji init).
Porządek kopcowy w drzewie �W drzewie jest zachowany porządek kopcowy, gdy w każdym węźle synowie przechowują wartości mniejsze od wartości w węźle. �Element maksymalny w drzewie z porządkiem kopcowym znajduje się w korzeniu (element minimalny jest w którymś z liści). �Gdy poruszamy się po drzewie od korzenia do liścia to odwiedzamy coraz to mniejsze (nie większe) wartości.
Kopiec binarny �Kopiec binarny to binarne drzewo zupełne z porządkiem kopcowym. �W n-elementowym kopcu binarnym jest n/2 liści. �N-elementowy kopic binarny jest drzewem regularnym gdy n jest nieparzyste albo posiada jeden węzeł tylko z lewym synem (liściem) gdy n jest parzyste. �Wysokość (krawędziowa) n-elementowego kopca binarnego wynosi log n.
Tablicowa implementacja kopca �Drzewo zupełne wstawiamy do tablicy poziomami, zaczynając od komórki nr 1 (komórka nr 0 nie jest wykorzystywana). �Numer lewego syna dla komórki i-tej wynosi: left(i) = i*2 = i << 1 a dla prawego syna: right(i) = i*2+1 = (i << 1) | 1 �Numer ojca komórki i-tej wynosi: parent(i) = i/2 = i >> 1
Przesiewanie �Rozważmy zmianę wartości jednego elementu w kopcu: gdy go zwiększymy element ten trzeba przesiać w górę, gdy go zmniejszymy trzeba go przesiać w dół. �Przesiewanie to przesuwanie elementu w górę kopca lub dół poprzez zamiany wartości w węzłach tak daleko, aż nie zostanie przywrócony porządek kopcowy.
Przesiewanie w górę kopca heap[1…n] sieve-up(int i) { if i=1 then return; h : = parent(i); if heap[i] <= heap[h] then return; heap[i] : =: heap[h]; sieve-up(h); } Czas: O(log n) gdzie n to ilość elementów w kopcu Pamięć: O(log n) – głębokość rekurencji (w procedurze iteracyjnej O(1))
Przesiewanie w dół kopca heap[1…n] sieve-down(int i) { if i > n/2 then return; m : = left(i); // lewy syn; if m+1 <= n then if heap[m+1] > heap[m] then m++; if heap[m] <= heap[i] then return; heap[i] : =: heap[m]; sieve-down(m); } Czas: O(log n) gdzie n to ilość elementów w kopcu Pamięć: O(log n) – głębokość rekurencji (w procedurze iteracyjnej O(1))
Implementacja operacji kopcowych � � � Wstawienie nowego elementu do kopca heap[1…n] dopisujemy element na końcu tablicy i przesiewamy go w górę: insert(comparable x) { n++; heap[n] : = x; sieve-up(n); } Wyciągnięcie elementu maksymalnego z kopca heap[1…n]: extract-max() { heap[1] : =: heap[n]; n--; sieve-down(1); return heap[n+1]; } Odczytanie elementu maksymalnego z kopca heap[1…n]: max() -> comparable { return heap[1]; } Czas: O(log n) dla operacji insert() i extract-max() oraz O(1) dla max(), gdzie n to ilość elementów w kopcu. Pamięć: O(log n) dla operacji modyfikujących i O(1) dla max() – głębokość rekurencji (implementując przesiewanie w sposób iteracyjny zapotrzebowanie na pamięć dla wszystkich operacji wyniesie O(1)).
Budowanie kopca Dana jest tablica n-elementowa z danymi. � Naszym zadaniem jest zbudowanie kopca. � Idea rozwiązania: budowa kopca od góry – czas O(n log(n)): � ◦ początkowy fragment tablicy jest kopcem; ◦ dołączamy następny element tablicy do istniejącego kopca. init-heap() { for i = 2 … n do sieve-up(i); } � Idea rozwiązania: budowa kopca od dołu – czas O(n) ◦ ◦ wszystkie liście są kopcami; jeśli do korzenia są podłączone dwa kopce, to wystarczy przesiać drzewo w dół od korzenia aby otrzymać kopiec. init-heap() { for i = n/2 … 1 do sieve-down(i); }
Sortowanie kopcowe �Idea: budujemy kopic poprzez wkładanie kolejnych elementów do kopca a potem usuwamy kolejno elementu od największego do najmniejszego i umieszczamy je w tablicy wynikowej od końca. �Czas: O(n ∙ log n) �Pamięć: O(1) gdy nie używamy rekurencji w przesiewaniach albo O(log n) w przeciwnym razie �Sortowanie kopcowe nie jest stabilne. �Sortowanie kopcowe może działać w miejscu, gdy wyeliminujemy rekurencję z przesiewania.
Zadanie 1: �Zdefiniuj szablon klasy dla kopca zaimplementowanego na tablicy. �Przetestuj działanie kopca dla danych różnego typu (liczby całkowite typu int, łańcuchy znakowe typu string, itp).
Zadanie 2: �Do zdefiniowanego w poprzednim zadaniu szablonu kopca dopisz statyczną metodę sortowania kopcowego. �Przetestuj działanie sortowania przez kopcowanie na danych różnego typu.
Zadanie 3: �Zdefiniuj szablon klasy dla kopca zaimplementowanego na tablicy. �Przetestuj działanie kopca dla danych różnego typu (liczby całkowite typu int, łańcuchy znakowe typu string, itp).
Złączalne kolejki priorytetowe �Złączalna kolejka priorytetowa (ang. meldable priority queue) to struktura danych pozwalająca efektywnie realizować na zbiorze dynamicznym z elementami pochodzącymi z określonego uniwersum z porządkiem liniowym operacje kolejkowe insert(x), max(), extract-max() oraz init() plus następujące operacje: ◦ decrease-key(x, y) –nadaje kluczowi elementu x w kolejce nową, mniejszą wartość y (operacja opcjonalna); ◦ delete(x) – usuwa element x z kolejki (operacja opcjonalna); ◦ meld(Q) – przyłączenie kolejki Q do zbioru.
Drzewa dwumianowe �Drzewo dwumianowe (ang. binomial tree) Bk jest drzewem zdefiniowanym rekurencyjnie następujący w sposób: ◦ drzewo dwumianowe rzędu 0 składa się z jednego węzła (korzenia); ◦ drzewo dwumianowe rzędu k składa się z korzenia oraz jego dzieci, którymi są drzewa dwumianowe rzędów k-1, k-2, . . . , 1, 0 (dokładnie w tej kolejności).
Drzewa dwumianowe �Drzewo dwumianowe rzędu k ma 2 k węzłów i wysokość k. �Drzewo dwumianowe rzędu k może być łatwo zbudowane z dwóch drzew rzędu k-1, przez dołączenie jednego z nich jako najbardziej lewego syna do korzenia drugiego. �Nazwa drzewa dwumianowego pochodzi od zależności (kd), która mówi o liczbie węzłów na poziomie d drzewa dwumianowego rzędu k.
Kopiec dwumianowy � Kopiec dwumianowy implementuje się jako zbiór drzew dwumianowych zachowujących porządek kopcowy: ◦ każde drzewo dwumianowe zachowuje własność kopca: wartość węzła jest większa lub równa niż wartość jego rodzica (w korzeniu znajduje się wartość minimalna); ◦ w kopcu może znajdować się co najwyżej jedno drzewo z każdego rzędu. � Pierwsza własność gwarantuje, że każdy korzeń drzewa dwumianowego zawiera najmniejszą wartość w drzewie, co stosuje się do całego kopca. � Dzięki drugiej własności wiemy, że kopiec dwumianowy zawierający n elementów składa się z co najwyżej log n + 1 drzew dwumianowych. Istotnie, liczba i rzędy tych drzew są jednoznacznie wyznaczone przez liczbę elementów w kopcu: każde drzewo odpowiada jedynce w reprezentacji dwójkowej liczby n.
Kopiec dwumianowy �W kopcu dwumianowym, operację min() (znalezienia najmniejszego elementu) wykonuje się poprzez sprawdzenie wszystkich korzeni drzew dwumianowych; wymaga to O(log n) czasu i O(1) pamięci.
Kopiec dwumianowy �Operację meld (przyłączenia kopca), wykonujemy w identyczny sposób jak dodawanie dwóch liczb binarnych – zaczynamy łączenie od najmniejszych drzew (najmniej znaczące bity w liczbie binarnej) i pamiętamy w razie potrzeby przeniesienie. �Połączenie dwóch drzew dwumianowych z porządkiem kopcowym odbywa się w taki sposób, że wybieramy drzewo w wartością mniejszą w korzeniu i przyłączamy do niego drugie drzewo. �Łączenie kopców dwumianowych wymaga O(log(n+m)) czasu i O(1) pamięci.
Kopiec dwumianowy �Operacja insert (wstawienie nowego elementu) sprowadza się do przyłączenie kopca jednoelementowego z zadaną wartością; wymaga to czasu O(log n). �W przypadku operacji deletemin (usunięcie najmniejszego elementu) najpierw wyszukujemy element minimalny, usuwamy korzeń znalezionego drzewa dwumianowego i dokonujemy operacji meld z kopcem dwumianowym, który powstał z potomków tego drzewa; wymaga to czasu O(log n).























