Zawartość ćwiczeń z ASD
Zaproponuj algorytm, który w czasie liniowym sortuje \( n \) liczb całkowitych z przedziału \( [0..n^3] \).
Zaproponuj algorytm, który w czasie liniowym sprawdzi, czy dane dwa \( n \)-wierzchołkowe drzewa są izomorficzne.
Wskazówka: udowodnij, że zadanie to można w czasie liniowym zredukować do zadania testowania izomorfizmu drzew ukorzenionych. Mając dwa drzewa z korzeniami przetwarzaj je poziomami, poczynając od poziomu na największej głębokości, sprawdzając, czy na każdym poziomie jest taka sama liczba drzew parami izomorficznych.
Udowodnij, że każdy algorytm wyznaczający minimum w zbiorze \( n \)-elementowym za pomocą porównań, wymaga wykonania w pesymistycznym przypadku co najmniej \( n-1\) porównań.
Zaproponuj algorytm wyznaczania elementu drugiego co do wielkości w zbiorze \( n \)-elementowym za pomocą co najwyżej \( n + \lceil \log n \rceil - 2 \) porównań. Uogólnij ten algorytm na algorytm znajdowania elementu \(k\)-tego co do wielkości za pomocą co najwyżej \(n- k + (k-1)\lceil \log (n-k+2) \rceil \) porównań.
Udowodnij, że każdy algorytm wyznaczający element drugi co do wielkości w zbiorze \( n \)-elementowym za pomocą porównań, wymaga wykonania w pesymistycznym przypadku co najmniej \( n + \lceil \log n \rceil - 2 \) porównań.
Wskazówka: pokaż, że każdy algorytm wyznaczający element drugi co do wielkości w pesymistycznym przypadku porównuje element minimalny z innymi co najmniej \( \lceil \log n \rceil \) razy.
Dokonaj analizy oczekiwanej złożoności obliczeniowej algorytmu selekcji Hoare'a w modelu losowej permutacji.
Poniżej zamieszczono zadania z pierwszych klasówek z ASD w latach 2007-2009. Niektóre zadania zawierają treści jeszcze nie poruszane na wykładzie i ćwiczeniach, ale dla pełności obrazu zawarto te klasówki w całości.
Dana jest tablica \(n \times n\), \(n > 1\), w której w każde pole wpisano liczbę całkowitą. Chcemy przejść z dolnego lewego rogu (z \( (1,1) \) ) do górnego prawego rogu (do \( (n,n) \) ) i wrócić, idąc w drodze z \( (1,1) \) zawsze w prawo lub w górę, a z powrotem - w lewo lub w dół. Z danego pola można przejść tylko na pola sąsiednie (współrzędne różnią się o 1 na dokładnie jednej pozycji). Żadne pole nie może się pojawić na całej trasie (czyli tam i z powrotem) więcej niż raz, poza polem (1,1), które pojawia się na początku i na końcu trasy. Zaprojektuj algorytm znajdowania najtańszej trasy, czyli takiej, na której suma wartości pól jest najmniejsza.
Niech \( n \) będzie liczbą całkowitą większą od 1, a \( X = \{ (x,y): x = 0, 1, \ldots, n-1, y = 0, 1, 2, 3, 4 \}\) zbiorem punktów na płaszczyźnie. Danych jest \( n \) różnych prostych, z których każda przechodzi przez dwa różne punkty ze zbioru \( X \), różniące się zawsze pierwszą współrzędną. Każda prosta zadana jest przez parę punktów \( [(x1,y1),(x2,y2)] \), \( x1 < x2 \). Zaprojektuj algorytm, który w czasie liniowym posortuje wszystkie proste niemalejąco względem ich kątów nachylenia do osi OX.
Podaj permutację liczb \( 1, 2, \ldots, 7 \), dla której algorytm HeapSort wykona największą liczbę porównań. Uwaga: należy wziąć pod uwagę obie fazy algorytmu – budowę kopca i właściwe sortowanie; poprawianie kopca odbywa się zawsze przez przesiewanie stosownego elementu w dół kopca; sortujemy rosnąco.
Uwaga: uzasadnij poprawność swoich rozwiązań oraz przeprowadź analizę złożoności obliczeniowej zaproponowanych algorytmów; każde zadanie rozwiązujemy na oddzielnej kartce.
Oto możliwa implementacji procedury Partition w algorytmie QuickSort:
procedure Partition(le,pr: integer); { 1<=le<=pr<=n; a[pr+1] >= max(a[le..pr]) } var i, j, s, v: integer; begin if le < pr then begin s := (le + pr) div 2; v := a[s]; a[s] := a[le]; i := le; j := pr+1; repeat repeat i := i+1 until a[i] >= v; repeat j := j -1 until a[j] <= v; if i < j then a[i] :=: a[j]; {zamiana wartości zmiennych} until j <= i; a[le] := a[j]; a[j] := v end end;
a) Podaj zawartość tablicy \( a[1..n] \) dla \( n = 7 \) - permutację liczb \(1, 2, \ldots, n \), dla której algorytm QuickSort z powyższą procedurą Partition wykona:
b) Podaj zawartość tablicy \( a[1..n] \) dla \( n = 15 \) - permutację liczb \( 1, 2, \ldots, n \), dla której algorytm HeapSort buduje kopiec (pierwsza faza algorytmu) za pomocą
Odpowiedzi uzasadnij.
Uwaga: porównania dotyczą elementów tablicy \( a\), interesuje nas sortowanie w porządku niemalejącym.
Zaproponuj wzbogacenie kopca zupełnego w taki sposób, żeby efektywnie w czasie zamortyzowanym wykonywane były operacje: Min, DeleteMin, Insert, CountMin. Ostatnia operacja polega na podaniu aktualnej liczby elementów w kopcu o wartości równej Min. Przeprowadź analizę kosztu (zamortyzowanego) wykonania poszczególnych operacji.
Zaproponuj sposób wykonania operacji Inc(L) dla licznika Fibonacciego L w taki sposób, żeby po wykonaniu \(n\)-tej operacji wartość liczby \( n \) zapisanej w liczniku wynosiła \( L[0]*F_0 + L[1]*F_1 + L[2]*F_2 + \ldots \), gdzie \( L[i] \) to wartość \(i\)-tego bitu w liczniku, \( F_i \), to \( i \)-ta liczba Fibonacciego (\( F_0 = 0, F_1 = 1 \)). Operacje elementarne w rozwiązaniu, to zmiana wartości jednego bitu i odczytanie wartości jednego bitu. Zaproponuj rozwiązanie z jak najmniejszym kosztem zamortyzowanym. Dokonaj analizy kosztu zamortyzowanego metodą funkcji potencjału. Możesz założyć, że licznik jest początkowo wyzerowany.
Opracuj strukturę danych, która pozwala wykonywać następujące operacje:
W Twoim rozwiązaniu operacje Insert i ExtractMin powinny by wykonywane w czasie \( O(\log n + k)\).
Udowodnij, że jeśli algorytm sortujący tablicę \( A[1..n] \) porównuje i zamienia wyłącznie elementy odległe co najwyżej o 2007 (tzn. jeśli porównuje \( A[i] \) z \( A[j] \), to \(|i-j| \le 2007\)), to jego pesymistyczny czas działania jest co najmniej kwadratowy.
Zaproponuj implementację następujących operacji na tablicy liczb naturalnych \(T[0..n+1]\), początkowo wypełnionej zerami, w taki sposób, żeby ich koszt zamortyzowany był stały.
Udowodnij, że dla każdego naturalnego \( n \) istnieje drzewo czerwono-czarne o co najmniej \( n \) wierzchołkach, które nie jest AVL-drzewem.
\(d\)-kopcem typu MIN, \( d \ge 2\), nazywamy zupełne drzewo \(d\)-arne z kluczami rozmieszczonymi w porządku kopcowym typu MIN.
Drzewem lewicowym nazywamy drzewo binarne, w którym dla każdego wierzchołka długość skrajnie prawej ścieżki w jego lewym poddrzewie jest nie mniejsza od długości skrajnie prawej ścieżki w jego prawym poddrzewie. Kopcem lewicowym typu MIN nazywamy drzewo lewicowe z kluczami rozmieszczonym w porządku kopcowym typu MIN.
Zaproponuj efektywne implementacje operacji kolejki priorytetowej (Build, Min, DeleteMin, DecraseKey) z użyciem kopców lewicowych.
Dana jest rodzina \(n\) niepustych podzbiorów zbioru \( \{1, 2, \ldots, n \}\), z których każdy to całkowitoliczbowy przedział postaci \([i,j]\), \(i \le j\). Zaprojektuj efektywny algorytm sprawdzania, czy zadana rodzina posiada system różnych reprezentantów, a jeśli tak, to podaje jeden z nich.
W algorytmie Dijkstry ciąg wartości otrzymywanych w wyniku wywołań funkcji Min jest niemalejący. Wykorzystaj ten fakt i zaproponuj implementację implementację algorytmu Dijkstry w czasie \( O(m + kn) \), gdy wagi krawędzi są liczbami całkowitym z przedziału \( [1..k ] \).
Dane jest drzewo z wyróżnionym wierzchołkiem \(s \) - źródłem rozgłaszania.
Opracuj sposób implementacji kolejki typu FIFO z pomocą dwóch stosów w taki sposób, żeby zamortyzowany koszt operacji stosowych był stały. Dokonaj analizy kosztu metodami księgowania i funkcji potencjału.
Reprezentujemy stos w dynamicznej tablicy \(S[1..n]\), gdzie \(n = 2^m\), dla pewnego całkowitego \(m \ge 0 \). Poszczególne operacje implementujemy następująco:
1 Ini:: //wykonywana tylko raz 2 begin 3 New(S[1..1]); 4 n := 1; k := 0; 5 end;
1 Push(e):: 2 begin 3 if k = n then 4 begin 5 New(T[1..2*n]); 6 for i := 1 to n do T[i] := S[i]; 7 dispose(S); 8 n := 2*n; 9 S := T 10 end; 10 k := k + 1; 11 S[k] := e 12 end;
1 Pop:: 2 begin 3 if k > 0 then 4 begin 5 e := T[k]; 6 k := k - 1 7 end; 8 if (n > 2) and (k = n/4) then 10 begin 11 New(T[1..n/2]); 12 for i := 1 to k do T[i] := S[i]; 13 dispose(S); 14 S := T; 15 n := n/2 15 end; 10 return e 11 end;
Załóżmy, że operacje alokacji i zwalniania pamięci wykonują się w czasie stałym. Zanalizuj koszt zamortyzowany operacji Push i Pop metodami księgowania i funkcji potencjału.
Dany jest spójny graf nieskierowany \(G=(V,E)\) z dodatnimi, całkowitoliczbowymi wagami na krawędziach oraz wyróżnione wierzchołki \( s \) i \( t \). Zaprojektuj algorytm, który spośród najlżejszych ścieżek pomiędzy \( s \) i \( t \) wyznaczy jedną, z najmniejszą liczbą krawędzi.
Dokonaj analizy kosztu pesymistycznego operacji DeleteMin, Insert oraz DecreaseKey na kopcach Fibonacciego.
Dokonaj analizy kosztu zamortyzowanego poszczególnych operacji w przypadku, gdy operacja DecreaseKey polega tylko na odcięciu węzła ze zmniejszanym kluczem od jego ojca i umieszczeniu go wraz ze swoim poddrzewem na liście korzeni.
Danych jest \(n\) par liczb całkowitych, które się parami różnią na każdej pozycji. Pierwsze elementy para to klucze, drugie to priorytety.
.
Zaproponuj wzbogacenie drzewa wyszukiwań binarnych o możliwość wyszukiwania \(k\)-tek klucza co do wielkości w drzewie. Czy wzbogacenie można przeprowadzić na AVL-drzewach.
Wykaż, że jeżeli budujemy drzewo typu "splay" poprzez wstawienie \(n\) kluczy do początkowo pustego drzewa, to możemy otrzymać drzewo binarne o wysokości liniowej.
Poniżej zamieszczono zadania z klasówek nr 2 z lat poprzednich.
a) [6 punktów] Drzewo AVL nazywamy wysmukłym jeśli zawiera minimalną liczbę wierzchołków wśród drzew AVL o wysokościach równych wysokości tego drzewa. Udowodnij, że wierzchołki każdego wysmukłego AVL-drzewa można pokolorować w taki sposób, żeby otrzymać drzewo czerwono-czarne.
b) [2 punkty] Podaj ciąg różnych liczb całkowitych, które po wstawieniu do początkowo pustego drzewa dadzą wysmukłe AVL-drzewo o wysokości 4. Kolejność liczb powinna być taka, żeby podczas całego procesu wstawiania wystąpiła dokładnie jedna pojedyncza rotacja.
c) [2 punkty] Podaj przykład 9-wierzchołkowego drzewa czerwono-czarnego, które nie jest AVL-drzewem.
Niech \( G \) będzie grafem dwuspójnym wierzchołkowo o co najmniej 3 wierzchołkach i niech \( T \) będzie DFS-drzewem rozpinającym grafu \( G \). Przez \(H(G,T)\) oznaczamy graf zorientowany otrzymany z \( G\) przez zorientowanie wszystkich krawędzi drzewowych w kierunku od ojca do syna, a krawędzi niedrzewowych w kierunku od potomka do przodka. Dla każdego wierzchołka \( v \) w grafie \( G \) definiujemy liczbę powrotną \( p(v) \) jako minimalną liczbę krawędzi niedrzewowych na ścieżce zorientowanej w \( H(G,T) \), prowadzącej z \( v \) do korzenia drzewa \( T \). Załóżmy, że graf G jest reprezentowany przez listy sąsiedztwa. Zaproponuj algorytm, który w czasie liniowym wyznacza pewne DFS-drzewo \( T \) w grafie \( G \) i oblicza dla każdego wierzchołka jego liczbę powrotną w \( H(G,T) \).
Uwaga: uzasadnij poprawność swoich rozwiązań oraz przeprowadź analizę złożoności obliczeniowej zaproponowanych algorytmów; każde zadanie rozwiązujemy na oddzielnej kartce.
Zaprojektuj (efektywny) algorytm, który sprawdza, czy w danym grafie (zadanym przez listy sąsiedztwa) istnieje cykl bez powtarzających się krawędzi o długości parzystej.
Dany jest skierowany multigraf \( G=(V,E) \), reprezentowany przez listy sąsiedztwa. \( G \) jest regularny, co oznacza, że dla dowolnych dwóch wierzchołków \( u, v \), ich stopnie wejściowe oraz ich stopnie wyjściowe są takie same i parzyste.
Opisz algorytm, który podzieli zbiór krawędzi grafu \( G \) na 2 rozłączne zbiory \( E_1 \) i \( E_2 \) w taki sposób, że multigrafy \( G_1=(V, E_1) \) i \( G_2=(V, E_2) \) są regularne, oraz dla dowolnych wierzchołków \( u, v \), liczby krawędzi od \( u \) do \( v \) w \( G_1 \) i \( G_2 \) różnią się co najwyżej o 1.
Uwaga: na liście krawędzi wychodzących z wierzchołka \( u \), każda multikrawędź \( u \rightarrow v \) jest reprezentowana przez swój koniec \( v \) i liczbę określającą jej krotność. Tak więc rozmiar danych wynosi co najwyżej \( O(n^2) \).
Niech \( W \) będzie \( n \)-kątem wypukłym, którego wierzchołki ponumerowano kolejno \( 1, 2, \ldots, n \), zgodnie z ruchem wskazówek zegara. Zaproponuj strukturę danych, która umożliwi (wydajne) wykonywanie następujących operacji:
\( Przekątna(u,v) \):: sprawdza, czy w wielokącie \( W \) jest przekątna łącząca wierzchołki \(u, v\).
\( DodajPrzekątną(u,v)\):: umieść w wielokącie \( W \) przekątną łączącą wierzchołki \( u, v \), jeśli tylko takiej przekątnej nie ma w wielokącie i nowa przekątna nie przetnie się z żadną inną przekątną we wnętrzu wielokąta.
\( UsuńPrzekątną(u,v)\):: usuń przekątną łączącą \(u\) z \(v\), jeśli tylko taka przekątna jest w wielokącie.
Początkowo w wielokącie nie umieszczono żadnej przekątnej.
Uwaga: uzasadnij poprawność swoich rozwiązań oraz przeprowadź analizę złożoności obliczeniowej zaproponowanych algorytmów; każde zadanie rozwiązujemy na oddzielnej kartce.
Poniżej zadania z egzaminów teoretycznych z lat ubiegłych. Archiwum zadań z egzaminów praktycznych można za to znaleźć pod adresem: http://smurf.mimuw.edu.pl/drupal6/node/766
Dane jest \( n \)-węzłowe drzewo binarne z \( n \) różnymi kluczami w węzłach. Rozważamy następujący algorytm rekurencyjny przywracania porządku kopcowego w takim drzewie:
Jeśli drzewo składa się z co najwyżej jednego węzła, to porządek kopcowy jest przywrócony.
W przeciwnym przypadku znajdujemy w drzewie węzeł z najmniejszym kluczem i zamieniamy klucze z korzenia i ze znalezionego węzła, a następnie rekurencyjnie przywracamy porządek kopcowy w lewym i prawym poddrzewie korzenia.
a) [4 punkty] Przyjmijmy, że węzeł z najmniejszym kluczem znajdujemy jedną z metod obchodzenia drzewa (preorder, inorder lub postorder).
a1) [1 punkt] Jaka jest (asymptotycznie) pesymistyczna złożoność przywracania porządku kopcowego wyrażona jako funkcja \(n\)?
a2) [3 punkty] Jaka jest złożoność powyższego algorytmu dla drzewa o wysokości \(\lfloor \log n \rfloor\)?
b) [8 punktów] Zaproponuj strukturę danych, które umożliwi efektywne wykonywanie następujących operacji na \( n \)-węzłowym drzewie binarnym:
\(Min(v)\):: znajdź w poddrzewie o korzeniu \(v\) węzeł z najmniejszym kluczem;
\(Exch(u,v)\):: zamień klucze z węzłów \(u i v\).
Zaprojektuj strukturę danych umożliwiającą wykonywanie następujących operacji na \( n \)-wierzchołkowym lesie \( G \) (grafie nieskierowanym bez cykli):
\(Init(G)\):: utwórz strukturę danych dla grafu \(G\) zadanego przez listy sąsiedztwa (możesz przyjąć \(V(G) = \{1,2, …, n\}\);
\(Remove(u,v)\):: usuń krawędź \(u--v\) z grafu \(G\);
\(Path(u,v)\):: sprawdź, czy w grafie \( G \) istnieje ścieżka pomiędzy wierzchołkami \( u \) i \( v \).
a) [10 punktów] Zaprojektuj takie rozwiązanie, w którym całkowity koszt wykonania operacji \( Init \) i \( k \) operacji \(Remove/Path\) nie przekracza \(O(k + nlog n)\).
b) [8 punktów] Załóżmy, że cały ciąg \( k \) operacji jest dany „off line” (tzn. jest znany w całości z góry), a celem jest obliczenie wyników wszystkich operacji \( Path \). Zaproponuj algorytm, który zrobi to w czasie \(o(nlog n)\), gdy \(k = O(n)\).
Zaproponuj efektywną strukturę danych, z pomocą której można wykonywać następujące operacje na dynamicznym zbiorze \( S \) odcinków domkniętych na prostej rzeczywistej:
\(Init(S)\):: utwórz pusty zbiór odcinków (wykonywana tylko raz na początku algorytmu);
\(Add(S,I)\):: dodaj do zbioru \(S\) nowy odcinek \(I\), ale tylko wtedy, gdy dla każdego odcinka \(J\) z \(S\), odcinek \(I\) ma puste przecięcie z \( J \) lub \( I \) jest zawarty w \( J \) lub \( J \) jest zawarty w \( I \);
\( Delete(S,I) \):: usuń \( I \) z \( S \);
\( Incl(S,I)\):: podaj ile odcinków z \(S\) jest zawartych w odcinku \(I\).
Uwaga: uzasadnij poprawność swoich rozwiązań oraz przeprowadź analizę złożoności obliczeniowej zaproponowanych algorytmów; każde zadanie rozwiązujemy na oddzielnej kartce.