Zadanie A "przy tablicy"
Zadanie 25
struct ElementListy { ElementListy *nastepny, *poprzedni; int liczba; // "zawartość" elementu listy }; // struktura reprezentujące element listy; // kazdy element listy zawiera "liczbę" struct Lista { ElementListy *glowa, *ogon; }; // struktura reprezentujące listę void NowaLista(Lista &l); // tworzy pustą listę void DodajElement(Lista &l, int liczba, int gdzie); // gdzie = GŁOWA lub OGON // (!!!) void UsunElement(Lista &l, int gdzie); // usuwanie elementu z jednego z końców listy // (!!!) int ListaJestPusta(Lista &l); // zwraca 1 jeśli lista jest pusta, w przeciwnym wypadku zwraca 0 void PokazListe(Lista &l, int gdzie); // parametr "gdzie" decyduje od którego końca pokazujemy listę void UsunCalaListe(Lista &l);
To drzewo binarne jest "prawie" pełne - ostatni poziom drzewa nie musi być pełny.
Tablica A powinna posiadać atrybuty:
int Parent(int i) { /* należy je samodzielnie zaprogramować ! */ } int Left(int i) { /* ... */ } int Right(int i) { /* ... */ }
Własność kopca:
dla każdego wierzchołka
o indeksie "i", który nie jest korzeniem:
|
A[i] = 16, 14, 10, 8, 7, 9, 3, 2, 4, 1 i 1 2 3 4 5 6 7 8 9 10odpowiada drzewo:
Poniżej opisuję pewne procedury wykonujące operacje na kopcu ...
Uwaga: Mówiąc że "i jest kopcem" rozumiem, że drzewo binarne którego korzeniem jest "i-ty element tablicy A" jest kopcem (czyli posiada własność kopca).
Procedura Heapify(A,i)
Opis procedury: Oznaczmy przez "l" i "r" lewego i prawego syna "i". Procedura znajduje największą z liczb A[i], A[l], A[r]. Jeśli jest to A[l] wtedy przestawia się elementy i-ty i l-ty, przez co psujemy kopiec "l", dlatego konieczne jest rekurencyjne wywołanie procedury dla "l". W przypadku gdy największy jest A[r] postępujemy podobnie. Oczywiście jeśli największe jest A[i] wtedy nic nie trzeba robić.
Czas działnia tej procedury jest równy wysokości drzewa, czyli O(log n), gdzie n=heapSize[A].
Opis procedury: Procedura ta przegląda wierzchołki drzewa od dolnej warstwy (przegląda tablicę od końca) i uruchamia Heapify(...) dla każdego elementu. Gwarantuje to że jeśli uruchamiamy Heapify(A,i) to synowie "i" już są kopcami !. Zauważmy, że liście drzewa są kopcami z natury rzeczy, dlatego procedura BuildHeap() rozpoczyna działanie od wierzchołka który nie jest liściem. Biorąc pod uwagę że mamy tu "prawie" pełne drzewo binarne można łatwo wyznaczyć indeks pierwszego (od końca) wierzchołka, który nie jest liściem.
"Na oko" czas działnia tej procedury wynosi O(n log n), jednak dokładniejsza
analiza biorąca pod uwagę że poddrzewa mają coraz mniejszą wysokość pokazuje
że czas ten wynosi O(n).
Czas działania procedury HeapSort to O(n log n)
struct Heap { int length; // długość tablicy int heapSize; int *A; // indeksujemy od 1 !!! };Należy zaimplementować następujące procedury:
void Heapify(Heap& H, int i); int BuildHeap(Heap& H); void HeapSort(Heap& H);Sprawdź HeapSort na małym ciągu liczb.
Procedura Insert(S,x)
struct Elem { int key; // klucz elementu int value; // wartość elementu }; struct HeapPri { int length; int heapSize; Elem *A; // indeksujemy od 1 !!! };Należy zaimplementować następujące procedury:
int HeapPri_Maximum(HeapPri& H);Implementacja oczywista (zwraca "value" elementu z maksymalnym "key")
void HeapPri_ExtractMax(HeapPri& H);Wskazówka: do A[1] przepisz ostatni element kopca; potem dokonaj odpowiednich uaktualnień.
void HeapPri_Insert(HeapPri& H, int key, int value);Wskazówka: dodajemy nowy liść "x" do drzewa (na koniec); oczywiście ojciec "x"-a może nie spełniać własności kopca. Istnieje ścieżka od "x"-a do korzenia drzewa. Przesuwamy "x" po tej ścieżce, w kierunku korzenia, aż nie znajdziemy dla niego właściwego miejsca (przesuwanie polega na "zamianie miejscami" z ojcem). Zauważmy, że mimo przesuwania "x"-a, drzewo o korzeniu w "x" jest zawsze kopcem - nie jest to takie całkiem oczywiste !.
Zadanie 28
void HeapPri_ChangeKey(HeapPri& H, int i, int key);Zmieniającą klucz elementu tablicy o indeksie "i" i zachowującą własność kopca.