Lista 2 - Instytut Sterowania i Systemów Informatycznych
Transkrypt
Lista 2 - Instytut Sterowania i Systemów Informatycznych
Uniwersytet Zielonogórski Instytut Sterowania i Systemów Informatycznych Teoretyczne Podstawy Informatyki Lista 2 – Analiza złożoności kodu wysokiego poziomu 1 Wprowadzenie Choć dzisiejsze systemy komputerowe są znacznie szybsze niż maszyny stosowane 10 lat temu to problem wydajności algorytmów nadal jest bardzo istotny. Badanie złożoności napisanego kodu to bardzo ważne zadanie, bowiem zastosowany algorytm w największym stopniu wpływa na szybkość działania późniejszego programu, czy całego systemu. 1.1 Przykład – zerowanie fragmentu macierzy Przykładem analizy będzie realizacja elementarnego zadania które polegać będzie na wypełnieniu zerami elementów które znajdują się pod główną przekątną macierzy łącznie z elementami leżącymi na diagonali. Co można zobrazować w następujący sposób: 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1 0 0 1 0 0 ⇒ 1 0 0 1 0 0 1 0 0 1 1 0 0 0 0 1 1 1 0 0 0 1 1 1 1 0 0 1 1 1 1 1 0 Funkcja która realizuje to zadanie np.: w języku C jest następująca: int tab [ n ] [ n ] ; void z e r o m a t r i x ( ) { int i , j ; i =0; while ( i <n ) { j =0; while ( j ¬ i ) { tab [ i ] [ j ] = 0 ; j=j +1; } i := i +1; } } Można także ową funkcję napisać za pomocą instrukcji for, jednak zastosowanie pętli typu while naturalnie nie zmienia złożoności problemu. Ponadto istnieje tylko jeden typ pętli, a mianowicie pętla typu while. Pozostałe typy pętli można skonstruować za pomocą pętli while. Do przeprowadzenia poprawnej analizy należy wprowadzić dodatkowe oznaczenia. Małą literą „c” oznaczamy czas porównania natomiast literą „a” czas przypisania. Nie będziemy rozróżniać czy przypisanie jest dokonywane na zmiennej prostej co niewątpliwe zabiera mniej czasu niż przypisanie pod określoną współrzędną w tablicy co zajmuje więcej czasu, gdyż należy obliczyć adres miejsca docelowego. Warto jednak czasem zwracać uwagę na postać tego rodzaju wyrażeń bo w wielu przypadkach mają one istotny wpływ na wykonanie się programu. Warto zwrócić uwagę na pętle while np.: o takiej postaci: i :=1; while i ¬ n do begin 1 I; i := i +1; end ; Pętla ta wykona się dokładnie n razy, jednak warunek zostanie sprawdzony n + 1 razy, co jest konsekwencją ostatniej iteracji która powoduje, że warunek pętli staje się fałszywy. Tłumacząc pętle funkcji zero matrix bezpośrednio na sumy oraz stosując oznaczenia „c” i „a” otrzymujemy następującą zależność: N i X X 2a + 2c + T (n) = c + a + (c + 2a) i=1 j=1 W pierwszej kolejności możemy usunąć wewnętrzną sumę bowiem zmienne c i a nie są zależne od zmiennej sumy. N X T (n) = c + a + (2a + 2c + i(c + 2a)) i=1 Następnie należy usunąć pierwszy znak sumy. Tym razem w wyrażeniu znajduje się zmienna zależna i, toteż wykorzystujemy sumę na ciąg arytmetyczny, zwracamy także uwagę na 2a + 2c: N (N + 1) (c + 2a) 2 Teraz należy wyrażenie uporządkować, otrzymujemy zależność z której już jasno wynika iż pokazana wcześniej funkcja ma złożoność rzędu O(n2 ). N2 2 T (n) = a(1 + 3N + N ) + c 1 + 2.5c + 2 T (n) = c + a + 2N (a + c) + 2 2.1 Wzory i oszacowania Sumy Suma wyrażeń stałych, gdzie zmienna i jest zmienną wolną, nie związaną: n X c = nc jeśli i w c jest zmienną wolną (1) i=1 Wzór na sumę kolejnych liczb naturalnych: n X i = 1 + 2 + 3 + ... + N = i=1 N (N + 1) 2 (2) Suma stałej oraz iloczynu stałej i zmiennej zależnej: n X k=0 1 (a + bi) = (a + bn)(n + 1) 2 (3) Ciąg geometryczny: n X axi = i=0 a − axn+1 1−x dla x 6= 1 (4) Trzy podstawowe prawa: n X cai = c i=1 n X (ai + bi ) = i=1 n X ai + i=1 X ai = i∈I n X 2 (5) bi (6) ap(i) (7) i=1 X p(i)∈I gdzie p(i) to dowolna permutacja indeksów. ai i=1 n X 2.2 Równania rekurencyjne Jednorodne równanie rekurencyjne o postaci: an = c1 an−1 + c2 an−2 + . . . + cr an−r (8) gdzie ci , i = 1, 2, 3, . . . , n − r są pewnymi stałymi. Ma równanie charakterystyczne o postaci: αr − c1 αr−1 − c2 αr−2 − . . . − cr = 0 (9) Jeśli równanie charakterystyczne ma r różnych pierwiastków to rozwiązaniem jednorodnej rekurencji jest dowolna kombinacja liniowa, czyli wzór jawny: an = A1 α1n + A2 α2n + . . . + Ar αrn (10) Znając wartości początkowe a0 , a1 , . . . , ar−1 i rozwiązując układ równań o postaci: ak = A1 α1k + A2 α2k + . . . + Ar αrk (11) gdzie 0 ¬ k ¬ r − 1, można wyznaczyć stałe A1 , A2 , . . . , Ar . Twierdzenie 1 (Twierdzenie o rekurencji o „głębokości” równiej dwa) Dla zależności o postaci an = c1 an−1 + c2 an−2 , istnieje równanie charakterystyczne: α2 − c1 α − c2 = 0, gdzie c1 oraz c2 są pewnymi stałymi niezerowymi. Gdy równanie charakterystyczne ma dwa różne rozwiązania α0 i α1 to wzór jawny przyjmuje postać an = A0 α0n + A1 α1n . Wartości stałych A0 , A1 można wyznaczyć gdy znane są a0 i a1 podstawiając naturalnie n = 0 i n = 1. Otrzymując w ten sposób układ dwóch równań z niewiadomymi A0 i A1 . Jeśli, równanie charakterystyczne ma tylko jedno rozwiązanie α0 to wzór jawny przyjmuje postać an = A0 α0n + A1 nα0n stałe A0 , A1 wyznaczamy podobnie jak w poprzednim przypadku. W rekurencjach o postaci an = c1 an−1 + c2 an−2 można jeszcze dodatkowo wyróżnić dwa dodatkowe przypadki gdy c2 = 0 to an = c1 an−1 , wtedy wzór jawny przyjmuje postać: an = cn1 a0 . (12) Natomiast, gdy c1 = 0 to mamy dwa wzory na parzyste oraz nieparzyste elementy: a2n = cn2 a0 oraz a2n+1 = cn2 a1 (13) Następne twierdzenie dotyczy równań o następującej postaci: s2n = 2sn + f (n) (14) Twierdzenie 2 Niech (sn ) będzie ciągiem rekurencyjnym o następującej postaci s2n = 2sn + f (n) dla n ∈ P Wtedy prawdziwe jest oszacowanie: " s2m = 2m m−1 1 X f (2i ) s1 + 2 i=0 2i 3 # dla m ∈ N (15) Jeśli, równanie rekurencyjne ma postać s2n = 2s1 + A + Bn dla stałych A i B, to oszacowanie jest następujące s2m = 2m · s1 + (2m − 1) · A + B m · 2 · m. 2 (16) W szczególności gdy n = 2m , to otrzymujemy sn = ns1 + (n − 1)A + B · n · log2 n 2 (17) Twierdzenie to można stosować do szybkiego wyznaczenia złożoności dla problemu np.: szukania największej liczby w zbiorze za pomocą metody „dziel i rządź” która zazwyczaj jest opisana za pomocą zależności: T (2n) = 2T (n) + A Stosując twierdzenie 2 dla przypadku gdy B = 0 otrzymamy T (2m ) = 2m · T (1) + (2m − 1) · A Ponieważ n = 2m to otrzymamy następującą zależność: T (n) = n · T (1) + (n − 1) · A = O(n) Jak widać jest to zależność liniowa względem rozmiaru n bowiem ostatecznie można przeglądnąć cały zbiór szukając elementu największego. Drugi przykład dotyczy zastosowania twierdzenia 2 np.: dla sortowania metodą „dziel i rządź” opisanego zależnością T (2n) = 2T (n) + Bn. Tym razem A = 0, więc otrzymamy T (2m ) = 2m · T (1) + B m · 2 · m. 2 Podobnie jak poprzednio dla n = 2m mamy T (n) = n · T (1) + B · n log2 n = O(n log2 n). 2 Twierdzenie 3 Dla rekurencji o postaci: T (1) = 1, T (n) = aT n b + f (n) dla n 2 oraz stałych a i b istnieją trzy przypadki: • jeśli a > f (b), to T (n) ∈ O(nlogb a ), • jeśli a < f (b), to T (n) ∈ O(nlogb f (b) ), dodatkowo gdy f (n) = nα to T (n) ∈ O(nα ) = O(f (n)) • jeśli a = f (b), to T (n) ∈ O(nlogb f (b) logb n), dodatkowo gdy f (n) = nα to T (n) ∈ O(nα logb n) Podobnie jak twierdzenie 2, również twierdzenie 3 stosujemy do oceny złożoności np.: algorytmu szukania binarnego. Równanie rekurencyjne przyjmuje postać: n T (1) = 2 T (n) = T +2 2 Jak widać nie jest ono w poprawnej postaci lecz można zastosować trywialne podstawienia: U (n) = T (n) − 1 ⇔ U (1) = T (1) − 1 = 1 oraz T (n) − 1 = 1 + T n ⇔ U (n) = U n + 1. 2 2 Równanie U (n) ma już odpowiednią postać. Wartości stałych to a = 1, b = 2 natomiast f (n) = 1. Ponieważ a = f (n) to szukana złożoność jest wyznaczana w następujący sposób: U (n) = O(nlog2 1 log2 n) = O(n0 log2 n) = O(log2 n) 4 3 Zadania Zadania opracowane na podstawie pozycji wymienionych w literaturze. Algorytmy oraz programy zostały zapisane przede wszystkim w języku Pascal ale także w języku C oraz za pomocą pseudokodu. 1. Wykazać, że pozostałe dwa typy pętli repeat oraz for można zbudować tylko za pomocą instrukcji while. 2. Wyprowadzić wzór 16, wykorzystując wzór s2n = 2s1 + A + Bn dla pewnego s1 . 3. Wyznaczyć złożoność poniższej funkcji, licząc operacje jednostkowe: function f n c ( x : r e a l ) : r e a l ; const Pi2 = 6 . 2 8 3 1 8 ; begin i f abs ( x ) ¬ 2 then y : = ( ( ( 3 ∗ x+1)∗x ) − 1 ) ∗ c o s ( Pi2 ∗x ) else y:= l n ( abs ( x ))+(4/ x ) ∗ s i n ( Pi2 ∗x )+12; end ; 4. Obliczyć złożoność dla poniższej funkcji: function f a c t o r i a l ( n : integer ) : integer ; var i , r : integer ; begin r :=1; f or i :=2 to n do r := i ∗ r ; f a c t o r i a l := r ; end ; 5. Wyznaczyć złożoność dla następującego zadania: wypełnić zerami elementy leżące na przekątnej i ponad nią macierzy kwadratowej dowolnego wymiaru. 6. Obliczyć złożoność dla funkcji silnia zdefiniowanej rekurencyjnie: int s i l n i a ( int n ) { i f ( n == 0 ) return 1 ; else return n∗ s i l n i a ( n − 1 ) ; } 7. Wyznaczyć złożoność obliczeniową problemu „Wież Hanoi”, udowodnić prawdziwość otrzymanego wzoru. 8. Wyznaczyć złożoność poniższej funkcji: const N=10; int t [N ] ; void f n c ( ) { int k , i ; int suma=0; while ( i < N) { while ( j ¬ t [ i ] ) 5 { suma=suma+2; j=j +1; } i=i +1; } } 9. Wyznaczyć złożoność programu: program minmax ; const m = 1 0 0 0 ; type i n d e x = 1 . .m; v e c t o r = array [ i n d e x ] of integer ; var i , j , k , n : index ; A : vector ; begin read ( n ) ; f or k:=1 to n do read (A[ k ] ) ; i :=1; j :=1; f or k:=2 to n do i f A[ k ] < A[ i ] then i :=k else i f A[ k]>A[ j ] then j :=k ; write (A[ i ] ) ; write (A[ j ] ) end . 10. Wyznaczyć złożoność algorytmu dzielenia: procedure i n t d i v ( x , y : integer ; var q , r : integer ) ; begin q : = 0 ; r :=x ; while y ¬ r do begin q:=q+ 1 ; r := r −y ; end ; end ; 11. Wyznaczyć złożoność algorytmu sortowania przez wybieranie: procedure s e l e c t i o n s o r t ( var A : v e c t o r ; n : i n d e x ) ; var i , j , maxi : i n d e x ; tmp : integer ; begin f or i :=n downto 2 do begin maxi : = 1 ; for j :=2 to i do i f A[ j ] > A[ maxi ] then maxi := j ; tmp:=A[ i ] ; A[ i ] : =A[ maxi ] ; A[ maxi ] : = tmp ; end ; end ; 6 12. Wyznaczyć złożoność poniższej funkcji wybierającej k-ty element najmniejszy: function s e l e c t k ( var A : v e c t o r ; n , k : i n d e x ) ; var i , j , mini : i n d e x ; tmp : integer ; begin f or i :=1 to k do begin mini := i ; for j := i +1 to n do i f A[ j ] < A[ mini ] then mini := j ; tmp:=A[ i ] ; A[ i ] : =A[ mini ] ; A[ mini ] : = tmp ; end ; s e l e c t k :=A[ k ] ; end ; 13. Wyznaczyć złożoność algorytmu sortowania bąbelkowego: procedure b u b b l e s o r t ( var A : v e c t o r ; n : i n d e x ) ; var i , j : index ; tmp : integer ; begin f or i :=n−1 downto 1 do for j :=1 to i do i f A[ j +1] < A[ j ] then begin tmp:=A[ j ] ; A[ j ] : =A[ j + 1 ] ; A[ j +1]:=tmp ; end ; end ; 14. Wyznaczyć złożoność algorytmu mnożenia macierzy kwadratowych: procedure matrix mul ( var A, B, C : matrix ; n : i n d e x ) ; var i , j , k : index ; q : integer ; begin f o r i :=1 to n do f or j :=1 to n do begin q :=0; for k:=1 to n do q:=q+A[ i , k ] ∗ B [ k , j ] ; C[ i , j ] : = q ; end ; end ; 15. Podać złożoność algorytmu znajdowania znaku permutacji: s :=1 f o r i :=1 to n do NOWY[ i ] : = true ; f o r i :=1 to n do i f NOWY[ i ] then 7 begin j :=P [ i ] ; while j 6= i do begin NOWY[ j ] : = f a l s e ; s := s ; j :=P [ j ] ; end ; end ; 16. Podać złożoność funkcji PERM dla poniższego programu generującego permutacje elementów tablicy P : const N = 1 0 0 ; var P : array [ 1 . . N] of integer ; i : integer ; function B(m, i : integer ) ; begin i f (m mod 2 = 0 ) and (m > 2 ) then i f i < m − 1 then B:= i ; else B:= m − 2 else B:= m − 1 end ; procedure PERM(m : integer ) ; begin i f m=1 then write ( P1 , P2 , . . . , Pn ) ; else for i :=1 to m do begin PERM(m− 1 ) ; i f i < m then P [ B(m, i ) ] : =P [m] ; end ; end ; begin f or i :=1 to N do P [ i ] : = i ; PERM(N ) ; end . 17. Obliczyć złożoność następującego algorytmu sprawdzającego, czy wierzchołek n jest osiągalny z wybranego wierzchołka a w grafie skierowanym. Tworzymy zbiór wierzchołków S i S = {a}. Każdy wierzchołek może być zaznaczony oraz niezaznaczony. Zaznaczenie wierzchołka i oznacza, że i znajdował się w zbiorze S na wcześniejszym etapie obliczeń lub obecnie znajduje się w zbiorze S. W każdej iteracji algorytmu wybieramy wierzchołek i ∈ S po czym zostaje on usunięty ze zbioru S. Następnie dodajemy te wierzchołki wychodzące z i do zbioru S które nie były jeszcze zaznaczone. Proces jest kontynuowany tak długo aż zbiór S stanie się pusty. Jeśli wierzchołek n jest zaznaczony to oznacza o, że jest osiągalny z a. 18. Wyznaczyć złożoność obliczeniową algorytmu NWD: function NWD(m, n : integer ) : integer ; var 8 a , b , t : integer ; begin a:=m; b:=n ; while b <> 0 do begin t :=b ; b:=a mod b ; a:= t ; end ; NWD:=a ; end ; 19. Rozwiązać następujące równania rekurencyjne: (a) T (0) = 1, (b) T (1) = 2, (c) T (1) = 1, (d) T (1) = 1, (e) T (1) = 1, T (n) = T (n − 1) + 1 T (n) = T n2 + 2 T (n) = T n2 + n3 T (n) = 2T n4 + 2n √ T (n) = 6T n3 + n T (n) = T (n − 1) − T (n)T (n − 1) √ (g) T (n) = 2T ( n) + 1 √ (h) T (n) = 2T ( n) + log2 n (f) T (0) = 1, 20. Zakładamy, że poniższa funkcja otrzymuje uporządkowane dane w sposób rosnący a jej zadaniem jest sprawdzenie, czy określony element znajduje się w tablicy: function m( var A : v e c t o r ; n : i n d e x ; x : integer ) : boolean ; var i , j ,m : i n d e x ; found : boolean ; begin i : = 1 ; j :=n ; found := f a l s e ; while ( i ¬ j ) and ( not found ) do begin m:=( i+j ) div 2 ; i f A[m]=x then found := true else i f x>A[m] then i :=m+1 else j :=m 1 end ; m:= found ; end ; Jaka jest złożoność obliczeniowa funkcji m? 21. Załóżmy, iż mamy uporządkowany rosnący ciąg A. Wyznaczyć złożoność poniższej funkcji która sprawdzi czy liczba x należy do ciągu: function b i n s r c h ( var A : v e c t o r ; i , j : i n d e x ; x : integer ) : boolean ; var m : index ; begin i f i=j then member x :=(A[ i ] = x ) 9 else i f i >j then member x:= f a l s e ; else begin m:=( i+j ) div 2 ; i f x=A[m] then member x:= true else i f x>A[m] then member x:=member x (A,m+ 1 , j , x ) else member x:=member x (A, i ,m− 1 , x ) end end ; 22. Napisać funkcję bądź procedurę która scala dwa posortowane ciągi w jeden ciąg, tak aby jej czas działania był linowy tzn. O(n). 23. Wyznaczyć złożoność obliczeniową algorytmu sortowania przez scalanie: MergeSort (A, p , r ) i f p < r then q := p+r d i v 2 ; MergeSort (A, p , q ) MergeSort (A, q+1, r ) Merge (A, p , q , r ) zakładamy, że funkcja bądź procedura merge scala dwa ciągi w czasie O(n) – co było do wykazania w poprzednim zadaniu. 24. Wyznaczyć złożoność pewnej funkcji którą można nazwać „demonem złożoności”: int A( int n , int p ) { i f ( n==0) return 1 ; i f ( ( p=0) && ( n>=1)) i f ( n==1) return 2 ; else return n+2; i f ( ( p>=1) && ( n>=1)) return A(A( n− 1 , p ) , p− 1 ) ; } 4 Dalsze informacje Poniższe pozycje odnoszą się do wszystkich list z ćwiczeniami z przedmiotu teoretyczne podstawy informatyki. Literatura [1] David Harel: Rzecz o istocie informatyki Algorytmika, Edycja polska Wydanie drugie Wydawnictwa Naukowo-Techniczne 2000 [2] Tomasz Bilski, Krzysztof Chmiel, Janusz Stokłosa: Zbiór zadań ze złożoności obliczeniowej algorytmów. Politechnika Poznańska 1992 10 [3] Janusz Stokłosa: Zadania ze złożoności obliczeniowej algorytmów, Politechnika Poznańska 1989 [4] L. Banachowski, Antoni Kreczmar: Elementy analizy algorytmów, Wydawnictwa Naukowo-Techniczne 1982 [5] John E.Hopcroft, Jeffrey D.Ullman: Wprowadzenie do teorii automatów, języków i obliczeń. Wydawnictwo Naukowe PWN 2003 [6] Mordechai Ben-Ari: Logika matematyczna w informatyce, Wydawnictwa Naukowo-Techniczne 2005 [7] Christos H.Papadimitriou: Złożoność obliczeniowa, Wydawnictwa Naukowo-Techniczne 2002 [8] R.L. Graham, D.E. Knuth, O.Patashnik: Matematyka konkretna,Wydawnictwo Naukowe PWN 2002 [9] Kenneth A.Ross, Charles R.B.Wright: Matematyka dyskretna, Wydawnictwo Naukowe PWN 2000 [10] Piotr Wróblewski: Algorytmy struktury danych i techniki programowania, Helion 1997 [11] Thomas H.Cormen, Charles E.Leiserson, Ronald L.Rivest: Wprowadzenie do algorytmów, Wydawnictwo Naukowe PWN 1997 11