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

Podobne dokumenty