Sprawdzian, zadanie 4c
Transkrypt
Sprawdzian, zadanie 4c
Sprawdzian, zadanie 4c Antoni Kościelski 1 Program W treści zadania opisany zosta program, który przedstawiam z niewielkimi zmianami. W szczególności, zostały określone wartości początkowe zmiennych n i m oraz do programu został wprowadzony licznik wykonań pętli t. W programie zostały też wyróżnione trzy miejsca. n ← N; m ← M; jeśli n < m: k ← n; k ← m; m ← k; jeśli m = 0: zwróć n jako wynik; p ← 1; t ← 0; /*(1)*/ dopóki n mod 3 = 0 oraz m mod 3 = 0: t ← t+1; m ← m/3; n ← n/3; p ← 3·p; /*(2)*/ dopóki m 6= 0: t ← t+1; k ← n mod m; n ← m; m ← k; /*(3)*/ zwróć iloczyn p·n jako wynik. 2 Specyfikacja Dane: dodatnie liczby naturalne M i N . Wynik: największy wspólny dzielnik danych liczb M i N . 3 Poprawność specyfikacji Aby przekonać się o poprawności podanej specyfikacji, należy sprawdzić, że w podanym programie, w miejsca oznaczonych (1), (2) i (3) wartości zmiennych spełniają następującą zależność: N W D(M, N ) = p · N W D(m, n). w uzasadnieniu powyższego korzystamy ze znanych (?) własności największego wspólnego dzielnika wyrażonych w następującym lemaciku: 1 2 Lemat 3.1 Dla dodatnich liczb naturalnych a i b mamy N W D(3 · a, 3 · b) = 3 · N W D(a, b) oraz N W D(a, b) = N W D(a mod b, b). Niech teraz p, m i n oznaczają wartości zmiennych o tych nazwach po zakończoniu działania programu. Wtedy m = 0, gdyż druga pętla programu przestaje być wykonywana dopiero, gdy wartość m staje się równa 0, a także zachodzi przytoczona równość. Mamy więc N W D(M, N ) = p · N W D(m, n) = p · N W D(0, n) = p · n. Obliczana przez program wartość p · n jest więc największym wspólnym dzielnikiem danych liczb M i N . 4 4.1 Złożoność programu Oczekiwane rozwiązanie, 1 Każdorazowe wykonanie instrukcji z pierwszej pętli powoduje podzielenie wartości każdej ze zmiennych m i n przez trzy. Instrukcje z takiej pętli zostaną wykonane najwyżej log3 M (także log3 N ) razy. Dalej program realizuje algorytm Euklidesa z danymi o wartościach nie przekraczających danych początkowych. Z analizy tego algorytmu wynika, ze pętla jest w nim wykonywana najwyżej log2 M + log2 N razy. Stąd liczba wykonań obu pętli nie przekracza wartości log3 M + log2 M + log2 N . Taki program ma asympotyczną złożoność czasową rzędu O(log2 M + log2 N ) = O(log2 (M N )). 4.2 Oczekiwane rozwiązanie, 2 Podczas wykonania programu śledzimy, jak zmienia się wartość iloczynu zmiennych m i n. Podczas każdego wykonania pierwszej pętli wartość tego iloczynu jest zmniejszana dziewięciokrotnie, a podczas wykonia drugiej – na mocy lematu przytoczonego w treści zadania – przynajmniej dwukrotnie. Jeżeli przyjmiemy, że każdorazowe wykonanie którejkolwiek pętli z programu zmniejsza ten iloczyn dwukrotnie, to pogorszymy ocenę złożoności. Wtedy otrzymamy, że łączna liczba wykonań pętli z programu nie przekroczy log2 (M N ). Stąd wynika, że rozważany program po uruchomieniu z danymi M i N wykonuje najwyżej C · log2 (M N ) + C instrukcji (dla odpowiednio dobranej, niedużej stałej C). Taki program ma złożoność czasową O(log2 (M N )). 4.3 Dokładniejsze rozwiązanie Będziemy analizować wykonanie podanego programu po uruchomieniu z danymi M i N . Zdefiniujemy dwa ciągi liczb m0 , m1 , . . . , mT oraz n0 , n1 , . . . , nT . Liczba mi jest wartością zmiennej m ustaloną w momencie, gdy program „znajdował” się w jednym z wyróżnionych miejsc (1), (2) lub (3) i zmienna t miała wartośc i. Analogicznie, posługując się zmienną n definiujemy liczbę ni . Z definicji otrzymujemy od razu, że m0 = M oraz n0 = N . Warto zauważyć, że wartości mi i ni są poprawnie zdefiniowane (podczas przejścia programu od dowolnego 3 wyróżnionego miejsca do następnego wyróżnionego jest związane ze wzrostem wartości zmiennej t). Ponadto liczby mi są określone dla kolejnych wartości i (jeżeli m5 jest określone, to także jest określone m3 ). To samo dotyczy liczb ni . Jeżeli te ciągi są skończone i T jest numerem ostatnich zdefiniowanych wyrazów, to T jest równe łącznej liczbie wykonań obu pętli podczas działania programu i dla pewnej stałej c, złożoność obliczeń można oszacować z góry przez c · T + c. Zauważmy, że dla wszystkich dopuszczalnych tutaj wartości i mamy 2mi+1 ni+1 ¬ mi ni . Aby się o tym przekonać, przyjmijmy najpierw, że zmienna t przyjęła wartość i + 1 podczas wykonywania drugiej pętli programu. Wtedy mi+1 = ni mod mi , ni+1 = mi , ni > mi , a także 2mi+1 ¬ (ni mod mi ) + mi ¬ (ni mod mi ) + mi · (ni div mi ) = ni . Po pomnożeniu ostatniej nierówności stronami przez ni+1 = mi otrzymujemy 2mi+1 ni+1 = 2mi+1 mi ¬ mi ni . a po zlogarytmowaniu i wprowadzeniu symbolu ai = log2 (mi ni ) – ai+1 + 1 = log2 (mi+1 ni+1 ) + 1 = log2 (2mi+1 ni+1 ) ¬ log2 (mi ni ) = ai . Ta sama nierówność jest prawdziwa dla liczb określanych w pierwszej wykonywanej pętli. Tym razem jest to nierówność nienaturalna, słabsza od faktycznych zależności, ale prawdziwa. Zauważmy, że ai+1 + 1 = log2 (2mi+1 ni+1 ) < log2 (9mi+1 ni+1 ) = log2 (9 · mi ni · ) = log2 (mi ni ) = ai . 3 3 Zachodzi też następujący Lemat 4.1 Jeżeli a0 , a1 , . . . , at jest ciągiem nieujemnych liczb takich, że ai+1 + 1 ¬ ai dla wszystkich możliwych i, to t ¬ ba0 c . (ba0 c to część całkowita a0 ). Co więcej, każdy taki ciąg jest skończony. Dowód. Najpierw, przez indukcję pokazujemy, że ai+j + j ¬ ai dla wszystkich możliwych i oraz j. Dla i = 0 i j = t otrzymujemy t ¬ at + t ¬ a0 . 2 4 Jeżeli chcemy zastosować powyższy lemat do analizy podanego programu lub algorytmu Euklidesa, to mamy jeszcze mały kłopot polegający na tym, że liczba aT = log2 (mT nT ) nie jest określona (ponieważ mT = 0). Mamy jednak T − 1 ¬ ba0 c = blog2 (M N )c ¬ log2 (M N ). Stąd T ¬ log2 (M N ) + 1 i rozważany program ma złożoność rzędu O(log2 (M N )). Jeżeli wykazane oszacowanie ma być prawdziwe rzeczywiście dla wszystkich dodatnich liczb M i N , to nie da się go poprawić. Równość w podanym wzorze zachodzi dla M = N = 1, choć może to być jedyny taki przypadek. 4.4 Szacowanie złożoności za pomocą niezmienników Spróbujemy teraz znaleźć oszacowanie liczby wykonań pętli w podanym programie za pomocą niezmienników. Po pierwsze trzeba zauważyć, że liczba T1 wykonań pierwszej pętli w tym programie jest równa wartości zmiennej t po zakończeniu wykonywania tej pętli. Podobnie, końcowa wartość T zmiennej t jest łączną liczbą wykonań obu pętli programu. Będziemy więc szacować wskazane wartości zmiennej t. Niezmiennikiem pierwszej pętli programu jest stwierdzenie n · 3t = N (także stwierdzenie m · 3t = M ). Inaczej: ta równość jest prawdziwa zawsze, gdy program znajduje się w miejscach oznaczonych (1) i (2). Stąd otrzymujemy nierówność 3T1 ¬ n · 3T1 = N. Mamy więc także T1 ¬ min{log3 M, log3 N }. Przejdźmy więc do analizy drugiej pętli programu. Niezmiennikiem tej pętli, stale prawdziwym przed uruchomieniem pętli oraz w miejscu oznaczonym (3), a nawet we wszystkich miejscach oznaczonych (1), (2) i (3), jest na przykład stwierdzenie 2t (m · n + 1) ¬ M N + 1. Aby się o tym przekonać w przypadku drugiej pętli, zauważmy, że jeżeli w pewnym momencie prawdziwa jest powyższa nierówność, to 2(n mod m) < m + (n mod m) ¬ (n div m) · m + n mod m = n (od trzeciej instrukcji stale jest prawdziwa nierówność m ¬ n). Stąd 2(n mod m) · m < n · m, a także 2((n mod m) · m + 1) ¬ n · m + 1 5 i w końcu 2t+1 ((n mod m) · m + 1) ¬ 2t (m · n + 1) ¬ M N + 1. Podczas wykonywania instrukcji z drugiej pętli t0 = t+1 staje się nową wartością zmiennej t, m0 = n mod m – nową wartością zmiennej m, a n0 = m – nową wartością zmiennej n. Mamy więc 0 2t (m0 · n0 + 1) ¬ M N + 1. Wobec tego, po wykonaniu podstawień z pętli również spełniona jest nierówność 2t (m · n + 1) ¬ M N + 1. Teraz mamy do wyboru dwie możliwości. Moemy sprawdzić, że nasza nierówność jest zachowywana również podczas wykonywania pierwszej pętli i zachodzi na początku programu. Jeżeli się o tym przekonamy, to okaże się, że nierówność zachodzi też po wykonaniu drugiej pętli. Wtedy jednak m = 0. W ten sposób dostajemy znane już oszacowanie 2T ¬ M N + 1. Możemy też korzystać z dokonanej analizy pierwszej pętli. Po jej zakończeniu mamy m= N M oraz n = . 3T1 3T1 Jeżeli przyjmiemy, że T2 jest liczbą wykonań instrukcji z drugiej pętli, to otrzymamy 2T2 ¬ MN + 1. 9T1 Stąd możma wyprowadzić, że T = T1 + T2 ¬ 3T1 + T2 ¬ log2 (M N ) + 1. 4.5 Przykład Może warto przeanalizować jak rozważany algorytm zachowuje się dla kilku par, ile razy wykonuje drugą pętlę i jak dokładne oszacowanie znaleźliśmy. Analizowanymi parami mogą być m = 5 i n = 13, m = 13 i n = 21 lub m = 34 i n = 55. Są to pary złożone z kolejnych wyrazów ciągu Fibonacciego, m = Fk i n = Fk+1 . Można też spróbować wykazać lepsze oszacowanie liczby wykonań pętli rozważanego algorytmu.