Algorytmy funkcjonalne i struktury danych Lista zadań nr 4 5 listopada 2009 Zadanie 1. Zaprogramuj strukturę Deque o sygnaturze signature DEQUE = sig type a Queue val empty : a Queue val isempty : a Queue -> bool val cons : a * a Queue -> a Queue val head : a Queue -> a val tail : a Queue -> a Queue val snoc : a Queue * a -> a Queue val daeh : a Queue -> a val liat : a Queue -> a Queue end Wybierz podobną reprezentację, co w przypadku zwykłych kolejek. Użyj (a) metody bankierów i (b) metody potencjałowej do wykazania, że zamortyzowany koszt wszystkich operacji wynosi O(1). Zadanie 2. Korzystając z (a) metody bankierów i (b) metody potencjałowej pokaż, że zamortyzowany koszt wykonania na kopcu dwumianowym operacji insert wynosi O(1), zaś merge i deletemin O(log n). Zadanie 3. Zaimplementuj bigger i smaller rozdzielające drzewo samoorganizujące się na części. (Zauważ, że smaller powinna wstawiać do drzewa nie tylko elementy mniejsze, lecz także równe podanemu. Powinna to robić bez osobnego sprawdzania równości.) Porównaj tę implementację z funkcją partition. Dla jakich drzew obie wersje będą dawać różne wyniki? Zadanie 4. Dla dowolnego drzewa samoorganizującego się (splay tree) zachodzi Φ(t) = O(n log n). Znajdź dolne oszacowanie potencjału dla tych drzew. Zadanie 5. Dokończ dowód twierdzenia (5.2) mówiącego, że A(t) 1 + 2φ(t), tj. rozważ wariant zig-zag kroku indukcyjnego. Z twierdzenia tego wyprowadź wniosek, że zamortyzowany koszt operacji insert dla drzew samoorganizujących się jest logarytmiczny. Zadanie 6. Udowodnij, że zamortyzowany koszt operacji deletemin dla drzew samoorganizujących się wynosi O(log n). Jak zapewnić niski koszt operacji findmin? 1
Zadanie 7. Napisz algorytm treesort wykorzystujący drzewa samoorganizujące się. Wykaż, że w przypadku, gdy dane wejściowe są prawie posortowane (np. posortowane malejąco, rosnąco itp), to algorytm sortujący wykonuje jedynie O(n) kroków (kopce lewicowe również mają tę własność, jednak jedynie w odniesieniu do danych posortowanych malejąco). Zadanie 8. Drzewa o zmiennej liczbie potomków można zastąpić drzewami binarnymi zgodnie ze schematem przedstawionym na rysunku: x x1 x x 1 x n x n Napisz funkcję tobinary, która przekształca kopiec parujący w drzewo binarne tego typu. Zauważ, że takie przekształcenie tworzy drzewo binarne częściowo uporządkowane: etykieta żadnego wierzchołka jest niewiększa niż każda etykieta wierzchołka jego lewego potomka. Napisz implementację kopców parujących używającą tej reprezentacji. Wykonaj analizę kosztu zamortyzowanego tej nowej implementacji i pokaż, że operacje merge oraz deletemin działają w zamortyzowanym czasie O(log n). Zadanie 9 (5.9, wersja 1). Podaj przykłady takich kopców dwumianowych, drzew samoorganizujących się oraz kopców parujących, dla których wykonanie operacji uwzględnionych w ich schematach amortyzacji wymaga czasu pesymistycznego (chodzi o operacje wykonywane zgodnie ze schematem gorliwej amortyzacji, tj. bez wykorzystywania trwałości struktur). Zadanie 10 (5.9, wersja 2). Podaj przykłady takich ciągów operacji uwzględnionych w schemacie gorliwej amortyzacji kopców dwumianowych, drzew samoorganizujących się oraz kopców parujących, które poprzez wykorzystanie trwałości struktur danych osiągają czas działania znacznie przekraczający granice zamortyzowane. Innymi słowy pokaż, że gorliwy schemat amortyzacji jest słuszny tylko w przypadku ulotnego wykorzystywania tych struktur. Zadanie 11 (6.1). Narysuj ślad wykonania następującego ciągu operacji: val a = snoc (empty, 0) val b = snoc (a, 1) val c = tail b val d = snoc (b, 2) val e = c ++ d val f = tail c val g = snoc (d, 3) Podaj dla każdego wierzchołka liczbę jego logicznych przyszłości. Zadanie 12 (6.2). Rozważmy implementację leniwych kolejek wykorzystującą strumienie. Przyjmijmy, że niezmiennikiem kolejki jest 2 f = r. Napisz implementację takich kolejek. Udowodnij metodą bankierów, że wszystkie operacje na tych kolejkach nadal maja zamortyzowany koszt stały przy korzystaniu z nich w sposób trwały. Która implementacja oryginalna, czy nowa jest bardziej efektywna w praktyce? Zadanie 13 (6.3). Udowodnij, że operacje findmin, deletemin i merge na leniwych kopcach dwumianowych (algorytm z rysunku 6.2) działają w zamortyzowanym czasie O(log n). 2
Zadanie 14 (6.4). Udowodnij, że jeżeli usuniemy słowo kluczowe lazy z implementacji funkcji merge i deletemin z poprzedniego zadania, to operacje te nadal będą działać w zamortyzowanym czasie O(log n). Zadanie 15 (6.5). Niedobrą konsekwencją odroczenia listy drzew w leniwej implementacji kopców dwumianowych jest spowolnienie funkcji isempty zamiast pesymistycznego czasu O(1) otrzymujemy zamortyzowany czas O(log n). Przywróć stały pesymistyczny czas działania tej operacji modyfikując implementację kopców dwumianowych w taki sposób, by rozmiar kopca był przetwarzany jawnie. Zamiast modyfikować istniejącą implementację, napisz funktor SizedHeap, podobny do ExplicitMin, który przekształca dowolną implementację kopców w implementację kopców, która jawnie przetwarza ich rozmiar. Zadanie 16 (6.6). Pokaż, że każda z poniższych optymalizacji leniwych monolitycznych kolejek w rzeczywistości psuje stałe zamortyzowane ograniczenie czasowe operacji wykonywanych na tych kolejkach. Przykłady te są ilustracją typowych błędów popełnianych podczas projektowania trwałych struktur zamortyzowanych. a) Zauważ, że check wymusza f podczas rotacji i wstawia wynik do w. Czy nie było by bardziej leniwie, a zatem lepiej, nigdy nie wymuszać f zanim w się nie opróżni? b) Zauważ, że podczas obliczania funkcji tail zastępujemy f przez $tl(force f). Tworzenie i wymuszanie odroczeń powoduje nietrywialny narzut czasowy, który, mimo iż stały, może znacząco zwiększać czas obliczeń. Czy nie było by bardziej leniwie, a zatem lepiej, nie zmieniać f, lecz tylko zmniejszać lenf, aby zaznaczyć, że element został usunięty? Zadanie 17 (6.7). Zmień reprezentację wewnętrzną kolekcji sortowalnej z odroczonej listy list na listę strumieni. (a) Zamortyzuj tę implementację przy pomocy metody bankiera. (b) Zaprogramuj funkcję wybierającą k najmniejszych elementów z kolekcji. Udowodnij, że działa ona w zamortyzowanym czasie O(k log n). Zadanie 18 (7.1). Udowodnij, że zastąpienie operacji l ++ reverse r przez rotate(f, r, $Nil) w implementacji kolejek bankiera zmniejsza pesymistyczny czas działania operacji snoc, head i tail z O(n) do O(log n). Zadanie 19 (7.2). Zaprogramuj funkcję obliczającą długość kolejki real-time na podstawie s i r. O ile szybciej będzie działać ta funkcja w porównaniu z implementacją wyznaczającą długości list (strumieni) f i r? Zadanie 20 (7.3). Pokaż, że jeżeli z definicji operacji insert w kopcu dwumianowym realtime usuniemy słowo kluczowe lazy, to pesymistyczny czas dziania wszystkich operacji się nie pogorszy. Zadanie 21 (7.4). Napisz efektywną, wyspecjalizowaną wersję funkcji mrg o nazwie mrgwith- List którą można wykorzystać bezpośrednio w implementacji operacji deletemin w kopcach dwumianowych bez konieczności wykonywania operacji map i listtostream (unikając dwukrotnego kopiowania listy). Zadanie 22 (8.1). Rozszerz implementację drzew czerwono-czarnych o operację delete implementującą batched rebuilding. Do konstruktora T dodaj pole boolowskie przechowujące informację o tym, czy dany wierzchołek jest jeszcze żywy. Przechowuj gdzieś (w korzeniu drzewa?) oszacowanie liczby żywych i martwych wierzchołków. 3
Zadanie 23 (8.2). Udowodnij, że dwukrotne wywołanie funkcji exec na początku rotacji i pojedyncze podczas każdej operacji wstawiania i usuwania elementów z kolejki Hooda- Melville a wytarcza, by zakończyć rotację na czas. Zadanie 24 (8.3). Zastąp pola lenf i lenr w kolejce Hooda-Melville a pojedynczym polem diff przechowującym różnicę długości f i r. Zawartość tego pola może być nieprawidłowa podczas przebudowy, lecz musi być poprawna w chwili, w której przebudowa się kończy. Zadanie 25 (8.4). Trudno wprost dołożyć operację cons do kolejek Hooda-Melville a, gdyż musielibyśmy wstawiać element do stanu odwracania. Możemy jednak zaprogramować funktor, który doda operację cons do dowolnej implementacji Q kolejek wystarczy połączyć tę kolejkę ze stosem przechowującym elementy wstawione przez cons: type a Queue = a list * a Q.Queue Zaprogramuj ten funktor. Zadanie 26 (8.5). Opisz implementację leniwych kolejek dwustronnych i ich amortyzację metodą bankiera a następnie udowodnij twierdzenie: cons i tail (i symetrycznie snoc i init) zachowują niezmienniki debetowe na obu strumieniach (przednim i tylnym) spłacając co najwyżej po 1 i c+1 debetów na strumień, gdzie c jest stałą z niezmiennika kolejki ( f c r +1 i r c f + 1). Zadanie 27 (8.6). Jak zmiana c wpływa na efektywność kolejek z poprzedniego zadania? Wskaż ciąg operacji, który zostanie wykonany znacząco szybciej dla c = 2 niż dla c = 4. Wskaż następnie ciąg operacji, które zostaną znacząco szybciej wykonane dla c = 4 niż dla c = 2. Zadanie 28 (8.6). Zdeamortyzuj kolejki dwustronne z poprzednich zadań wprowadzając harmonogram wymuszeń obu strumieni. Udowodnij, że uda się zdążyć na czas wymuszając po jednym odroczeniu w każdym ze strumieni przy każdej operacji. Zadanie 29 (9.1). Zaprogramuj funkcję drop : int * a RList -> a RList usuwającą k pierwszych elementów listy o dostępie swobodnym. Funkcja powinna działać w czasie O(log n), gdzie n jest rozwmiarem listy. Zadanie 30 (9.2). Zaprogramuj funkcję create : int * a -> a RList tworzącą listę o bezpośrednim dostępie zawierającą n kopii podanego elementu. Funkcja powinna działać w czasie O(log n). Zadanie 31 (9.3). Zaprogramuj listę o dostępie swobodnym wykorzystując reprezentację rzadką: datatype a Tree = Leaf of a Node of int * a Tree * a Tree type a RList = a Tree list Zadanie 32 (9.3). Niech 4
datatype Digit = One Two type Nat = Digit list Zaprogramuj funkcje inc, dec i add dla liczb w tej reprezentacji (b i = 2 i ). Zadanie 33 (9.4 9.6). Zaprogramuj listy o dostępie swobodnym bazujące na powyższej reprezentacji kładąc datatype a Digit = One of a Tree Two of a Tree * a Tree Pokaż, że lookup i update działają w tej reprezentacji w czasie O(i), gdzie i jest indeksem przetwarzanego elementu. Zadanie 34 (9.7). Drzewa czerwono-czarne również można rozważać jako reprezentacje numeryczne. Odkryj ich analogię do pewnego systemu liczenia. Znajdź podobieństwa i różnice między listami o dostępie swobodnym w reprezentacji z poprzedniego zadania i drzewami czerwono-czarnymi w których wstawianie jest ograniczone jedynie do skrajnie lewej pozycji. Skup się na operacjach cons i insert i na postaci niezmienników spełnionych przez struktury tworzone przez te operacje. 5