Notatki AiSD Mat

You might also like

Download as pdf or txt
Download as pdf or txt
You are on page 1of 43

Notatki z wykładów

Algorytmy i struktury danych


kier. Matematyka, st. I, sem. III

wykład/laboratoria: dr hab. inż. Marcin Woźniak, Prof. PŚ,


laboratoria: mgr Martyna Kobielnik, dr inż. Dawid Połap

Notatki z wykładów powstały, aby ułatwić studentom pracę nad zestawem zadań w trybie
zdalnej edukacji lub regularnego przygotowywania się do zajęć, co w rezultacie pozwoli osiągnąć
efekty kształcenia i wydajniej doskonalić swoją wiedzę w zadanym przedmiocie. Gorąco zachę-
camy studentów uczęszczających na wykład i zajęcia laboratoryjne do wydrukowania ninieszych
”notatek z wykładów” i wykorzystania ich w trakcie zajęć właśnie jako notatnik, z którym można
łatwiej i lepiej sporządzać notatki z zajęć.

1 Plan wykładów
• Zasady zaliczenia • Algorytm N

• Pojęcia i definicje • Algorytm Karpa-Rabina

• Założoność i analiza algorytmów • Algorytm Boyer-Moore

• Tablice • Kodowanie
• Kodowanie Huffmana
• Stosy
• Kodowanie Shannona-Fano
• Kolejki
• Algorytm Rivesta-Shamira-Adlemana
• Listy
• Sortowanie przez wstawianie
• Grafy
• Sortowanie bąbelkowe
• Drzewa
• Sortowanie szybkie
• Kopce
• Sortowanie przez kopcowanie
• Algorytmy grafowe
• Sortowanie przez scalanie
• Problem komiwojażera
• Heurystyka
• Algorytm Dijkstry • Algorytm Genetyczny
• Algorytm A* • Ewolucja różnicowa
• Algorytm Floyda-Warshalla • Algorytm Kukułki
• Wyszukiwanie wzorca • Algorytmy roju

1
• Algorytm mrówkowy • Algorytm świetlika

• Algorytm pszczeli • Algorytm nietoperza

2 Warunki zaliczenia
Do zaliczenia wymagane jest uzyskanie conajmniej 41 punktów, w tym 30% z każdej grupy zadań
sprawdzające dane efekty kształcenia.
• Kolokwium zaliczeniowe na wykładzie 40 punktów
• Kolokwium praktyczne na laboratoriach 20 punktów
• Zadania z listy w języku Python lub C 30 punktów. Do każdego algorytmu student oddaje:
implementacje + schemat blokowy
• Aktywność i dodatkowe zadania 10 punktów

