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.

Podobne dokumenty