Algorytmy wyklad 1


Wprowadzenie do Teorii Algorytmów
(Introduction to Algorithms Theory)
Prof. Dr.habil. Alexander Prokopenya
Szkoła Główna Gospodarstwa Wiejskiego w Warszawie
Katedra Zastosowań Informatyki
Cele kursu:
zapoznanie studentów
z metodami konstrukcji, analizy kosztów oraz poprawności algorytmów,
z efektywnymi algorytmami rozwiązywania popularnych zagadnień;
nabycie przez studentów umiejętności konstruowania i analizy prostych algorytmów.
Metody dydaktyczne:
Wykłady (30 h)  przekazywanie wiedzy teoretycznej z przedmiotu z wykorzystaniem pomocy
dydaktycznych: komputer, rzutnik multimedialny.
Ćwiczenia (15 h)  rozwiązywanie problemów związanych z projektowaniem algorytmów oraz
analizą ich kosztów i poprawności.
Forma i warunki zaliczenia przedmiotu:
Wykłady : ZALICZENIE KOLOKWIUMU pisemnego.
Ćwiczenia: zaliczenie na ocenę, na podstawie sumy punktów uzyskanych podczas 2 prac
kontrolnych; warunkiem do zaliczenia jest zdobycie co najmniej 50% od maksymalnej liczby
pkt.
Literatura podstawowa.
1. (CLR) Cormen Th.H., Leiserson Ch.E., Rivest R.L. Wprowadzenie do algorytmów.
Warszawa: Wydawnictwa Naukowo-Techniczne, 2004.
2. (DPV) Dasgupta S., Papadimitriu C., Vazirani U. Algorytmy. Warszawa, Wydawnictwo
Naukowe PWN, 2010.
3. (BDR) Banachowski L., Diks K., Rytter W. Algorytmy i struktury danych. Warszawa:
Wydawnictwa Naukowo-Techniczne, 2003.
4. (DLMRSS) Dańko A., Lan Le T., Mirkowska G., Rembelski P., Smyk A., Sydow M.
Algorytmy i struktury danych - zadania. Warszawa: Wydawnictwo PJWSTK, 2006.
5. (AHU) Aho A.V., Hopcroft J.E., Ullman J.D. Algorytmy i struktury danych. Gliwice: Hekion,
2003.
Literatura uzupełniająca
6. Knuth D. Sztuka programowania. Tom 1: Algorytmy podstawowe. Tom 2: Algorytmy semi-
numeryczne, Tom 3: Sortowanie i wyszukiwanie. Warszawa: WNT, 2002.
7. Lipski W. Kombinatoryka dla programistów. Warszawa: WNT, 2004.
8. Wirth N. Algorytmy+struktury danych=programy. Warszawa: WNT, 2004.
9. Materiały publikowane w sieci Internet:
http://wazniak.mimuw.edu.pl/ ; http://edu.pjwstk.edu.pl/wyklady/
1
Wykład 1. Wprowadzenie. Podstawowe zasady analizy algorytmów
Projektowanie i analiza algorytmów są podstawa informatyki. Niniejszy wykład
stanowi przegląd najważniejszych problemów teoryi algorytmów: pojęcie specyfikacji al-
gorytmu, poprawności i kosztu algorytmu. Na konkretnych przykładach będziemy śledzili
proces przejścia od problemu i jego specyfikacji do gotowego rozwiązania w postaci po-
prawnego programu.
1. Czym jest algorytm?
Algorytm  w matematyce oraz informatyce to skończony, uporządkowany ciąg jasno zdefinio-
wanych czynności, koniecznych do wykonania pewnego zadania.
[http://pl.wikipedia.org/wiki/Algorytm]
Nieformalnie, algorytm jest pewną ściśle określoną procedura obliczeniową, która dla
właściwych danych wejściowych  produkuje żądane dane wyjściowe zwane wynikiem dziłania
algorytmu. Algorytm jest więc ciągiem kroków obliczeniowych prowadzących do przekształca-
nia danych wejściowych w wyjściowe.
Słowo algorytm często łączone z imieniem greckiego matematyka Euklidesa (365-300
p.n.e.) i jego słynnym przepisem na obliczanie największego wspólnego dzielnika dwóch liczb a
i b: NWD(a,b) .
Specjaliści zajmujący się historią matematyki odnalezli najbardziej prawdopodobne zró-
dło słowa  algorytm : termin ten pochodzi od nazwiska perskiego pisarza-matematyka Abu
Ja far Mohammed ibn Musa al-Khowarizmi (IX wieku n.e.). Jego zasługą jest dostarczenie kla-
rownych reguł wyjaśniających krok po kroku zasady operacji arytmetycznych wykonywanych
na liczbach dziesiętnych: metody dodawania, odejmowania, mnożenia, dzielenia liczb, oblicza-
nia pierwiastków kwadratowych, oblicznia kolejnych cyfr rozwinięcia dziesiętnego liczby p .
Przykład 1. (Dodawanie liczb)
Weiście: dwie liczby całkowite x i y, x, y ł 0.
Wyjście: liczba całkowita x + y
2 7 4 5 7
1 5 1 7 3 8
a) System dziesiętny : albo ;
4 2 2 1 9 5
0 1 1 0 1 1 0 0 0 1 1 1 0 0 1 0 0 1
0 0 1 1 1 1 0 1 1 0 1 1 0 0 1 0 1 0
b) System binarny: albo
1 0 1 0 1 0 1 0 0 0 1 0 0 1 0 0 1 1
c) Liczby rzymskie: XXVII + XV = XXXXII - w jaki sposób dodawać?
Przykład 2. (algorytm Euklidesa  obliczanie największego wspólnego dzielnika)
Weiście: dwie liczby całkowite x i y, x, y ł 0.
Wyjście: liczba całkowita nwd(x, y)
While x ą y do
if x > y then x = x - y else y = y - x
od
Wynik: y;
2
x = 56, y = 35 ,
(x, y) = (56,35) (21,35) (21,14) (7,14) (7,7)
Przykład 3. (problem sortowania, Rys. 1)
Weiście: ciąg n liczb a1,a2,a3,...,an .
Wyjście: permutacja a'1,a'2 ,a'3 ,...,a'n ciągu wejściowego taka, że a'1 Ł a'2 Ł a'3 Ł ... Ł a'n .
Rys. 1. Sortowanie talii kart za pomocą sortowania przez wstawianie
(Source: CLR)
Każdy algorytm:
posiada dane wejściowe (w ilości większej lub równej zero) pochadzące z dobrze zde-
finiowanego zbioru (np. algorytm Euklidesa operuje na dwóch liczbach całkowitych);
produkuje pewien wynik (niekoniecznie numeryczny);
jest precyzyjnie zdefiniowany (każdy krok algorytmu musi być jednoznacznie określo-
ny);
jest skończony (wynik algorytmu musi zostać  kiedyś dostarczony  mając algorytm A
i dane wejściowe D powinno być możliwe precyzyjne określenie czasu wykonania T(A) ).
Algorytm można przedstawić w postaci programu komputerowego albo zrealizować
sprzętowo. Jedynym wymaganiem jest precyzja opisu wynikającej z niego procedury oblicze-
niowej.
2. Język algorytmów
Jaki jest najlepszy język do opisu algorytmu?
Jest to przykład problemu nierozstrzygalnego. Niewątpliwie język ojczysty jest najlepszym języ-
kiem potocznym, a ulubiony język programowania jest najlepszym językiem do implementacji
algorytmu.
Język, którym będziemy opisywać algorytmy, jest gdzieś pomiędzy tymi językami  ję-
zyk potoczny nie wystarcza, a konkretny język programowania może spowodować, że "prosty"
algorytm się zrobi nieczytelny. Będziemy używać, o ile się da, nieformalnych konstrukcji pro-
gramistycznych, a w przypadkach bardzo prostych będziemy się starali pisać algorytm w pseu-
dojęzyku Pascalopodobnym, który zawiera instrukcję przypisania oraz instrukcję złożenia, wa-
runkową i pętli.
3
1. Przypisanie wartości zmiennej:
zmienna := wartość
2. Operatory zapisywane są za pomocą powszechnie używanych w matematyce symboli
(i := i -1, i := n mod 2 , i := n div 2 ).
3. Instrukcja warunkowa:
if warunek then
ciąg instrukcji-1
else
ciąg instrukcji-2
fi
Blok else może zostać pominięty:
if warunek then
ciąg instrukcji
fi
4. Pętla while:
while warunek do
ciąg instrukcji
od
5. Pętla for:
for zmienna = wart_pocz to wart_konc step krok do
ciąg instrukcji
od
Opuszczenie fragmentu step oznacza przyjęcie kroku równego 1.
6. W pętlach lub procedurach będziemy używać instrukcji exit, która przerywa działanie pętli,
lub instrukcji return pozwalającej przerywać wykonanie funkcji lub procedury i wrócić do in-
strukcji następującej po jej wywołaniu.
7. Dostęp do tablic uzyskuje się przez podanie nazwy i indeksu (np. A[i] jest i-tym elementem
tablicy A). A[1..j] oznacza podtablicę zawierającą elementy A[1], A[2], & , A[j]. Aby określić
liczbę elementów w tablicy A piszemy length(A).
8. Dopuszczalne jest zapisanie niektórych operacji za pomocą opisu słownego.
9. Symbol // oznacza, że reszta wiersza jest komentarzem. Komentarze, specyfikujące zachowa-
nie fragmentów programu, są bardzo pomocne przy uzasadnianiu semantycznej poprawności
programu.
Z dokładnością do ortografii, są to znane instrukcje z popularnych języków pro-
gramowania (Pascal, C, C++, Java). Symbole  do  od ,  if  then else - fi pełnią jedynie
rolę nawiasów, podobnie jak klamry  { } albo  begin - end . Na ogół, nie będziemy się
zajmować deklaracjami zmiennych, chyba że przy omawianiu konkretnych implementacji,
lub, gdy deklaracje typu zmiennych są konieczne do zrozumienia działania algorytmu.
Będziemy natomiast zakładali, że każdy algorytm ma predefiniowaną zmienną result, któ-
ra służy do zapamiętania wyników algorytmu. Jej typ będzie zależyć od konkretnej sytua-
cji i od konkretnego algorytmu.
Instrukcje składania, warunkowa i instrukcja pętli są podstawowymi metodami
konstruowania algorytmów. Dodatkowo, dopuszczać będziemy instrukcję wywołania
wcześniej zdefiniowanej procedury lub funkcji. Takie wywołanie zapiszemy w postaci:
nazwa_algorytmu(parametry_aktualne).
4
Przykład 4. (sortowanie przez wstawianie)
INSERTION-SORT(A)
1 for j := 2 to length(A) do
2 key := A[j]
3 // Wstaw A[j] w posortowany ciąg A[1..j-1]
4 i := j-1
5 While i > 0 and A[i] > key
6 do A[i+1] := A[i]
7 i := i-1 od
8 A[i+1] := key od
3. Analiza algorytmów
Analiza algorytmów polega na określeniu zasobów, jakie są potrzebne do jego wykonania. Za-
sobem zasadniczym jest dla nas czas obliczeń, jednakże innymi zasobami mogą być: pamięć,
szerokość kanalu komunikacyjnego lub układy logiczne. Zwykle analizowanie kilku algorytmów
dla tego samego problemu prowadzi do wyboru najoptymalniejszego z nich. Będziemy przyj-
mować dalej, że naszym podstawowym modelem obliczeń jest jednoprocesorowa maszyna o do-
stępie swobodnym do pamięci (RAM od ang. Random Access Machine), a naszy algorytmy są
realizowane jako programy komputerowe. W modelu RAM instrukcje są wykonywane jedna po
drugiej (sekwencyjnie).
3.1. Poprawność algorytmu
Przez poprawność algorytmu rozumiemy to, że dla dowolnych danych wejściowych daje on ta-
kie odpowiedzi, jakich oczekujemy. Żeby ustalić poprawność algorytmu, musimy jasno sformu-
łować intencje, podać tzw. jego specyfikację.
Specyfikacją algorytmu nazywać będziemy parę własności: < wp, wk > , gdzie wp jest
warunkiem początkowym, a wk warunkiem końcowym. Intuicyjnie, warunek początkowy to ten,
który mają spełniać dane początkowe, a warunek końcowy to ten, który ma być spełniony po
wykonaniu algorytmu. Ogólnie, oba warunki powinny opisywać zależności między zmiennymi
przed i po wykonaniu algorytmu.
W przykładzie 4 dla sortowania przez wstawianie mamy na wejściu ciąg n liczb
a1,a2,a3,...,an . Naturalnym warunkiem początkowym będzie założenie niepustości ciągu: nie
można sortować pusty ciąg. Możemy go zapisać w postaci: wp = {n > 0}. Warunek końcowy,
natomiast, powinien charakteryzować oczekiwany wynik i może mieć postać:
wk = {a'1,...,a'n | ("i),a'i Ł a'i+1}. Algorytm rozwiązujący ten problem możemy zapisać w posta-
ci:
INSERTION-SORT(A)
1 if length(A) = 0 then exit // sprawdzenie waruneku n>0
2 for j := 2 to length(A) do
3 key := A[j]
4 i := j-1
5 while i > 0 and A[i] > key
6 do A[i+1] := A[i]
7 i := i-1 od
8 A[i+1] := key od
Warto zauważyć, że dla każdego niepustego ciągu liczb A = a1,a2,a3,...,an algorytm zatrzy-
muje się i wydaje posortowany ciąg liczb, czyli po wykonaniu algorytmu warunek końcowy
wk = {a'1,...,a'n | ("i),a'i Ł a'i+1} jest spełniony. Ten warunek wk wydaje się być rozsądnym z
5
punktu widzenie badanego problemu. Jest jednak jasne, że dla jednego algorytmu można zapro-
ponować wiele różnych par postaci < wp, wk > , nie każda jest jednak dla nas interesująca, np.:
specyfikacja postaci
wp = {n > 0}, wk = { j = n}
Chociaż opisuje własności zmiennych występujących w algorytmie INSERTION-SORT, nie da-
je nam dostatecznej informacji o tym, czego mamy się spodziewać po wynikach i co właściwie
robi ten algorytm. Specyfikacja ma precyzować problem, ma umożliwić odpowiedz na pytanie,
czy algorytm rozwiązuje postawiony problem, czy nie. Specyfikacja ma opisać co ma robić al-
gorytm, jednak bez wskazywania, jak ma to robić.
Definicja. Całkowita poprawność algorytmu.
Powiemy, że algorytm Alg działający w pewnej strukturze danych Str jest całkowicie poprawny
ze względu na warunek początkowy wp i warunek końcowy wk wtedy i tylko wtedy, gdy dla
wszystkich danych spełniających warunek początkowy wp w strukturze Str, algorytm kończy
obliczenie i jego wyniki spełniają warunek końcowy wk.
Podkreślmy jeszcze raz, że jeśli algorytm Alg jest całkowicie poprawny ze względu na
specyfikację < wp, wk > , to warunek początkowy daje gwarancję, że algorytm zakończy obli-
czenie. Tak właśnie jest w poprzednim przykładzie: algorytm INSETION-SORT zatrzymuje się
dla dowolnych danych. Rzeczywiście, zmienna j, kontrolująca pętlę for, przyjmuje jako swoje
wartości kolejne liczby naturalne, a parametr n też jest liczbą naturalną. Zatem po skończonej
liczbie kroków zmienna j przyjmie wartość n. Dla każdej wartości j ilość kroków w wewnętrznej
pętli  while nie przekracza j -1 (0 < i < j) .
Aatwo zauważyć, że nie są tu istotne wartości elementów ciągu, ani ich typy. To samo
rozumowanie możemy powtórzyć dla ciągów o elementach z dowolnej liniowo uporządkowanej
przestrzeni, byle tylko n było liczba naturalną. Z tego powodu możemy stwierdzać, że algorytm
INSERTION-SORT jest całkowicie poprawny ze względu na specyfikację
n > 0;a'1,...,a'n | ("i),a'i Ł a'i+1 w każdej strukturze danych, w której relacja porównywania
elementów  Ł  jest relacją liniowego porządku.
Dowodzenie poprawności dla wielu algorytmów jest bardzo skomplikowane. Będziemy
postępowali zgodnie z ideą Floyda opisów programów. Skorzystamy przy tym ze strukturalnej
budowy algorytmu. Aby udowodnić poprawność algorytmu postaci {P1;P2;} ze względu na spe-
cyfikację < wp, wk > , będziemy się starali znalezć taką własność pośrednią a, że algorytm {P1}
jest poprawny ze względu na specyfikację < wp,a > , a algorytm {P2} jest poprawny ze względu
na specyfikację < a, wk > .
Dla analizy algorytmów zawierających pętli może być stosowana metoda niezmienników
Hoare.
Definicja. Niezmiennik
Powiemy że formuła lub warunek a jest niezmiennikiem pętli {while g do P od} w strukturze
Str, jeżeli z tego że formuła (g Ła) jest spełniona przed wukonaniem programu P (tzn. treści
pętli) wynika, że formuła a jest spełniona po wykonaniu programu P w strukturze Str.
Przykład 5.
Problem: Znalezć największy wspólny dzielnik dwóch danych liczb naturalnych x, y. Niech spe-
cyfikacja poszukiwanego algorytmu ma postać:
wp ={x + y > 0},
wk = {result | ($n,m), x = nresult, y = mresult | ("z)(x = n1 z, y = m1 z, z Ł result)}.
6
Warunek początkowy gwarantuje, że x i y nie są równocześnie równe 0, a warunek końcowy, że
uzyskany wynik (wartość zmiennej result) jest dzielnikiem zarówno x jak i y, oraz każdy inny
wspólny dzielnik x i y ma mniejszą od niego wartość. Znany wszystkim algorytm Euklidesa
rozwiązuje postawiony w tym przykładzie problem.
NWD(x,y)
1 if x*y = 0 then result := x + y else // jeżeli jedna z liczb x, y jest równa 0, to NWD
// tych dwóch liczb jest równy drugiej z nich
2 // NWD(x,y) = k
while x ą y do
3 if x > y then
4 x := x  y // NWD(x,y) = k
5 else
6 y := y  x fi // NWD(x,y) = k
7 od
8 result := y // jeżeli x = y, to NWD(x,y) = k = x = y
Niezmiennikiem pętli w tym algorytmie jest formuła NWD(x, y) = k . Uzasadnienie:
NWD(x, y) = NWD(x - y, y), gdy x > y oraz NWD(x, y) = NWD(y - x, x) , gdy y > x . Zatem
niezależnie od tego, którą część instrukcji warunkowej wykonamy, największy wspólny dzielnik
nowych wartości zmiennych x i y jest taki sam, jak liczb przypisanych tym zmiennym przed wy-
konaniem programu.
Nie na wiele przyda nam się niezmiennik, jeżeli nie jest on prawdziwy przed wykona-
niem programu. Jeżeli jednak jest prawdziwy przed wejściem do pętli i pozostaje prawdziwy po
każdej iteracji, to, o ile algorytm się zatrzyma, uzyskane wartości zmiennych również go spełnia-
ją. Pozwala nam to często zrozumieć co robi program i uzasadnić jego częściową poprawność.
Wróćmy na chwilę do przykładu poprzedniego. Niech k będzie największym wspólnym
dzielnikiem danych liczb naturalnych x i y. Jeżeli x*y = 0, to jedna z liczb x lub y musi być ze-
rem, ale wtedy k jest równe sumie tych liczb. Jeżeli obie liczby x i y są różne od zera i przed
wykonaniem pętli mamy NWD(x, y) = k , to własność ta nie zmienia się w kolejnych iteracjach
pętli. W momencie wyjścia z pętli mamy (NWD(x, y) = k Ł x = y) , z czego wynika, że zmienna
result ma wartość k, a więc wartość największego wspólnego dzielnika danych liczb. Pozostaje
jeszcze pytanie, czy ten algorytm zatrzymuje się dla wszystkich danych spełniających warunek
początkowy. Zauważmy, że suma (x + y) jest w tym algorytmie zawsze liczbą naturalną różną
od zera. Co więcej, wartość tej sumy jest w każdym następnym kroku równa większej z wartości
x i y w kroku poprzednim. Zatem ciąg wartości sum (x + y) jest ściśle malejący, a więc nie może
być nieskończony. To dowodzi, że po skończonej liczbie kroków algorytm Euklidesa zatrzyma
się.
3.2. Koszt algorytmu
Podstawowymi miarami kosztu algorytmu są czas i pamięć.
Jak mierzyć czas? Oczywiście chcemy by miara była niezależna od komputera, na którym reali-
zowany będzie algorytm. Jest oczywiste, że realizacja tego samego algorytmu na różnych kom-
puterach dla tych samych danych, nie musi zająć tyle samo czasu. Zatem informacja, ile jedno-
stek czasu wymaga wykonanie algorytmu dla konkretnych danych, na konkretnym komputerze,
niewiele mówili o samym algorytmie. Miara kosztu algorytmu powinna zależeć od typu proble-
mu i rodzaju rozwiązania, a nie od komputera, na którym realizujemy obliczenia. Czas działania
algorytmu dla konkretnych danych wejściowych jest wyrażony liczbą wykonanych prostych
operacji lub  kroków , np.
a) Liczbą instrukcji;
7
b) Liczbą operacji arytmetycznych;
c) Liczbą porównań wykonanych w trakcie realizacji algorytmu
d) Liczbą wywołań rekurencyjnych procedur, itd.
Jest dogonne zrobienie założenia, że operacja elementarna jest maszynowo niezależna. Na razie
przyjmujemy, że do wykonania jednego wiersza naszego programu wymagamy stalego czasu.
Wykonanie jednego wiersza programu nie musi trwać tyle co wykonanie innego wiersza. My
zakładamy, że każde wykonanie i-tego wiersza wymaga czasu ci , przy czym ci jest stała. Taki
punkt widzenia jest zgodny z przyjętym modelem RAM i odzwierciedla sposób implementacji
naszego programu na nowoczesnych komputerach.
W prowadzonych poniżej rozważaniach wyrażenie opisujące czas działania algorytmu
INSERTION-SORT otrzymamy z wyniku uproszczenia skomplikowanej formuly, w której wy-
korzystuje się koszty ci , czyniąc go bardziej zwięzlym i łatwiejszym do przekształceń. Ta
uproszczająca notacja pozwala łatwo rozpatrzywać, które z algorytmów są bardziej efektywne.
Rozpoczynamy od prezentacji procedury INSERTION-SORT z podanym  kosztem
każdej instrukcji i liczbą jej wykonań. Dla każdego j = 2,3,...,n , gdzie n = length(A) , niech t
j
będzie liczbą sprawdzeń warunku wejścia do pętli while w wierszu 5 dla danej wartości j.
INSERTION-SORT(A) koszt Liczba wykonań
2 for j := 2 to n do n
c1
3 key := A[j] n -1
c2
4 i := j  1 n -1
c3
n
5 while i > 0 and A[i] > key
c4
t j
j =2
n
6 do A[i+1] := A[i]
c5
(t j -1)
j=2
n
7 i := i  1 od
c6
(t -1)

j
j=2
8 A[i+1] := key od n -1
c7
Czas działania algorytmu jest sumą czasów wykonania poszczególnych instrukcji; jeśli instruk-
cja wykonuje się w czasie ci i jest powtarzana n razy, to mamy w sumie czas ci n . Aby obli-
czyć czas działania T(n) procedury INSERTION-SORT, sumujemy iloczyny kosztów i liczby
wykonań, otrzymując
n n n
T(n) = c1n + c2(n -1) + c3(n -1) + c4 j + c5 j -1) + c6 j -1) + c7(n -1) .
t (t (t
j=2 j=2 j=2
Czas działania algorytmu może się jednak różnić nawet dla danych o tym samym rozmiarze. W
procedurze INSERTION-SORT na przykład najlepszy przypadek (optymistyczny) występuje
wówczas, gdy wejściowa tablica jest już posortowana. Dla każdego j = 2,3,...,n stwierdzamy, że
A[i] Ł key w wierszu 5, gdy i ma początkową wartość j -1. Zatem t = 1 dla j = 2,3,...,n , a
j
minimalny czas działania wynosi
T(n) = c1n + c2(n -1) + c3(n -1) + c4(n -1) + c7(n -1) = (c1 + c2 + c3 + c4 + c7)(n -1) + c1 .
Ten czas działania można wyrazić jako an + b dla stalych a, b zależnych od kosztów ci poje-
dynczych instrukcji; jest to zatem funkcja liniowa względem n.
Jeśli tablica jest posortowana w porządku odwrótnym  to znaczy w porządku malejącym
 mamy do czynienia z przypadkiem najgorszym (pesymistycznum). Musimy porównać każdy
8
element A[ j] z każdym elementem podtablicy A[1.. j -1] , a więc t = j dla j = 2,3,...,n . Zau-
j
ważmy, że
n n
n(n +1) n(n -1)
j = -1, ( j -1) = .

2 2
j=2 j=2
Wnioskujemy zatem, że czas działania procedury INSERTION-SORT wynosi
n(n +1) n(n -1) n(n -1)

T(n) = c1n + c2(n -1) + c3(n -1) + c4ć -1 + c5ć + c6ć + c7(n -1) =

2 2 2
Ł ł Ł ł Ł ł
n2 c4 c5 c6
ćc
= (c4 + c5 + c6) + + c2 + c3 + + + + c7 n - (c2 + c3 + c4 + c7) .

1
2 2 2 2
Ł ł
Ten pesumistyczny czas działania można przedstawić jako an2 + bn + c dla stałych a, b, c, które
znowu zależą od kosztów ci ; jest to zatem funkcja kwadratowa względem n.
W typowych sytuacjach, takich jak sortowanie przez wstawianie, czas działania algoryt-
mu jest ustalony dla określonych danych wejściowych, natomiast w dalszych rozdzialach poka-
żemy algorytmy probabilistyczne, których czasy działania mogą być różne dla tych samych da-
nych wejściowych.
3.3. Złożoność pesymistyczna i średnia
Czas działania algorytmu zależy od danych wejściowych. Złożoność algorytmu może być rozu-
miana w sensie złożoności najgorszego przypadku lub złożoności średniej. Złożoność najgorsze-
go przypadku nazywamy złożonością pesymistyczną  jest to maksymalna złożoność dla danych
tego samego rozmiaru n albo górna granica możliwego czasu działania algorytmu dla każdych
danych wejściowych. Dla niektórych algorytmów pesymistyczny czas działania występuje dosyć
często. Zdarza sie tak na przykład przy wyszukiwaniu w bazie danych informacji której w tej
bazie nie ma. W praktyce ważniejsza może się okazać złożoność średnia lub oczekiwana. W tym
przypadku T(n) jest średnią (oczekiwaną) wartością złożoności dla wszystkich problemów roz-
miaru n. Tego typu złożoność zależy istotnie od tego, jaka się pod tym kryje przestrzeń probabi-
listyczna danych wejściowych. Z reguły zakładamy, że wszystkie dane wejściowe tego samego
rozmiaru mogą się pojawić z tym samym prawdopodobieństwem. Jednakże jest to często mało
realistyczne założenie. Przestrzeń probabilistyczna danych wejściowych może być bardzo skom-
plikowana. Prowadzić to może do bardzo trudnych analiz.
Definicja.
Niech D(n) będzie zbiorem danych rozmiaru n dla pewnego problemu P oraz Alg niech będzie
algorytmem rozwiązującym ten problem. Pesymistyczną złożoność czasową oznaczamy przez
W(Alg,n), a średnią (oczekiwaną) złożoność czasową przez A(Alg,n). Wielkości te są zdefinio-
wane następująco:
W(Alg,n) = max{T(Alg,d): dD(n)} A(Alg,n) =S{p(d)T(Alg,d): dD(n)}
gdzie d jest egzemplarz danych ze zbioru D(n), p(d) jest prawdopodobieństwem wystąpienia da-
nych d, T(Alg,d) liczbą operacji dominujących, wykonanych przez algorytm Alg dla danych d.
Jeżeli wartość T(Alg,d) jest taka sama dla wszystkich danych d z klasy D(n), to złożoność
pesumistyczna jest równa złożoności średniej i mówimy po prostu o złożoności czasowej algo-
rytmu o wielkości T(Alg, n).
5. Notacje asymptotyczne
9
Kiedy rozmiar danych weiściowych n staje się bardzo duży, powstaje problem porównywania
efektywności i złożoności różnych algorytmów. Ten problem można rozwiązać na podstawie
rzędu wielkości funkcji czyli ich zachowaniu asymptotycznym. Dla dostatecznie dużych danych
weiściowych stałe współczynniki i mniej znaczące składniki we wzorze na czas działania są
zdominowane przez rozmiar samych tych danych. Na przykład, złożoność optymistyczna algo-
rytma sotrowania przez wstawianie T1(n) = an + b będzie mniejsza niż złożoność pesymistyczna
T2(n) = cn2 + dn + e dla dowolnych stalych a, b, c, d, e, jesli rozmiar danych weiściowych n staje
się duży (Rys. 1).
Rys. 1. Funkcji T1(n) = 10000n + 5000 , T2(n) = 5n2 +100n
Kiedy dla dostatecznie dużych danych wejściowych liczymy jedynie rząd wielkości czasu dzia-
łania algorytmu, wtedy zajmujemy się asymptotyczną złożonościa algorytmów. Oznacza to, że
interesuje nas, jak szybko wzrasta czas dzialania algorytmu, gdy rozmiar danych wejściowych
dąży do nieskończoności. Zazwyczaj dla dostatecznie dużych danych najlepszy jest algorytm
asymptotycznie bardziej efektywny. Zwykle staramy sie podać jak najprostszą funkcję charakte-
ryzującą rząd wielkości W(Alg,n) i A(Alg,n), na przykład n, nlog n , n2 , n3 .
Będziemy używać następujących oznaczeń dla rzędów wielkości funkcji.
Definicja.
Dla danej funkcji g(n) oznaczamy przez Q(g(n)) zbior funkcji
Q(g(n)) = {f (n) : ($c1,c2,n0 > 0),"n ł n0,0 Ł c1g(n) Ł f (n) Ł c2g(n)}.
Funkcja f (n) należy do zbioru Q(g(n)) , jeśli istnieją dodatnie stałe c1 oraz c2 takie, że funkcja
może być  wstawiona między c1g(n) i c2g(n) dla dostatecznie dużych n. (Rys. 2). Chociaż
Q(g(n)) jest zbiorem, piszemy f (n) = Q(g(n)) , żeby wyrazić, że f (n) jest elementem
Q(g(n)) . Dla wszystkich wartości n większych od n0 wartość f (n) znajduje się między c1g(n)
i c2g(n). Inaczej mówiąc, dla wszystkich n ł n0 funkcja f (n) jest równa g(n) z dokładnością
do stalego współczynnika. Mówimy, że g(n) jest asymptotycznie dokładnym oszacowaniem dla
1
f (n) . Na przykład, n2 - 3n = Q(n2) , co oznacza że można pominąć składniki niższych rzę-
2
dów. To można latwo udowodnić, używając formalnych definicji. W tym celu musimy znalezć
dodatnie stałe c1 , c2 oraz n0 takie, że
1
c1n2 Ł n2 - 3n Ł c2n2
2
dla każdego n ł n0 . Dzieląc powyższą zależność przez n2 , otrzymujemy
10
1 3
c1 Ł - Ł c2 .
2 n
Prawa strona jest prawdziwa dla każdej wartości n ł 1, gdy wybierzemy c2 ł 1/ 2. Podobnie,
lewa strona jest prawdziwa dla każdej wartości n ł 7, gdy wybierzemy c1 Ł 1/14. Wybierając
1
c1 = 1/14, c2 = 1/ 2 oraz n0 = 7 , możemy sprawdzić, że n2 - 3n = Q(n2) .
2
Rys. 2. Za pomocą notacji Q szacuje się funkcję z dokładnością do stalego współczynnika. Pi-
szemy f (n) = Q(g(n)) , jeśli istnieją dodatnie stałe n0 , c1 , c2 takie, że na prawo od n0 wartość
f (n) leży zawsze między c1g(n) i c2g(n) .
Definicja.
Dla danej funkcji g(n) oznaczamy przez O(g(n)) zbior funkcji
O(g(n)) = {f (n) : ($c,n0 > 0),"n ł n0,0 Ł f (n) Ł c2g(n)}.
Notacja Q asymptotycznie ogranicza funkcję od góry oraz od dołu. Kiedy mamy tylko asympto-
tyczną granicę górną, używamy notacji O (Rys. 3).
Rys. 3. Notacja O daje górne organiczenie funkcji z dokladnością do stalego współczynnika.
11
Definicja.
Dla danej funkcji g(n) oznaczamy przez W(g(n)) zbior funkcji
W(g(n)) = {f (n) : ($c,n0 > 0),"n ł n0,0 Ł cg(n) Ł f (n)}.
Notacja W asymptotycznie ogranicza funkcję od dołu (Rys. 4).
Rys. 4. Notacja W daje dolne organiczenie funkcji z dokladnością do stalego współczynnika.
Rzędy wielkości dwóch funkcji f (n) i g(n) mogą być porównane przez obliczenie gra-
nicy
f (n)
E = lim .

g(n)
Jeśli E = +Ą, to g(n) = O( f (n)), ale nie f (n) = O(g(n)) .
Jeśli E = c > 0 , to f (n) = Q(g(n)).
Jeśli E = 0 , to f (n) = O(g(n)) , ale nie g(n) = O( f (n)).
Na przykład stosując regułę de L Hospitala, otrzymujemy
nlog n ln n 1/ n
lim = lim = lim = 0 ,
nĄ nĄ
nln 2 ln 2
n2 nĄ
czyli nlog n = O(n2) , ale nie n2 = O(nlog n) .
Większość rozważanych algorytmów ma złożoność czasową proporcjonalną do jednej z
podanych tu funkcji:
log n  złożoność logarytmiczna
n  złożoność liniowa
n log n  złożoność liniowo-logarytmiczna
n2  złożoność kwadratowa
n3 , n4 ,...  złożoności wielomianowe
2
nlog n = 2log n  złożoność podwykładnicza
2n  złożoność wykładnicza 2n
n!  złożoność wykładnicza n!
12


Wyszukiwarka

Podobne podstrony:
Inforamtyka Algorytmy wyklady
Algorytmy wyklad 4 5
Algorytmy wyklad 2 3
Algorytmy wyklad 6 7
Algorytmy grafowe, wykład
Algorytmy genetyczne i procesy ewolucyjne Wykład 2
PLC mgr wyklad 11 algorytmy
Algorytmy genetyczne i procesy ewolucyjne Wykład 4
wyklad algorytmy
Wykład 1 Standardowe algorytmy regulacji i sterowania
algorytmy pytania na egzamin pytania wyklad4
Algorytmy I Struktury Danych (Wyklady) info
Algorytmy i struktury danych Wyklad 4
algorytmy pytania na egzamin pytania wyklad7
Algorytmy i struktury danych Wyklad 3
Wykład 3 Realizacja algorytmu DES
Algorytmy genetyczne i procesy ewolucyjne Wykład 1

więcej podobnych podstron