Zaawansowane algorytmy
Organizacja
Wykład: poniedziałek 815-10 – Aula
Ćwiczenia: …
Każdy student musi realizować projekty (treść podawana na wykładzie) : – Ilość projektów : 5-7
– Na realizację każdego projektu studenci będą mieli 2 tygodnie
– Ocena projektów: od 0 do 10 punktów (0 –brak projektu, 7 – projekt poprawnie wykonany, bez zastrzeżeń, 10 – wybitne rozwiązanie)
– Oddanie projektu tydzień po terminie powoduje utratę 2 punktów (później nie będzie już oceniany)
Zaliczenie ćwiczeń:
– Minimalna ilość punktów na zaliczenie: (ilość projektów-1) * 5+2
– Dokładna punktacja dla poszczególnych ocen – później (zależna od ilości projektów)
Egzamin – pisemny ( termin „zerowy” - ?)
Tematyka wykładu
Wprowadzenie (przypomnienie podstaw)
Drzewa binarne i drzewa BST
Drzewa AVL i 2-3-4
Drzewa zbalansowane (czerwono-czarne)
Tablice z haszowaniem
Kompresja danych
Wyszukiwanie wzorca w tekście
Algorytmy grafowe:
– Przeszukiwanie (wszerz i w głąb) – Drzewo rozpinające
– Znajdowanie najlepszej drogi
– Znajdowanie najkrótszych ścieżek pomiędzy wszystkimi wierzchołkami
Literatura
T. Cormen, Ch. Lieserson, R. Rivest, Wprowadzenie do Algorytmów, WNT, 1997 R. Sedgewick, Algorytmy w C++, RM, 1999
R. Sedgewick, P. Rzechonek, Algorytmy w C++. Grafy , RM, 2003
O co w tym wszystkim chodzi?
Rozwiązywanie problemów:
– Układanie planu zajęć
– Balansowanie własnego budżet – Symulacja lotu samolotem
– Prognoza pogody
Dla rozwiązania problemów potrzebujemy procedur, recept, przepisów – inaczej mówiąc algorytmów
Historia
Nazwa pochodzi od perskiego matematyka Muhammeda ibn Musa Alchwarizmiego (w łacińskiej wersji Algorismus) – IX w n.e.
Pierwszy dobrze opisany algorytm – algorytm Euklidesa znajdowania największego wspólnego podzielnika, 400-300 p.n.e.
XIX w. – Charles Babbage, Ada Lovelace.
XX w. – Alan Turing, Alonzo Church, John von Neumann
Struktury danych i algorytmy
Algorytm – metoda, zestaw działań (instrukcji) potrzebnych do rozwiązania problemu
Program – implementacja algorytmu w jakimś języku programowania
Struktura danych – organizacja danych niezbędna dla rozwiązania problemu (metody dostępu etc.)
Ogólne spojrzenie
Cele algorytmiczne:
- poprawność, - efektywność,
Cele implementacji:
- zwięzłość
- możliwość powtórnego wykorzystania
Wykorzystanie komputera:
Projektowanie programów (algorytmy, struktury danych)
Pisanie programów (kodowanie)
Weryfikacja programów (testowanie)
Problemy algorytmiczne
Ilość instancji danych spełniających
specyfikację wejścia może być nieskończona, np.:
posortowana niemalejąco sekwencja liczb naturalnych, o skończonej długości:
1, 20, 908, 909, 100000, 1000000000.
3, 44, 211, 222, 433.
3.
Specyfikacja wejścia
?
Specyfikacja wyjścia, jako funkcji
wejścia
Rozwiązanie problemu
– Algorytm opisuje działania, które mają zostać przeprowadzone na danych – Może istnieć wiele algorytmów rozwiązujących ten sam problem
Instancja
wejściowa (dane), odpowiadająca
specyfikacji
algorytm Wyniki
odpowiadające danym
wejściowym
Definicja algorytmu
Algorytmem nazywamy skończoną sekwencję jednoznacznych instrukcji pozwalających na rozwiązanie problemu, tj. na
uzyskanie pożądanego wyjścia dla każdego legalnego wejścia.
Własności algorytmów:
– określoność
– skończoność
– poprawność
– ogólność
– dokładność
Pseudokod
Zbliżony do Ady, C, Javy czy innego języka programowania:
– struktury sterujące (if … then … else, pętle while i for) – przypisanie (←)
– dostęp do elementów tablicy: A[i]
– dla typów złożonych (record lub object) dostęp do pól: A.b
– zmienna reprezentująca tablicę czy obiekt jest traktowana jak wskaźnik do tej struktury (podobnie, jak w C).
Warunki początkowe i końcowe (precondition, postcondition)
Ważne jest sprecyzowanie warunków początkowego i końcowego dla algorytmu:
– INPUT: określenie jakie dane algorytm powinien dostać na wejściu
– OUTPUT: określenie co algorytm powinien wyprodukować. Powinna zostać przewidziana obsługa specjalnych przypadków danych wejściowych
Sortowanie przez wstawianie (Insertion Sort)
A
1 j n
3 4 6 8 9 7 2 5 1
i
Strategia
• zaczynamy od “pustej ręki”
• wkładamy kartę we właściwe miejsce kart poprzednio już posortowane
• kontynuujemy takie postępowanie aż wszystkie karty zostaną
wstawione Strategia
• zaczynamy od “pustej ręki”
• wkładamy kartę we właściwe miejsce kart poprzednio już posortowane
• kontynuujemy takie postępowanie aż wszystkie karty zostaną
wstawione
INPUT: A[1..n] – tablica liczb całkowitych OUTPUT: permutacja A taka, że A[1]≤
A[2]≤ …≤A[n]
for j←2 to n do key←A[j]
wstaw A[j] do posortowanej sekwencji A[1..j-1]
i←j-1
while i>0 and A[i]>key do A[i+1]←A[i]
i--
INPUT: A[1..n] – tablica liczb całkowitych OUTPUT: permutacja A taka, że A[1]≤
A[2]≤ …≤A[n]
for j←2 to n do key←A[j]
wstaw A[j] do posortowanej sekwencji A[1..j-1]
i←j-1
while i>0 and A[i]>key do A[i+1]←A[i]
i--
Analiza algorytmów
Efektywność:
– Czas działania
– Wykorzystanie pamięci
Efektywność jako funkcja rozmiaru wejścia:
– Ilość danych wejściowych (liczb, punktów, itp.) – Ilość bitów w danych wejściowych
Analiza sortowania przez wstawianie
for j←2 to n do key←A[j]
wstaw A[j] do posortowanej sekwencji A[1..j-1]
i←j-1
while i>0 and A[i]>key do A[i+1]←A[i]
i-- A[i+1]:=key
czas c1 c2
? c3 c4 c5 c6 c7
ile razy n
n-1 n-1 n-1
n-1
2 n
j tj
∑
=2( 1)
n
j tj
= −
∑
2( 1)
n
j tj
= −
∑
Określany czas wykonania jako funkcję rozmiaru wejścia
Przypadki: najlepszy/najgorszy/średni
Najlepszy przypadek: elementy już są posortowane →→→→ tj=1, czas wykonania liniowy (Cn).
Najgorszy przypadek: elementy posortowane nierosnąco (odwrotnie posortowane) →→→→ tj=j, czas wykonania kwadratowy (Cn2)
Przypadek „średni” : tj=j/2, czas wykonania kwadratowy (Cn2)
Przypadki: najlepszy/najgorszy/średni
– Dla ustalonego n czas wykonania dla poszczególnych instancji:
1n 2n 3n 4n 5n 6n
Przypadki: najlepszy/najgorszy/średni
– Dla różnych n:
1n 2n 3n 4n 5n 6n
Czas działania
najlepszy przypadek
„średni” przypadek najgorszy przypadek
Przypadki: najlepszy/najgorszy/średni
Analizę najgorszego przypadku stosuje się zwykle wtedy, kiedy czas działania jest czynnikiem krytycznym (kontrola lotów, sterowanie podawaniem leków itp.)
Dla pewnych zadań „najgorsze” przypadki mogą występować dość często.
Określenie przypadku „średniego” (analiza probabilistyczna) jest często bardzo kłopotliwe
Poprawność – praktyczna i całkowita
Praktyczna
Poprawne dane algorytm Wynik
Jeśli ten punkt został
osiągnięty to otrzymaliśmy poprawny wynik
Całkowita poprawność
Poprawne dane algorytm Wynik
i otrzymaliśmy poprawny wynik Ten punkt został
osiągnięty
Dowodzenie
W celu dowiedzenia poprawności algorytmu wiążemy ze specyficznymi miejscami algorytmu stwierdzenia (dotyczące stanu wykonania).
– np., A[1], …, A[k] są posortowane niemalejąco
Warunki początkowe (Precondition) – stwierdzenia, których prawdziwość zakładamy przed wykonaniem algorytmu lub podprogramu (INPUT)
Warunki końcowe (Postcondition) – stwierdzenia, które muszą być prawdziwe po wykonaniu algorytmu lub podprogramu (OUTPUT)
Niezmienniki pętli
Niezmienniki – stwierdzenia prawdziwe za każdym razem kiedy osiągany jest pewien punkt algorytmu (może to zdarzać się wielokrotnie w czasie wykonania algorytmu, np. w pętli)
Dla niezmienników pętli należy pokazać :
– Inicjalizację – prawdziwość przed pierwszą iteracją
– Zachowanie – jeśli stwierdzenie jest prawdziwe przed iteracją to pozostaje prawdziwe przed następną iteracją
– Zakończenie – kiedy pętla kończy działanie niezmiennik daje własność przydatną do wykazania poprawności algorytmu
Przykład: sortowanie przez wstawianie
niezmiennik: na początku każdego wykonania pętli for, A[1…j-1] składa się z
posortowanych elementów
for j=2 to length(A) do key ← A[j]
i ← j-1
while i>0 and A[i]>key do A[i+1] ← A[i]
i--
A[i+1] ←key for j=2 to length(A)
do key ← A[j]
i ← j-1
while i>0 and A[i]>key do A[i+1] ← A[i]
i--
A[i+1] ←key
inicjalizacja: j = 2, niezmiennik jest trywialny, A[1] jest zawsze posortowana zachowanie: wewnątrz pętli while przestawia się elementy A[j-1], A[j-2], …,
A[j-k] o jedną pozycję bez zmiany ich kolejności. Element A[j] jest wstawiany na k-tą pozycję, tak że A[k-1]≤A[k]≤A[k+1]. Stąd A[1..j-1] jest
posortowane.
zakończenie: kiedy pętla się kończy (j=n+1) niezmiennik oznacza, że cała
Notacje asymptotyczne
Cel: uproszczenie analizy czasy wykonania, zaniedbywanie „szczegółów”, które mogą wynikać ze specyficznej implementacji czy sprzętu
– “zaokrąglanie” dla liczb: 1,000,001 ≈ 1,000,000 – “zaokrąglanie” dla funkcji: 3n2 ≈ n2
Główna idea: jak zwiększa się czas wykonania algorytmu wraz ze wzrostem rozmiaru wejścia (w granicy).
– Algorytm asymptotycznie lepszy będzie bardziej efektywny dla prawie wszystkich rozmiarów wejść (z wyjątkiem być może „małych”)
Notacje asymptotyczne
Notacja O (duże O)
– Asymptotyczne ograniczenie górne
– f(n) = O(g(n)), jeżeli istnieje stała c i n0, takie, że f(n) ≤≤≤≤ c g(n) dla n ≥ n0
– f(n) i g(n) są nieujemnymi funkcjami całkowitymi
Korzysta się z niej przy analizie najgorszego przypadku.
) ( n f
( ) c g n⋅
n0 Rozmiar wejścia
Czas działania
Notacja ΩΩΩΩ (duża ΩΩΩ)Ω
– Asymptotyczne ograniczenie dolne – f(n) = Ω(g(n)) jeśli istnieje stała c i n0,
takie, że c g(n) ≤≤≤≤ f(n) dla n ≥ n0
Opisuje najlepsze możliwe zachowanie się algorytmu
Rozmiar wejścia
Czas działania )(
n f
( ) c g n ⋅
n0
Notacje asymptotyczne
Notacje asymptotyczne
Prosta zasada: odrzucamy mniej istotne dla czasu składniki i czynniki stałe.
– 50 n log n jest O(n log n) – 7n - 3 jest O(n)
– 8n2 log n + 5n2 + n jest O(n2 log n)
O jest ograniczeniem górnym więc np. (50 n log n) jest typu O(n5), ale interesuje nas najlepsze możliwe oszacowanie – w tym przypadku jest to O(n log n)
Notacja ΘΘΘ ((((duża ΘΘ ΘΘΘ )
– Dokładne oszacowanie asymptotyczne – f(n) = Θ(g(n)) jeżeli istnieją stałe c1, c2, i
n0, takie, że c1 g(n) ≤≤≤≤ f(n) ≤≤≤≤ c2 g(n) dla n ≥ n0
f(n) = ΘΘΘΘ(g(n)) wtedy i tylko wtedy, gdy f(n) = ΟΟΟΟ(g(n)) i f(n) = ΩΩΩΩ(g(n))
Rozmiar wejścia
Czas działania )(
n f
n0
Notacje asymptotyczne
) (n g c2⋅
) (n g c1⋅
Notacje asymptotyczne
Analogie do zależności pomiędzy liczbami:
– f(n) = O(g(n)) ≅ f ≤≤≤≤ g – f(n) = Ω(g(n)) ≅ f ≥ g – f(n) = Θ(g(n)) ≅ f = = = = g – f(n) = o(g(n)) ≅ f < < < < g – f(n) = ω(g(n)) ≅ f > > > > g
Zwykle zapisujemy: f(n) = O(g(n)) , co formalnie powinno być rozumiane jako f(n) ∈∈∈O(g(n)) ∈
Porównanie czasów wykonania
31 25
19 2n
244 88
31 n4
42426 5477
707 2n2
7826087 166666
4096 20n log n
9000000 150000
2500 400n
1 godzina 1 minuta
1 sekunda
Maksymalny rozmiar problemu (n)