Problem:
W n elementowym zbiorze wyszukać element o zadanych własnościach.
Zadanie przeszukiwania sekwencyjnego polega na przeglądaniu kolejnych elementów zbioru znaleziony element zostaje zwrócony, zwykle interesuje nas nie sam element, a jego pozycja w zbiorze, lub algorytm zwraca informacje ze poszukiwanego elementu nie ma. Ponieważ wymagane jest sprawdzenie kolejnych elementów zbioru to w przypadku pesymistycznym (brak lub element jest na samym końcu), algorytm musi wykonać n porównań, pesymistyczna złożoność
Jeśli elementy są rozłożone losowo to prawdopodobieństwo poszukiwanego elementu na każdej pozycji w zbiorze jest takie samo i wynosi zatem oczekiwana zlożoność algorytmu to:
Dane wejściowe: X - wartość poszukiwana d[] - zbiór elementu
Dane wyjściowe: p - pozycja elementu w zbiorze D - jesli p jest 0 to elementu w zbiorze nie ma
Lista kroków:
- p←1
- dopóki (p⇐n)^(x=/=d[p]) wykonaj p←p+1
- Jeśli p>n to p ← 0
- Koniec
Opis: Przeszukiwanie zbioru rozpoczynamy od pierwszego elementu zmienna p przechowuje pozycje sprawdzanego elementu i na początku przyjmuje wartość 1. W pętli warunkowej najpierw sprawdzamy czy pozycja p wzkazuje element wewnątrz zbioru, jeśli nie pętla jest przerywana, drugi test w pętli sprawdza czy element zbioru na pozycji p jest różny od poszukiwanego elementu x jeśli TAK to pozycja p jest inkrementowana wzkazujac kolejny element zbioru i pętla kontynuuje się, jeśli NIE jest różny to element został znaleziony i pętla zostaje przerwana. Wyjście z pętli przeszukującej zbiór następuje w 2 przypadkach.
- Przejrzany został cały zbiór elementu x nieznaleziono i pozycja p jest wieksza od n
- Znaleziono x na pozycji p
Uwaga: Wyzerowanie p jest czytelne i intuicyjne w zbiorze nie ma elementu na pozycji 0. Zwracanie 0-a w przypadku niepowodzenia jest często stosowaną praktyką w programowaniu, jeśli 0 jest możliwą wartością to często zwraca się -1 dla zaznaczenia porażki.
Przeszukiwanie Binarne
Problem:
W uporządkowanym zbiorze należy wyszukać element o wartości k
Wyznaczamy element środkowy zbioru sprawdzamy czy jest on poszukiwanym elementem, jeśli TAK to koniec. Jeśli NIE to poszukiwany element jest albo mniejszy od elementu środkowego, albo większy. DivideAndConquer Zatem w następnym obiegu zbiór możemy zredukować do I lub II połówki, z nowym zbiorem postępujemy identycznie, poszukiwanie kontynuujemy w odpowiedniej połówce zbioru aż znajdziemy poszukiwany element, lub do chwili gdy po podziale połówka nie zawiera dalszych elementów.
Ponieważ w każdym obiegu pętli algorytm redukuje liczbę elementów o połowę to jego klasa złożoności obliczeniowej jest równa jest to bardzo korzystna złożoność. Np. w algorytmie przeszukiwania sekwencyjnego (liniowego) przy milionie elementów trzeba wykonać milion porównań, aby stwierdzić że poszukiwanego elementu nie ma w zbiorze. W algorytmie przeszukiwania binarnego wystarczy 20 porównań.
Uwaga: Algorytm przeszukiwania binarnego może być stosowany tylko i wyłącznie w przypadku zbiorów uporządkowanych. W przeciwieństwie do algorytmu przeszukiwania liniowego, który możemy stosować dla dowolnego zbioru.
Dane wejściowe: - indeks pierwszego elementu w tablicy z - indeks ostatniego elementu w tablicy z - tablica do wyszukiwania elementu - wartość poszukiwanego elementu (klucz)
Dane wyjściowe: - indeks elementu o kluczu p = -1 - elementu nie znaleziono w danym zbiorze - indeks elementu środkowego
Lista kroków:
- p ← -1
- Dopóki wykonuj kroki …
- =
- jeśli k =/=
- p ←
- idz do
- jeśli to idz do
- ←
- Nastepny obieg pętli
- ← - 1
- Koniec
Rekurencja a iteracja
Definicja rekurencja (rekursja) - to sposób definiowania procedur i funkcji (w algorytmach i programoach zapisany w językach wysokiegopoziomu) polegający na umieszczeniu w treści procedury (funkcji) odwołań do tej samej procedury (funkcji).
Przykład - wyznaczanie silnii
Stosowanie rekurencji jest charakterystyczne dla algorytmów Dziel i Zwycięzaj DivideAndConquer Zastosowanie rekurencji:
- algorytmy wyszukiwania
- rekurencyjne struktury danych
Problemy:
- Najwiecej problemów związanych z rekurencją wiąże się z ograniczeniami stosu wywołań, a właściwie jego pojemności, na stosie są odkładane kolejne wywołania metody i dopiero gdy dojdziemy do ostatniego elementu dane te są zbierane. Bardzo łatwo więc o sytuacje gdy stos przepełnimy.
- Zwiększyć rozmiar stosu (ostateczność)
- Wykonać optymalizacje i przerobić metodę na rekurencję ogonową(kolejne wywołanie metody otrzymuje jako kolejny parametr wyliczoną wartość i na stosie nie są wywolane kolejne elementy) (Problem: java nie wspiera tej rekurencji ogonowej)
- Przepisać algorytm w wersji iteracyjnej
- Czas wykonania - tu lepszym rozwiązaniem jest iteracja
Iteracja to czynność powtarzania najczęściej wielokrotnego tej samej instrukcji lub wielu instrukcji w pętli, mianem iteracji określa się także operacje wewnątrz takiej pętli, w odróżnieniu do rekursji, która działa do góry, iteracja do obliczenia wartości wykorzystuje poprzednia n-tą iteracje, rekurencja potrzebuje zejść do 1-operacji. W większości języków programowania istnieje conajmniej kilka instrukcji iteracyjnych (for, while, do…while)
Różnice pomiędzy iteracją a rekurencją
-
Rekurencja występuje wtedy gdy metoda w programie wielokrotnie wywołuje samą siebie podczas gdy iteracja ma miejsce gdy zestaw instrukcji w programie jest wielokrotnie wykonywany.
-
Rekurencja zawiera zestaw instrukcji, wywołanie samej instrukcji, i warunek zakończenia, a instrukcja iteracji zawiera inicjalizacje, inkrementacje, warunek, zestaw instrukcji w pętli i zmienną sterującą
-
Zdanie warunkowe decyduje o zakończeniu rekurencji, a wartości zmiennej sterującej decydują o zakończeniu instrukcji iteracyjnej
-
Jeśli metoda nie doprowadzi do stanu zakończenia, przechodzi w nieskończoną rekurencję, z drugiej strony jeśli zmienna sterująca nigdy nie prowadzi do wartości do zakończenia, instrukcja iteracji, iteruje w nieskończoność
-
Nieskończona rekurencja może doprowadzić do awarii systemu, a nieskończona iteracja pochłania cykle procesora
-
Rekurencja jest zawsze stosowana do metody, podczas gdy iteracja jest stosowana do zbioru instrukcji
-
Rekurencja zużywa więcej pamięci, ponieważ każde wywołanie metody odkłada się na stosie wywołań, natomiast iteracja zajmuje stałą ilość pamięci niezależnie od liczby powtórzeń.
-
Rekurencję łatwiej stosować w problemach o strukturze hierarchicznej lub dzielonej na podproblemy (np. drzewa, sortowanie szybkie, przeszukiwanie binarne), podczas gdy iteracja jest bardziej efektywna w problemach sekwencyjnych lub liniowych.
-
Rekurencję można często zastąpić iteracją dla zwiększenia wydajności, jednak kosztem czytelności kodu i prostoty algorytmu.
-
Rekurencja skraca dlugość kodu a iteracja wydluża
Problem Solenizant tnie tort i musi sie upewnić że wszyscy dostali po kawalku.
Rekurecyjne: Weź kawalek ciasta z tacy i podaj tace kolejnej osobie Iteracyjne: Ja chodze i daje kazdemu kawalek