« poprzedni punkt | następny punkt » |
Niech E będzie dowolnym, niepustym zbiorem, a X jego dowolnym podzbiorem. Problem wyszukiwania polega na ustaleniu, czy dany element x należy do zbioru X. Rozwiązaniem problemu będzie algorytm, który dla dowolnego ciągu i dowolnego elementu x daje w wyniku wartość prawda wtedy i tylko wtedy, gdy c jest jednym z elementów ciągu. Zakładamy, że elementy zbioru X są umieszczone w tablicy e jako e[1], e[2], ..., e[n] i, że odpowiedź zostanie zapamiętana na zmiennej booleowskiej result.
Specyfikacja poszukiwanego algorytmu składa się z dwóch warunków: warunku początkowego wp określającego dane wejściowe i warunku końcowego wk określającego oczekiwane własności wyniku:
wp = { n > 0 }
wk = { result = true wttw istnieje takie i, że x = e[i] oraz i £n}
Metoda: Będziemy porównywali x z kolejnymi elementami ciągu poczynając od pierwszego. Jeśli badany element ciągu jest różny od x, to kontynuujemy przeszukiwanie, a jeśli jest równy x, to kończymy postępowanie. Algorytm odpowiadający tej metodzie, nazywany algorytmem poszukiwań sekwencyjnych, jest przedstawiony poniżej. Dla uproszczenia rozważań związanych z uzasadnieniem poprawności, szukany element jest umieszczony dodatkowo na pozycji (n+1)-szej danego ciągu, e[n+1]=x. Po takiej modyfikacji ciągu e, mamy pewność, że przeglądając kolejne pozycje zmodyfikowanego ciągu, trafimy na element x.
Member | { | ||
e[n+1] := x; i := 1; | //przed wykonaniem pętli x
Ï{e[1],...,e[i-1]}
|
||
while (not x = e[i]) do | //Po sprawdzeniu i-tego elementu, x
Ï{e[1],...,e[i-1]
} oraz x
¹e[i] |
||
i := i+1 | //Po zmianie wartości i, x Ï{e[1],...,e[i-1]} | ||
od ; | |||
if (i £ n) then result := true | //Jeśli pętla zatrzymała się na indeksie mniejszym od n+1, to xÎ{e[1],...,e[n]} | ||
else | |||
result := false | //Jeśli pętla zatrzymała się na indeksie równym n+1, to x Ï{e[1],...,e[n]} | ||
fi | |||
} |
Zauważmy, że typ elementów ciągu nie ma tu żadnego znaczenia. Jest tylko konieczne, aby struktura, w której działa algorytm, miała zdefiniowaną relację równości dla obiektów ze zbioru E.
Algorytm przegląda kolejne pozycje zmodyfikowanego ciągu i zatrzymuje się na pozycji, którą zajmuje element x. W przypadku, gdy jest to pozycja ostatnia (n+1)-sza mamy pewność, że x nie występował w danym ciągu. Przeanalizujmy dokładniej zachowanie tego programu, stosując metodę niezmienników.
Łatwo zauważyć, że formuła a = (x¹e[1] Ù ... Ù x¹e[i-1]) jest niezmiennikiem pętli "while" w tym algorytmie. Przed wykonaniem pętli, gdy jeszcze nie sprawdziliśmy żadnego elementu, a i=1, formuła a jest spełniona trywialnie. Jeśli po i-tej kolejnej iteracji stwierdziliśmy, że x ¹ e[i], to wiemy, że na żadnej z pozycji e[1],...,e[i] nie ma elementu x. W takiej sytuacji zwiększamy wartość indeksu i, zatem informacja jaką posiadamy o elementach ciągu e ma postać: (x¹e[1] Ù ... Ù x¹e[i-1]). Jeśli natomiast po i-tej iteracji stwierdzamy, że e[i] = x, to algorytm opuszcza pętlę. Jedyna instrukcja jaką pozostało wykonać, to instrukcja warunkowa, ustalająca wartość wyniku. Jeśli znaleźliśmy x na pozycji od 1 do n, to znaczy, że x jest elementem danego ciągu. Jeśli wyszliśmy z pętli z wartością i = n+1, to znaczy, że nie znaleźliśmy x w danym ciągu. Z powyższych rozważań wynika następujące twierdzenie.
Algorytm Member jest poprawnym rozwiązaniem problemu wyszukiwania,
tzn. w dowolnej strukturze danych algorytm zatrzymuje się, a po jego
wykonaniu prawdziwe jest zdanie
result = true wttw x Î {e[1],...,e[n]}.
Analiza kosztu
Operacją, która (oprócz operacji arytmetycznych) powtarza się w tym algorytmie, jest porównywanie elementów. Koszt czasowy będziemy więc mierzyli liczbą wykonanych porównań elementów ciągu.
Oczywiście, może się zdarzyć, że element x znajduje się na pierwszej pozycji, ale może się też zdarzyć, że nie ma go w naszym ciągu. Zatem:
Ustalmy (dowolnie) strukturę danych, z której pochodzą elementy ciągu e[1],...,e[n]. Koszt średni policzymy ze wzoru:
A(n) = SdÎD (P(d) ´ t(d),
gdzie D jest zbiorem wszystkich danych rozmiaru n, P(d) jest prawdopodobieństwem wystąpienia danych d, a t(d) jest liczbą porównań wykonanych w czasie realizacji algorytmu 1 dla danych d.
Wszystkie możliwe dane rozmiaru n można podzielić na dwie grupy: wszystkie te ciągi, do których x należy i wszystkie te ciągi, do których x nie należy. Co więcej, wszystkie ciągi rozmiaru n, w których x występuje, można poklasyfikować następująco (są to klasy abstrakcji pewnej relacji równoważności na zbiorze ciągów długości n):
Zatem zdarzenie x Î {e[1],...,e[n]} rozbija się na sumę zdarzeń x = e[i] dla i=1,2,..., n. Zauważmy jeszcze, że dla wszystkich danych z tej samej klasy zachowanie algorytmu jest takie samo, tzn. liczba wykonanych porównań będzie taka sama.
Przyjmijmy następujące uproszczenia. Niech p będzie prawdopodobieństwem zdarzenia x Î {e[1],...,e[n]} i niech prawdopodobieństwo tego, że x = e(i) będzie dla każdego i = 1,...,n takie samo. Mamy więc
P(x = e[i]) = p/n.
Ostatecznie, średni koszt rozważanego algorytmu wyraża się wzorem :
A(n) = P(x = e[1]) ´1 + ...+ P(x = e[i])
´ i + ... + P(x = e[n])
´
n + (1-p)´(n+1).
Stąd, po podstawieniu P(x=e[i]) = p/n, otrzymujemy A(n) =
p´(n+1)/2 +(1-p)(n+1) = n+1 - p´(n+1)/2.
Jeśli wiemy, że
x należy do danego ciągu, tzn. p=1,
wtedy koszt średni wyraża się funkcją:
A(n) = (n+1)/2.
Koszt pamięciowy mierzymy liczbą
miejsc pamięci komputera, koniecznych do przechowania danych i wyników wykonania algorytmu. W tym
przypadku wynosi on n+3 (dany ciąg + zmienne pomocnicze). Koszt
pamięciowy algorytmu Member jest liniowy ze względu na rozmiar danych.
Uwaga: Właściwie należałoby traktować inaczej zmienne pomocnicze arytmetyczne i zmienne służące go przechowywania obiektów zbioru E. Typu elementów zbioru E nie znamy, a przecież mogą to być skomplikowane obiekty zajmujące dużo więcej miejsca, niż prosta zmienna typu integer.
Pytanie 1: Jaka jest długość ciągu, jeżeli całkowity czas wykonania algorytmu Member dla tego ciągu i pewnego x, wyniósł 1s, a każde porównanie elementów tego ciągu zajmuje 10-6s (koszt związany z wszystkimi innymi operacjami wykonywanymi przez algorytm zaniedbujemy)?
« poprzedni punkt | następny punkt » |