Cwiczenia z ASD ´
Micha l Knapik
Ostatnio kompilowane: 19 marca 2021
Cwiczenia 1 ´
Poprawno´ s´ c cz
e´ , sciowa i pe lna algorytm´ ow
1.1 Rozgrzewka
Zadanie 1.1.1. Rozwa˙zmy nastepuj, ace funkcje:, FUN1(n):
1. k = 1
2. while (k < n):
3. k := 2*k
4. return k
FUN2(n):
1. k = 1
2. while (k*k < n):
3. k := k + 1
4. return k
Operacje dominujace w FUN1 i FUN2 to dzia lania arytmetyczne. Oblicz dok ladn, a, z lo˙zono´s´c obliczeniowa obu funkcji.,
Zadanie 1.1.2. Uszereguj niemalejaco wzgl, edem rz, edu nast, epuj, ace funkcje:, 1. n!
2. 0.001n5+ n3 3. log(n!) 4. n1000− n999 5. 10000n4+ 999n3, 6. (1.000001)n 7. log(log(n)) 8. n log(n) 9. nn
Zadanie 1.1.3. W dalszej cze´,sci zaje´,c bedziemy zajmowali sie m.in. sortowaniem., Dobre algorytmy sortowania pozwalaja na posortowanie tablicy o rozmiarze n wy-, konujac oko lo nlogn operacji elementarnych. S labsze algorytmy mog, a wymaga´, c n2 operacji elementarnych.
Skr´ot MIPS oznacza liczbe milion´, ow operacji na sekunde. Procesor Zilog Z80, (opracowany w 1976) mo˙ze wykona´c 0.5 MIPS. Procesor AMD Ryzen Threadrip- per 3990X (2020) mo˙ze wykona´c 2300000 MIPS. Przyjrzymy sie, jak brutalna si la, obliczeniowa ma sie do dobrego wyboru algorytm´, ow.
1. Dobry programista opracowa l algorytm sortujacy w czasie nlogn i uruchomi l go, na maszynie z procesorem Z80. S labszy programista napisa l program sortujacy, w czasie n2i uruchomi l go na maszynie z procesorem AMD Ryzen Threadripper 3990X. Znajd´z wielko´s´c tablicy kt´ora Z80 posortuje szybciej ni˙z AMD Ryzen., 2. Z poprzedniego punktu wynika, i˙z tablica taka bedzie bardzo du˙za. Czy rze-, czywi´scie jest to sukces? Ile czasu potrwa sortowanie tej tablicy przy pomocy maszyny AMD? A ile czasu potrwa loby, gdyby zastosowano na tej maszynie algorytm sortujacy o z lo˙zono´, sci ok. nlogn?
Przyk ladowe rozwiazanie:,
def compare_exec_time(mips1, mips2, n):
"""True iff the worse algorithm is faster or data of size n."""
return n**2/mips1 < n*math.log(n)/mips2 n = 10
while compare_exec_time(2300000, 0.5, n):
n += 100
print(f’Z80 starts winning at n={n}’)
print(f’It would take AMD {(n**2/2300000)/(60*60*24*365)}’
’years using slower algorithm...’)
print(f’and {(n*math.log(n,2)/2300000)/(60)} minutes with faster?!’) Zadanie 1.1.4. Por´ownaj nastepuj, ace funkcje:,
FUN3(n):
1. m = 1
2. for i = 1 to 3
3. m := m*m
4. for j = 1 to n
5. m := m+j
4. return m
FUN4(n):
1. m = 1
2. for i = 1 to n
3. m := m*m
4. for j = 1 to 3
5. m := m+j
4. return m
Kt´ora ma wieksz, a z lo˙zono´, s´c obliczeniowa? Oblicz szybko oszacowanie dolne na, warto´s´c (nie z lo˙zono´s´c) F U N 3(n) i F U N 4(n), u˙zywajac notacji Ω. Jakiego ro-, dzaju gwarancje daje to oszacowanie? Przetestuj eksperymentalnie implementacje w, Pythonie.
1.2 Podstawowe definicje
Algorytm ma w lasno´s´c pe lnej poprawno´sci, gdy dla wszystkich danych wej´sciowych:
1. je´sli dane wej´sciowe sa poprawne (zgodne ze specyfikacj, a wej´, scia), to algorytm po zatrzymaniu sie zwr´, oci poprawny wynik (zgodny ze specyfikacja),,
2. je´sli dane wej´sciowe sa poprawne, to algorytm w ko´, ncu zatrzyma sie.,
Algorytm jest cze´,sciowo poprawny, gdy spe lniony jest pierwszy z powy˙zszych warunk´ow. Drugi warunek jest zazwyczaj nazywany warunkiem stopu i jest, swoja, droga, nierozstrzygalny - tzn. nie istnieje algorytm U, kt´, ory biorac pseudokod do-, wolnego algorytmu A i dowolne dane wej´sciowe x powie nam, czy A zatrzyma sie, po wykonaniu na danych x.
Zadanie 1.2.1. Napisa´c jednolinijkowy algorytm, trywialnie cze´,sciowo poprawny dla dowolnych danych.
Przyk ladowe rozwiazanie:, Alg(x):
while (true) PASS
Powy˙zszy algorytm jest cze´,sciowo poprawny, niezale˙znie od specyfikacji - bowiem nigdy sie nie zatrzyma.,
Specyfikacja danych wej´sciowych sk lada sie zazwyczaj z opisu typu, wraz z ew., dodatkowym jego zawe˙zeniem. Poprawne dane to dane zgodne z t, a specyfikacj, a,, czesto wyra˙zon, a jako formu la pewnej logiki. Poprawno´, s´c zwracanego wyniku wery- fikowana jest w podobny spos´ob - powinna by´c zgodna z pewna specyfikacj, a. Istniej, a, jezyki takie jak Ada-Spark, w kt´, orych mo˙zna formu lowa´c w/w warunki poprawno´sci explicite i pr´obowa´c statycznie weryfikowa´c programy. Jezyki te s, a u˙zywane do´, s´c sporadycznie, g l´ownie w tzw. zastosowaniach krytycznych.
1.3 Metoda niezmiennik´ ow
Metoda niezmiennik´ow s lu˙zy do dowodzenia poprawno´sci program´ow. Niezmiennik petli to warunek (specyfikowany formalnie lub w j, ezyku bardziej naturalnym), kt´, ory jest:
prawdziwy przed pierwsza iteracj, a p, etli (inicjalizowanie);,
oraz je´sli by l prawdziwy przed dana iteracj, a p, etli, to b, edzie prawdziwy i po, niej (utrzymanie).
Je´sli wiec udowodnimy, ˙ze dana w lasno´, s´c jest niezmiennikiem petli oraz p, etla za-, trzyma sie po sko´, nczonej liczbie iteracji, to wyka˙zemy, ˙ze niezmiennik bedzie praw-, dziwy po zako´nczeniu petli. Oczywi´, scie musi by´c on zbudowany tak zrecznie, by, wynika lo z tego, ˙ze badany program jest poprawny.
Zadanie 1.3.1 (Schemat Hornera). Celem jest efektywne obliczenie warto´sci wielo- mianu f (x) = Pn
i=0aixi. Jako dane wej´sciowe podana jest tablica wsp´o lczynnik´ow wielomianu A[0, . . . , n]. Algorytm powinien realizowa´c obliczenia zgodnie ze wzor- cem:
f (x) = a0+ x(a1+ x(a2+ . . . x(an−1+ xan))).
Zapisz pseudokod algorytmu i zbadaj pe lna poprawno´, s´c algorytmu.
Przyk ladowe rozwiazanie:, HORNER(A,x):
1. res := 0
2. i := len(A) - 1 3.
4. while (i >= 0):
5. res += A[i] + x*res
6. i--
7.
8. return res
Warunek stopu jest spe lniony w oczywisty spos´ob - petla while musi si, e zatrzy-, ma´c dla poprawnych danych wej´sciowych (a wiec domy´, slnie dla i = n, gdzie n ∈ N), i to w n + 1 krokach.
Niezmiennik petli. Zacznijmy od przyk ladu, kt´, ory pozwoli na zbudowanie pew- nej intuicji. Za l´o˙zmy, ˙ze f (x) = 9x3− 3x2 + 7x − 4 i przyjrzyjmy sie zawarto´, sci zmiennej res podczas kolejnych iteracji:
Gdy i = 0 to res = 0 przed wykonaniem iteracji i res = 9 po wykonaniu.
Gdy i = 1 to res = 9 przed i res = 9x − 3 po wykonaniu iteracji.
Gdy i = 2 to res = 9x − 3 przed i res = 9x2− 3x + 7 po.
Gdy i = 3 to res = 9x2− 3x + 7 przed i res = 9x3− 3x2+ 7x − 4 po.
Mo˙zemy wiec sformu lowa´, c niezmiennik petli w nast, epuj, acy spos´, ob:
Gdy i = n, to res = 0.
W przeciwnym przypadku res = Pnj=i+1A[j]xj−(i+1).
Prawdziwo´s´c tego warunku dla i = n jest oczywista. Dla porzadku zauwa˙zmy, ˙ze, po pierwszej iteracji petli mamy i = n − 1 i res = A[n] =, Pn
j=n−1+1A[j]xj−((n−1)+1). Zajmijmy sie utrzymaniem warunku. Je´, sli przed rozpoczeciem i–tej iteracji (tzn., znajdujac si, e w linijce 4) mamy:,
res =
n
X
j=i+1
A[j]xj−(i+1),
to wykonanie linijki 5 zmodyfikuje nam zawarto´s´c zmiennej res nastepuj, aco:,
res = A[i] + x ∗ (
n
X
j=i+1
A[j]xj−(i+1)),
a po dalszych transformacjach otrzymamy warto´s´c zmiennej res po i–tej iteracji:
res = A[i] +
n
X
j=i+1
A[j]xj−i=
n
X
j=i
A[j]xj−i.
W kolejnej linijce warto´s´c zmiennej i jest zmniejszona o jeden, wiec powy˙zsza za-, le˙zno´s´c zapisana przy u˙zyciu nowej warto´sci tej zmiennej przyjmie posta´c:
res =
n
X
j=i+1
A[j]xj−(i+1).
Ko´nczymy, przygladaj, ac si, e zawarto´, sci zmiennej res po wykonaniu ostatniego kroku petli. Krok ten zaczyna si, e z i = 0 a ko´, nczy z i = −1.
res =
n
X
j=−1+1
A[j]xj−(−1+1)=
n
X
j=0
A[j]xj.
A to jest dok ladnie to o co chodzi lo, czyli warto´s´c f (x).
1.4 W stron e analizy z lo˙zono´
,sci
Schemat Hornera pozwala na obliczenie warto´sci wielomianu w czasie liniowym. Na- iwne podej´scie do tego problemu daje algorytm dzia lajacy w czasie kwadratowym., Zr´´ od lem z lo˙zono´sci jest tu potegowanie, realizowane przez szereg mno˙ze´, n. Spr´obu- jemy teraz zaprojektowa´c algorytm pozwalajacy na szybkie pot, egowanie.,
Zadanie 1.4.1. Wykorzystaj przedstawienie wyk ladnika w postaci binarnej do za- implementowania efektywnego potegowania i oszacuj z lo˙zono´, s´c algorytmu. Zapisz pseudokod funkcji FASTPOW(n, k) = nk. Podpowied´z: zauwa˙z, ˙ze n11dec = n1011bin = n1·23+0·22+1·21+1·20 = n23 · n21 · n20. Zaobserwuj zgodnie z jaka regu l, a jest tworzony, nastepuj, acy ci, ag: n, 1, (n2)2= n22, ((n2)2)2= n23, . . .
Przyk ladowe rozwiazanie (z przeliczaniem decymalne-binarne w locie):,
FASTPOW(n,k):
1. if k == 0 return 1
2. if (k mod 2 == 1) res := n 3. else res := 1
4. k = k/2 8. mult = n*n 5. while (k > 0)
6. if (k mod 2 == 1) res *= mult
7. k = k/2
8. mult = mult*mult
9. return res
Zadanie 1.4.2. Wykorzystaj zale˙zno´s´c:
nk= (
n(nk−12 )2 dla k nieparzystych, (nk2)2 dla k parzystych
do zaprojektowania algorytmu efektywnego potegowania. U˙zyj rekursji (metoda dziel, i rzad´, z). Przygotuj r´ownanie rekurencyjne na pesymistyczna z lo˙zono´, s´c czasowa roz-, wiazania i oszacuj j, a.,
Przyk ladowe rozwiazanie:, RPOW(n,k):
1. if (k == 0) return 1
2. if (k mod 2 == 1) return n * (rpow(n, (k-1)/2)* rpow(n, (k-1)/2)) 3. else return (rpow(n, k/2)* rpow(n, k/2))
R´ownanie rekurencyjne dla z lo˙zono´sci pesymistycznej:
T (k) =
1 + T (k−12 ) dla k nieparzystych, 1 + T (k2) dla k parzystych
Cwiczenia 2 ´
Wyszukiwanie i sortowanie - pocz atki ,
2.1 Algorytm wyszukiwania binarnego
Idea algorytmu binarnego wyszukiwania elementu e w ciagu uporz, adkowanym arr:, 1. Inicjalizacja l := 0, p := len(arr) − 1.
2. Element e, o ile jest obecny w ciagu, znajduje si, e w przedziale indeksowanym, [l, p]. Je´sli p < l, zwr´o´c -1 (e nie wystepuje w arr).,
3. Obejrzyj warto´s´c mediany: med = arr[bl+p2 c]. Je´sli med = e, zwr´o´c bl+p2 c.
4. Je˙zeli med < e, niech l = bl+p2 c + 1 i wr´o´c do 2.
5. Je˙zeli med > e, niech p = bl+p2 c − 1 i wr´o´c do 2.
Zadanie 2.1.1. Wypisz elementy z kt´orym zostanie por´ownany klucz e przy wyszuki- waniu go w ciagu arr:,
e = 251, arr = [0, 1, 3, 7, 11, 210, 113, 251, 325, 412, 1213, 2351].
e = 2, arr = [0, 1, 3, 7, 11, 210, 113, 251, 325, 412, 1213, 2351].
Zadanie 2.1.2. Implementacja z wyk ladu wyszukiwania binarnego nie korzysta z re- kurencji. Zaprojektuj rekurencyjna wersj, e wyszukiwania binarnego. Jakie s, a zalety, a jakie wady rekurencji?
Przyk ladowe rozwiazanie:, Rec-BinSearch(s,l,p,e):
if l > p return -1 m = floor((l+p)/2) if s[m] = e return e
if s[m] < e return Rec-BinSearch(s, m+1, p) if s[m] > e return Rec-BinSearch(s, l, m-1)
Zadanie 2.1.3. UVA: 10611 - The Playboy Chimp. Podpowied´z: zastosuj algorytm wyszukiwania binarnego zwracajacy ostatnio sprawdzany indeks zamiast -1 i obejrzyj, elementy sasiaduj, ace ze zwr´, oconym indeksem.
Zadanie 2.1.4. Metoda bisekcji s lu˙zy do znajdowania pierwiastk´ow r´ownania f (x) = 0. Zak ladamy, ˙ze f jest funkcja rzeczywist, a, ci, ag l, a, okre´, slona na przedziale [a, b], i taka, ˙ze f (a) · f (b) < 1 (dlaczego to wystarczy, by istnia l pierwiastek w w/w, przedziale?). Zaprojektowa´c algorytm, kt´ory dla zadanego > 0 znajduje przedzia l [c, d] ⊆ [a, b] zawierajacy pierwiastek f i taki, ˙ze (d − c) ≤ .,
2.2 Algorytm turniejowy
Cel algorytmu turniejowego: znale´z´c drugi najmniejszy element e w ciagu nieupo-, rzadkowanym arr zawieraj, acym elementy bez powt´, orze´n. Idea dzia lania:
1. Zbuduj najni˙zsze pietro drzewa binarnego, z lo˙zone z element´, ow arr. Bedziemy, budowali drzewko od tej podstawy, ku zwie´nczeniu.
2. Budujemy nowe pietro. Iteruj (np. od lewej) po parach s, asiednich w, ez l´, ow po- przedniego pietra (˙zadne dwie pary nie maj, a wsp´, olnych element´ow). Z ka˙zdej takiej pary (node(arr[i]), node(arr[i + 1])) wybierz mniejszy element i umie´s´c go w nowym we´,zle. Lewa i prawa ga la´,z nowego wez la powinny prowadzi´, c do, odpowiednio, wez l´, ow zawierajacych arr[i] i arr[i + 1]. Je´, sli poprzednie pietro, mia lo nieparzysta liczb, e element´, ow, przepisz element, kt´ory nie wzia l udzia lu, w por´ownaniach do nowego pietra.,
3. Je˙zeli najnowsze pietro ma wi, ecej ni˙z jeden element, wr´, o´c do punktu2.
4. Je´sli ma tylko jeden element, jest to element najmniejszy mine. Zejd´z od korzenia w d´o l drzewa po wez lach zawieraj, acych mine, ogl, adaj, ac elementy, por´ownywane z mine. Wybieramy najmniejszy z tych element´ow.
Zadanie 2.2.1. Narysuj drzewko por´ownywa´n w algorytmie turniejowym dla ciagu, arr = [7, 4, 9, 1, 8, 15].
Zadanie 2.2.2. Dlaczego element znaleziony w czasie schodzenia w d´o l drzewka jest drugim najmniejszym w ciagu?,
2.3 Statystyki pozycyjne
Niech arr bedzie ci, agiem (dla uproszczenia - r´, o˙znych) nieuporzadkowanych liczb., i–ta statystyk, a pozycyjn, a nazwiemy i–ty najmniejszy element arr, gdzie 0 ≤ i <,
|arr| (tradycyjnie, liczymy od 0). Na wyk ladzie przedstawiono zarys algorytmu wykorzystujacego podzia ly Hoare’a, obliczaj, acego statystyki pozycyjne w ´, srednim czasie liniowym (przy za lo˙zeniu zrandomizowanego wyboru elementu dzielacego w, podziale Hoare’a). Za l´o˙zmy, ˙ze mamy procedure Hoare-Partition(arr, p, r),, kt´ora:
1. zwraca indeks q taki, ˙ze p ≤ q ≤ r, oraz
2. permutuje tablice arr[p . . . r] w taki spos´, ob, ˙ze elementy arr[p . . . (q − 1)] sa, mniejsze od element´ow arr[(q + 1) . . . r], za´s element arr[q] jest na w la´sciwym miejscu (tzn. tam, gdzie znalaz lby sie po posortowaniu arr).,
Ilustracja: je´sli arr = [5, 1, 4, 9, 3, 8], to przyk ladowe wykonanie procedury Hoare- Partition(arr, 0, 6) mo ˙ze zwr´oci´c q = 3 i zmodyfikowa´c tablice arr = [3, 1, 4, 5, 9, 8]., Pomimo, i˙z nie jest posortowana, to warto´s´c 5 znalaz la sie na swoim miejscu.,
Procedura Select(arr, p, r, i) wykorzystuje podzia ly Hoare’a w celu reku- rencyjnego wyszukania i–tej statystyki tablicy arr [p. . . r].
Select(arr, p, r, i):
if p = r then return arr[p]
q = Hoare-Partition(arr, p, r) k = q - p + 1
if i = k then return arr[q]
if i < k then return Select(arr, p, q-1, i) else return Select(arr, q+1, r, i-k)
Szczeg´o ly procedury Hoare-Partition(arr, p, r) zostana przedstawione przy, okazji algorytmu QuickSort.
Zadanie 2.3.1. Znale´z´c najgorszy przypadek dla procedury Select i okre´sli´c jego z lo-
˙zono´s´c.
2.4 Sortowanie - pocz atki
,Algorytm sortowania przez wstawianie (InsertionSort) polega na wstawianiu w ju˙z posortowana pocz, atkow, a cz, e´,s´c tablicy kolejnego elementu e. Polega to na przesu- waniu element´ow w prawo, dop´oty, dop´oki nie znajdziemy w la´sciwego miejsca do wstawienia e. Algorytm ma do´s´c elegancka i prost, a implementacj, e (patrz wyk lad),, ale jego ´srednia z lo˙zono´s´c jest kwadratowa.
Zadanie 2.4.1. Zasymuluj dzia lanie InsertionSort na tablicy arr = [4, 1, 5, 1, 6, 9, 10].
Okre´sl pesymistyczny (najwieksza liczba por´, owna´n) i optymistyczny (najmniejsza) przypadek dla tego algorytmu.
Zadanie 2.4.2. InsertionSort ma swoja nieco bardziej praktyczn, a wersj, e - sortowanie, Shella (ShellSort). Zapoznaj sie z opisem tego algorytmu i przetestuj kilka wybranych, sekwencji wyboru kroku.
Algorytm sortowania jest stabilny, je´sli posortowana tablica zachowuje pierwotna, kolejno´s´c element´ow o tych samych kluczach. Rozwa˙zmy tablice arr = [3, 1, 4,, 3, 5].
Zak ladamy tutaj, ˙ze elementy tablicy posiadaja jak, a´,s strukture wewn, etrzn, a, odr´, o˙z- niajace czerwon, a tr´, ojke od niebieskiej. Stabilny algorytm sortowania zastosowany do, arr zwr´oci [1,3,3, 4, 5]. Niestabilny mo˙ze zwr´oci´c [1,3,3, 4, 5]. Stabilno´s´c jest war- to´sciowa cech, a - rozwa˙zmy sytuacj, e, gdy sortujemy ksi, a˙zk, e telefoniczn, a, najpierw, po imionach, potem po nazwiskach.
Zadanie 2.4.3. Rozwa˙zmy dwie wersje algorytmu InsertionSort (tym razem zapisane w pseudoJavie):
insertionSortA(arr, len){
for(next = 1; next < len; next++){
curr = next;
temp = arr[next];
while((curr > 0) && (temp < arr[curr - 1])){
arr[curr] = arr[curr - 1];
curr--;
}
arr[curr] = temp;
} }
insertionSortB(arr, len){
for(next = 1; next < len; next++){
curr = next;
temp = arr[next];
while((curr > 0) && (temp <= arr[curr - 1])){
arr[curr] = arr[curr - 1];
curr--;
}
arr[curr] = temp;
} }
Kt´ory z nich jest stabilny i dlaczego?