Gdzie znaleźć listy z zadaniami? Platforma Zdalnej Edukacji Wydziału Matematyki Sto-
sowanej Politechniki Śląskiej (https://platforma.polsl.pl/rms/).

3 Notatki z wykładów
Zmienne to podstawowe typy danych (takie jak liczby czy znaki) są przechowywane w zmien-
nych, czyli w wydzielonym miejscu w pamięci zapisane w postaci bitowej.

Struktury danych to uformowane zbiory złożone z pewnych typów danych, które układane
są w pewien, sprecyzowany sposób. Przykładem jest tablica, stos czy lista.

Algorytm to skończony opis przetwarzania danych wejściowych w dane wyjściowe.


Problem obliczeniowy można przedstawić jako trójkę (I, O, f ), gdzie
• I to zbiór danych wejściowych,
• O to zbiór danych wyjściowych,
• f to funkcja określoną jako f : I → O.
Własności algorytmu
1. Adekwatność – wykonanie obliczeń zgodnie z założonym celem,
2. Jednoznaczność – sprecyzowanie algorytmu w taki sposób, że każdy krok jest automatyczny
i zgodny z założeniem,
3. Powtarzalność – każde wykonanie algorytmu wykonywane jest w identyczny sposób,
4. Własność stopu – kryterium zatrzymania algorytmu jest dokładnie sprecyzowane dla wa-
runków poprawnego i niepoprawnego przebiegu obliczeń,
5. Złożoność obliczeniowa – nakład czasu/zasobów potrzebnych dla prawidłowego wykonania
wszystkich obliczeń.
Załóżmy, że mamy algorytm A, który rozwiązuje problem (I, O, f ).

2
Algorytm częściowo poprawny

∀i ∈ I jezeli A|i, to wynik o ∈ O spelnia f (i) = o, (1)

gdzie A|i oznacza, że dla danego wejścia i algorytm A się zatrzyma.

Własność stopu
∀i ∈ I, A|i. (2)

Algorytm poprawny A będzie algorytmem poprawnym gdy jest zarówno częściowo poprawny
i posiada własność stopu.

Zapis algorytmu

• opisowy – przy użyciu języka naturalnego,

• kod w zadanym języku programowania,

• graficzny – schemat blokowy,

• pseudokod – połączenie języka naturalnego z językiem wyższego rzędu.

Przykład
Użytkownik wprowadza współczynniki równania kwadratowego (ax2 + bx + c = 0, gdzie
a, b, c, x1 , x2 ∈ R), a program wyświetla rozwiązanie.

Konwencja opisowa Komunikat z prośbą o podanie współczynników zostaje wyświetlony.


Następnie użytkownik wprowadza wartości współczynników a, b, c.
Wartość ∆ jest obliczana zgodnie z następującym wzorem
p
∆= b2 − 4ac, (3)

W zależności od znaku ∆ obliczane są pierwiastki

• Jeśli ∆ > 0, obliczane są oba pierwiastki zgodnie z



−b ± ∆
xi = , i ∈ {1, 2}, (4)
2a
i odpowiedni komunikat jest wyświetlony.

• Jeśli ∆ = 0, tylko jeden, podwójny pierwiastek jako


−b
xi = , i ∈ {1, 2}, (5)
2a
i odpowiedni komunikat jest wyświetlony.

• W innym wypadku, komunikat o braku rozwiązań w R.

3
Kod w zadanym języku

double a, b, c, r1, r2;


Console.WriteLine("Podaj kolejno wspolczynniki");
a = Convert.ToDouble(Console.ReadLine());
b = Convert.ToDouble(Console.ReadLine());
c = Convert.ToDouble(Console.ReadLine());

double delta = b * b - 4 * a * c;
if (delta > 0) {
r1 = (-b - Math.Sqrt(delta)) / (2 * a);
r2 = (-b + Math.Sqrt(delta)) / (2 * a);
Console.WriteLine("Rownanie ma dwa rozwiazania rowne "
+ r1 + " oraz " + r2);
}
else if (delta == 0) {
r1 = -b / (2 * a);
Console.WriteLine("Rownanie ma jedno, podwojne
rozwiazanie rowne " + r1);
}
else
Console.WriteLine("Rownanie nie ma rozwiazan
w dziedzinie rzeczywistej.");

Pseudokod to zapis w postaci kolejnych procedur

Start
Wyświetl prośbę o podanie współczynników
Pobierz wartości współczynnik a, b i c
Oblicz wartość ∆ zgodnie z równaniem (3)
if ∆ > 0 then
Oblicz pierwiastki używając równania (4)
Wyświetl wynik
else
if ∆ == 0 then
Oblicz pierwiastki używając równania (5)
Wyświetl wynik
else
Wyświetl komunikat o braku pierwiastków w R
end
end
Stop

4
Schemat blokowy to grficzny zapis kolejnych operacji Cztery podstawowe elementy konwencji
graficznej

• blok graniczny • blok wejścia-wyjścia

• blok decyzyjny • blok operacyjny

Klasyfikacja algorytmów jest problematycznym zadaniem, gdyż klasyfikacji można dokonać


w zależności od różnych cech.
Klasyfikacja ze względu na sposób wykonywania

• sekwencyjne,

• iteracyjne,

• rekurencyjne.

Klasyfikacja ze względu na paradygmat tworzenia

• dziel i zwyciężaj,

• programowanie dynamiczne,

5
• metoda zachłanna,

• programowanie liniowe,

• wyszukiwanie wyczerpujące

• heurystyka.

Analiza algorytmów zleży od komponentów. Przykładowymi są


1. pamięc dyskowa lub RAM (złożoność zasobowa),

2. czas obliczeń (złożoność obliczeniowa).

• wielomianowa, np.: liniowa – (n), czyli T (n) = 12n;


• ograniczona przez wielomian, np.: quasiliowa – (n log n), czyli T (n) = 2n log(n−1)+24
• nie wielomianowa, np.: NP-zupełna lub wykładnicza – (an ), czyli u(n) = en + 2n23

Zdefiniujemy trzy najczęstsze notacje

• Notacja O – funkcja złożoności T (n) jest rzędu f (n), czyli T (n) = O(f (n)) jeżeli

∃n0 ∈ N ∃c ∈ R ∀n > n0 T (n) ¬ c · f (n), (6)

Przykład: 3n2 = O(n3 ) dla c = 1 i n0 = 2,

• Notacja Θ – funkcja złożoności T (n) jest rzędu f (n), czyli T (n) = Θ(f (n)) jeżeli

∃c1 , c2 ­ 0 ∃n0 ∈ N ∀n ­ n0 c1 f (n) ¬ T (n) ¬ c2 f (n), (7)

• Notacja Ω – funkcja złożoności T (n) jest rzędu f (n), czyli T (n) = Ω(f (n)) jeżeli

∃c > 0 ∃n0 ∈ N ∀n ­ n0 cT (n) ¬ f (n). (8)

Z powodu różnorodności danych wejściowych, złożoność będzie występowała w trzech typach


• optymistyczna (ang. optimistic),

• oczekiwana/średnia (ang. average),

• pesymistyczna (ang. worst).

Obliczmy złożoność czasową dla algorytmu liczącego iloczyn liczb całkowitych z przedziału
{1, n}, gdzie n jest zadane przez użytkownika.

T (n) = t1 + t2 + t3 + t3 + (n + 1)t4 + nt3 + nt3 + t1 + t5

T (n) = 2t1 + t2 + (n + 1)(2t3 + t4 ) + t5


Podstawiając pod t6 = 2t3 + t4 , t7 = 2t1 + t2 + t5 , mamy

T (n) = t6 (n + 1) + t7 .
Na podstawie T (n) stwierdzamy, że algorytm ma liniową złożoność.

6
Krok Operacja Czas wykonania
1 Wyświetl komunikat 1 · t1
2 Wczytaj liczbę n 1 · t2
3 k:=1 1 · t3
4 i:=0 1 · t3
5 jeżeli i == n przejdź do kroku 8 (n + 1) · t4
6 i++ n · t3
7 k*=i n · t3
8 Wyświetl k 1 · t1
9 Koniec 1 · t5

Tablica jednowymiarową to zbiór zmiennych pewnego, określonego typu. Każda tablica ma


zadeklarowany rozmiar, a więc wyznaczone miejsce w pamięci. Dla tablicy n–elementowej zmien-
nych np. typu int, zajęte zostanie dokładnie n · sizeof(int). W trakcie działania programu, raz
zadeklarowana wielkość nie może ulec zmianie. Umiejscowienie tablicy w pamięci jest połączone
ze wskaźnikiem, który wskazuje, na jej początek. Każda tablica jest indeksowana od 0 do n − 1,
a każdy numer jest nazywany indeksem. Odwołanie do elementu odbywa się poprzez indeks za
pomocą operatora [].

Realokacja pamięci wykorzystywana jest, gdy potrzebne jest zwiększenie wymiaru tablicy.

Algorithm 1 Relokacja pamięci


Start
Zdefiniuj tablicę tab złożoną z n elementów
Zdefiniuj nowy wymiar k (k > n)
Utwórz nową, pustą tablicę newT ab o wymiarze k
for i ← 0 to n by 1 do
newT ab[i] = tab[i]
end
Usuń tablicę tab z pamięci
Zwróć tablicę newT ab o wymiarze k
Stop

Stos to dynamiczna struktura liniowo uporządkowanych danych, w której mamy dostęp jedynie
do ostatniego elementu (wierzchołka stosu). Zapisywanie i pobieranie danych jest wykonywane
za pomocą strategii np. Last In – First Out (LIFO).

7
Stosy kojarzone są ze stertą książek, z którą jedyne, co możemy zrobić to położyć na szczy-
cie nową książkę lub zdjąć jedną z góry (aby sterta się nie zawaliła, jesli wyjmiemy z dołu).
Przykładowe zastosowania stosów

• przechowywanie zmiennych lokalnych (np.: w C),

• przechowywanie rejestrów,

• składanie przekształceń przestrzennych w grafice,

• Odwrotna Notacja Polska (ONP).

Odwotna Notacja Polska (ONP) to sposób notacji wyrażeń algebraicznych, w którym naj-
pierw zapisujemy operandy, następnie odpowiednio operatory. Jest to tzw. zapis postfiksowy.

Uwaga: W ONP nie używa się nawiasów, co jest dobrym rozwiązaniem upraszaczającym ob-
liczenia dla komputerów. Dlatego też stosuje się ją w kompilatorach języków wyższego rzędu.
ONP wykorzystuje stosy

• do przechowywania wyników pośrednich podczas obliczania wartości danego wyrażenia,

• do przechowywania nawiasów oraz operatorów podczas konwersji notacji infiksowej na post-


fiksową.

Notacja infiksowa Notacja postfiksowa


9-3*2 932*-
(4-1)*(3+5)ˆ2 41-35+2ˆ*

Implementacja stosu
struct stos {
i n t r o z m i a r ; // maksymalny r o z m i a r s t o s u
i n t gora ;
i n t ∗ elementy ;
};

s t r u c t s t o s ∗ NowyStos ( i n t pojemnosc ) {
s t r u c t s t o s ∗ wskaznik =( s t r u c t s t o s ∗ ) m a l l o c ( s i z e o f ( s t r u c t s t o s ) ) ;

wskaznik−>r o z m i a r=pojemnosc ;
wskaznik−>g o r a =−1;
wskaznik−>elementy =( i n t ∗ ) m a l l o c ( s i z e o f ( i n t ) ∗ pojemnosc ) ;

r e t u r n wskaznik ;
}
Ilość elementów w stosie
i n t S i z e ( s t r u c t s t o s ∗ wskaznik ) {
r e t u r n wskaznik−>g o r a +1;
}

8
Czy stos jest pusty?
i n t IsEmpty ( s t r u c t s t o s ∗ wskaznik ) {
r e t u r n wskaznik−>g o r a==−1;
}
Czy stos jest pełen?
i n t I s F u l l ( s t r u c t s t o s ∗ wskaznik ) {
r e t u r n wskaznik−>g o r a == wskaznik−>rozmiar −1;
}
Nowy element x na szczyt stosu
v o i d Push ( s t r u c t s t o s ∗ wskaznik , i n t nowyElement ) {
i f ( i s F u l l ( wskaznik ) ) p r i n t f ( ” s t o s j e s t p e l e n \n ” ) ;
e l s e wskaznik−>elementy[++wskaznik−>wskaznik ]= nowyElement ;
}

Kolejka FIFO (First In – First Out) nazywamy dynamiczną strukturę liniowo uporządko-
wanych danych, w której mamy dostęp do elementów na początku i na końcu. Możemy usuwać
elementy z początku kolejki, a dodawać na końcu.
Intuicyjnie kojarzone są ze zwykłą kolejką ludzi, np. studentów do dziekanatu czy kolejkę do
kasy w hipermarkecie.

Kolejka priorytetowa to kolejka, w której każdy element ma swój priorytet, względem któ-
rego jest ustawiony. Im wyższy priorytet, tym bliżej początku kolejki.
Przykładem takiej kolejki jest kolejka w sklepie, gdzie kobiety w ciąży mają pierwszeństwo.
Podobnie jak stos, kolejka ma pewną pojemność. Jednakże, przepełnienie nie powoduje tak
dużych problemów – nadwymiarowy element jest wstawiany w miejsce najwcześniejszego. Im-
plementacja tej struktury danych korzysta z buforu cyklicznego. Technicznie, taki bufor jest
implementowany jako tablica z dwoma wskaźnikami lub zmiennymi indeksującymi. W kolejce
priorytetowej każdy element posiada dodatkową wartość nazywaną kluczem, który odpowiada
za ważność tego elementu. Umiejscowienie danego obiektu w kolejce zależy od klucza, a więc
dodatkowo będzie potrzebny mechanizm przeszukiwania lub sortowania.

Uwaga: Poprawna implementacja kolejki powinna zawierać mechanizm powiadomienia i ostrze-


żenia o przepełnieniu.

Lista to dynamiczna struktura liniowo uporządkowanych danych, w której mamy dostęp do


każdego z elementów, ponieważ każdemu rekordowi odpowiada pole zawierające adres kolejnego
rekordu listy.

Uwaga: Odnośnik do pierwszego rekordu nazywamy korzeniem listy, natomiast wskaźnik ostat-
niego elementu jest pusty.
Rodzaje list

1. Listy jednokierunkowe – każdy element posiada wskaźnik do swojego następnika. Przeszu-


kiwanie zaczyna się od korzenia i jest przeprowadzane w jednym kierunku.

9
2. Listy dwukierunkowe – każdy element posiada wskaźnik zarówno do swojego poprzednika,
jak i następnika, co pozwala na przeprowadzanie przeszukiwania w obu kierunkach.
3. Listy cykliczne – pierwszy element listy jest następnikiem ostatniego, dzięki czemu tworzy
się cykl. Listy cykliczne mogą być jednokierunkowe albo dwukierunkowe.
4. Listy z wartownikiem – lista, która zawiera dodatkowy element zwany wartownikiem, który
jest niewidoczny dla programisty stosującego tę strukturę. Wspomniany obiekt występuje
w pustej liście.

Graf nieskierowany jest to para G = (V, E), gdzie


• V jest zbiorem skończonym wierzchołków,
• E ⊆ {(u, v) : u, v ∈ V, u 6= v} jest zbiorem krawędzi.

Graf skierowany jest to para G = (V, E), gdzie


• V jest skończonym zbiorem wierzchołków,
• E ⊆ {(u, v) : u, v ∈ V } = V × V jest zbiorem krawędzi (strzałek).

Uwaga: Definicja grafu skierowanego dopuszcza krawędzie postaci (v, v) nazywane pętlami.

Sąsiedztwo, a incydentność dla grafu zorientowanego i e = (u, v) ∈ E


• u nazywamy początkiem e,
• v nazywamy końcem e.
Dla zadanego grafu G = (V, E) będziemy nazywali
• dwa wierzchołki u, v ∈ V sąsiednimi, jeżeli {u, v} ∈ E,
• dwie krawędzie e1 , e2 sąsiednimi, gdy e1 ∩ e2 6=,
• wierzchołek v ∈ V incydentny z krawędzią e ∈ E, jeżeli v ∈ e.

Stopień wierzchołka jest cechą każdego wierzchołka v ∈ V , oznaczany jako degG (v). Stopień
charakteryzuje się ilością krawędzi dochodzących do tego wierzchołka.

Reprezentacja grafu dla zadanego grafu nieskierowanego G = (V, E), gdzie V = {1, 2, . . . , n}
i E = {e1 , e2 , . . . , em } ma
1. Postać graficzną,
2. Macierz sąsiedztwa A = A(G) ∈ Mn×n (Z)
(
0, i, j ∈
/E
Aij = (9)
1, i, j ∈ E

3. Macierz incydencji B = B(G) = Mn×m (Z)


(
0, i∈/ ej ,
Bij = (10)
1, i ∈ ej

10
Reprezentacja grafu dla zadanego grafu skierowanego G = (V, E), gdzie V = {1, 2, . . . , n} i
E = {e1 , e2 , . . . , em } ma

1. Postać graficzna,

2. Macierz sąsiedztwa A = A(G) ∈ Mn×n (Z)


(
0, i, j ∈
/E
Aij = (11)
1, i, j ∈ Eoraz i jest końcem ei

3. Macierz incydencji B = B(G) = Mn×m (Z)



−1, i ∈ ei oraz i jest końcem ei


Bij = 0, i∈/ ej , (12)

i ∈ ej oraz i jest początkiem ei

1,

Dla zadanego grafu G, zapiszmy jego macierz sąsiedztwa.


 
0 1 1
A(G) =  1 0 0 
 
1 0 0

Ścieżka to ciąg wierzchołków {v0 , v1 . . . , vn } prowadzących z wierzchołka v0 do vn (gdzie po-


między każdą parą sąsiednich wierzchołków jest łącząca krawędź).

Droga to ścieżka, w której żaden wierzchołek się nie powtarza.

Podgraf to graf powstały po usunięciu pewnych wierzchołków.

Cykl to droga zamknięta, czyli jeśli D = (v0 , e1 , v1 , e2 , . . . , ek , vk ) będzie drogą w grafie, wtedy
jeżeli v0 = vk , to D nazywamy cyklem (lub drogą zamkniętą).

Spójność to cecha graf niezorientowanego G = (V, E), jeżeli dla każdych dwóch wierzchołków
u, v ∈ V istnieje droga o końcach i u i v.

Acykliczność w grafie jest jeśli nie będzie istniał w nim żaden cykl.

11
Drzewo to dynamiczna struktura danych, która składa się z węzłów, w których przechowy-
wane są dane i krawędzi, wyrażających zależności między poszczególnymi węzłami. Drzewo jest
szczególnym przypadkiem grafu (jeżeli jest to graf acykliczny i spójny).

• Rodzic – węzeł, który jest w relacji z co najmniej jednym węzłem znajdującym się niżej od
niego w strukturze.

• Dziecko – węzeł, który posiada rodzica.

• Rodzeństwo – dzieci jednego rodzica.

• Korzeń – węzeł znajdujący się na górze drzewa i nie posiadający rodzica.

• Węzeł wewnętrzny – węzeł, który posiada zarówno rodzica, jak i co najmniej jedno dziecko.

• Liść - węzeł, który nie posiada dzieci.

Podstawowe pojęcia związane z drzewami

• Ścieżka – ciąg węzłów w0 , w1 , . . . , wk takich, że wi jest dzieckiem węzła wi−1 . Długość takiej
ścieżki jest równa k.

• Poziom węzła (głębokość węzła) – długość ścieżki (ilość krawędzi) prowadzącej od korzenia
do tego węzła.

• Wysokość drzewa – maksimum z poziomów węzłów. (Wysokość drzewa pustego wynosi -1.)

• Stopień węzła – ilość dzieci danego rodzica (węzłów z niego wychodzących).

• Stopień drzewa – maksimum ze stopni węzłów. (Stopień drzewa pustego wynosi -1.)

Własności:

1. Z każdego węzła istnieje dokładnie jedna ścieżka prowadząca do dowolnego innego węzła.

2. W drzewach nie występują cykle.

3. Każdy węzeł, który nie jest korzeniem posiada dokładnie jednego rodzica.

4. Węzeł może mieć dowolną liczbę dzieci.

5. Relacje rodzic–dziecko można rozszerzyć do relacji pomiędzy przodkami i potomkami.

6. Dowolny węzeł wraz ze swoimi potomkami tworzy podrzewo wyjściowego drzewa.

• Węzeł A jest rodzicem węzłów


B i C.

• Węzły D i E są dziećmi węzła B,


a jednocześnie rodzeństwem.

• Wysokość drzewa jest równa 3.

• Stopień węzła B wynosi 2.

12
• Stopień drzewa jest równy 2.

Binary Search Tree Drzewem przeszukiwań binarnych nazywamy drzewo binarne, w którym
węzły są oznaczone etykietami (przypisane są klucze), a wśród nich można wprowadzić relację
mniejszości.
Musi być spełniona własność drzewa BST, czyli dowolny węzeł jest niemniejszy od swojego
lewego dziecka i niewiększy od swojego prawego dziecka.

• Element najmniejszy w takim drzewie znajdziemy, jeśli w trakcie przechodzenia niżej za-
wsze będziemy wybierać lewą krawędź.

• Element największy w takim drzewie znajdziemy, jeśli w trakcie przechodzenia niżej zawsze
będziemy wybierać prawą krawędź.

Wstawianie nowego elementu jako algorytm szukania miejsca dla nowego elementu można
przedstawić w krokach.

1. Znajdujemy się przy korzeniu drzewa.

2. Jeśli nowy element ma wartość mniejszą od korzenia, to przechodzimy w lewo, a jeśli


większą, to w prawo. W przypadku równości wybór jest dowolny.

3. Przechodząc do kolejnego węzła posługujemy się metodą z punktu 2.

4. Proces powtarzamy tak długo, aż znajdziemy wolne miejsce (brak kolejnego węzła lub
liścia) dla nowego elementu.

5. Wstawiamy nowy element do drzewa.

Uwaga: Analogiczne jest przeszukiwanie drzewa. Co ważne, własność drzewa BST daje pew-
ność, że szukanego elementu nie ma w poddrzewie, którym się nie zajmujemy.

Binary Space Partitioning Tree Drzewa Binarnego Podziału Przestrzeni, czyli drzewa bi-
narne wykorzystywane do podziału (np. na sektory) przestrzeni 3D lub płaszczyzn 2D (trakto-
wanych jako rzuty ścian na płaszczyznę) są rozwinięciem praktycznym drzew BST.

13
Kopiec (heap) to specjalny rodzaj drzewa binarnego T = (V, E, r) z kluczami. Każdy wierz-
chołek jest nie większy niż jego poprzednik. Z tego warunku powstają własności

• W korzeniu znajduje się największy element,

• Na ścieżkach od korzenia do liścia, elementy są posortowane nierosnąco.

Kopiec zupełny to kopiec i drzewo binarne w jednym. Wszystkie poziomy tego kopca są
całkowicie zapełnione z wyjątkiem co najwyżej ostatniego, który jest spójnie wypełniony od
lewej strony.
Kopce jako struktury danych mają zastosowanie w

• sortowaniu (algorytm sortowania przez kopcowanie),

• sposobie implementacji kolejki priorytetowej.

Drzewo Decyzyjne jest indukcyjną metodą podejmowania decyzji. Zbudowane jest z węzłów,
gałęzi i liści. Węzły są rozumiane jako testy na danych atrybutach, gałęzie to wyniki testu, a
liście etykiety klas.

Decyzja o ładnej pogodzie: 1 - ładna, 0 - brzydka


Drzewa decyzyjne buduje się poprzez działanie zwane zstępowaniem. Jest to działanie rozu-
miane jako wyjście z jednego korzenia do kolejnego poziomu (poddrzewa). W efekcie, podejmując
jedną decyzję, możemy spotkać się z konsekwencją podjęcia kolejnej.

14
Kryterium stopu Załóżmy, że drzewo jest konstruowane w sposób rekurencyjny. Zaczynamy
od korzenia i przemieszczamy się w dół podejmując decyzję czy tworzyć kolejny liść czy nie. Jeśli
nasze kryterium będzie spełnione, stworzymy liść i na podstawie podzbioru ustalamy etykietę.
W drugim przypadku, tworzymy węzeł i wybieramy test.
1. Jeśli podzbiór zawiera przykłady należące tylko do jednej klasy
2. Podzbiór jest pusty
3. Znacząca większość pochodzi z jednej klasy (generalizacja)
4. Zbiór testów jest pusty

Problem komiwojażera określa się gdy mamy zadany zbiór miast. Komiwojażer planuje
odwiedzić każde miasto w pobliżu, ale dokładnie raz. Dodatkowo, po odwiedzeniu ostatniego
miasta, chce wrócić do aktualnego (czyli do punktu wyjścia). Problem polega na znalezieniu jak
najkrótszej trasy pomiędzy wszystkimi miastami, ale tak, aby w każym mieście, komiwojażer
pojawił się tylko raz i swoją wędrówkę zakończył w pierwszym z nich.

Algorytm Prima stosowany jest do znajdowania drzewa o najmniejszym koszcie, a dokładniej


minimalnego drzewa rozpinającego.
• Wierzchołki grafu dzielimy na dwa zbiory – T i W . Punkt wyjścia, czyli pierwszy wierz-
chołek umieszczamy w zbiorze T , a pozostałe w zbiorze W .
• W każdym kroku algorytmu, wybieramy wierzchołek ze zbioiru W taki, że łączy się krawę-
dzią z wierzchołkiem w wzbiorze T , ale o najmniejszym koszcie.
• Ten wierzchołek dodajemy do zbioru T i usuwamy go ze zbioru W . Algorytm wykonujemy
do momentu, aż zbiór W nie będzie pusty.

Algorytm Dijkstry został zaproponowany przez Edsgera Dijkstrę służy do znajdowania naj-
krótszej ścieżki w grafie. Algorytm zakłada, że koszt na krawędziach grafu jest liczbą dodatnią.

• Tworzymy dwa zbiory wierzchołków – Q i S. Wszystkie wierzchołki należą do zbioru Q.


– Koszt startowego ustawiamy na 0.
– Dodatkowo, dla każdego wierzchołka u (prócz startowego v) ustawiamy koszt dojścia
na nieskończość, czyli d(u) = ∞.
• Poprzednik wierzchołka u oznaczymy jako p(u) i ustawiamy na niezdefiniowanego (ang.
undefined ). Poprzedniki wyznaczają ścieżkę (w kierunku odwrotnym).

15
Algorithm 2 Algorytm Dijkstry
Start
Tworzymy dwa zbiory wierzchołków – Q i S
while zbiór Q nie jest pusty do
Wybieramy ze zbioru Q wierzchołek u o najmniejszej wartości d(u)
Wierzchołek u usuwamy ze zbioru Q
Wierzchołek u dodajemy do zbioru S
Dla każdego sąsiada w ∈ Q wierzchołka u
if d(w) > d(u) + t(ewu ) then
obliczamy koszt przejścia do wierzchołka w jako

d(w) = d(u) + t(ewu ) (13)

gdzie t(ewu ) jest wagą krawędzi pomiędzy wierzcholkiem u i w

end
Następnie, wierzchołek u staje się poprzednikiem w, czyli p(w) = u

end
Stop

u 0 1 2 3 4 5
d(u) 0 3 4 6 3 5
p(u) −1 0 1 5 0 4

Algorytm A* został opisany już w 1968 roku przez Petera Harta, Nilsa Nilssona i Bertmama
Raphaela. Idea jest oparta na algorytmie Dijkstry. Algorytm poszukuje ścieżki pomiędzy dwoma
wierzchołkami w taki sposób, aby zminimalizować funkcję

f (v) = g(v) + h(v), (14)

gdzie g(v) jest to droga pomiędzy wierzchołkiem początkowym, a aktualnym v (czyli suma kosz-
tów na krawędziach), a h(v) to przewidywana droga od v do docelowego wierzchołka.

16
Algorithm 3 Algorytm A*
Start
Tworzymy dwa zbiory wierzchołków – Q i S
while zbiór Q nie jest pusty do
Wybieramy ze zbioru Q wierzchołek u o najmniejszej wartości d(u)
Wierzchołek u usuwamy ze zbioru Q
Wierzchołek u dodajemy do zbioru S
Dla każdego sąsiada w ∈ Q wierzchołka u sprawdamy sąsiadów

f (v) = g(v) + h(v) (15)

gdzie g(v) jest to droga pomiędzy wierzchołkiem początkowym, a aktualnym v (czyli suma
kosztów na krawędziach), a h(v) to przewidywana droga od v do docelowego wierzchołka

if wierzchołek ma sąsiada w Q z mniejszą wartościa f then


pomijamy ten wierzchołek
end
if wierzchołek ma sąsiada w S z mniejszą wartościa f then
pomijamy ten wierzchołek
else
dodajemy ten wierzchołek do Q
end
Następnie, wierzchołek przekładamy do S

end
Stop

Algorytm Floyda-Warshalla służy do znajdowania najkrótszej drogi z jednego wierzchołka


do drugiego. W wyniku działania algorytmu otrzymujemy dwie macierze: d, której elementy d[i, j]
są najkrótszymi drogami między wierzchołkami i i j, i p, przy pomocy której możemy odtworzyć
najkrótszą drogę. Początkowo elementy tych macierzy są zadane następująco:

0, jeśli i = j


d0 [i, j] = waga krawędzi (i, j), jeśli (i, j) ∈ E (16)

∞, jeśli (i, j) 6∈ E

(
i, jeśli (i, j) ∈ E
p0 [i, j] = (17)
0, w przeciwnym wypadku

17
Algorithm 4 Algorytm Floyda-Warshalla
Input: Graf skierowany z wagami G = (V, E)
Output: Macierze d i p
Start
Definiujemy macierze d0 i p0
i=0
foreach u ∈ V do
foreach v ∈ V, v 6= u do
foreach w ∈ V, w 6= u, v do
l = di [v, u] + di [u, w]
if l < di [v, w] then
di+1 [v, w] = l
pi+1 [v, w] = pi [u, w]
end
else
di+1 [v, w] = di [v, w]
pi+1 [v, w] = pi [v, w]
end
end
end
i++
end
Zwróć ostatnie wyznaczone di i pi
Stop

Zobaczmy jak działa algorytm Floyda-Warshalla.

1
3 4 5
5 7
4
2 1
1 2 1
6
2 7 6
3

Na podstawie rysunku wyznaczamy macierze d0 i p0 . Na tym etapie znamy tylko bezpośrednie


połączenia pomiędzy wierzchołkami.

d0 1 2 3 4 5 6 7 p0 1 2 3 4 5 6 7
1 0 1 5 ∞ ∞ ∞ ∞ 1 0 1 1 0 0 0 0
2 ∞ 0 2 ∞ ∞ ∞ ∞ 2 0 0 2 0 0 0 0
3 ∞ ∞ 0 ∞ ∞ ∞ ∞ 3 0 0 0 0 0 0 0
4 7 ∞ ∞ 0 1 ∞ ∞ 4 4 0 0 0 4 0 0
5 ∞ ∞ ∞ ∞ 0 1 ∞ 5 0 0 0 0 0 5 0
6 2 ∞ ∞ 4 ∞ 0 ∞ 6 6 0 0 6 0 0 0
7 6 ∞ ∞ ∞ ∞ 3 0 7 7 0 0 0 0 7 0

Odkrywamy drogi prowadzące przez wierzchołek u = 1. W tym celu rozważamy wszystkie

18
pary wierzchołków v, w ∈ V różnych od u i sprawdzamy, czy d0 [v, u] + d0 [u, w] < d0 [v, w].

• Dla v = 2, 3 nie istnieje droga prowadząca przez u = 1 do żadnego z wierzchołków. Przepi-


sujemy odpowiednie wartości do macierzy d1 i p1 .

• v=4

– w=2
Sprawdzamy odległości

d0 [4, 1] + d0 [1, 2] = 8 < d0 [4, 2] = ∞,

więc została odkryta droga z wierzchołka 4 do 2 przechodząca przez 1. Umieszczamy


tę wartość w macierzy d1 [4, 2] = 8 i przyjmujemy p1 [4, 2] = p0 [1, 2] = 1
– w=3
Sprawdzamy odległości

d0 [4, 1] + d0 [1, 3] = 12 < d0 [4, 2] = ∞

Odkryliśmy kolejną drogę, tym razem z wierzchołka 4 do 3. Umieszczamy tę wartość


w macierzy d1 [4, 3] = 12 i przyjmujemy p1 [4, 3] = p0 [1, 3] = 1
– Wierzchołek u = 1 nie jest połączony z pozostałymi wierzchołkami w = 4, 5, 6, 7, więc
w tych przypadkach żadna nowa droga nie zostanie odkryta.

• Powtarzamy to rozumowanie dla pozostałych wierzchołków v. W wyniku otrzymujemy


macierze d1 , p1 przedstawione w tablicy. Kolorem niebieskim zostały zaznaczone zmienione
wartości.

d1 1 2 3 4 5 6 7 p1 1 2 3 4 5 6 7
1 0 1 5 ∞ ∞ ∞ ∞ 1 0 1 1 0 0 0 0
2 ∞ 0 2 ∞ ∞ ∞ ∞ 2 0 0 2 0 0 0 0
3 ∞ ∞ 0 ∞ ∞ ∞ ∞ 3 0 0 0 0 0 0 0
4 7 8 12 0 1 ∞ ∞ 4 4 1 1 0 4 0 0
5 ∞ ∞ ∞ ∞ 0 1 ∞ 5 0 0 0 0 0 5 0
6 2 3 7 4 ∞ 0 ∞ 6 6 1 1 6 0 0 0
7 6 7 11 ∞ ∞ 3 0 7 7 1 1 0 0 7 0

Przyjmując u = 2 znajdujemy drogi między wierzchołkami, które mogą przechodzić przez 1


i 2. Znowu rozważać będziemy wszystkie pary
v, w ∈ V wierzchołków różnych od u.

• Dla v = 1 rozważymy tylko przypadek w = 3, ponieważ nie istnieje żadna odkryta droga
z 2 do innych wierzchołków. Sprawdzamy, że

d1 [1, 2] + d0 [2, 3] = 3 < d0 [1, 3] = 5.

To oznacza, że znaleźliśmy drogę z 1 do 3, która jest krótsza niż wcześniej znana. Zapisujemy
jej długość d2 [1, 3] = 3 i ustalamy wartość p2 [1, 3] = p1 [2, 3] = 2. Na rysunku kolorem
czerwonym zaznaczona jest „stara” droga z wierzchołka 1 do 3, a na niebiesko nowo odkryta,
lepsza droga.

19
• Powtarzamy rozumowanie dla pozostałych wierzchołków v. Macierze d2 i p2 wyznaczone
w tym kroku przedstawia tablica.

d2 1 2 3 4 5 6 7 p2 1 2 3 4 5 6 7
1 0 1 3 ∞ ∞ ∞ ∞ 1 0 1 2 0 0 0 0
2 ∞ 0 2 ∞ ∞ ∞ ∞ 2 0 0 2 0 0 0 0
3 ∞ ∞ 0 ∞ ∞ ∞ ∞ 3 0 0 0 0 0 0 0
4 7 8 10 0 1 ∞ ∞ 4 4 1 2 0 4 0 0
5 ∞ ∞ ∞ ∞ 0 1 ∞ 5 0 0 0 0 0 5 0
6 2 3 5 4 ∞ 0 ∞ 6 6 1 2 6 0 0 0
7 6 7 9 ∞ ∞ 3 0 7 7 1 2 0 0 7 0

Na każdym kroku, przyjmując u = k, uwzględniamy drogi, które mogą przechodzić przez


dowolne z dotąd rozważonych wierzchołków 1, 2, . . . , k. Po zakończeniu całej procedury otrzymu-
jemy macierze przedstawione w tablicy.

d 1 2 3 4 5 6 7 p 1 2 3 4 5 6 7
1 0 1 2 ∞ ∞ ∞ ∞ 1 0 1 2 0 0 0 0
2 ∞ 0 2 ∞ ∞ ∞ ∞ 2 0 0 2 0 0 0 0
3 ∞ ∞ 0 ∞ ∞ ∞ ∞ 3 0 0 0 0 0 0 0
4 4 5 7 0 1 2 ∞ 4 6 1 2 0 4 5 0
5 3 4 6 5 0 1 ∞ 5 6 1 2 6 0 5 0
6 2 3 5 4 5 0 ∞ 6 6 1 2 6 4 0 0
7 5 6 8 7 8 3 0 7 6 1 2 6 4 7 0

Z macierzy d możemy odczytać jaka jest długość najkrótszej drogi z 7 do 3 (d[7, 3] = 8). W
celu jej odtworzenia wykorzystamy macierz p w następujący sposób:
• Szukamy drogi 7−? − 3.
• Odczytujemy p[7, 3] = 2, stąd najlepsza droga z 7 do 3 przechodzi przez wierzchołek 2,
więc ma postać 7−? − 2 − 3.
• Teraz szukamy najlepszego sposobu na dostanie się z 7 do 2. Odczytujemy p[7, 2] = 1, a
więc szukamy drogi 7−? − 1 − 2 − 3.
• p[7, 1] = 6. Odkrywamy kolejny wierzchołek na trasie 7−? − 6 − 1 − 2 − 3.
• p[7, 6] = 7, co oznacza, że najlepszym połączeniem między 7 a 6 jest krawędź łącząca te
wierzchołki. Odtworzyliśmy więc całą najlepszą drogę z 7 do 3 w postaci 7 − 6 − 1 − 2 − 3.
Droga ta została oznaczona kolorem niebieskim.

1
3 4 5
5 7
4
2 1
1 2 1
6
2 7 6
3

20
Wyszukiwanie wzorca jest klasycznym problemem przetwarzania tekstów. Problem ten wy-
stępuje również w biologii (wyszukiwanie pewnych kombinacji genów) czy data mining (problemy
klasyfikacji). Załóżmy, że definiujemy go przy użyciu danych wejściowych:

• alfabet – skończony zbiór symboli Σ, np.: Σ = {a, b, . . . , z},

• tekst – tablica T wymiaru n, gdzie każdy element należy do alfabetu, czyli T [i] ∈ Σ,
i ∈ {0, 1, . . . , n − 1},

• wzorzec - tablica P wymiaru m, gdzie P [i] ∈ Σ, i ∈ {0, 1, . . . , n − 1}.

Wyszukujemy wszystkie wystąpienia wzorca P w tekście T . Istnieje możliwość, że wzorzec


może występować z pewnym przesunięciem s. Taka sytuacja zachodzi gdy 0 ¬ s ¬ n − m i
T [i] = P [j] (i ∈ {s + 1, s + 2, . . . , s + m}, j ∈ 1, 2, . . . , m).

Algorytm N (z ang. naive czyli naiwny ) jest najprostszym algorytmem tego typu. Złożo-
ność tego algorytmu jest równa Θ((n−m+1)m). Idea polega na porównywaniu każdego elementu
z tekstu ze wzorcem i jego przesuwanie do przodu.

Algorithm 5 Algorytm N
Start
Pobierz tekst T
Pobierz wzorzec P
n := length(T )
m := length(P )
i := 0
for i < n − m do
j := 0
For (j < m) if (T [i + j] = P [j]) then
if (i + j == m − 1) then
Wypisz wzorzec

j++
i++

end
Stop

21
Algorytm Karpa-Rabina podobnie jak dla algorytmu N, czas działania ma określany jako
pesymistyczny i wynosi tyle samo, czyli Θ((n − m + 1)m), a w innej notacji O(n2 ). Alfabet
traktujemy jako zbiór liczb (o zadanej podstawie d) i tak je porównujemy. W efekcie, tekst
będzie jedną, n-elementową liczbą. W obliczeniach najczęściej wykorzystujemy funkcję kodującą.

Algorithm 6 Algorytm Karpa-Rabina


Start
Pobierz tekst T
Pobierz wzorzec P
n := length(T )
m := length(P )
P 0 = f (P )
i := 0
for i < n do
Stwórz tablicę K o wymiarze m
j := 0
Forj < m K[j] = T [i + j]
K 0 = f (K)
if K 0 == P 0 then
Wypisz wzorzec

i++

end
Stop

Czy tekst abaaba zawiera abaab.


Jako funkcję haszującą przyjmijmy następującą

f (u2 ) = u1 0, (18)

gdzie u2 jest słowem zapisanym w postaci bitowej, a u1 0 w postaci dziesiętnej. Przeliczamy


wzorzec korzystając z powyższej funkcji w następujący sposób

f (abaab2 ) = f (010012 ) = (0 · 24 + 1 · 23 + 0 · 22 + 0 · 21 + 1 · 20 )1 0 = 9.

Tekst: abaaba
+++++
Wzorzec: 9

Porównanie: 9 ? 9 - tak

f (abaab2 ) = f (010012 ) = 9

Algorytm Boyer-Moore jest stosowany często w przeglądarkach internetowych, notatnikach


i wszędzie tam gdzie znajduje się polecenie Szukaj. Polega na porównywaniu z prawej strony
wzorca. Jeżeli wzorzec nie będzie pasował, to algorytm korzysta z dwóch funkcji nazywanych
good-suffix shift oraz bad-character shift. W praktyce algorytm zakłada, że jeżeli sprawdzany
znak nie pasuje do wzorca, to można przeskoczyć w tekście o jego długość. Przeważnie skoki są
większe niż 1 element. Stąd duża efektywność algorytmu.

22
Algorithm 7 Algorytm Boyer-Moore
Start
Pobierz tekst T
Pobierz wzorzec P
n := length(T )
m := length(P )
i := m − 1
for (i > n − 1) do
j := m − 1
for (j < m − 1) do
if (P [j] == T [i]) then
if (j == 0) then
Wypisz wzorzec

else
j−−
i−−

end
else
i+ = m − M in(j, 1 + last[T [i]])
j =m−1

end
end
end
Stop

Tekst: Zdam szybko i efektywnie AiSD


| | | | ||| |
efe | | | ||| |
| | | ||| |
efe | | ||| |
| | ||| |
efe | ||| |
| ||| |
efe ||| |
||| |
efe|| |
|| |
efe| |
| |
efe | i tak dalej...
|
efe

Odległość Levenshteina jest miarą odmienności skończonych ciągów znaków. Oznacza naj-
mniejszą liczbę działań prostych, przeprowadzająca jeden ciąg znaków w drugi.

23
Kodowanie i dekodowanie to przyporządkowanie wybranym elementom jakiegoś innego al-
fabetu np.: binarnego. Dekodowanie jest procesem odwrotnym do kodowania.

Kodowanie Shannona-Fano to klasyczna metoda kompresji bezstratnej.

• Załóżmy, że mamy podany zbiór do zakodowania S = {s1 , s2 , . . . , sn } i zbiór prawdopodo-


bieństw ich wystąpienia P = {p1 , p2 , . . . , pn }.

• Oba zbiory należy posortować względem malejącego prawdopodobieństwa.

• Dzielimy zbiór S na dwa podzbiory w taki sposób, aby różnica sum prawdopodobieństw
między nimi była jak najmniejsza. Pierwszemu zbiorowi przydziel wartość 0, a drugiemu
1.

• Teraz, każdy ze zbiorów rozpatrujemy osobno i ponownie dzielimy dwa podzbiory i przy-
pisujemy 0 i 1. Każdy ze zbiorów dzielimy do momentu otrzymania zbiorów jednoelemen-
towych.

Zakoduj alfabet S = {a, i, s, d} z prawdopodobieństwem P = {0.5, 0.2, 0.15, 0.15}.

Znak
a 0
i 1 0
s 1 1 0
d 1 1 1

Kodowanie Huffmana to jednen z najprostszych i najłatwiejszych do implementacji algoryt-


mów kompresji.

• Załóżmy, że mamy podany zbiór do zakodowania S = {s1 , s2 , . . . , sn } i zbiór prawdopodo-


bieństw ich wystąpienia P = {p1 , p2 , . . . , pn }.

• Tworzymy listę drzew binarnych, które w węzłach będą miały dwie wartości – symbol i
prawdopodobieństwo.

• W pierwszym kroku, to drzewo jest złożone tylko z korzenia.

• Dopóki liści jest więcej niż jedno drzewo, powtarzamy następujące operacje

– Usuwamy z listy dwa drzewa o najmniejszym prawdopodobieństwie zapisanym w ko-


rzeniu.
– Dokładamy nowe drzewo, a w jego korzeniu sumę prawdopodobieństw usuniętych
drzew. W efekcie tego, one tworzą poddrzewa.

Zakoduj następujący zbiór symboli S = {A, I, S, D} z prawdopodobieństwem wystąpienia


P = {0.1, 0.2, 0.3, 0.4} używając kodowania Huffmana.
Z drzewa odczytujemy następujące kodowanie: A → 000, B → 001, C → 01 i D → 1.

24
Szyfrowanie i deszyfrowanie wykorzystuje się dwa klucze

• publiczny – używany do szyfrowania danych,

• prywatny – używany do odszyfrowywania danych i jest tajny.

Działanie algorytmów można opisać następująco

1. obydwa klucze są generowane przez algorytm odbiorcy,

2. odbiorca przesyła do nadawców klucz publiczny,

3. nadawca szyfruje dane (bądź wiadomość) dzięki kluczowi publicznemu i wysyła zaszyfro-
waną wiadomość do odbiorcy,

4. odbiorca dzięki tajnemu kluczowi odszyfrowuje dane.

Szyfr Cezara to jedna z najstarszych i najprostszych technik szyfrowania symetrycznego.


Szyfr polega na zastępowaniu znaków innymi, oddalonymi o pewną stałą liczbę - przesunięciu w
alfabecie. Bardziej rozbudowana wersja tego szyfru nosi nazwę szyfru Vigenére’a.

Algorytm Rivesta-Shamira-Adlemana - RSA jest jednym z pierwszych asymetrycznych


algorytmów kryptograficznych. Jako główną zaletę RSA podaje się problem faktoryzacji dużych
liczb złożonych (na których opiera się cały algorytm). RSA można opisać poprzez dwa kroki

• generowanie kluczy,

• szyfrowanie/deszyfrowanie.

W celu wygenerowania pary kluczy

• wybieramy dwie losowe duże liczby pierwsze p i q,

• obliczamy wartość n = pq,

25
• obliczamy wartość funkcji Eulera dla n przy pomocy

ϕ(n) = (p − 1)(q − 1), (19)

• wybieramy liczbę e (1 < e < ϕ(n)) względnie pierwszą z ϕ(n),

• znajdujemy liczbę d przy pomocy następującej kongruencji

de ≡ 1 mod ϕ(n). (20)

W taki sposób generujemy klucz publiczny (n, e) i prywatny (n, d).


Mając klucze szyfrowane dane dzielimy na bloki m (nie większe niż n), a następnie każdy z
bloków szyfrujemy stosując
c ≡ me mod n. (21)
Zaszyfrowane dane będą złożone z bloków długości c, więc deszyfrowanie będzie zachodziło
poprzez następująca formułę
m ≡ cd mod n. (22)
RSA – przykład generowania kluczy Wybieramy dwie, losowe liczby pierwsze: p = 13 i q = 11.

Obliczamy wartość n = 11 · 13 = 143 i funkcję Eulera z tej liczby ϕ(143) = (13 − 1)(11 − 1) =
120.

Znajdujemy liczbę e. Wiemy, że e ma być względnie pierwsza z ϕ(143) = 120. Przykładem


takiej liczby jest e = 7.

Znajdujemy wartość d z równania 7d mod 120 ≡ 1. Wynikiem tego jest liczba d = 103.

W taki sposób klucz publiczny to (7, 143), a prywatny (103, 143).


Otrzymaliśmy klucz publiczny (7, 143). Korzystając z niego zaszyfrujemy liczbę 123, a więc
liczymy
c = 1237 mod 143 = 425927596977747 mod 143 ≡ 7
Do adresata przesyłamy zaszyfrowaną wiadomość o treści 7.
Adresat otrzymuje wiadomość 7 oraz posiada klucz prywatny (103, 143), a więc oblicza

t = 7103 mod 143 = 113 · 16 · 113 · 49 · 7 mod 143 ≡ 123.

Sortowanie przez wstawianie jest jednym z podstawowych algorytmów sortowania. Porów-


nujemy poszczególne znaki sortowanego ciągu:

1. Wybieramy największy element i wstawiamy go na koniec badanego ciągu.

2. Następnie sortujemy pozostały ciąg.

3. Ponieważ największy element znajduje się już na końcu ciągu procedura porównania od-
bywa się dla n − 1 elementów pierwotnego ciągu.

4. Taka procedura jest powtarzana do momentu uzyskania ciągu jednoelementowego rozumia-


nego jako najmniejszy ze wszystkich porównywanych elementów.

26
Sortowanie bąbelkowe to jeden z ważniejszych algorytmów ze względu na jego sposób dzia-
łania. Różnica polega jednak na tym, że zamiana elementów dokonana zostaje w poszczególnej
iteracji poprzez wszystkie sąsiednie elementy. Nie zamieniamy, tak jak poprzednio jedynie ele-
mentu ostatniego w ciągu ze znalezionym największym. Porównujemyze sobą kolejne elementy
w każdej rozpatrywanej parze. Znalezioną wartość ekstremalną przesuwamy w ustalonym kie-
runku. Następnie porównujemy elementy w kolejnej utworzonej parze szukając największego i
przesuwając go na koniec. Takie porównania wykonujemy, do końca ciągu aż element największy
znajdzie się na ostatnim miejscu.

27
Sortowanie quick sort polega na wyborze elementu środkowego w badanym ciągu. Następ-
nie układamy wszystkie elementy mniejsze od środkowego z lewej strony i jednocześnie wszystkie
elementy większe lub równe elementowi środkowemu z prawej jego strony.

Niestety koszt wyznaczenia elementu środkowego bezpośrednio przed rozpoczęciem algorytmu


szybkiego sortowania jest zależny od złożoności czasowej tej metody. Przyjęcie takiego rozwią-
zania powoduje zwiększenie czasu działania algorytmu, dlatego najczęściej przyjmuje się indeks
elementu środkowego jako wartość losową, wybraną spośród wszystkich możliwych indeksów.

Sortowanie heap sort Polega na sortowaniu poprzez rozmieszczenie elementów w strukturze


kopca, którą następnie porządkujemy. W celu zmniejszenia złożoności obliczeniowej sortowania
zauważmy, że drzewo binarne pełne pomoże nam uprościć algorytm. Drzewo binarne to drzewo,
w którym każdy z węzłów ma dwa węzły potomne, poza ostatnim rzędem tzw. liści. Drzewo pełne
oznacza, ze nie może brakować węzła wewnątrz drzewa i jednocześnie wszystkie liście są dosunięte
do lewej strony. Numeracja w drzewie rozpoczyna się od 1 do n i przechodzi od znajdującego się
na samym szczycie węzła poprzez wszystkie kolejne poziomy.

28
Sortowanie merge sort polega na porównaniu elementów na szczycie stosów i wstawianiu ich
do innej tablicy. Otrzymujemy w ten sposób uporządkowany ciąg zawierający wszystkie elementy
obu ciągów ułożone w porządku hierarchicznym. Popatrzmy na działanie tego algorytmu na
przykładzie dwu uporządkowanych stosów.

Heurystyka 1 (gr. heuresis – odnaleźć, odkryć, heureka – znalazłem) jest to metoda znajdowa-
nia rozwiązań, dla której nie ma gwarancji znalezienia rozwiązania optymalnego, a często nawet
prawidłowego. Heurystyki to algorytmy służące do rozwiazywania problemów optymalizacyjnych
lub innych, gdzie algorytm rozwiązujący jest zbyt kosztowny lub gdy jest nieznany. Często uży-
wamy modelu heurystycznego opartego o wzorce ze świata zwierząt lub roślin.

Funkcja kryterialna (ang. Fitness Function) jest to funkcja wykorzystywana w optymalizacji


i modelowaniu, która definiuje cechy modelowanego zjawiska w postaci modelu matematycznego
opisującego zależności optymalnego działania badanego zjawiska.
Funkcja testowa - De Jonga 1

1
https://pl.wikipedia.org/wiki/Heurystyka

29
N
X
fdeJong1 (x) = x2i , (23)
i=1

gdzie N to wymiar punktu x o współrzędnych xi , min. fdeJong1 (x) = 0 w pkt. x = 0. Powyżej


funkcja dwuwymiarowa fdeJong1 (x1 , x2 ) = x21 + x22 .
Funkcja testowa - Rastragina
N
X
fRastr (x) = 10 · N + (x2i − 10 cos(2π · xi )), (24)
i=1

gdzie N to wymiar punktu x o współrzędnych xi , min. fRastr (x) = 0 w punkcie x = 0. Powyżej


funkcja dwuwymiarowa fRastr (x1 , x2 ) = 10 · 2 + (x21 − cos(2π · x1 )) + (x22 − cos(2π · x2 )).

Optymalizacja gradientowa jest jedną z metod optymalizacji. Algorytm numeryczny wyko-


rzystuje kierunek gradientu w przestrzeni poszukiwań rozwiązań modelu matematycznego.
Algorytm rozpoczyna się od losowego wyboru punktu x0 , który jest pierwszą losową opty-
malną pozycją. W dalszych krokach kierujemy się gradientem. Gradient ∇fxi jest kierunkiem
najszybszego wzrostu funkcji mierzonym w punkcie xi , a ujemny gradient ∇fxi jest kierunkiem
najszybszego spadku funkcji w punkcie xi . Następnie nowy punkt oparty na kierunku dxi znaj-
dujemy za pomocą formuły
xt+1
i = xti − λ · ∇fxi , (25)
gdzie dla punktów xi parametr λ reprezentuje wartość kroku, a t to horyzont czasu reprezen-
towany przez iteracje. Następnie obliczamy wartość funkcji kryterialnej dla starych i nowych
punktów. Jeśli wartość funkcji kryterialnej dla nowego punktu xt+1i jest mniejsza niż dla sta-
t t+1
rego punktu xi , to nowy punkt xi zamienia stary. Algorytm jest wykonywany do spełnienia
warunku zatrzymania, np. liczba iteracji (horyzont czasowy t jest określony w jakiś sposób) lub
metoda zatrzyma się po osiągnięciu określonej wartości dla warunku sprawności.

Algorithm 8 Optymalizacja gradientowa


Zdefiniuj krok λ i horyzont czasowy T
t = 0: Stwórz loswy punkt startowy
while t ¬ T do
Oblicz xt+1
i w kierunku gradientu (25)
t+1
if f (xi ) ¬ f (xti ) then
xti = xt+1
i
end
else
xti = xti
end
Nowa iteracja t = t + 1
end
Zwróć xti

Metoda optymalizacji gradientowej zostanie zaprezentowana na przykładzie minimalizacji


funkcji Styblinski-Tanga:
1 4 
f (x, y) = x − 16x2 + 5x + y 4 − 16y 2 + 5y
2

30
w zbiorze [−5, 5]. Gradient tej funkcji przyjmuje postać:
∂f ∂f 1 3  1
    
∇f = , = 4x − 32x + 5 , 4y 3 − 32y + 5 .
∂x ∂y 2 2
Przyjmiemy, że w każdej iteracji będzie 5 punktów przybliżających minimum i λ = 0.01.
Po zakończeniu zwracamy najlepsze przybliżenie z ostatniej iteracji. W przykładzie wyliczone
wartości będą zaokrąglane do 0.001.
1. Losujemy pierwsze przybliżenia (x0i , yi0 ), i = 1, . . . , 5 i obliczamy wartości funkcji f i gra-
dientu funkcji w tych punktach (3 pierwsze wiersze tabeli). Dla przykładu, dla punktu
(x01 , y10 ) = (3, 5) obliczamy wartość funkcji f (x01 , y10 ) = 101, oraz gradient funkcji
∇f (x01 , y10 ) = [8.5, 172.5].
2. Wyznaczamy nowe punkty x1i , yi1 w oparciu o gradienty w odpowiednich punktach. Dla
punktu (x11 , y11 ) ten proces wygląda następująco:
• Obliczamy λ∇f (x01 , y10 ) = [0.085, 1.725]
• Wyznaczamy współrzędne x11 = 3 − 0.085 = 2.915 i y11 = 5 − 1.725 = 3.275
3. Liczymy wartość funkcji f w nowych punktach (f (x11 , y11 ) = −44.687).
4. Porównujemy nowe wartości funkcji z odpowiadającymi starymi. Jeśli nowa wartość jest
mniejsza od poprzedniej, to nowy punkt zostanie uwzględniony w kolejnej iteracji. W na-
szym przypadku f (x01 , y10 ) = 101 > f (x11 , y11 ) = −44.687, więc nowy punkt przechodzi dalej.
W tabeli 1 znajdują się pośrednie wartości dla pozostałych punktów.

x01 y10 x02 y20 x03 y30 x04 y40 x05 y50
(x0i , yi0 )
3 5 3 3 0 5 1 -2 3 -3
f (x0i , yi0 ) 101 -48 125 -34 63
∂f ∂f ∂f ∂f ∂f ∂f ∂f ∂f ∂f ∂f
∇f (x0i , yi0 ) ∂x ∂y ∂x ∂y ∂x ∂y ∂x ∂y ∂x ∂y
8.5 172.5 8.5 8.5 2.5 172.5 -11.5 18.5 8.5 -3.5
λ∇f (x0i , yi0 ) 0.085 1.725 0.085 0.085 0.025 1.725 -0.115 0.185 0.085 -0.035
x11 y11 x12 y21 x13 y31 x14 y41 x15 y51
(x1i , yi1 )
2.915 3.275 2.915 2.915 -0.025 3.275 1.115 -2.185 2.915 -2.965
f (x1i , yi1 ) -44.687 -49.178 -20.165 -38.645 -63.688

Tabela 1: Pierwsza iteracja algorytmu optymalizacji gradientowej

Pośrednie obliczenia dla drugiej iteracji przedstawia tabela 2.

x11 y11 x12 y21 x13 y31 x14 y41 x15 y51
(x1i , yi1 )
2.915 3.275 2.915 2.915 -0.025 3.275 1.115 -2.185 2.915 -2.965
f (x1i , yi1 ) -44.687 -49.178 -20.165 -38.645 -63.688
∂f ∂f ∂f ∂f ∂f ∂f ∂f ∂f ∂f ∂f
∇f (x1i , yi1 ) ∂x ∂y ∂x ∂y ∂x ∂y ∂x ∂y ∂x ∂y
5.399 20.353 5.399 5.399 2.9 20.353 -12.568 16.597 5.399 -2.192
λ∇f (x1i , yi1 ) 0.054 0.204 0.054 0.054 0.029 0.204 -0.126 0.166 0.054 -0.022
x21 y12 x22 y22 x23 y32 x24 y42 x25 y52
(x2i , yi2 )
2.861 3.071 2.861 2.861 -0.054 3.071 1.241 -2.351 2.861 -2.943
f (x2i , yi2 ) -48.124 -49.661 -23.452 -42.847 -63.969

Tabela 2: Druga iteracja algorytmu optymalizacji gradientowej

31
Algorytm Gentyczny został stworzony przez Johna Hollanda w 1973. Główną ideą było
stworzenie programu komputerowego rozwiązującego problemy w sposób podobny do naturalnego
procesu ewolucji. Zastosowano ewolucję populacji składającej się z pewnej liczby osobników, w
której każda osoba miała ustawiony binarny kod genetyczny odpowiedający chromosomom w
organizmach oraz operacje, które zachodzą podczas biologicznego procesu ewolucji: mechanizm
przekształcania kodów binarnych - dziedziczenie, mutacje i selekcje.
Pierwszym krokiem GA jest inicjalizacja, która polega na ustaleniu wielkości populacji,
kodowaniu chromosomów i funkcji kryterialnej z zestawem ograniczeń. Elementy początkowej
populacji znajdujemy w sposób losowy.
Kolejnym krokiem jest reprodukcja. Osobnik xi jest przenoszony do następnego pokolenia
z prawdopodobieństwem, które rośnie wraz z wyższym poziomem funkcji kryterialnej f (xi ). Dla
wybranej grupy w populacji P t określamy zmienną losową pr przez

 p (xt ) = f (xti )
r i xti . (26)
 t
x ∈P t
i

Losowa zmienna jest próbkowana kilka razy i wprowadzana jest funkcja rozkładu prawdopodo-
bieństwa rozmnażania
i
X
Pr (xti ) = pr (xti ). (27)
j=1

Następnie pobierana jest losowa liczba α ∈ (0, 1). Jeśli xi jest zgodne z założeniem

Pr (xti−1 ) < α ¬ Pr (xti ) (28)

to jest reprodukowane. Reprodukcja jest kontrolowana przez operator genetyczny, który tworzyć
zróżnicowaną populację. Operatory klasyczne to mutacje i krzyżowanie.
Mutacja to proces zmiany chromosomów, na przykład dodanie losowego rzeczywistego ξ ∈
(0, 1)
xit+1 mutated = xti + ξ. (29)
Wartości wektora λ, którego długość jest równa liczbie osobników w populacji, są wybierane
losowo. Każdy chromosom jest zmutowany, jeśli

λi < pm , (30)

gdzie pm prawdopodobienstwem mutacji.


Krzyżowanie to operacja, w której wymieniany jest materiał genetyczny między dwoma
osobami - rodzicami. Krzyżowanie dwóch rodziców tworzy dwie osoby, które są dodawane do
populacji. Następnie chromosomy rodzicielskie są uśredniane za pomocą następującej formuły

xi t+1 = xi t + η(xti+1 − xti ), (31)

gdzie xi , xi+1 to chromosomy rodzice, η to losowa liczba rzeczywista z (0, 1).


W następnym kroku wszystkie osoby muszą zostać ocenione, w jaki sposób pasują do środo-
wiska. Nowa populacja zastępuje starszą. Ten proces nazywa się sukcesją.

32
Algorithm 9 Algorytm Genetyczny
t = 0: Stwórz populację początkową
while t ¬ generation do
foreach wektor xi z populacji X do
Sortuj populację według funkcji kryterialnej
Losuj prawdopodobienstwo mutacji wektorów i mutuj (29)
Krzyżuj wektory (31)
if f (wektorpopulacji) < f (rezerwowy) then
zmień
end
else
pozostaw
end
end
Nowa iteracja t = t + 1
end

Zaprezentujemy działanie algorytmu genetycznego do minimalizacji funkcji de Jonga f (x, y, z) =


x2 +y 2 +z 2 w zbiorze [−5, 5]. Jako funkcję przystosowania przyjmiemy F (x, y, z) = 75−f (x, y, z)
(funkcja przystosowania powinna przyjmować wartości dodatnie, oraz, w przypadku zadania mi-
nimalizacji, przyjmować większe wartości dla punktów, dla których funkcja celu f przyjmuje
wartości mniejsze). Przyjmiemy wielkość populacji n = 4, prawdopodobieństwo mutacji pm = 0.1
i prawdopodobieństwo krzyżowania pc = 1 (osobniki wylosowane do reprodukcji zawsze będą się
krzyżowały). Mutacja będzie się odbywać pooprzez dodanie do mutowanej współrzędnej losowej
wartości ξ ∈ [−0.5, 0.5] (zmutowane osobniki będziemy oznaczać stosując indeks górny m), a
krzyżowanie osobników xti , xtj będzie doprowadzało do powstania dwóch nowych w postaci:

xki = xti + η(xtj − xti ), xkj = xtj + η(xti − xtj ),

gdzie η ∈ [0, 1] jest losową liczbą. Po krzyżowaniu następuje porównanie dopasowania „starych”
i „nowych” osobników. Te o wyższych wartościach funkcji F dołączą do kolejnego pokolenia.
Selekcja osobników do reprodukcji odbywać się będzie metodą ruletki. Każdemu elementowi
aktualnej populacji zostanie przyporządkowany fragment przedziału [0, 1] tym większy, im więk-
sze jest jego przystosowanie. Następnie, aż do wytworzenia nowej populacji o tej samej liczebności
co populacja początkowa, będą losowane liczby α, β ∈ [0, 1] i w zależności do którego kawałka
przedziału trafią, te osobniki zostaną wzięte do reprodukcji (ten sam osobnik może zostać wy-
losowany wielokrotnie w tej samej iteracji). Na wybranych osobnikach będziemy przeprowadzać
operacje mutacji i krzyżowania. Po wytworzeniu nowego pokolenia przechodzimy do kolejnej
iteracji.

1. Losujemy populację początkową P 0 , obliczamy przystosowanie jej elementów, dzielimy


przedział [0, 1] na fragmenty. Efekt przedstawiony jest w tabeli 3 (osobniki uporządko-
wane są zgodnie z wartościami funkcji dopasowania). λ0i oznacza prawy koniec przedziału
odpowiadającego elementowi x0i

33
i 1 2 3 4
x0i (-0.25, -0.95, 0.82) (3.69, 0.94, -3.16) (-2.26, 4.93, -1.13) (3.92, -4.29, 1.75)
F (x0i ) 73.36 50.51 44.31 38.17
λ0i 0.36 0.6 0.82 1

Tabela 3: Pokolenie początkowe P 0

2. Losujemy (α, β) = (0.74, 0.68) i na tej podstawie dwukrotnie wybieramy do mutacji i


krzyżowania osobnika x03

3. Dla jednego z rodziców losujemy liczby γ1 , γ2 , γ3 ∈ [0, 1]. Jeśli γk < pc , to k−ta współrzędna
zostanie zmutowana. Wynikiem losowania jest γ1 = 0.42, γ2 = 0.84, γ3 = 0.79, a więc żadna
ze współrzędnych nie zostanie zmutowana.
Losujemy γ1 , γ2 , γ3 ∈ [0, 1] dla drugiego rodzica. Wynik losowania: γ1 = 0.67, γ2 = 0.17, γ3 =
0.17, a więc drugi rodzic również nie zostanie zmutowany.

4. W związku z tym, że do reprodukcji został wylosowany dwa razy ten sam element, a jego
współrzędne nie zostały zmutowane, w wyniku krzyżowania otrzymamy dokładnie te same
elementy. Dołączają one zatem do następnego pokolenia P 1 .

5. P 1 zawiera 2 < n = 4 elementy, a więc reprodukcję należy przeprowadzić ponownie.


Losujemy (α, β) = (0.31, 0.65). Do reprodukcji użyjemy elementów x01 , x03 .

6. Losujemy γ1 = 0.25, γ2 = 0.05, γ3 = 0.51, więc druga współrzędna x01 zostanie zmutowa-
na przed krzyżowaniem. Losujemy ξ = −0.14, czyli xm 1 = (−0.25, −0.95 − 0.14, 0.82) =
(−0.25, −1.09, 0.82).
Dla x03 wylosowano γ1 = 0.58, γ2 = 0.08, γ3 = 0.11, oraz ξ = −0.1
stąd xm3 = (−2.26, 4.93 − 0.1, −1.13) = (−2.26, 4.83, −1.13).

7. Przechodzimy do krzyżowania xm m
1 z x3 . Losujemy η = 0.95. Mamy:

xk1 = (−0.25 + 0.95(−2.26 + 0.25), −1.09 + 0.95(4.83 + 1.09), 0.82 + 0.95(−1.13 − 0.82))

= (−2.16, 4.53, −1.03).


Analogicznie, drugi potomek to xk3 = (−0.35, −0.79, 0.72).

8. Porównujemy dopasowanie:

Element x01 xk1 Wygrał x03 xk3 Wygrał


Dopasowanie 73.36 48.71 x01 44.31 73.72 xk3

Do następnej iteracji włączymy x01 i xk3

9. W populacji P 1 mamy już 4 elementy, więc (jeśli warunek stopu nie został spełniony)
rozpoczynamy kolejną epokę.

10. Po zakończeniu działania programu zwracamy element o największej wartości funkcji do-
pasowania. Tabela 4 przedstawia populację po kilku pierwszych iteracjach.

34
x0i (-0.25,-0.95,0.82) (3.69,0.94,-3.16) (-2.26,4.93,-1.13) (3.92,-4.29,1.75)
P0
F (x0i ) 73.36 50.51 44.31 38.17
x1i (-0.35,-0.79,0.72) (-0.25,-0.95,0.82) (-2.26,4.93,-1.13) (-2.26,4.93,-1.13)
P1
F (x1i ) 73.74 73.36 44.31 44.31
x2i (-0.77,0.56,0.32) (-0.35,-0.79,0.72) (-0.35,-0.79,0.72) (-0.25,-0.95,0.82)
P2
F (x2i ) 74 73.74 73.74 73.36
x3i (-0.77,0.56,0.32) (-0.77,0.56,0.32) (-0.02,-1.14,0.72) (-0.02,-1.14,0.72)
P3
F (x3i ) 74 74 73.18 73.18
...
x5i 0 (-0.09,0.08,0.11) (-0.09,0.08,0.1) (-0.11,0.08,0.09) (-0.14,0.08,0.06)
P 50
F (x5i 0) 74.9745 74.9744 74.9726 74.9692

Tabela 4: Kilka iteracji algorytmu genetycznego

Ewolucja Różnicowa została wprowadzona przez Storn i Price w 1997. Algorytm jest heu-
rystycznym podejściem do minimalizowania nieliniowych i nierozróżnialnych funkcji przestrzeni
ciągłej. Algorytm opiera się :
1. N P - wielkość populacji,

2. F - parametr kontroli mutacji,

3. CR - prawdopodobienstwo krzyżowania.
Działa na populacji X osobników xi , gdzie i = 1, ..., N P i xi = [x1i , x2i , ..., xDi ]T . Najpierw
tworzymy losowo populację xi G , gdzie G = 1 jest liczbą osób w populacji.
Mutacja to losowe permutacje wektorów xi poprzez różnicowanie dwóch wektorów z popu-
lacji X skalując je przez stałą wartość. Dla każdego wektora xi G generujemy zmutowany wektor
vi G
vi G+1 = xr1 G + F · (xr2 G − xr3 G ), (32)
o losowych indeksach r1, r2, r3 ∈ {1, 2, . . . , N P } oraz r1 6= r2 6= r3 6= i, F ∈ [0, 2] jest stałą
rzeczywistą.
Krzyżowanie to mieszanie losowych elementów wektora macierzystego xi G oraz wektora
vi G+1 po mutacji
(
G+1
vji if rndj < CR or j = di
uG+1
ji = G , j = 1, . . . , D, (33)
xji if rndj > CR and j 6= di

gdzie CR ∈ [0, 1] jest stałą krzyżowania, rndj jest jednolitym generatorem liczb losowych ∈ [0, 1]
oraz di ∈ 1, 2, . . . , D jest losowym indeksem.

Krzyżowanie jest równoważne optymalizacji. Jeśli f (uG+1 i ) < f (xG G+1


i ) to xi = uG+1
i ,
G+1 G
gdzie f (·) jest funkcją kryterialną. W innym przypadku xi = xi .

Algorytm Kukułki został wprowadzony przez Yang and Deb w 2009. CS jest bardzo sku-
teczną techniką optymalizacji bezgradientowej, w której stosujemy rozkład Gaussa. Algorytm
symuluje zachowanie kukułek, które mają szczególny model rozmnażania. Kukułka leci i szuka
gniazda, by złożyć jajo. Wybiera gniazdo, gdzie są już jaja. Kukułki składają jajo i odlatują.
Kiedy gospodarze wracają do domu albo pozbywają się jaja intruza, albo po prostu akceptują
nową sytuację.

35
Algorithm 10 Ewolucja Różnicowa
t = 0: Stwórz populację początkową xG i
while t ¬ generation do
foreach wektor xG i z populacji X do
Mutuj viG (32)
Krzyżuj wektory (33)
if f (uG+1
i ) < f (xG
i ) then
G+1 G+1
xi = ui –zmień
end
else
xiG+1 = xGi –pozostaw
end
end
Nowa iteracja t = t + 1
end

Proces poszukiwania gniazda dla jaj jest modelowany i stosowany jako algorytm CS, gdzie
zakładamy:

1. Kukułka to po prostu xi .

2. Każda kukułka ma tylko jedno jajo do złożenia.

3. Najlepsze gniazda zawierające jajka są przenoszone do następnej generacji.

4. Reszta populacji kukułki jest rozmieszczana losowo.

5. Hosty mogą stwierdzić jajo intruza z prawdopodobieństwem 1 − pα ∈ h0, 1i i pozbyć się go.
W tym przypadku nowa kukułka jest umieszczana losowo.

W każdym pokoleniu modelujemy wybór miejsca, w którym kukułka składa jajo. Ten ruch
wykorzystuje koncepcję błądzenia losowego, co umożliwia wyszukiwanie w różnych przestrze-
niach.

Wirtualny ruch kukułki


xt+1
i = xti + µ · L(β, γ, δ), (34)
gdzie xt+1
i to nowe rozwiązanie w CS, µ dlugośc kroku w błądzeniu losowym o normalnej dystry-
γ 
bucji N cuckoos ; 0, 1 , L(β,γ,δ) to lot Lévy o kroku β, δ minimalna długość kroku w błądzeniu
losowym, γ to parametr skalujący lot Lévy.
Lot Lévy jest izotropiczny w losowych kierunkach
 q γ
γ exp[− 2(β−δ) ]

2π 3 , 0<β<δ<∞
L(β, γ, δ) = (β−δ) 2 . (35)
0, other

na koniec decydujemy czy jajo kukułki zostało odkryte


(
1 − pα wyrzucamy jajo
H(xt+1
i ) = , (36)
pα jajo zostaje

36
Algorithm 11 Algorytm Kukułki
t = 0: Stwórz populację początkową
while t ¬ generation do
Przemieść kukułki (34), (35)
Decyzja czy ptaki odkryły jajo intruza (36)
Posortuj punkty według wartości wunkcji kryterialnej
Wybierz najlepsze, a resztę zastąp losowo
Nowa iteracja t = t + 1
end

gdzie H(xt+1
i )to decyzja ptaków tzn. jaja kukułki, pα ∈ h0, 1i prawdopodobieństwo pozostania
jaja.
Algorytm kukułki zaprezentujemy na przykładzie minimalizacji funkcji Rastragina
f (x, y) = 20 + x2 − 10 cos(2πx) + y 2 − cos(2πy)
na zbiorze [−10, 10] × [−10, 10]. Przyjmiemy następujące wartości parametrów: δ = 1, γ =
1, pα = 0.6. Dodatkowo, by nie tracić najlepszych rozwiązań, w tym przykładzie ustalimy, że
wyrzucone mogą zostać tylko dwa najgorsze jajka. W każdej iteracji będziemy uwzględniać n = 5
kukułek (jaj).
1. Losujemy początkową populację i sortujemy ją ze względu na wartość funkcji f (dwa pierw-
sze wiersze tabeli 5).

P0 (−3, 2) (−2, 3) (−5, 0) (4, −3)


f (P 0 ) 13 13 25 25
µL() [1.79, −0.62] [1.24, −3.09] [−0.62, −1.91] [−1.73, 6.07]
1
Ptmp (−1.21, 1.38) (−0.76, −0.09) (−5.62, −1.91) (2.27, 3.07)
1 )
f (Ptmp 28.17 11.51 54.08 26.78
1
Ptmp,sort (−0.76, −0.09) (−3, 2) (−5, 0) (4, −3)
p − − 0.32 0.59
1
Ptmp2 (−0.76, −0.09) (−3, 2) (4.54, 1.28) (4, −3)
P1 (−0.76, −0.09) (−3, 2) (4, −3) (4.54, 1.28)
f (P 1 ) 11.51 13 25 53.81

Tabela 5: Pierwsza iteracja algorytmu kukułki

2. Dla każdego rozwiązania tworzymy nowe przesuwając je o losowy wektor. Dla pierwszego
z nich mamy:
(x11 , y11 ) = (x01 , y10 ) + µL(β, γ, δ) = (−3, 2) + [1.79, −0.62] = (−1.21, 1.38).
Obliczamy f (x11 , y11 ) = 28.17 > f (x01 , y10 ) = 13, a więc stare jajko zostaje uwzględnione w
kolejnych krokach. Wektory przesunięcia dla każdego punktu przedstawione są w tabeli 5
wraz z otrzymanymi nowymi rozwiązaniami. Wiersz Ptmp,sort 1 zawiera jaja uwzględnione w
kolejnych działaniach posortowane zgodnie z wartościami funkcji f .
3. W tym kroku niektóre jaja zostaną odkryte przez gospodarza i wyrzucone z gniazd. Zostaną
one zastąpione losowym rozwiązaniem. Dla dwóch ostatnich rozwiązań losujemy p. Jeśli p <
1−pα = 0.4, to rozwiązanie zostaje zastąpione. Dwa ostatnie wiersze zawierają posortowane
rozwiązania otrzymane w tej iteracji.

37
P1 (−0.76, −0.09) (−3, 2) (4, −3) (4.54, 1.28)
f (P 1 ) 11.51 13 25 53.81
µL() [2.02, 1.67] [−1.42, 0.05] [0.45, −0.77] [−1.41, −0.61]
2
Ptmp (1.26, 1.58) (−4.42, 2.05) (4.45, −3.77) (3.13, 0.67)
2 )
f (Ptmp 33.47 42.99 62.27 28.21
2
Ptmp,sort (−0.76, −0.09) (−3, 2) (4, −3) (3.13, 0.67)
p − − 0.17 0.22
2
Ptmp2 (−0.76, −0.09) (−3, 2) (−1.05, −0.68) (1.38, 2.99)
P2 (−0.76, −0.09) (−3, 2) (−1.05, −0.68) (1.38, 2.99)
f (P 2 ) 11.51 13 16.31 28.15
µL() [−0.39, 1.53] [0.65, −3.75] [−2.06, −0.06] [−0.18, 3.01]
3
Ptmp (−1.15, 1.44) (−2.35, −1, 75) (−3.11, −0.74) (1.2, 6)
3 )
f (Ptmp 26.82 34.46 23.14 44.35
3
Ptmp,sort (−0.76, −0.09) (−3, 2) (−1.05, −0.68) (1.38, 2.99)
p − − 0.05 0.63
3
Ptmp2 (−0.76, −0.09) (−3, 2) (−1.07, 3.98) (1.38, 2.99)
P3 (−0.76, −0.09) (−3, 2) (−1.07, 3.98) (1.38, 2.99)
f (P 3 ) 11.51 13 18.02 53.81

Tabela 6: Kilka iteracji algortmu kukułki

4. Rozpoczynamy kolejną iterację. Kilka z nich przedstawia tabela 6.

Algorytmy rojowe powstają w wyniku inspiracji zachowaniem gromad ptaków, ryb, zwie-
rząt lądowych czy rojów owadów. Tworzą one symulacje komputerowe nasladujące ruch stada
ptaków lub ławicy ryb, gdzie staramy się odwzorować zachowania stadne pozwalające im po-
ruszać się synchronicznie pomimo częstych gwałtownych zmian kierunku lotu, rozproszenia czy
przegrupowania stada.

Algorithm 12 Algorytm rojowy


Start
Zdefiniuj funkcję kryterialną
Zdefiniuj współczynniki algorytmu
Rozmieść losowo populację początkową w przestrzeni decyzji
for t = 0 to t ¬ liczba pokoleń do
Przesuń osobniki zgodnie z modelem ruchu
Posortuj osobniki w populacji według funkcji kryterialnej
t++
end
Pozycje najlepszych osobników z ostaniego pokolenia tworzą rozwiązanie
Stop

Algorytm Roju Cząstek został opracowany przez Kennedy’egou, Eberharta i Shi w 1996 jako
symulator zachowań społecznych ruchu organizmów w stadzie ptaków lub ławicy ryb. Cząstki

38
przemieszczają się według równania
xi t+1 = xi t + βvi t (37)
gdzie xi t jest pozycją cząstki w przestrzeni rozwiązań w iteracji t, vi t jest prędkością cząstki
obliczaną według równania

vi t = vi t + φp · α · (Pi − xi t ) + φs · β · (Ps − xi t ), (38)


gdzie α, β ∈ [0, 1] są wartościami losowymi, Pi jest pozycją najlepszej cząstki i w iteracji t, Ps
jest pozycją najlepszej cząstki w całym procesie, φp oraz φs są współczynnikami korekcji. Jeśli
φs > φp rój porusza się w kierunku najlepszej cząstki. Jeśli φs < φp rój porusza się losowo.
Algorytm roju cząstek zaprezentujemy na przykładzie minimalizacji funkcji
f (x, y) = x2 + y 2
na zbiorze [−10, 10] × [−10, 10]. Przyjmiemy φp = 1, φs = 1. Liczba cząstek wynosi n = 5.
Wartości w tabelach są podane z dokładnością do 0.01.
1. Losujemy początkowe położenie cząstek, wyznaczamy wartości funkcji f , i losujemy pręd-
kości (trzy pierwsze wiersze tabeli 7). Wylosowane punkty są jednocześnie najlepszymi
dotychczas położeniami cząstek Pi = (x0i , yi0 ). Spośród cząstek wybieramy tę o najmniej-
szej wartości funkcji f i zapisujemy jako najlepsze rozwiązanie Ps = (x03 , y30 ) = (2, −4).

x01 y10 x02 y20 x03 y30 x04 y40 x05 y50 Ps0
(x0 , y 0 )
-3 6 1 5 2 -4 5 5 -5 -6 2 -4
f (x0 , y 0 ) 45 26 20 50 61 20
vx01 vy01 vx02 vy02 vx03 vy03 vx04 vy04 vx05 vy05
(vx0 , vy0 )
2.15 0.88 1.57 -3.99 -4.5 -1.62 -0.42 -1.06 -4.61 -0.2
(x1 , y 1 ) -0.85 6.88 2.57 1.01 -2.5 -5.62 4.58 3.94 -9.61 -6.2
f (x1 , y 1 ) 48 7.62 37.87 36.55 130.72
x1 y1 x2 y2 x3 y3 x4 y4 x5 y5 Ps1
Pi1
-3 6 2.57 1.01 2 -4 4.58 3.94 -5 -6 2.57 1.01
f (Pi1 ) 45 7.62 20 3.94 61 7.62

Tabela 7: Pierwsza iteracja algorytmu PSO

2. Przesuwamy każdą z cząstek uwzględniając jej prędkość, np. dla i = 1 mamy:


x11 = x01 + vx01 = −3 + 2.15 = −0.85, y11 = y10 + vy01 = 6 + 0.88 = 6.88,
i obliczamy wartości f w nowych punktach.
3. Jeśli dla danej cząstki f (x1i , yi1 ) < f (x0i , yi0 ), to Pi = (x1i , yi1 )
(np. f (x12 , y21 ) = 7.62 < f (x02 , y20 ) = 26, a więc P2 = (x12 , y21 ) = (2.57, 1.01).
4. Wybieramy cząstkę o najniższej wartości f i zapisujemy ją w Ps (Ps = (2.57, 1.01)).
5. Aktualizujemy prędkości cząstek (wylosowano α = 0.48, β = 0.6), np. dla i = 1 mamy:
vx11 =vx01 + φp · α(P10 − x01 ) + φs · β(Ps0 − x01 )
= 2.15 + 1 · 0.48 · (−3 − (−3)) + 1 · 0.6 · (2 − (−3)) ≈ 5.17
vy11 =vy01 + φp · α(P10 − y10 ) + φs · β(Ps0 − y10 )
= 0.88 + 1 · 0.48 · (6 − 6) + 1 · 0.6 · (−4 − 6) ≈ −5.17

39
6. Jeśli warunek stopu nie został spełniony, przechodzimy do kolejnej iteracji (kilka z nich
przedstawia tabela 8, z kolei w tabeli 9 znajdziemy najlepsze położenia cząstek w tych
krokach).

(x1i , yi1 ) (−0.85, 6.88) (2.57, 1.01) (−2.5, −5.62) (4.58, 3.94) (−9.61, −6.2)
f (x1i , yi1 ) 48 7.62 37.87 36.55 130.72
v1 [5.17, −5.17] [2.17, −9.43] [ − 4.5, −1.62] [ − 2.12, −6.5] [ − 0.37, 1.01]
(xi , yi2 )
2 (4.32, 1.7) (4.74, −8.42) (−6.99, −7.25) (2.35, −2.56) (−9.98, −5.19)
f (x2i , yi2 ) 21.56 93.45 101.43 12.09 126.51
v2 [5.70, −8.54] [2.17, −9.43] [0.43, 2.53] [ − 3.23, −7.95] [8.13, 4.68]
3
(xi , yi3 ) (10, −6.84) (6.91, −10) (−6.56, −4.72) (−0.87, −10) (−1.85, −0.51)
f (x3i , yi3 ) 146.79 147.75 65.31 100.76 3.68

Tabela 8: Kilka iteracji PSO dla α = 0.54 oraz β = 0.49

Pst
Pi0 (−3, 6) (1, 5) (2, −4) (5, 5) (−5, 6) (2, −4)
f (·) 45 26 20 50 61 20
Pi1 (−3, 6) (2.57, 1.01) (2, −4) (4.58, 3.94) (−5, −6) (2.57, 1.01)
f (·) 45 7.62 20 36.55 61 7.62
Pi2 (4.32, 1.7) (2.57, 1.01) (2, −4) (2.35, −2.56) (−5, −6) (2.57, 1.01)
f (·) 21.56 7.62 20 12.09 61 7.26
Pi3 (4.32, 1.7) (2.57, 1.01) (2, −4) (2.35, −2.56) (−1.85, −0.51) (−1.85, −0.51)
f (·) 21.56 7.62 20 12.09 3.68 3.68

Tabela 9: Najlepsze położenia dla kilku iteracji PSO

Algorytm Mrówkowy opracował Dorigo, jako probabilistyczną technike rozwiązywania pro-


blemów poprzez szukanie dobrych dróg w grafach. Jest on zainspirowany zachowaniem mrówek
szukających pożywienia dla swojej kolonii. Mrówki poruszają się w sposób losowy, gdy znajdą
pożywienie wracają do kolonii pozostawiając ślad feromonów. Gdy inna mrówka znajdzie ten
ślad, przestaje poruszać się w sposób losowy i podąża za nim w kierunku pożywienia.
Poziom feromonów uaktualniamy według równania

f t+1 (xi , xj ) = (1 − ρ)f t (xi , xj ) + Γti , (39)

gdzie ρ jest współczynnikiem wyparowania, t numerem iteracji, n liczbą mrówek w kolonii po-
dróżujących z punktu xi na odległość Γti .
Odległość jest obliczana według wzoru
n
X 1
Γti = , (40)
i=1
Ltij

gdzie Ltij jest długością ścieżki od xi do xj opisana metryką Kartezjusza


v
u 2
uX
t t t
Lij = kxi − xj k = t (xti,k − xtj,k )2 , (41)
k=1

40
gdzie w iteracji t mrówki poruszją się pomiędzy xi t , xj t o współrzędnych xti,k , xtj,k .
Prawdopodobieństwo wyboru ścieżki z xi do xj jest obliczane według wzoru

[f t (xi , xj )]α
pt (xi , xj ) = X , (42)
[f t (xi , xα )]α
k
Ltiα
α∈Ni

gdzie α wsp. oddziaływania feromonów, Lij jest odległością pomiędzy xi oraz xj (41), Nik jest
zbiorem pozycji mrówek z punktu xi , których mrówka k nie odwiedziła.
Ruch mrówek jest zależny od prawdopodobieństwa wyboru ścieżki (42). Mrówki przemiesz-
czają się według równania
xi t+1 ← xi t |max pt (xi ,xj ) (43)
gdzie max pt (xi , xj ) jest największym prawdopodobienstwem przemieszczenia z xi do xj .

Algorytm Pszczeli opracował Karaboga w 2005 jako technikę optymalizacji inspirowaną za-
chowaniem pszczół miodnych w ulu. Zakładamy, że jest tylko jedna pszczoła dla każdego źródła
pożywienia. Innymi słowy, liczba wykorzystanych pszczół w kolonii jest równa liczbie źródeł
żywności wokół ula. Zatrudnione pszczoły trafiają do źródła pożywienia i wracają do ula, gdzie
tańczą przekazując współrzędne.
Wymiana informacji między poszczołami jest modelowana równaniem
F (xi )
p(xi ) = n , (44)
X
F (xi )
i=1

gdzie F (xi ) jest wartością funkcji kryterialnej dla najlepszej pozycji zapamiętanej przez daną
pszczołę.
Dzieki tej informacji pszczoły lecą poszukiwać pożywienia w najlepszym kierunku. Model
ruchu jest opisany równaniem

xi t+1 = xi t + αk · β · ∆xik (45)

gdzie k jest losowym indeksem pszczoły pośród najlepszych pozycji, αk jest wartością losową z
przedziału [−1, 1], ∆xik jest obliczana

∆xik = (xij − xkj ), (46)

gdzie j jest losową współrzędną.

Algorytm Świetlika opracował Yang w 2008 jako technikę optymalizacji inspirowaną zacho-
waniem świetlików podczas godów, kiedy na podstawie świetlnych błysków poszukują najlepszego
partnera.

1. Wszystkie świetliki są jednopłciowe, dzięki czemu każdy pojedynczy świetlik zostanie przy-
ciągnięty do wszystkich innych świetlików.

2. Atrakcyjność jest proporcjonalna do jasności, czyli jaśniejszy przyciąga słabiej świecące.


Jasność powinna być powiązana z funkcją celu.

3. Intensywność światła zmniejsza się wraz ze wzrostem wzajemnej odległości.

41
4. Jeśli nie ma świetlików jaśniejszych niż dany świetlik, porusza się on losowo.

Aby model FA symulował rzeczywiste środowisko, określamy cechy biologiczne jako czynniki
numeryczne dla modelowanych osobników: Ipop - indywidualne natężenie światła, µ - losowy ruch
jednostek, βpop - atrakcyjność, γ - absorpcja światła otoczenia.

Odległość pomiędzy dwoma świetlikami i oraz j jest obliczana


v
u 2
uX
rij = kxi − xj k = t (xti,k − xtj,k )2 ,
t t t
(47)
k=1

gdzie xi t , xj t są punktami w przestrzeni stanów obiektu, xti,k , xtk,j są k współrzędnymi punktów


xi t oraz xj t w t iteracji.
Atrakcyjność lokalizacji i oraz j zmniejsza się wraz ze wzrostem odległości i jest proporcjo-
nalna do zmiany wartości funkcji kryterialnej i intensywności światła zgodnie ze wzorem
t 2
eγ·(Fij ) t 2 −(r t )2
t
βij t
(rij ; Fijt ) = βpop · Ipop · t )2
(rij
= βpop · Ipop · eγ·(Fij ) ij (48)
e
t (r t ; F t ) jest atrakcyjnością punktu i w stosunktu do j w t iteracji, r t jest odległością
gdzie βij ij ij ij
między nimi (47), Fijt jest wartością różnicy funkcji kryterialnej między tymi punktami, γ jest
współczynnikiem absorbcji światła symylującym naturalne warunki.
Świetlik przejdzie do najatrakcyjniejszej lokalizacji mierząc intensywność światła na odle-
głość. Model ruchu z punktu i w kierunku punktu j jest wykonywany zgodnie ze wzorem

xi t+1 = xi t + (xj t − xi t ) · βij


t t
(rij t
; HSBij ) + µt (49)

gdzie xi t oraz xj t asą stanami obiektu, µt ijest wpółczynnikiem losowości ruchu w iteracji t,
t (r t ; F t ) jest atrakcyjnością danego punktu (48).
βij ij ij

Algorytm Nietoperza opracował Yang w 2010 jako technikę optymalizacji inspirowaną za-
chowaniem echolokacji nietoperzy, ze zmiennymi pulsami emisji i głośności.

1. Wszystkie nietoperze używają echolokacji, aby wyznaczyć odległość.

2. Wszystkie nietoperze znają różnice pomiędzy jedzeniem/ofiarą oraz ograniczeniami podło-


ża.

3. Nietoperz i leci z losową prędkością vi na pozycji xi mając ustaloną częstotliwość pulsacji


fm in (czy długość fali λmin ).

4. Długość fali λ (lub częstotliwość f ) oraz głośność pulsów może się zmieniać w trakcie
polowania.

5. Nietoperze mogą automatycznie dostosować długość fali i częstość emisji pulsów, w zależ-
ności od bliskości ich celu.

6. W trakcie polowania głośność zmienia się od głośno (występuje) A0 do minimalnej wartości


Amin .

42
Najpierw wyznaczymy lidera (punkt o najlepszych własnościach wzgledem funkcji kryte-
rialnej) w danej iteracji ∗xt . Następnie mierzymy odległość pozostałych osobników (punktów)
względem wybranego lidera. Odległość pomiędzy dowolnym nietoperzem i oraz liderem ∗xt w
badanej przestrzeni określimy metryką kartezjańską
v
u 2
uX
t t
ri = ||xi − ∗x || = t (xi,k − ∗xtk )2 (50)
k=1

gdzie xi,k to współrzędna przestrzenna, ∗xtk to współrzędna przestrzenna najlepszego osobnika,


t iteracja.
Ruch nietoperza i-tego jest wykonywany w stronę wyznaczonego w danej epoce lidera ∗xt przy
wykorzystaniu informacji o pozostałych osobnikach populacji, które modelujemy równaniami

fit = fmin + (fmax − fmin ) · βit (51)

vit = vit−1 + (xti − ∗xt ) · fit (52)


gdzie fmin to minimalna częstotliwość pulsacji, fmax to maxymalna częstotliwość pulsacji, fit
częstotliwość pulsacji w danej iteracji, vit prędkość nietopera i w iteracji t, βit ∈ [0, 1] to losowy
wektor rozkładu jednostajnego.
Ruch nietoperza (
t xi t−1 + (−1) · N (0, 1) dla rit > ∗γ
xi = (53)
xi t−1 +  · vit dla rit ¬ ∗γ
gdzie xi t to nowa pozycja nietoperz i, γ współczynnik korekcji oddalenia od lidera, rit odległość
nietoperza i od lidera w iteracji t, t iteracja.

Literatura

• Skiena S., Algorithm Design Manual, Springer London 2010.

• Cormen T., Introduction to Algorithms, MIT University Press Group Ltd 2009.

• Heineman G., Pollice G., Selkow S., Algorytmy. Almanach. Helion 2010.

• Rytter W., Diks K., Banachowski L., Algorytmy i struktury danych, Wydawnictwo Nauko-
we PWN, Warszawa 2017.

• Aho A., Hopcroft J., Ullman J., Algorytmy i struktury danych, Helion 2003.

• Bäck T., Fogel D., Michalewicz Z., Handbook of evolutionary computation, CRC Press
1997.

• Bonabeau E., Dorigo M., Theraulaz G., Swarm Intelligence: From Natural to Artificial
Systems, Oxford University Press, 1999.

43

You might also like