Wstęp do informatyki

Podobne dokumenty
prowadzący dr ADRIAN HORZYK /~horzyk tel.: Konsultacje paw. D-13/325

PODSTAWY INFORMATYKI wykład 1.

Algorytmy i struktury danych

PODSTAWY INFORMATYKI wykład 4.

PODSTAWY INFORMATYKI wykład 5.

Sortowanie. Kolejki priorytetowe i algorytm Heapsort Dynamiczny problem sortowania:

PODSTAWY INFORMATYKI wykład 6.

Programowanie strukturalne. Opis ogólny programu w Turbo Pascalu

PoniŜej znajdują się pytania z egzaminów zawodowych teoretycznych. Jest to materiał poglądowy.

Algorytmy i struktury danych

Definicja. Ciąg wejściowy: Funkcja uporządkowująca: Sortowanie polega na: a 1, a 2,, a n-1, a n. f(a 1 ) f(a 2 ) f(a n )

Podstawy programowania skrót z wykładów:

Język programowania PASCAL

Złożoność obliczeniowa algorytmu ilość zasobów komputera jakiej potrzebuje dany algorytm. Pojęcie to

Dynamiczny przydział pamięci w języku C. Dynamiczne struktury danych. dr inż. Jarosław Forenc. Metoda 1 (wektor N M-elementowy)

1. Nagłówek funkcji: int funkcja(void); wskazuje na to, że ta funkcja. 2. Schemat blokowy przedstawia algorytm obliczania

Pascal typy danych. Typy pascalowe. Zmienna i typ. Podział typów danych:

Podstawy programowania 2. Temat: Drzewa binarne. Przygotował: mgr inż. Tomasz Michno

Sortowanie - wybrane algorytmy

Wstęp do programowania. Różne różności

Wykład II PASCAL - podstawy składni i zmienne, - instrukcje wyboru, - iteracja, - liczby losowe

Algorytmy i Struktury Danych

WSTĘP DO INFORMATYKI. Struktury liniowe

Algorytmy i struktury danych. Wykład 4 Tablice nieporządkowane i uporządkowane

Podstawowe algorytmy i ich implementacje w C. Wykład 9

Algorytmy i struktury danych. wykład 5

INSTRUKCJA PUSTA. Nie składa się z żadnych znaków i symboli, niczego nie robi. for i := 1 to 10 do {tu nic nie ma};

PODSTAWY INFORMATYKI wykład 10.

Kolejka priorytetowa. Często rozważa się kolejki priorytetowe, w których poszukuje się elementu minimalnego zamiast maksymalnego.

INFORMATYKA SORTOWANIE DANYCH.

TEORETYCZNE PODSTAWY INFORMATYKI

Algorytmy sortujące i wyszukujące

Wstęp do informatyki. Maszyna RAM. Schemat logiczny komputera. Maszyna RAM. RAM: szczegóły. Realizacja algorytmu przez komputer

Drzewa binarne. Drzewo binarne to dowolny obiekt powstały zgodnie z regułami: jest drzewem binarnym Jeśli T 0. jest drzewem binarnym Np.

operacje porównania, a jeśli jest to konieczne ze względu na złe uporządkowanie porównywanych liczb zmieniamy ich kolejność, czyli przestawiamy je.

Zadanie 1 Przygotuj algorytm programu - sortowanie przez wstawianie.

Wykład 3. Złożoność i realizowalność algorytmów Elementarne struktury danych: stosy, kolejki, listy

Podstawy Informatyki. Metody dostępu do danych

Podstawy Informatyki. Wykład 6. Struktury danych

ZASADY PROGRAMOWANIA KOMPUTERÓW ZAP zima 2014/2015. Drzewa BST c.d., równoważenie drzew, kopce.

Analiza algorytmów zadania podstawowe

Algorytmy i Struktury Danych.

WSTĘP DO INFORMATYKI. Drzewa i struktury drzewiaste

Programowanie w VB Proste algorytmy sortowania

Podstawy Informatyki. Sprawność algorytmów

Przykładowe B+ drzewo

Sortowanie. Bartman Jacek Algorytmy i struktury

Luty 2001 Algorytmy (7) 2000/2001

Porządek symetryczny: right(x)

Sortowanie bąbelkowe

Algorytmy i struktury danych. Wykład 4

Tadeusz Pankowski

Wykład II PASCAL - podstawy składni i zmienne, - instrukcje wyboru, - iteracja cz. 1

Algorytmika i pseudoprogramowanie

Jeszcze o algorytmach

Klasa 2 INFORMATYKA. dla szkół ponadgimnazjalnych zakres rozszerzony. Założone osiągnięcia ucznia wymagania edukacyjne na. poszczególne oceny

Wykład 5. Sortowanie w czasie liniowologarytmicznym

DIAGRAMY SYNTAKTYCZNE JĘZYKA TURBO PASCAL 6.0

2:8,7 3:9,4 / \ / \ / \ / \ 4:7,3 5:8 6:9,2 7:4

EGZAMIN - Wersja A. ALGORYTMY I STRUKTURY DANYCH Lisek89 opracowanie kartki od Pani dr E. Koszelew

Struktury danych: stos, kolejka, lista, drzewo

Instrukcje podsumowanie. Proste: - przypisania - wejścia-wyjścia (read, readln, write, writeln) - pusta - po prostu ; (średnik) Strukturalne:

Zaawansowane algorytmy i struktury danych

WYŻSZA SZKOŁA INFORMATYKI STOSOWANEJ I ZARZĄDZANIA

Złożoność obliczeniowa zadania, zestaw 2

Sortowanie. LABORKA Piotr Ciskowski

Rekurencja. Dla rozwiązania danego problemu, algorytm wywołuje sam siebie przy rozwiązywaniu podobnych podproblemów. Przykład: silnia: n! = n(n-1)!

Informatyka A. Algorytmy

Podstawy programowania. Wykład PASCAL. Zmienne wskaźnikowe i dynamiczne. dr Artur Bartoszewski - Podstawy prograowania, sem.

Informatyka 1. Wyrażenia i instrukcje, złożoność obliczeniowa

Zasady analizy algorytmów

5. Rozwiązywanie układów równań liniowych

Złożoność algorytmów. Wstęp do Informatyki

Algorytm. a programowanie -

Algorytm selekcji Hoare a. Łukasz Miemus

Elżbieta Kula - wprowadzenie do Turbo Pascala i algorytmiki

Matematyczne Podstawy Informatyki

WSTĘP DO INFORMATYKI. Złożoność obliczeniowa, efektywność i algorytmy sortowania

TEORETYCZNE PODSTAWY INFORMATYKI

Sortowanie w czasie liniowym

Lab 9 Podstawy Programowania

Wprowadzenie do złożoności obliczeniowej

Algorytmy. Programowanie Proceduralne 1

Algorytmy i złożoność obliczeniowa. Wojciech Horzelski

Struktury danych i złozoność obliczeniowa. Prof. dr hab. inż. Jan Magott

Opis problemu i przedstawienie sposobu jego rozwiązania w postaci graficznej. Gimnazjum nr 3 im. Jana Pawła II w Hrubieszowie 1

Algorytmy i struktury danych

Algorytmy Równoległe i Rozproszone Część V - Model PRAM II

Algorytmy i struktury danych. Drzewa: BST, kopce. Letnie Warsztaty Matematyczno-Informatyczne

Wstęp do programowania

Programowanie w Turbo Pascal

Grafem nazywamy strukturę G = (V, E): V zbiór węzłów lub wierzchołków, Grafy dzielimy na grafy skierowane i nieskierowane:

Wykład z Technologii Informacyjnych. Piotr Mika

< K (2) = ( Adams, John ), P (2) = adres bloku 2 > < K (1) = ( Aaron, Ed ), P (1) = adres bloku 1 >

Pascal - wprowadzenie

Drzewa podstawowe poj

Algorytmy i. Wykład 5: Drzewa. Dr inż. Paweł Kasprowski

Podstawy Programowania C++

Algorytmy i struktury danych. Wykład 6 Tablice rozproszone cz. 2

Definicje. Algorytm to:

Transkrypt:

Wstęp do informatyki LITERATURA: L. Banachowski, K. Diks, W. Rytter: Algorytmy i struktury danych, WNT, Warszawa, 2001 M. Iglewski, J. Madey, S. Matwin: Pascal A. Marciniak: Turbo Pascal W. J. Savith: Pascal A. Sielicki i in.: Labolatorium programowania w Pascalu M. Sysło: Elementy Informatyki A.Szepietowski: Podstawy Informatyki J. Szczepkowicz: Turbo Pascal R. Tadeusiewicz, P. Moszner, A. Szydełko: Teoretyczne podstawy informatyki R. Tadeusiewicz: Elementarne wprowadzenie do techniki sieci neuronowych z przygkładowymi programami, PLJ, Warszawa 1998 W. M. Turski: Propedeutyka informatyki N. Wirth: Wstęp do programowania systematycznego N. Wirth: ALGORYTMY + STRUKTURY DANYCH = PROGRAMY http://www.mini.pw.edu.pl/miniwyklady/grafy/ INFORMATYKA Dziedzina nauki i techniki zajmująca się ogółem zagadnień automatyzacji przetwarzania informacji. ALGORYTM Opis rozwiązywania problemu wyrażony za pomocą takich operacji, które wykonawca (np. komputer) rozumie i potrafi wykonać. Prz. 1. Obsługa automatycznego garażu wielopoziomowego: Dane: c = ciężar samochodu Wynik: n = numer poziomu lub brak miejsca (n=0) Algorytm: (1) Zważ samochód na automatycznej wadze, ciężar c ; krok (2) (2) Jeśli c > 10 T to krok (6), inaczej krok (3) (3) Jeśli c > 5 T to krok (5), inaczej krok (4) (4) Jeśli jest miejsce na poziomie III to "WJEDŹ NA POZIOM III"; zmniejsz liczbę miejsc na poziomie III; STOP, inaczej krok (5) (5) Jeśli jest miejsce na poziomie II to "WJEDŹ NA POZIOM II"; zmniejsz liczbę miejsc na poziomie II; STOP, inaczej krok (6) (6) Jeśli jest miejsce na poziomie I to "WJEDŹ NA POZIOM I"; zmniejsz liczbę miejsc na poziomie I; STOP, inaczej krok (7) (7) "NIESTETY BRAK MIEJSCA" ; STOP Prz. 2. Obliczanie największego wspólnego dzielnika - NWD Dane: a,b - liczby naturalne > 1 Wynik: c = NWD(a,b) 2.1. Algorytm NWD szkolny - opisowo (1) Pobierz liczby a i b. (2) Wypisz czynniki pierwsze liczby a, otrzymasz zbiór A = {c 1,c 2,...,c n } (3) Wypisz czynniki pierwsze liczby b, otrzymasz zbiór B = {b 1,b 2,...,b m } (4) Utwórz zbiór C jako przecięcie zbiorów A i B (5) Oblicz c jako iloczyn wszystkich elementów ze zbioru C (Jeśli C jest zbiorem pustym podstaw c = 1) 1

(6) Wypisz c i zatrzymaj się. Przykład: (1) dane: a = 4200, b = 7524 (2) a = 2*2*2*3*5*5*7 A = {2,2,2,3,5,5,7} (3) b = 2*2*3*3*11*19 B = {2,2,3,3,11,19} (4) C = {2,2,3} (5) c = 2*2*3 = 12 (6) wynik: 12 = NWD(4200,7524) Pamięć komputera składa się z komórek, w których zawarte są informacje. Informacje te są ciągami bitów i mogą oznaczać liczby, napisy, rozkazy oraz ich zestawy. Komórki w pamięci mają swoje numery zwane adresami. Podstawowe rozkazy, które potrafi wykonać komputer: Rozkazy ORGANIZACYJNE: zatrzymaj się, nic nie rób, przyłącz urządzenie... Rozkazy PRZESYŁANIA: informację z komórki A zapisz w komórce B... Rozkazy STERUJĄCE: jako następny wykonaj rozkaz z komórki R... (tzw. skoki bezwarunkowe) jeśli w komórce A jest PRAWDA, to jako następny wykonaj rozkaz z komórki R (tzw. skoki warunkowe) Rozkazy ARYTMETYCZNO-LOGICZNE: w komórce Z umieść sumę liczb z komórek X i Y w komórce P umieść wartość wyrażenia A>7 Rozkazy WEJŚCIA/WYJŚCIA: wprowadź dane z urządzenia U do komórki D wyprowadź wyniki z komórki W na urządzenie U. Inne algorytmy poszukujące NWD: 2.2.1 Algorytm NWD najprostszy - opisowo (1) wczytaj dane: a, b (2) zapamiętaj a w komórce c, b w komórce d (3) Dopóki c <> d wykonuj (3.1) Jeśli c > d to zmniejsz c o d inaczej zmniejsz d o c (4) wypisz wynik: c (5) STOP 2.2.2 Algorytm NWD najprostszy formalnie BEGIN czytaj ( a, b ); c := a; d := b; WHILE c <> d DO IF c > d THEN c := c-d ELSE d := d-c; wypisz ( c ) END Zmiany zmiennych c i d w czasie działania algorytmu dla danych a=4200, b=7524: c 4200 876 180 24 12 d 7524 3324 2448 1572 696 516 336 156 132 108 84 60 36 12 2.3 Algorytm NWD Euklidesa BEGIN czytaj ( a, b ); c := a; d := b; REPEAT e := c MOD d; c := d; d := e 2 UNTIL d = 0; wypisz ( c ) END Przykład: Zmiana zmiennej e w czasie działania algorytmu dla danych a = 4200, b = 7524:

e 4200 3324 876 696 180 156 24 12 0 Prz. 3. Wyszukiwanie elementu w ciągu uporządkowanym: CONST mx = 100000; TYPE slowo = String[25]; slownik = ARRAY [ 1.. mx ] OF slowo ; { dla A.4.1. } slownik = ARRAY [ 0.. mx ] OF slowo ; { dla A.4.2. } dane: n : Longint - liczba słów w słowniku [2,mx] x : slowo - poszukiwane słowo a : slownik - uporządkowany zbiór n słów wyniki: k = 0 gdy nie ma słowa 'x' w słowniku 'a' k <> 0 gdy x = a[k] 3.1. Prosty: 3.2. Z wartownikiem: BEGIN BEGIN czytaj ( n, a, x ); czytaj ( n, a, x ); k := 0; i := 0; k := n + 1; REPEAT a[0] := x; // Wartownik i := i + 1; REPEAT IF a[i] = x THEN k := i k := k 1 UNTIL (i=n) OR (k>0); UNTIL a[k] = x; // Prostszy warunek do sprawdzenia Write ( k ) Write ( k ) END END 3.3. Szybkie wyszukiwanie w słowniku posortowanych słów wg wzrastającej wartości: BEGIN czytaj ( n, a, x ); k := 0; prw := 1; ost := n; REPEAT 1. prw srd ost srd := (prw+ost) DIV 2; 2. prw srd ost IF a[srd] = x 3. prw srd ost THEN k := srd ELSE IF a[srd] > x THEN ost := srd 1 1 2 3 4 5 6 7 8 9 k=10 11 12 13 14 15 16=n ELSE prw := srd + 1 UNTIL (prw>ost) OR (k>0); Write ( k ) END Prz. 4. Potęgowanie o wykładniku naturalnym y = x n : 4.1. 4.2. BEGIN BEGIN Read ( x, n ); Read ( x, n ); y := 1; y := 1; m := n; z := x; m := n; WHILE m > 0 DO WHILE m > 0 DO BEGIN BEGIN IF Odd ( m ) // m - nieparzyste m := m - 1; THEN y := y * z; y := y * x m := m DIV 2; END; z := z * z 3

Write ( y ) END; END Write ( y ) END Prz. 5. Obliczanie dnia tygodnia dla podanej daty: dane: d [1,31] dzień, m [1,12] miesiąc, r [1901,2099] rok wynik: t [0,6] { 0-niedziela,...,6-sobota } BEGIN Read ( d, m, r ); n := 6 + d + r - 1900 + ( r - 1900 ) DIV 4; CASE m OF 4, 7 : ; 1,10 : n := n + 1; 5 : n := n + 2; 8 : n := n + 3; 2, 3,11 : n := n + 4; 6 : n := n + 5; 9,12 : n := n + 6 END; IF ((r MOD 4)=0) AND (m<3) THEN n := n - 1; t := n MOD 7; Write ( t ) END Zadanie: Skonstruować program umożliwiający wyznaczenie dnia tygodnia od początku naszej ery, tj. od roku 0. Prz. 6. Obliczanie wartości wielomianu: CONST mx = 25 ; TYPE wsp = ARRAY [ 0.. mx ] OF Real ; dane: n : Integer - stopien wielomianu [0,mx] x : Real - argument a : wsp - wspolczynniki wielomianu wyniki: y = a[0] + a[1] * x +... + a[n] * x ^ n 6.1. BEGIN czytaj ( n, x, a ); y := a[0]; i := 0; WHILE i < n DO BEGIN i := i + 1; j := 0; p := 1; REPEAT j := j + 1; p := p * x UNTIL j = i; y := y + a[i] * p END; Write ( y ) 6.2. BEGIN czytaj ( n, x, a ); y := a[0]; i := 0; p := 1; WHILE i < n DO BEGIN i := i + 1; p := p * x; y := y + a[i] * p END; Write ( y ) END 6.3. Algorytm Hornera: BEGIN czytaj ( n, x, a ); y := a[n]; i := n; WHILE i > 0 DO BEGIN i := i - 1; y := y * x + a[i] END; Write ( y ) END 4

END ARYTMETYKA KOMPUTEROWA Wartości znaków i cyfr w różnych systemach pozycyjnych: 0-zero 1-jeden 2-dwa 3-trzy 4-cztery 5-pięć 6-sześć 7-siedem 8-osiem 9-dziewięć A-dziesięć B-jedenaście C-dwanaście D-trzynaście E-czternaście F-piętnaście Różne sposoby zapisu kolejnych liczb naturalnych: 16 10 2 rzymski --- --- ------- ------- 0 0 0 1 1 1 I 2 2 10 II 3 3 11 III 4 4 100 IV 5 5 101 V Tabliczki mnożenia: 6 6 110 VI [10] 1 2 3 4 5 6 7 8 9 7 7 111 VII 2 4 6 8 10 12 14 16 18 8 8 1000 VIII 3 6 9 12 15 18 21 24 27 9 9 1001 IX 4 8 12 16 20 24 28 32 36 A 10 1010 X 5 10 15 20 25 30 35 40 45 B 11 1011 XI 6 12 18 24 30 36 42 48 54 C 12 1100 XII 7 14 21 28 35 42 49 56 63 D 13 1101 XIII 8 16 24 32 40 48 56 64 72 E 14 1110 XIV 9 18 27 36 45 54 63 72 81 F 15 1111 XV 10 16 10000 XVI [2] 1 11 17 10001 XVII Tabliczki mnożenia: [16] 1 2 3 4 5 6 7 8 9 A B C D E F 2 4 6 8 A C E 10 12 14 16 18 1A 1C 1E 3 6 9 C F 12 15 18 1B 1E 21 24 27 2A 2D 4 8 C 10 14 18 1C 20 24 28 2C 30 34 38 3C 5 A F 14 19 1E 23 28 2D 32 37 3C 41 46 4B 6 C 12 18 1E 24 2A 30 36 3C 42 48 4E 54 5A 7 E 15 1C 23 2A 31 38 3F 46 4D 54 5B 62 69 8 10 18 20 28 30 38 40 48 50 58 60 68 70 78 9 12 1B 24 2D 36 3F 48 51 5A 63 6C 75 7E 87 A 14 1E 28 32 3C 46 50 5A 64 6E 78 82 8C 96 B 16 21 2C 37 42 4D 58 63 6E 79 84 8F 9A A5 C 18 24 30 3C 48 54 60 6C 78 84 90 9C A8 B4 D 1A 27 34 41 4E 5B 68 75 82 8F 9C A9 B6 C3 5

E 1C 2A 38 46 54 62 70 7E 8C 9A A8 B6 C4 D2 F 1E 2D 3C 4B 5A 69 78 87 96 A5 B4 C3 D2 E1 Ta sama liczba przedstawiona w układach o różnych podstawach: 11111001011 [2] = 2201220 [3] = 133023 [4] = 30440 [5] = 13123 [6] = 5550 [7] = 3713 [8] = 2656 [9] = 1995 [10] = 1554 [11] = 11A3 [12] = BA6 [13] = A27 [14] = 8D0 [15] = 7CB [16] Algorytmy konwersji (liczb naturalnych): Załóżmy, że umiemy wykonywać działania w układzie o podstawie p. A. Dane : p, q, n, c[n],..., c[1], c[0] Przykłady: Wyniki : L tak by było spełnione: (c n... c 1 c 0 ) [q] = (L) [p] 1.) p=10,q=3,n=6,c 6 =2,c 5 =2,c 4 =0,c 3 =1,c 2 =2,c 1 =2,c 0 =0 ((((((2)*3+2)*3+0)*3+1)*3+2)*3+2)*3+0 = (((((8)*3+0)*3+1)*3+2)*3+2)*3+0 = ((((24)*3+1)*3+2)*3+2)*3+0 = (((73)*3+2)*3+2)*3+0 = ((221)*3+2)*3+0 = (665)*3+0 = 1995 a więc (2201220) [3] = (1995) [10] 2.) p=8, q=10, n=3 c 3 =1, c 2 =9, c 1 =9, c 0 =5 (((1) * 12 + 11) *12 + 11 ) * 12 + 5 = ((23) * 12 + 11 ) * 12 + 5 = (307) * 12 + 5 = 3713 a więc (1995) [10] = (3713) [8] B. Dane : p,q,l Przykłady: Wyniki : n, c[n],..., c[1], c[0] tak by (L) [p] = (cn... c1 c0) [q] 1.) p = 10, q = 2, L = 1995 2.) p = 10, q = 6, L = 1995 3.) p = 10, q = 16, L = 1995 1995 : 2 = 997 r 1 1995 : 6 = 332 r 3 1995 : 16 = 124 r 11 (B) 997 : 2 = 498 r 1 332 : 6 = 55 r 2 124 : 16 = 7 r 12 (C) 498 : 2 = 249 r 0 55 : 6 = 9 r 1 7 : 16 = 0 r 7 (7) 249 : 2 = 124 r 1 9 : 6 = 1 r 3 (1995) [10] = (7CB) [16] 124 : 2 = 62 r 0 1 : 6 = 0 r 1 (n = 2) 62 : 2 = 31 r 0 (1995) [10] = (13123) [6] 31 : 2 = 15 r 1 (n = 4) 15 : 2 = 7 r 1 4.) p = 2, q = 10, L = 11111001011 7 : 2 = 3 r 1 11111001011 : 1010 = 11000111 r 101 (5) 3 : 2 = 1 r 1 11000111 : 1010 = 10011 r 1001 (9) 1 : 2 = 0 r 1 10011 : 1010 = 1 r 1001 (9) 6

(1995) [10] = (11111001011) [2] 1 : 1010 = 0 r 1 (1) (n=10) (11111001011) [2] = (1995) [10] (n=3) Algorytmy konwersji (skończonych i nieskończonych rozwinięć pozycyjnych): a) Przetwarzanie skończonych rozwinięć pozycyjnych i ułamków właściwych : rozwiniecie skończone liczbę wymierną rozwiniecie nieskończone: okresowe liczbę wymierną nieokresowe liczbę niewymierną C. Dane : p, q, m, c -1, c -2,..., c -m Wyniki : U tak by ( 0. c -1... c -m ) [q] ( U ) [p] Przykłady: 1.) p=10, q=4, m=5, c -1 =0, c -2 =3, c -3 =1, c -4 =1, c -5 =2 (3112) [4] (214) [10] (107) [10] (0.03112) [4] = ------------------ = ------------------ = --------------- = (0.208984375) [10] (100000) [4] (1024) [10] (512) [10] (((( 2 / 4 + 1 ) / 4 + 1 ) / 4 + 3 ) / 4 + 0 ) / 4 = (((1.5 / 4 + 1 ) / 4 + 3 ) / 4 + 0 ) / 4 = ((1.375 / 4 + 3 ) / 4 + 0 ) / 4 = (3.3475 / 4 + 0 ) / 4 = 0.8359375 / 4 = 0.208984375 a więc: (0.03112) [4] = (0.208984375) [10] 2.) p=10, q=12, m=2, c -1 = 5, c -2 = B (5B) [12] (71) [10] (0.5B) [12] = --------------- = -------------- = (0.4930(5))[10] (100) [12] (144) [10] (11/12+5)/12 = 5.91666666.../12 = 0.493055555... = 0.4930(5) a więc: (0.5B) [12] = (0.493055555...) [10] = (0.4930(5)) [10] 3.) p=10, q=7, m=1, c -1 = 3 (3) [7] (3) [10] (0.3) [7] = ----------- = ------------ = (0.(428571)) [10] (10) [7] (7) [10] (0.3) [7] = (0.428571428571...) [10] = (0.(428571)) [10] 4.) p=2, q=10, m=2, c -1 = 4, c -2 = 5 (45) [10] (101101) [2] (0.45) [10] = ------------- = ------------------- = (0.01(1100)) (100) [10] (1100100) [2] 7

( 101 / 1010 + 100 ) / 1010 = 100.1 / 1010 = (0.011100110011...) = (0.01(1100)) a więc: (0.45) [10] = (0.01(1100)) [2] D. Dane : p, q, m, U Wyniki : c -1, c -2,..., c -m tak by ( U ) [p] ( 0. c -1... c -m ) [q] Przykłady: 1. p=10, q=5, m=8, U=0.768 2. p=10, q=6, m=9, U=0.4375 (0).768 x 5 (0).4375 x 6 (3).840 x 5 (2).6250 x 6 (4).200 x 5 (3).7500 x 6 (1).000 (4).5000 x 6 (3).0000 (0.768) [10] = (0.341000) [5] = (0.341) [5] (0.4375) [10] = (0.2343000) [6] = (0.2343) [6] 3. p=10, q=16, m=6, U=0.56 (0).56 x 16 [8] = (8).96 x 16 [F] = (15).36 x 16 [5] = (5).76 x 16 [C] = (12).16 x 16 [2] = (2).56 x 16 (0.56) [10] = (0.8F5C28F5C28F5C...) [16] = (0.(8F5C2)) [16] (0.8F5C29) [16] (0.381) [10] =(0.21414353410200324225...) [6] = (0.214(1435341020032422550452131)) [6] = (0.214143534102) [6] = { zaokrąglenie } (0.2141440) [6] 4. p=10, q=6, m=12, U=0.381 (0).381 x 6 (2).286 x 6 (1).716 x 6 (4).296 x 6 (1).776 x 6 (4).656 x 6 (3).936 x 6 (5).616 x 6 (3).696 x 6 (4).176 x 6 (1).056 x 6 (0).336 x 6 (2).016 x 6 (0).096 x 6 (0).576 x 6 (3).456 x 6 (2).736 x 6 (4).416 x 6 (2).496 x 6 8

(2).976 x 6 (5).856 x 6 9

Język programowania PASCAL - umożliwia zapis algorytmów i ich kompilację na postać zrozumiałą przez komputer. Rozróżniamy instrukcje: 1. procedury 2. przypisania 3. puste 4. skoku 5. sekwencji 6. warunkowe 7. wyboru 8. iteracyjne 9. pętli 10. wiążące 1. Instrukcja procedury składnia: nazwa_procedury nazwa_procedury ( <LISTA_PARAMETRÓW_AKTUALNYCH> ) Przykłady: Exit Writeln Readln ( dzien, miesiac, rok ) Write ( 'liczba przykladow = ',n ) Assign ( plik, 'dane.txt' ) UWAGA! Liczba, kolejność i typy parametrów muszą być zgodne z definicją procedury. 2. Instrukcja przypisania składnia: nazwa_zmiennej := <WYRAŻENIE> nazwa_funkcji := <WYRAŻENIE> Przykłady: srednia := (a+b)/2 dataur.miesiac := 5 wektor[1] := wektor_roboczy nwd := c klasa[1].uczen.nazwisko := 'Jan'+' '+'Nowak' UWAGA! Typ wyrażenia musi być zgodny z typem zmiennej. 3. Instrukcja pusta Przykłady:... IF <WARUNEK> THEN {*} ELSE <INSTRUKCJA>...... REPEAT {*} UNTIL...... BEGIN {*} ; {*} ; {*} END... UWAGA! W miejscach oznaczonych {*} występuje instrukcja pusta. 4. Instrukcja skoku składnia: GOTO <ETYKIETA> Przykład: LABEL 13; { deklaracja etykieta numer 13 }... { Deklaracja, skok i instrukcja } GOTO 13; { etykietowana muszą znajdować }... { się w tym samym bloku. } 13 : <INSTRUKCJA> UWAGA! Nie należy używać bez wyraźnej potrzeby! Zaburza dobry styl programowania! 10

5. Instrukcja sekwencji składnia: BEGIN <CIĄG_INSTRUKCJI> END <CIĄG_INSTRUKCJI> <INSTRUKCJA> <CIĄG_INSTRUKCJI> <CIĄG_INSTRUKCJI>; <INSTRUKCJA> Przykład: BEGIN Write('podaj dwie liczby calkowite '); Readln(a,b); c:=a+b; Writeln(a,'+',b,'=',c) { Brak! } END 6. Instrukcje warunkowe składnia: IF <WARUNEK> THEN <INSTRUKCJA> IF <WARUNEK> THEN <INSTRUKCJA> ELSE <INSTRUKCJA> Przykłady: IF x<0 THEN x:=-x { to samo, co x:=abs(x) } IF c>d THEN c:=c-d ELSE d:=d-c IF delta>0 THEN BEGIN x1 := (-b-sqrt(delta))/(2*a); x2 := (-b+sqrt(delta))/(2*a) END ELSE IF delta=0 THEN x1 := -b/2/a ELSE {nic nie rób} 1) x:=0; IF a>b THEN BEGIN IF c>a THEN x:=5 ELSE x:=2 END; 2) x:=0; IF a>b THEN BEGIN IF c>a THEN x:=5 END ELSE x:=2; 3) x:=0; IF a>b THEN IF c>a THEN x:=5 ELSE x:=2 ; a b c 1) x 2) x 3) x 7 6 8 5 5 5 7 6 4 2 0 2 6 7 8 0 2 0 7. Instrukcja wyboru składnia: CASE <WYRAŻENIE> OF <SEKW.INSTR.WYB.> END <SEKW.INSTR.WYB.> <INSTR.WYB.> <SEKW.INSTR.WYB.> <SEKW.INSTR.WYB.>; <INSTR.WYB.> <INSTR.WYB.> <ETYKIETY>: <INSTRUKCJA> <ETYKIETY> <ETYKIETA> <ETYKIETY> <ETYKIETY>, <ETYKIETA> <ETYKIETA> <STAŁA> <ETYKIETA> <STAŁA>... <STAŁA> 11

Przykłady: CASE numer_dnia_tygodnia OF 0 : Writeln ( 'niedziela ' ); 1 : Writeln ( 'poniedziałek' ); 2 : Writeln ( 'wtorek ' ); 3 : Writeln ( ' roda ' ); 4 : Writeln ( 'czwartek ' ); 5 : Writeln ( 'piątek ' ); 6 : Writeln ( 'sobota ' ); END CASE miesiac OF 1,3,5,7,8,10,12 : liczba_dni := 31 ; 4,6,9,11 : liczba_dni := 30 ; 2 : IF (rok MOD 4)=0 THEN liczba_dni := 29 ELSE liczba_dni := 28 END CASE miesiac OF 4,7 : {instrukcja pusta! } ; 1,10 : redukt := redukt + 1 ; 5 : redukt := redukt + 2 ; 8 : redukt := redukt + 3 ; 2,3,11 : redukt := redukt + 4 ; 6 : redukt := redukt + 5 ; 9,12 : redukt := redukt + 6 ; END CASE punkty OF 0.. 22 : ocena := 'niedostateczny' ; 23.. 27 : ocena := ' dostateczny' ; 28.. 30 : ocena := ' + dostateczny' ; 31.. 35 : ocena := ' dobry' ; 36.. 38 : ocena := ' + dobry' ; 39.. 43 : ocena := ' bardzo dobry' ; ELSE ocena := 'nieklasyfikowany' {TP} END UWAGI: <WYRAŻENIE> musi być typu wyliczeniowego, <ETYKIETY> muszą być typu <WYRAŻENIE>, zbiory etykiet muszą być rozłączne, <WYRAŻENIE> musi przyjmować wartość której <ETYKIETY> 12

8.-9. Instrukcje iteracyjne i pętli - składnia WHILE <WARUNEK> DO <INSTRUKCJA> REPEAT <CIĄG_INSTRUKCJI> UNTIL <WARUNEK> FOR <ZMIENNA> := <W.P.> TO <W.K.> DO <INSTRUKCJA> FOR <ZMIENNA> := <W.P.> DOWNTO <W.K.> DO <INSTRUKCJA> UWAGI: <W.P.> - wyrażenie początkowe, <W.K.> - wyrażenie końcowe <ZMIENNA>, <W.P.>, <W.K.> muszą być tego samego typu porządkowego, <ZMIENNA> nie może być jawnie modyfikowana wewnątrz pętli, jeżeli " TO " to <W.P.> <= <W.K.>, jeżeli "DOWNTO " to <W.P.> >= <W.K.>. Przykład 1.: { obliczanie y = x^(-n) tj. x do potęgi -n } { x<>0, n>=0 } { x<>0, n>0 } { x<>0, n>0 } y := 1; y := 1; y := 1; i := n; i := n; FOR i := n DOWNTO 1 WHILE i>0 DO BEGIN REPEAT DO y := y/x y := y/x; y := y/x; i := i-1 i := i-1 END UNTIL i=0 REPEAT UNTIL Keypressed; {najczęściej stosowane} WHILE Keypressed DO z:=readkey ; {czyszczenie bufora} Przykład 2.: { czytanie ciągu n danych: d[1],d[2],...,d[n] } czytaj(n); czytaj(n); czytaj(n); i := 1; i := 1; WHILE i<=n DO REPEAT FOR i:=1 TO n DO BEGIN czytaj(d[i]); czytaj(d[i]); czytaj(d[i]); i := i+1 i:=i+1 END UNTIL i>n Przykład 3.: { czytanie z pliku danych } { wprowadzanie kolejnych } { gdy nie znamy ich liczby } { danych przez użytkownika } Reset ( plik ); n := 0; n := 0; REPEAT WHILE NOT Eof(plik) DO n := n + 1; BEGIN wprowadz(d[n]); n := n + 1; Write('koniec? [t/n] '); Read(plik,d[n]) Readln(z); END UNTIL z='t' 13

Dla poniższych przykładów dane są typy: CONST mx = 1000 ; TYPE student = RECORD imie, nazwisko, uczelnia, wydzial, kierunek : STRING[12] ; rok, nrind : Integer END ; VAR ls : Integer ; { liczba studentów } baza : ARRAY [1..mx] OF student ; ster : Char ; { znak sterujący } BEGIN {============================== minibaza studentów } ls:=0 ; REPEAT Clrscr ; Writeln ; Writeln ('Aktualnie jest ',ls,' elementów w bazie'); Writeln ; Writeln ('Load = załadowanie bazy z pliku') ; Writeln ('Save = zachowanie bazy na pliku') ; Writeln ('Append = dopisanie elementu do bazy') ; Writeln ('Delete = wyrzucenie elementu z bazy') ; Writeln ('Write = wydruk elementów bazy') ; Writeln ('Quit = zakończenie pracy programu') ; Writeln ; Write ('Wprowadz litere : ') ; Readln (ster) ; CASE ster OF 'L','l' : Loadt ; 'S','s' : Savet ; 'A','a' : Appendt ; 'D','d' : Deletet ; 'W','w' : Writet ; END ; UNTIL ster IN ['Q','q'] END. 10. Instrukcja wiążąca - składnia WITH <ZMIENNA> DO <INSTRUKCJA> Przykład: Zamiast pisać tak: można pisać tak: BEGIN WITH baza[25] DO BEGIN baza[25].imie := 'Jan'; baza[25].nazwisko := 'Nowak'; baza[25].uczelnia := 'UJ'; baza[25].kierunek := 'informatyka'; baza[25].rok := 1 END imie := 'Jan'; nazwisko := 'Nowak'; uczelnia := 'UJ'; kierunek := 'informatyka'; rok := 1 END 14

Podstawowe struktury danych Wprowadzenie: Przy rozwiązywaniu problemu (za pomocą komputera, czy też bez niego) trzeba dokonać wyboru abstrakcyjnego modelu rzeczywistości, czyli zdefiniować zbiór danych mających reprezentować rzeczywistą sytuację. Wybór odpowiednich danych podyktowany jest poprzez rozwiązywany problem oraz możliwości obliczeniowe komputera. Wybór reprezentacji danych zależny jest nie tylko od możliwości komputera, lecz również zależny jest od przewidywanych operacji, jakie na danych będą wykonywane. Podstawowe struktury danych: 1. Tablica jest strukturą jednorodną, składa się ze składowych tego samego typu, charakteryzuje się swobodnym dostępem do danych, zaś składowe tablicy są indeksowane: type T = array [I] of T 0 np. type Wektor = array [1..50] of real Deklaracja tablicy: var x: Wektor Odwołanie do elementów tablicy poprzez odpowiedni indeks i : x[i] Indeks tablicy może być stałą (np. x[3]), zmienną (np. x[i]), jak również wyrażeniem indeksowym (np. x[2*i+1]). Przykład 1. Poszukiwanie najmniejszego indeksu i składowej o wartości x var a: array[1..n] of T {N>0} i:=0; repeat i:=i+1 until (a[i]=x) or (i=n); if a[i] <> x then nie ma takiego elementu w a Jeżeli elementy tablicy zostały wcześniej uporządkowane (posortowane) przeszukiwanie można znacznie przyspieszyć. W takim przypadku stosuje się metodę połowienia przedziału, w którym może się znajdować poszukiwany element. metodę tę nazywamy bisekcją lub przeszukiwaniem połówkowym: i:=1; j:=n; repeat k:=(i+j) div 2 if x>a[k] then i:=k+1 else j:=k-1 until (a[k]=x) or (i>j) Górną granicą wymaganej liczby porównań jest log 2 N Macierz(tablica dwuwymiarowa) to tablica, której składowe są również tablicami: np. M: array [1..10] of Wektor jest tablicą o 10 składowych (wierszach), z których każdy składa się z 50 składowych typu real i nazywa się macierzą 10x50 o składowych rzeczywistych. Odwołanie do elementów macierzy poprzez odpowiednie indeksy: M[i][j] lub M[i,j] Deklarację macierzy można uprościć: M: array [1..10] of array [1..50] of real lub M: array [1..10,1..50] of real 15

2. Rekord jest najprostszym sposobem tworzenia typów złożonych, składających się z elementów dowolnego typu, np.: type LiczbaZespolona = record re: real; im: real; end type Osoba = record nazwisko: alfa; imię: alfa; dataurodzenia: Data; płeć: (kobieta, mężczyzna); stancywilny: (wolny, żonaty, owdowiały, rozwiedziony) end Deklaracja zmiennej typu rekord: z: LiczbaZespolona; p: Osoba; Konstujemy rekord przez: z = LiczbaZespolona (1.0, -2.0); p = Osoba ( Nowak, Jan, Data(19,3,1976), mężczyzna, wolny); 3. Zbiór odnosi się do elementów tego samego typu: type Z = set of Z 0 - definicja zbioru Z składającego się z elementów typu Z 0 np. type zbiorznakow = set of char deklaracja zbioru: zz := [ +, -, d, g, 5 ] Na wszystkich typach zbiorowych są określone następujące operatory elementarne, uszeregowane według priorytetów: * przecięcie (mnożenie) zbiorów + suma (dodawanie) zbiorów - różnica zbiorów in należenie do zbioru r*s+t = (r*s) + t, r-s*t = r-(s*t), r-s+t = (r-s)+t, x in s+t = x in (s+t) Zbiór s wygodnie jest reprezentować w pamięci komputera za pośrednictwem jego funkcji charakterystycznej C(s). Jest to wektor wartości logicznych, którego i-ta składowa określa występowanie bądź brak wartości i w zbiorze, np. zbiór liczb całkowitych: S = [1,4,8,9] jest reprezentowany przez ciąg wartości logicznych F (fałsz) i T (prawda) następująco: C(s) = (FTFFTFFFTT) jeśli typem podstawowym zbioru s jest 0..9. W pamięci komputera sekwencja wartości logicznych jest reprezentowana przez ciąg bitów: S: 0 1 0 0 1 0 0 0 1 1 - reprezentacja w pamięci 0 1 2 3 4 5 6 7 8 9 - kolejne bity 16

Reprezentacja zbioru przez jego funkcję charakterystyczną ma tę zaletę, że operacje obliczania sumy, przecięcia i różnicy dwóch zbiorów można realizować w maszynie cyfrowej jako elementarne operacje logiczne. Sprawdzanie czy element x należy do zbioru możemy wykonać poprzez badanie warunku: x in (c 1, c 2,..., c n ] zamiast (x= c 1 ) or (x= c 2 ) or or (x= c n ) 4. Plik sekwencyjny (ciąg) należą do struktur danych o mocy nieskończonej type tekst = file of char definicja typu pliku znakowego (teksowego) rewrite(x) konsturowanie pliku/ciągu pustego put(x) wydłuża plik/ciąg x poprzez dołączenie na jego końcu elementu z buffora reset(x) zapoczątkowuje przeglądanie pliku/ciągu get(x) przejście do następnej składowej, odczytanie jej i przypisanie do buffora eof(x) służy do badania, czy został osiągnięty koniec pliku read(x,v) odczytanie elementu z ciągu x i zapisanie go do zmiennej v write(x,v) wydłuża plik/ciąg x poprzez dołączenie na jego końcu elementu v Przykłady: rewrite (x); while p do begin Operacja(v); write (x,v) end; reset (x); while not(eof(x)) do begin read(x,v); Operacja(v) end; Teksty odgrywają szczególnie istotną rolę w przetwarzaniu danych, albowiem służą do komunikacji użytkownika z komputerem. type tekst = file of char var input, output: tekst writeln(f) dołącz znak końca wiersza do pliku f readln(f) przeskocz ciąg znaków pliku f aż do znaku występującego bezpośrednio po najbliższym znaczniku końca wiersza\ eoln(f) funkcja boolowska, która przyjmuje wartość true, jeśli aktualna pozycja pliku wskazuje na separator wiersza, w przeciwnym przypadku przyjmuje wartość false. rewrite(f); while not(q) do begin while not(p) do begin operacja(x); write (f,x) end; writeln(f) end reset(f); while not eof(f) do begin operacja1(); while not eoln(f) do begin read(f,x); operacja2(x); end; operacja3(); readln(f) end 17

Sortowanie tablic Sortowaniem nazywamy proces ustawiania zbioru obiektów w porządku. Sortowanie stosuje się w celu ułatwienia późniejszego wyszukiwania elementów sortowanego zbioru. Sortowanie jest bardzo powszechnym działaniem w wielu różnych dziedzinach. Sortowane są obiekty w listach płac, książka telefonicznych i adresowych, w bibliotekach, słownikach, magazynach i wszędzie tam, gdzie występuje potrzeba szybkiego przeszukiwania i dostępu do składowanych obiektów. Wybór algorytmu sortowania zależny jest od wykorzystywanej struktury danych. W związku z tym metody sortowania dzielimy na dwie podstawowe klasy: 1. Sortowanie wewnętrzne (tablic, które są przechowywane w szybkiej, o dostępie swobodnym wewnętrznej pamięci komputerów). 2. Sortowanie zewnętrzne (plików sekwencyjnych, które są zazwyczaj przechowywane w wolniejszej pamięci zewnętrznej z dostępem bezpośrednim tylko do wierzchu każdej sterty danych). Niech sortowanie będzie działaniem na obiektach a 1, a 2,..., a n i polega na takim ponumerowaniu tych obiektów a k1, a k2,..., a kn, że dla dla zdanej funkcji porządkującej f spełniona jest: f(a k1 ) f(a k2 )... f(a kn ). Często obiekty rzeczywiste a i sortowane są według pewnego określonego klucza służącego identyfikacji obiektów, które zdefiniujemy sobie następująco: type obiekt = record klucz: integer; {deklaracje innych składowych} end gdzie inne składowe reprezentują właściwe dane dotyczące obiektu. Do najważniejszych kryteriów oceny jakości metod sortowania należą: ilość potrzebnej pamięci (oszczędność pamięci) liczba koniecznych porównań kluczy liczba koniecznych przesunięć/przestawień obiektów. Dobre algorytmy sortowania wymagają porównań rzędu: n log n. Metody proste wymagają zazwyczaj porównań rzędu: n 2. Do dużych zbiorów danych stosujemy metody skomplikowane o złożoności obliczeniowej O(n log n), jednak dla dostatecznie małych n metody proste są bardziej efektywne ze względu na swoją prostotę. Metodę sortowania nazywamy stabilną, jeżeli podczas procesu sortowania pozostaje niezmieniony względny porządek obiektów o identycznych kluczach. Metody proste dzielimy na trzy zasadnicze grupy: 1. Sortowanie przez wstawianie 2. Sortowanie przez wybieranie 3. Sortowanie przez zamianę Przyjmijmy, że programy będą działać na tablicy a i odwoływać się będą do danych typu obiekt oraz typu indeks zdefiniowanych następująco: type indeks = 0..n; var a: array[1..n] of obiekt; 18

Sortowanie przez proste wstawianie: Metoda ta jest powszechnie stosowana przez grających w karty, które są podzielone umownie na ciąg wynikowy a 1,...,a i-1 i ciąg źródłowy a i,...,a n. W każdym kroku począwszy od i=2 i zwiększając i o jeden, i-ty element ciągu źródłowego przenosi się do ciągu wynikowego wstawiając go w odpowiednim miejscu, np.: Klucze początkowe: 44 55 12 42 94 18 06 67 i = 2 44 55 12 42 94 18 06 67 i = 3 12 44 55 42 94 18 06 67 i = 4 12 42 44 55 94 18 06 67 i = 5 12 42 44 55 94 18 06 67 i = 6 12 18 42 44 55 94 06 67 i = 7 06 12 18 42 44 55 94 67 i = 8 06 12 18 42 44 55 67 94 procedure SortowaniePrzezProsteWstawianie; var i,j: indeks; x: obiekt; begin for i:=2 to n do begin x:=a[i]; a[0]:=x; {ustawienie wartownika} j:=i-1; while x.klucz < a[j].klucz do begin a[j+1]:=a[j]; j:=j-1; end; a[j+1]:=x end end Analiza złożoności obliczeniowej metody prostego wstawiania: Liczba porównań Po i kluczy w i-tym przesiewaniu wynosi do najwyżej i-1, co najmniej 1 oraz przy założeniu, że wszystkie permutacje n kluczy są równie prawdopodobne średnio i/2. Liczba przesunięć (przestawień obiektów) Pr i wynosi Po i +2 razem z ustawieniem wartownika. Otrzymujemy więc następujące liczby porównań i przesunięć: Po min = n 1 Pr min = 2(n-1) Po śr = (n 2 + n 2)/4 Pr śr = (n 2 + 9n 10)/4 Po max = (n 2 + n)/2 1 Pr max = (n 2 + 3n - 4)/2 Liczby te są najmniejsze, gdy obiekty są uporządkowane już w ciągu źródłowym, największe, gdy są ustawione w odwrotnej kolejności. W tym sensie sortowanie przez proste wstawianie wykazuje zachowanie naturalne. Algorytm ten jest również stabilny, gdyż pozostawia niezmieniony porządek obiektów z identycznymi kluczami. 19

Sortowanie przez wstawianie połówkowe: Korzystając z faktu, że ciąg wynikowy a 1,...,a i-1, w którym należy umieścić nowy obiekt, jest już uporządkowany, można zastosować szybszą metodę ustalenia miejsca wstawienia nowego obiektu. Najprostszym sposobem jest zastosowanie metody przeszukiwania połówkowego, w której próbkuje się ciąg wynikowy w środku i dzieli go dalej na połowę, aż znajdzie się miejsce wstawienia nowego obiektu: procedure SortowanieWstawianiePolowkowe; var i,j,l,p,m: indeks; x: obiekt; begin for i:=2 to n do begin x:=a[i]; l:=1; p:=i-1; while l<=p do begin m:=(l+p) div 2; if x.klucz < a[m].klucz then p:=m-1 else l:=m+1 end; for j:=i-1 downto l do a[j+1]:=a[j]; a[l]:=x end end Analiza złożoności obliczeniowej metody wstawiania połówkowego: Miejsce wstawienia nowego obiektu jest znalezione, jeśli a j.klucz a j+1.klucz. Tak więc, przeszukiwany przedział musi mieć końcową długość 1, a stąd wynika, że połowienie przedziału złożonego z i kluczy wykonuje się (log 2 i) razy, więc liczba porównań: Po = log 2 1 + log 2 2 +... + log 2 n n(log n c) + c; gdzie c = 1/ln 2 = 1,44 Liczba porównań nie zależy więc od początkowego ustawienia obiektów. Stosowanie dzielenia całkowitego do połowienia długości przeszukiwanego przedziału powoduje, że rzeczywista liczba porównań potrzebnych dla i obiektów może być o 1 większa od spodziewanej. Wynika to z faktu, że miejsce wstawienia obiektu w niższym końcu jest przeciętnie znajdowane nieco szybciej od miejsca wstawiania w wyższym końcu faworyzowane są więc przypadki, w których obiekty są początkowo bardzo nieuporządkowane. Jest to zatem przypadek nienaturalnego zachowania się algorytmu sortowania. Niestety, dzięki zastosowaniu metody przeszukiwania połówkowego zmniejszyła się tylko liczba porównań, zaś przesuwanie obiektów jest zwykle czynnością bardziej czasochłonną. Ponadto sortowanie już posortowanej tablicy zabiera więcej czasu niż w przypadku sortowania przez proste wstawianie. Ze względu na konieczność przesuwania całych wierszy obiektów metody sortowania przez wstawianie nie są efektywne. 20

Sortowanie przez proste wybieranie: Ideą przewodnią metody jest przesuwanie tylko jednego obiektu zamiast całego wiersza obiektów, jak temu jest w metodzie sortowania przez wstawianie. W metodzie tej wybieramy obiekt o najmniejszym kluczu i wymieniamy go z pierwszym obiektem z listy. Taką operację powtarzamy z pozostałymi n-1 obiektami, następnie z n-2 obiektami itd.: Klucze początkowe: 44 55 12 42 94 18 06 67 06 55 12 42 94 18 44 67 06 12 55 42 94 18 44 67 06 12 18 42 94 55 44 67 06 12 18 42 94 55 44 67 06 12 18 42 44 55 94 67 06 12 18 42 44 55 94 67 06 12 18 42 44 55 67 94 procedure SortowaniePrzezProsteWybieranie var i,j,k: indeks; x: obiekt; begin for i:=1 to n-1 do begin k:=i; x:=a[i]; for j:=i+1 to n do if a[j].klucz < x.klucz then begin k:=j; x:=a[j] end; a[k]:=a[i]; a[i]:=x; end end Analiza złożoności obliczeniowej metody przez proste wybieranie: Liczba porównań kluczy nie zależy od ich początkowego ustawienia Po = (n 2 -n)/2, więc metoda ta zachowuje się mniej naturalnie niż metoda przez proste wstawianie. Zaś liczba przesunięć jest równa: Pr min = 3(n-1) Pr śr = n(ln n+γ) gdzie γ=0,577216 jest stałą Eulera Pr max = trunc(n/4) 2 + 3(n-1) Algorytm sortowania przez proste wybieranie jest przeważnie lepszy od algorytmu sortowania przez proste wstawianie, aczkolwiek w przypadkach, gdy klucze są już prawie posortowane, proste wstawianie jest nieco szybsze. 21

Sortowanie przez prostą zamianę (sortowanie bąbelkowe): Metoda ta opiera się na zasadzie porównywania i zamiany par sąsiadujących ze sobą obiektów dopóty, dopóki wszystkie obiekty nie będą posortowane, np.: Klucze początkowe: 44 55 12 42 94 18 06 67 i = 2 06 44 55 12 42 94 18 67 i = 3 06 12 44 55 18 42 94 67 i = 4 06 12 18 44 55 42 67 94 i = 5 06 12 18 42 44 55 67 94 i = 6 06 12 18 42 44 55 67 94 i = 7 06 12 18 42 44 55 67 94 i = 8 06 12 18 42 44 55 67 94 procedure SortowanieBąbelkowe var i,j: indeks; x: obiekt; begin for i:=2 to n do begin for j:=n downto i do if a[j-1].klucz > a[j].klucz then begin x:=a[j-1]; a[j-1]:=a[j]; a[j]:=x; end end end Powyższy algorytm działa bardzo nieefektywnie, albowiem wymaga bardzo dużej liczby porównań i przesunięć: Po = (n 2 -n)/2 Pr min = 0 Pr śr = 3*(n 2 -n)/4 Pr max = 3*(n 2 -n)/2 Ponadto, w powyższym algorytmie dane są przeglądane niezależnie od tego, czy są już posortowane, czy nie, więc prostym jego usprawnieniem byłoby badanie, czy w ostatnim przejściu dokonano jakiejś zamiany. Ponadto wiadomo, iż obiekty poniżej pozycji ostatniej zmiany są już ustawione w pożądanej kolejności. Ponadto wyraźnie źle posortowane obiekty można przez proste wybieranie przesunąć na początek, ażeby nie trzeba było źle ustawiony bąbelek żmudnie przesuwać z końca tabeli do jej początku. 22

DYNAMICZNE STRUKTURY DANYCH charakteryzują się zmiennością swoich struktur podczas procesu obliczeniowego. Zmiany te dotyczą przede wszystkim ich rozmiarów (ilości zajętej pamięci). Na dynamiczne struktury danych kompilator nie może z góry przydzielić określonej ilości pamięci podczas dokonywanej translacji kodu, gdyż jej ilość może się zmieniać. Mówimy więc o tzw. dynamicznym przydzielaniu pamięci już podczas wykonywania programu. Oznacza to przydzielanie pamięci każdej ze składowych w chwili powołania danego obiektu do życia. Kompilator w procesie translacji przydziela więc tylko stałą ilość pamięci na zapamiętanie adresu składowej umieszczanej dynamicznie w pamięci zamiast samej składowej. Zmienna statyczna powiązana jest z konkretnym miejscem i rozmiarem pamięci już w momencie kompilacji programu. Zmienna dynamiczna jest tworzona i usuwana podczas wykonywania programu. Tak więc pamięć potrzebna do zapamiętania zmiennej dynamicznej może być przydzielana (w miarę dostępnych zasobów), zwalniana i znowu przydzielana innej zmiennej. W Pascalu można tworzyć proste zmienne dynamiczne oraz dynamiczne struktury danych, np. listy. Dynamiczne struktury danych mogą się rozszerzać lub kurczyć w miarę potrzeb w trakcie wykonywania programu. Listy są przydatne w sytuacjach, gdy nie można przewidzieć zapotrzebowania na pamięć. Tablice można traktować jako przeciwieństwo list, gdyż te wymagają określenia wielkości w momencie ich tworzenia bez dalszej możliwości ich zwiększania czy zmniejszania. Wadą tablic jest więc konieczność przydzielania takiej ilości pamięci, ile wymaga maksymalna przewidziana liczba danych. Takie postępowanie może być dużym marnotrawstwem pamięci. Dynamiczny przydział pamięci jest realizowany za pomocą wskaźników, które są specjalnym typem zmiennej, która nie przechowuje żadnych danych lecz adres ich położenia w pamięci. W programie można przedefiniować wskaźnik, tak aby wskazywał na inne miejsce pamięci lub żeby nie wskazywał żadnego miejsca (nil). Można też zwolnić miejsce związane z określonym wskaźnikiem i przydzielić je innym zmiennym. Wskaźniki mogą odwoływać się do danych dowolnego typu, np.: VAR Wsk_Int : ^integer; deklaruje wskaźnik wskazujący na zmienną typu integer, następnie trzeba przydzielić pamięć dla danej typu integer wskazywanej przez w/w wskaźnik: NEW (Wsk_Int); Przypisanie: Wsk_Int^ := 500; Zmienna = Wsk_Int^; Działania na wskaźnikach ograniczają się do porównania, np.: IF Wsk1 = Wsk2 THEN...; i do nadania wartości, np.: Wsk1 := Wsk2; 23

W wyniku takiego działania zmienia się miejsce odwołania wskaźnika Wsk1 na takie samo, jakie ma wskaźnik Wsk2. Miejsce pamięci wskazywane wcześniej prze Wsk1 jest po wykonaniu działania niedostępne (o ile nie wskazywał na nie inny wskaźnik). Zwalnianie miejsca pamięci wskazywanego przez wskaźnik: DISPOSE(Wsk_Int); Funkcje i procedury powiązane ze wskaźnikami i dynamicznymi strukturami danych: New (funkcja lub procedura) tworzy nową dynamiczną zmienną I przypisuje jej adres zmiennej: procedure New(var P: Pointer); Dispose (procedura) zwalnia pamięć przydzieloną dynamicznej zmiennej: procedure Dispose(var P: Pointer); type Str18 = string[18]; var P: ^Str18; begin New(P); P^ := 'Now you see it...'; Dispose(P); { Now you don't... } end. GetMem (procedure) tworzy dynamiczną zmienną o podanej wielkości i przypisuje adres tego bloku do wyspecyfikowanego wskaźnika: procedure GetMem(var P: Pointer; Size: Word); FreeMem (procedure) zwalnia pamięć zajmowaną przez dynamiczną zmienną o określonej wielkości: procedure FreeMem(var P: Pointer; Size: Word); type TFriendRec = record Name: string[30]; Age : Byte; end; var p: pointer; begin if MaxAvail < SizeOf(TFriendRec) then 24

Writeln('Not enough memory') else begin { Allocate memory on heap } GetMem(p, SizeOf(TFriendRec)); {...} {...Use the memory... } {...} { Then free it when done } FreeMem(p, SizeOf(TFriendRec)); end; end. Wskaźniki umożliwiają nam tworzenie skomplikowanych dynamicznych struktur danych: 1. Listy (pojedynczo lub podwójnie wiązane) 2. Drzewa (binarne, rozpinające, pozycyjne, turniejowe) 3. Kolejki FIFO (first in first out) 4. Stosy FILO (first in last out) 5. Grafy (skierowane i nieskierowane, cykliczne, acykliczne, planarne) Przykład listy pojedynczo wiązanej: LISTY Wklejanie elementu (rekordu) na początek listy: Wklejanie elementu (rekordu) do środka listy: 25

Przykład drzewa: DRZEWA Każde drzewo posiada: korzeń liście wierzchołki wewnętrzne (posiadające swoje poprzedniki i następniki, przodków i potomków). Gdy mamy do czynienia z wieloma drzewami powiązanymi razem mówimy o lesie. Drzewa charakteryzowane są przez: ilość następników, głębokość/poziom wierzchołka, wysokość drzewa i wierzchołka szerokość, stopień wyważenia. operacja : char end type wezel = record prawe: ^wezel; lewe, 26

Implementacja drzewa w tablicy: t: array [1..11] of record lewe, prawe : integer operacja : char end STOS KOLEJKA 27

PRZESZUKIWANIE DRZEW Przeszukiwanie w szerz 1 BFS Przeszukiwanie w głąb 1 DFS 2 3 4 5 6 2 6 7 11 12 7 8 9 10 11 12 13 3 5 8 9 13 17 18 14 15 16 17 18 19 4 10 14 15 16 19 GRAFY Graf nieskierowany Drzewo poszukiwań dla grafu 8 9 1 6 4 1 2 7 8 2 3 9 7 5 6 5 3 4 Graf skierowany Drzewo poszukiwań dla grafu 1 8 2 5 9 6 4 1 2 7 9 7 4 6 8 5 3 3 Graf etykietowany 6 23 5 4 8 42 51 30 15 18 97 1 35 2 26 84 3 12 9 58 47 7 28

PROBLEM SKOCZKA: ALGORYTMY REKURENCYJNE PROBLEM 8 HETMANÓW: 29

PROBLEM TRWAŁEGO MAŁŻEŃSTWA: 30

PODSTAWOWE ZASADY ANALIZY ALGORYTMÓW Analiza algorytmów dział informatyki zajmujący się szukaniem najlepszych algorytmów dla zadań komputerowych. Udziela odpowiedzi na pytania: 1. Czy dany problem może być rozwiązany w dostępnym czasie i pamięci? 2. Który ze znanych algorytmów należy zastosować w danych okolicznościach? 3. Czy istnieje lepszy algorytm od rozważanego? 4. Jak uzasadnić, że stosując dany algorytm, rozwiąże się zamierzone zadanie? Złożoność obliczeniowa algorytmu ilość zasobów komputerowych (przede wszystkim czas działania złożoność pamięciowa i ilość zajmowanej pamięci złożoność czasowa) potrzebnych do jego wykonania. Złożoność czasowa powinna być własnością samego algorytmu jako metody rozwiązania problemu, więc powinna być niezależna od komputera, języka programowania czy sposobu jego kodowania. W tym celu wyróżnia się tzw. operacje dominujące takie, których łączna liczba jest proporcjonalna do liczby wykonań wszystkich operacji jednostkowych w dowolnej komputerowej realizacji algorytmu. Za jednostkę czasową przyjmuje się wykonanie jednej operacji dominującej. Mówimy, że algorytm f ma złożoność co najwyżej rzędu g, co zapisujemy f(n) = O(g(n)), jeśli : f ( n) c g( ) c> 0, n n> n n. 0 0 Mówimy, że algorytm f ma złożoność co najmniej rzędu g, co zapisujemy f(n) = Ω(g(n)), jeśli g(n) = O(f(n)). Mówimy, że algorytm f ma złożoność dokładnie rzędu g (f jest asymptotycznie równoważne g), co zapisujemy f(n) = Θ(g(n)), jeśli f(n) = O(g(n)) oraz 2 2 2 2 2 2 g(n) = O(f(n)), np.: n + 2n n, gdyż n + 2n 3n, jak również n + 2n n dla każdego n 0. Mówimy o złożoności: a) logarytmicznej: log n b) liniowej: n c) liniowo-logarytmicznej: n log n d) kwadratowej: n 2 e) wielomianowej rzędu c: n c f) podwykładnicza: n log n, g) wykładniczej: 2 n, n! 2 log 2 n METODY UKŁADANIA ALGORYTMÓW Proces układania algorytmów ma charakter twórczy, nie dający się zalgorytmizować. Często jednak można skorzystać z istniejących schematów i ogólnych metod rozwiązywania problemów, np.: 1. Metoda dziel i zwyciężaj (np. algorytm wyszukiwania binarnego) 2. Metoda zachłanna (od najmniejszych do największych) (np. problem sortowania lub zapełniania plecaków x 1, x 2,..., x n o pojemności odpowiednio c 1 >0, c 2 >0,..., c n >0 N przedmiotami o rozmiarach p 1, p 2,...,p N, gdzie N n) 31

BADANIE EFEKTYWNOŚCI ALGORYTMÓW Sortowanie szybkie (wymiana obiektów odległych) QUICKSORT: i j 44 55 12 (42) 94 06 18 67 18 06 12 (42) 94 55 44 67 j i i j i j 18 (06) 12 42 94 (55) 44 67 06 18 12 42 44 55 94 67 j i j i i j i j 06 (18) 12 42 44 55 (94) 67 06 12 18 42 44 55 67 94 j i j i procedure SortowanieSzybkie procedure sortuj(k,p: indeks); var i,j: indeks; x,w: obiekt; begin i:=k; j:=p; x := a[(k+p) div 2]; repeat while a[i].klucz < x.klucz do i:=i+1; while a[j].klucz > x.klucz do j:=j-1; if i<=j then begin w:=a[i]; a[i]:=a[j]; a[j]:=w; i:=i+1; j:=j-1; end until i>j; if k<j then sortuj (k,j); if i<p then sortuj (i,p) end; begin sortuj (1,n); end Po wybraniu ograniczenia x (zajmującego m-te miejsce tablicy) posuwamy się przez całą tablicę dokonując dokładnie n porównań. Liczbę wymian szacujemy probabilistycznie jako liczbę elementów lewego (mniejszego) podziału równą n-m razy prawdopodobieństwo zamiany klucza równe (n-m+1)/n. Sumując po wszystkich możliwych wyborach ograniczenia x i dzieląc przez ich liczbę n otrzymujemy: 32

n 1 ( n m)( n m + 1) n 1 Pr = = n m= 1 n 6 6n Stąd spodziewana liczba wymian wynosi około n/6. Jeśli udałoby się zawsze jako ograniczenie wybrać medianę, to każdy proces podziału rozbije tablicę na dwie jednakowe części, a wówczas potrzebna do posortowania tablicy liczba przejść będzie równa log n. Więc całkowita liczba porównań wynosić będzie n log n, a całkowita liczba wymian n/6 log n. Nie można jednak liczyć na to, że zawsze jako ograniczenie zostanie wybrana mediana, gdyż szansa trafienia na nią wynosi 1/n, jednak przy losowym wyborze ograniczenia średnia efektywność sortowania szybkiego jest gorsza od optymalnej tylko o 2 ln 2 razy (im bliżej mediany trafimy tym lepiej zadanie jest dobrze uwarunkowane). Jeżeli jako ograniczenie zostałby wybrany zawsze największy lub najmniejszy obiekt, wtedy potrzeba wykonać n podziałów, co powoduje spadek efektywności metody do poziomu n 2. Porównanie efektywności metod sortowania: Sortowanie Min (minimalna) Proste wstawianie Po = n-1 Pr = 2(n-1) Proste wybieranie Po = (n 2 -n)/2 Pr = 3(n-1) Prosta zamiana (bąbelkowe) Po = (n 2 -n)/2 Pr = 0 Szybkie Po = n log n Pr = n/6 log n Śr (oczekiwana) Po = (n 2 +n-2)/4 Pr = (n 2-9n-10)/4 Po = (n 2 -n)/2 Pr = n(ln n+0,57) Po = (n 2 -n)/2 Pr = 3(n 2 -n)/4 Po = (3n log n)/2 Pr = n/4 log n Max (pesymistyczna) Po = (n 2 -n)/2-1 Pr = (n 2 +3n-4)/2 Po = (n 2 -n)/2 Pr = n 2 /4+3(n-1) Po = (n 2 -n)/2 Pr = 3(n 2 -n)/2 Po = (n-1)(n+1) Pr = n 2 /2 Efektywność operacji na listach: wstaw, usuń, znajdź, pobierz, posortuj lista Iiczność LISTA PODWÓJNA (PODWÓJNIE WIĄZANA) NIL NIL głowa (head) p ogon (tail) 33

KOLEJKI PRIORYTETOWE Kolejki priorytetowe struktura danych, która realizuje następujące operacje: Buduj(q, S) - utworzenie multizbioru S={a 1,...,a n } dla danej listy q=[a 1,...,a n ], Wstaw(x, S) - S := S {x}, UsuńMax(S) - usunięcie największego elementu z S. W porównaniu do zwykłej kolejki (FIFO) z kolejki priorytetowej usuwany jest zawsze maksymalny element (czyli element o najwyższym priorytecie). Złożoność obliczeniowa operacji (pesymistyczna) Sposób implementacji Buduj Wstaw UsuńMax Lista nieuporządkowana O(n) O(1) O(n) Lista uporządkowana O(n log n) O(n) O(1) Kopiec O(n) O(log n) O(log n) Kopiec drzewo binarne, w węzłach którego znajdują się elementy reprezentowanego multizbioru S i jest spełniony tzw. warunek kopca, mianowicie: Jeśli węzeł x jest następnikiem węzła y, to element w węźle x jest nie większy niż element w węźle y. Jeśli spełniony jest warunek kopca mówimy, że drzewo ma uporządkowanie kopcowe, a jego elementy zachowują porządek kopcowy. Uporządkowanie kopcowe zapewnia: w korzeniu drzewa znajduje się największy element (lub jeden z największych, gdy jest ich kilka) na ścieżkach w drzewie, od korzenia do liścia, elementy są uporządkowane w porządku nierosnącym. Kopiec zupełny to zupełne drzewo binarne, czyli takie, w którym wszystkie poziomy są wypełnione całkowicie z wyjątkiem co najwyżej ostatniego, który jest spójnie wypełniony od strony lewej: Ze względu na regularną strukturę kopca zupełnego, można go reprezentować w prosty sposób w tablicy: Następniki węzła k (o ile istnieją) mają odpowiednio numery 2k i 2k+1, zaś poprzednik węzła k (różny od korzenia) ma numer k/2 : 34

Wstawianie elementu (operacja Wstaw(x, S)) do kopca zupełnego: Usuwanie elementu maksymalnego (operacja UsuńMax(S)) z kopca zupełnego: ALGORYTM SORTOWANIA KOPCOWEGO (HEAPSORT) daje się zapisać za pomocą operacji kolejki priorytetowej następująco: Buduj(q, S); O(n) Powtórz n-1 razy UsuńMax(S); (n-1)*o(log n) = O(n log n) dla danej listy q=[a 1,...,a n ] charakteryzujący się pesymistyczną złożonością czasową O(n log n). 35

procedure insert (v : integer); // Pesymistyczna złożoność czasowa log n + 1. begin n :=n+1; a[n] := v; upheap (n) end {insert}; procedura upheap (k : integer); var 1, v : integer; begin v :=a[k]; a[0) : + ; l:= k div 2; {warunek kopca jest zaburzony co najwyżej tylko dla v} while a[l] < v do begin {węzeł l jest poprzednikiem węzła k} a[k] := a[l]; k:=l; l :=l div 2 end; a[k] := v end {upheap}; procedure deletemax : integer; begin deletemax := a[1]; a[1] := a[n]; n := n 1; downheap(1) end {deletemax}; // Pesymistyczna złożoność czasowa 2 log n. procedure downheap(k : integer); label 0; var i, j, v : integer; begin v := a[k]; while k n div 2 do begin j := 2 * k; {j jest następnikiem k} if j < n then if a[j] < a[j + 1] then j := j + 1; if v a[j] then goto 0; a[k] := a[j]; k := j end; 0: a[k] := v end {downheap}; procedure construct; // Pesymistyczna złożoność czasowa O(n). {Elementy listy q=[a 1,...,a n ] znajdują się w tablicy a[1..n] } var i : integer; begin for i := n div 2 downto 1 do downheap(i) end {construct}; procedure heapsort; // Pesymistyczna złożoność czasowa 2 n log n + O(n) = O(n log n). { Tablica a[1..n] zawiera elementy do posortowania } var m, i : integer; begin m := n; construct; for i := m downto 2 do a[i] := deletemax; n := m end {heapsort}; 36

DRZEWA I STRUKTURY DRZEWIASTE Dolne ograniczenie dla problemów sortowania z wykorzystaniem porównań: W algorytmach sortowania za pomocą porównań jedynym źródłem informacji o porządku ALGORYTMIKA EFEKTYWNOŚĆ - SORTOWANIE Dolne ograniczenie dla problemów sortowania z wykorzystaniem porównań: W algorytmach sortowania za pomocą porównań jedynym źródłem informacji o porządku między elementami ciągu a 1,..., a n są porównania między nimi. Wszystkie algorytmy sortowania za pomocą porównań można prezentować w postaci drzew decyzyjnych. Poniżej przedstawiono drzewo decyzyjne dla algorytmu sortowania przez wstawianie: Każdy węzeł w drzewie decyzyjnym ma etykietę a i : a j dla pewnych i oraz j z przedziału 1 i, j n, gdzie n jest liczbą elementów w ciągu wejściowym. Z każdym liściem związana jest jedna permutacja π(1), π(2),..., π(n). Wykonanie algorytmu sortującego odpowiada przejściu ścieżki od korzenia drzewa decyzyjnego do jednego z jego liści, tj. znalezieniu poprawnej permutacji elementów ciągu. W każdym węźle wewnętrznym zostaje wykonane porównanie typu a i a j. W lewym poddrzewie znajdują się porównania wykonywane przez algorytm, jeśli okaże się, że a i a j, a prawe poddrzewo zawiera możliwe scenariusze dla przeciwnego przypadku, tj. a i > a j. Jeśli algorytm sortujący działa poprawnie, to każda z n! permutacji musi wystąpić jako jeden z liści drzewa dezycyjnego. Długość najdłuższej ścieżki od korzenia drzewa decyzyjnego do dowolnego z jego liści odpowiada pesymistycznej liczbie porównań wykonywanych przez algorytm sortujący. Tw. Każde drzewo decyzyjne, odpowiadające algorytmowi poprawnie sortującemu n elementów, ma wysokość Ω(n lg n). 37

Sortowanie przez kopcowanie jest asymptotycznie optymalnym algorytmem za pomocą porównań, gdyż jego górne ogranicznie O(n log n) równe jest dolnej granicy Ω(n log n). 38

39

40

41

42

Czy można sortować szybciej? Sortowanie przez zliczanie (Counting-Sort): Załóżmy, że każdy z n sortowanych elementów jest liczbą całkowitą z przedziału od 1 do k dla pewnego ustalonego k takiego, że k = O(n). Wtedy sortowanie przez zliczanie działa w czasie O(n). Idea: Dla każdej liczby wejściowej x wyznaczamy, ile elementów jest od niej mniejszych. W ten sposób znamy dokładną pozycję x w posortowanym ciągu, więc możemy ją bezpośrednio na tej pozycji umieścić. 43

procedure Counting-Sort; var i : integer; begin for i := 1 to k do c[i] := 0; O(k) for i := 1 to n do c[a[i]] := c[a[i]] + 1; // c[i] zawiera liczbę elementów = i O(n) for i := 2 to k do c[i] := c[i] + c[i-1]; // c[i] zawiera liczbę elementów i O(k) for i := n downto 1 do O(n) begin b[c[a[i]]] := a[i]; c[a[i]] := c[a[i]] - 1; end end {Counting-Sort}; Przy założeniu, że k = O(n), O(n+k) = O(n) Czas działania algorytmu przez zliczanie (nie wykorzystuje porównań!) wynosi O(n), a więc mniej niż wynosi dolna granica sortowań za pomocą porównań. Algorytm ten jest stabilny, gdyż liczby o tych samych wartościach występują w tablicy wynikowej w takiej samej kolejności, jak w tablicy początkowej. Stabilność algorytmu sortowania jest tylko wtedy istotna, gdy z sortowanymi danymi są związane dodatkowe dane, (które np. mogą być już posortowane wg innego klucza, zaś brak stabilności algorytmu spowodowałby zniszczenie uzyskanego wcześniej porządku). itd. 44

Sortowanie pozycyjne (Radix-Sort): W sortowaniu pozycyjnym stosujemy pewien trik, polegający na posortowaniu w pierwszej kolejności najmniej znaczących cyfr/znaków/itd (niejako od końca). Następnie sortujemy według drugiej, trzeciej,... najmniej znaczącej cyfry/znaku powtarzając cały proces dla wszystkich d pozycji cyfr/znaków. Do sortowania stosujemy algorytm sortowania przez zliczanie, gdyż jego stabilność zapewnia poprawność sortowania pozycyjnego, dodatkowo zapeniając jego szybkość. procedure Radix-Sort; var i : integer; begin for i := 1 to d do Counting-Sort (A według cyfry i); end {Radix -Sort}; Jeżeli każda z cyfr należy do przedziału 1..k, a k nie jest zbyt duże, to należy użyć sortowania przez zliczanie. Wtedy każdy przebieg przez n d-cyfrowych liczb wymaga czasu Θ(n+k), co daje w sumie Θ(dn+dk), jeśli zaś d jest stałą i k=o(n), to sortowanie pozycyjne działa w czasie liniowym O(n). Sortowanie pozycyjne odgrywa szczególną rolę wtedy, gdy do posortowania rekordów danych wykorzystuje się klucz składający się z wielu pól: sortowanie według daty: najpierw roku, potem miesiąca a następnie dnia; sortowanie słów w słowniku wg kolejnych liter; wielocyfrowe liczby całkowite wg kolejnych cyfr itp. 45

Sortowanie kubełkowe (Bucket-Sort): Sortowanie kubełkowe stosujemy przy założeniu, że dane wejściowe będą liczbami rzeczywistymi wybieranymi z przedziału [0, 1) zgodnie z rozkładem jednostajnym (tj. taki, że dla każdego zdarzenia elementarnego s należącego do przestrzeni skończonej prawdopodobieństwo wynosi 1/ S ). Idea: Sortowanie kubełkowe opiera się na triku polegającym na podziale przedziału [0,1) na n podprzedziałów jednakowych rozmiarów, tzw. kubełków, a nastęnie rozrzuceniu n liczb do kubełków, do których należą. Ponieważ liczby są jednostajnie rozłożone w przedziale [0,1), więc oczekujemy, że w każdym kubełku nie będzie ich zbyt wiele. Aby otrzymać ciąg wynikowy, sortujemy najpierw liczby w każdym z kubełków, a następnie wypisujemy je, przeglądając kolejno kubełki. Kubełki implementujemy w postaci list, do których wstawiamy elementy przy pomocy algorytmu sortowania przez proste wstawianie. W ten sposób uzyskujemy kubełki składające się z posortowanych elementów. procedure Bucket-Sort; var i : integer; begin for i := 1 to n do Wstaw a[i] do posortowanej listy b[ n * a[i] ]; Połącz listy b[0], b[1],..., b[n-1]; end {Bucket -Sort}; Posortowana lista elementów ciągu Pesymistyczny czas wstawiania kolejnych elementów do n list algorytmem n 1 2 n 1 2 prostego wstawiania wynosi O( E[ ni ]) = O( E[ ni ]) = O( n), gdyż ze względu 0 0 2 2 na przyjętą jednostajność rozkładu: E[ n ] [ ] ( [ ]) (1 ) 2 1 i = Var ni + E ni = np p + np = = Θ( 1). n 46

TEORIA GRAFÓW W osiemnastym wieku mieszkańcy Królewca lubili spacerować po mostach na rzece Pregole, których mieli w mieście siedem. Plan mostów pokazuje rysunek. Ale takie zwykłe spacerowanie po jakimś czasie im się znudziło, i zaczęli zastanawiać się, czy jest taka trasa spacerowa, która przechodzi przez każdy most dokładnie raz, żadnego nie omija, i pozwala wrócić do punktu wyjścia. Nie potrafili sami rozwiązać tego problemu, więc napisali do znanego już wtedy matematyka Leonharda Eulera. Euler pokazał, że nie istnieje rozwiązanie tego zadania. (Jeśli wejdzie się po raz trzeci na wyspę, nie ma jak z niej wyjść.) Można tę sytuację przedstawić jako graf o wielokrotnych krawędziach: Trzeba w tym grafie znaleźć cykl Eulera, czyli cykl przechodzący przez wszystkie wierzchołki i wszystkie krawędzie tego grafu, ale przez każdą krawędź tylko raz. W opublikowanej w 1736 roku pracy Euler sformułował pierwsze twierdzenie teorii grafów, które dziś zapisujemy następująco: W grafie można znaleźć cykl Eulera wtedy i tylko wtedy, gdy graf jest spójny i każdy jego wierzchołek ma parzysty stopień. Znając to twierdzenie zawsze można stwierdzić, czy łamigłówka typu "narysuj figurę nie odrywając ołówka od kartki" ma rozwiązanie. Fleury podał algorytm, który pozwala znaleźć cykl Eulera w każdym grafie, w którym on istnieje. Spróbuj samodzielnie znaleźć cykl Eulera w poniższym grafie: 47

W tak zaetykietowanym grafie rozwiązaniem jest na przykład droga przechodząca kolejno przez wierzchołki: 1, 2, 3, 1, 12, 11, 1, 10, 11, 13, 12, 3, 13, 14, 3, 4, 14, 15, 4, 5, 6, 4, 16, 15, 13, 22, 15, 17, 16, 6, 17, 18, 6, 7, 18, 19, 17, 22, 11, 21, 22, 19, 7, 8, 9, 7, 20, 19, 21, 20, 9, 21, 10, 9, 1. (w tym rozwiązaniu przechodzimy po wszystkich krawędziach stopniowo poprzez trójkąty). Trzeba pamiętać, że nie jest to jedyne rozwiązanie, a zapis cyklu można zacząć od dowolnego wierzchołka. Graf to zbiór wierzchołków, który na rysunku zwykle reprezentujemy kropkami, na przykład: oraz krawędzi łączących wierzchołki, co na rysunku można przedstawić następująco: Czasem dopuszcza się wielokrotne krawędzie i pętle (czyli krawędzie o początku i końcu w tym samym wierzchołku): Niekiedy wygodnie jest rozważać grafy o krawędziach skierowanych (grafy skierowane): 48

Wiele zastosowań mają grafy ważone, w których każdej krawędzi przyporządkowano liczbę - wagę, która może oznaczać na przykład odległość między wierzchołkami: W grafach etykietowanych każdy wierzchołek ma swoją nazwę - etykietę: Przykład grafu skierowanego, ważonego, o zaetykietowanych wierzchołkach: Każdy rysunek grafu to tylko jedna z wielu jego reprezentacji graficznych. Każdy graf można narysować na wiele sposobów, które nazywamy reprezentacjami graficznymi tego grafu: 49

Co to jest algorytm grafowy? Problemem 'grafowym' nazywamy problem, który można rozwiązać używając pojęcia grafu. Algorytm rozwiązujący pewien problem 'grafowy' nazywamy algorytmem grafowym. Oto kilka przykładów ważniejszych problemów grafowych: 1. Problem kojarzenia małżenstw Dlaczego kojarzenia małżenstw? Załóżmy, że mamy k kawalerów i p panien oraz dla każdej z panien podany jest zbiór kawalerów, których zna. Czy jest możliwe wydanie za mąż każdej z kobiet za kawalera, którego zna? Sformułowanie problemu przy pomocy grafów. Zbudujmy graf, którego zbiór wierzchołków składa się z dwóch rozłącznych podzbiorów: zbioru kawalerów K i zbioru panien P. Wierzchołek x ze zbioru K łączymy krawędzią z wierzchołkiem y z P, jeśli panna y zna kawalera x. W otrzymanym grafie nie istnieją krawędzie między żadnymi dwoma wierzchołkami ze zbioru P ani żadnymi dwoma ze zbioru K. Jest to więc graf dwudzielny. Poszukiwany jest w tym grafie zbiór krawędzi M taki, że: 1. żadna para krawędzi należących do M nie ma wspólnego wierzchołka (tj. małżeństwa są rozłączne tzn. nie dopuszczamy bigamii), 2. każdy wierzchołek ze zbioru P jest końcem pewnej krawędzi ze zbioru M (tj. każda panna wychodzi za mąż). Zbiór krawędzi spełniający takie warunki nazywamy skojarzeniem doskonałym. Kiedy rozwiązanie problemu kojarzenia małżeństw istnieje? P. Hall sformułował i udowodnił w 1935 roku twierdzenie, które podaje warunek konieczny i wystarczający na to, by problem kojarzenia małżeństw miał rozwiązanie: Twierdzenia Halla Problem kojarzenia małżeństw ma rozwiązanie wtedy, gdy każde m panien zna łącznie co najmniej m kawalerów, dla m=1,2,...p. 50

Przykład. Oto przykładowy graf dla zbioru P złożonego z trzech panien i zbioru K złożonego z trzech kawalerów. Każda z panien zna dokładnie dwóch kawalerów. Skojarzenie doskonałe istnieje i może wyglądać następująco: Ania może wyjść za Tomka, Kasia za Arka a Zosia za Jasia. Popatrzmy teraz na następujący graf. W tym grafie skojarzenie doskonałe nie istnieje. Ania i Kasia znają tylko Tomka. Nie uda się więc znaleźć równocześnie męża dla obydwu panien. Zastosowania. Problem ten posiada bardziej poważne zastosowania. Przy użyciu tej samej metody możemy rozwiązać problem polegający na przydzieleniu pracownikom zajęć zgodnie z ich kwalifikacjami. W tym przypadku przez P należy rozumieć zbiór pracowników, K zbiór żądań do wykonania. Dwa wierzchołki x i y łączymy krawędzią, jeśli praca y jest zgodna z kwalifikacjami pracownika x (to znaczy może on ją wykonywać). 51

Problem znajdowania najkrótszej drogi Wyobraźmy sobie pewną mapę. Na mapie zaznaczone są drogi między poszczególnymi miastami oraz długości tych dróg. Wybierając z tej mapy dowolne dwa miasta A i B chcemy zaplanować najkrótszą trasę z miasta A do miasta B. Jak rozwiązać ten problem używając grafów? Stwórzmy graf, którego wierzchołki odpowiadają miastom znajdującym się na danej mapie. Wierzchołki łączymy krawędzią, jeśli istnieje bezpośrednia (nie przebiegająca przez żadne inne miasto zaznaczone na tej mapie) droga łącząca odpowiadające im miasta. Krawędziom nadajemy wagi równe długości danej drogi. Oczywiście długość drogi możemy zastąpić przez czas trwania podróży lub jej koszt. Znalezienie najkrótszej drogi z miasta A do miasta B oznacza znalezienie pomiędzy odpowiadającymi im wierzchołkami drogi o możliwie najmniejszej sumie wag krawędzi. Kiedy rozwiązanie tego problemu istnieje? Jesteśmy w stanie znaleźć najkrótszą drogę, jeśli droga między danymi miastami (wierzchołkami w grafie) w ogóle istnieje. Aby można było znaleźć najkrótsze drogi między dowolną parą miast utworzony dla danej mapy graf musi być spójny. Algorytm rozwiązujący ten problem. Najbardziej znanym algorytmem rozwiązującym ten problem jest algorytm Dijkstry. Opis algorytmu Dijkstry Algorytm Dijkstry znajduje najkrótszą drogę z wierzchołka s (zwanego źródłem) do wierzchołka t (zwanego ujściem) w grafie, w którym wszystkim krawędziom nadano nieujemne wagi. Polega na przypisaniu wierzchołkom pewnych wartości liczbowych. Taką liczbę nazwijmy cechą wierzchołka. Cechę wierzchołka v nazwiemy stałą (gdy jest równa długości najkrótszej drogi z s do v) albo, w przeciwnym przypadku, tymczasową. Na początku wszystkie wierzchołki, oprócz s, otrzymują tymczasowe cechy. źródło s otrzymuje cechę stałą równą 0. Następnie wszystkie wierzchołki połączone krawędzią z wierzchołkiem s otrzymują cechy tymczasowe równe odległości od s. Potem wybierany jest spośród nich wierzchołek o najmniejszej cesze tymczasowej. Oznaczmy go v. Cechę tego wierzchołka zamieniamy na stałą oraz przeglądamy wszystkie wierzchołki połączone z v. Jeśli droga z s do któregoś z nich, przechodząca przez v ma mniejszą długość od tymczasowej cechy tego wierzchołka, to zmniejszamy tą cechę. Ponownie znajdujemy wierzchołek o najmniejszej cesze tymczasowej i zamieniamy cechę tego wierzchołka na stałą. Kontynuujemy to postępowanie aż do momentu zamiany cechy wierzchołka t na stałą (czyli obliczenia długości najkrótszej drogi z s do t). Zastosowania. Algorytmy znajdujące najkrótszą drogę w grafie są wykorzystywane do wyznaczania najlepszej trasy pomiędzy dwoma miastami na 'komputerowych' mapach. Mapy takie przydatne są w pracy np. firm transportowych. 52

Problem chińskiego listonosza Dlaczego listonosza? W swojej pracy, listonosz wyrusza z poczty, dostarcza przesyłki adresatom, by na końcu wrócić na pocztę. Aby wykonać swoją pracę musi przejść po każdej ulicy w swoim rejonie co najmniej raz. Oczywiście chciałby, aby droga, którą przebędzie, była możliwie najkrótsza. Dlaczego chińskiego? Problem ten został sformułowany po raz pierwszy w języku teorii grafów przez chińskiego matematyka Mei Ku Kwana w 1962 roku. Sformułowanie problemu. Rozważmy graf, którego krawędzie odpowiadają ulicom w rejonie, odsługiwanym przez listonosza. Wierzchołki to po prostu skrzyżowania ulic. Krawędziom nadajemy wagi, które oznaczają odległości między dwoma skrzyżowaniami. Znalezienie możliwie najkrótszej drogi, którą musi przejść listonosz sprowadza się do znalezienia w tym grafie drogi o minimalnej sumie wag krawędzi, która przechodzi przez każdą krawędź co najmniej raz. Jeśli graf posiada cykl Eulera. Jeśli dany graf posiada cykl Eulera, to istnieje taka droga, która zaczyna i kończy się w tym samym punkcie i wymaga przejścia po każdej ulicy dokładnie raz. Zauważmy, że ponieważ każdy cykl Eulera przechodzi raz przez każdą krawędź to suma wag krawędzi (długość drogi, którą musi przejść listonosz) jest zawsze taka sama (nie zależy od wierzchołka, w którym cykl ten zaczyna się i kończy). Rozwiązaniem jest więc dowolny cykl Eulera w tym grafie. Jeśli graf nie posiada cyklu Eulera. W takim przypadku listonosz będzie zmuszony przejść niektórymi ulicami wielokrotnie. Rozwiązanie jest więc cyklem, w którym suma długości krawędzi wybranych więcej niż raz jest możliwie najmniejsza. Przykład Na rysunku pokazany jest pewien układ ulic, które tworzą rejon listonosza. Obok nazw ulic umieszczone są odległości w metrach. Prostokąt oznaczony literą P oznacza miejsce, w którym umieściliśmy pocztę, na której pracuje 'nasz' listonosz. Oto jak wygląda graf odpowiadający danemu układowi ulic. Zauważmy, że graf ten nie ma cyklu Eulera ponieważ posiada dwa wierzchołki, z których wychodzi nieparzysta liczba krawędzi. Na poniższym rysunku zaznaczono czerwoną kreską rozwiązanie czyli optymalną trasę listonosza. 53

Zwróćmy uwagę, że listonosz musi przejść dwukrotnie tylko ulicę Nowakowskiego, zaś pozostałe ulice dokładnie raz. Powyższa droga jest najkrótszą z możliwych ponieważ odcinek, po którym przechodzi dwukrotnie liczy tylko 500 metrów i nie istnieje trasa spełniająca zadane warunki o krótszym odcinku, który listonosz musi pokonać więcej niż raz. Czym różni się problem mostów królewieckich od problemu chińskiego listonosza? Rozwiązanie problemu chińskiego listonosza istnieje zawsze o ile graf jest spójny. Natomiast problem mostów królewieckich, a mówiąc ogólniej problem znalezienia cyklu Eulera w grafie, nie zawsze ma rozwiązanie. Jeśli jednak graf posiada cykl Eulera, to rozwiązanie jednego z tych problemów jest również rozwiązaniem dla drugiego. Algorytm Fleury'ego Jest to algorytm znajdujący cykl Eulera w grafie. Jak już wiemy, jeśli graf posiada taki cykl, to jest on rozwiązaniem rozważanego problemu. Po wprowadzeniu pewnych modyfikacji może posłużyć również do rozwiązywania problemu chińskiego listonosza w przypadku grafów, które nie mają cyklu Eulera. Opis Algorytmu Fleury'ego Startujemy z dowolnego wierzchołka. Każda kolejna krawędź, po której przechodzimy, wybierana jest spośród krawędzi wychodzących z wierzchołka, w którym aktualnie się znajdujemy. Wybieramy oczywiście krawędź, po której jeszcze nie przeszliśmy. O ile jest to możliwe, usunięcie wybranej krawędzi nie powinno rozciąć grafu na dwa 'kawałki'. Jeśli uda nam się, postępując w ten sposób, dojść do wierzchołka, z którego wyruszyliśmy i przejść przez wszystkie krawędzie, to otrzymana droga jest cyklem Eulera. 54

Problem komiwojażera Dlaczego komiwojażera? Komiwojażer ma do odwiedzenia pewna liczbę miast. Chciałby dotrzeć do każdego z nich i wrócić do miasta, z którego wyruszył. Dane są również odległości miedzy miastami. Jak powinien zaplanować trasę podróży, aby w sumie przebył możliwie najkrótsza drogę? Przez 'odległość' między miastami możemy rozumieć odległość w kilometrach, czas trwania podróży między tymi miastami albo koszt takiej podróży (na przykład cenę biletu lotniczego). W tym ostatnim przypadku, poszukiwanie optymalnej trasy polega na zminimalizowaniu całkowitych kosztów podróży. Tak więc możemy poszukiwać trasy najkrótszej albo najszybszej albo najtańszej. Zakładamy przy tym, że odległość miedzy dowolnymi dwoma miastami jest nie większa niż długość jakiekolwiek drogi łączącej te miasta, która wiedzie przez inne miasta. Założenie to tylko z pozoru wydaje się być zawsze spełnione. Rozważmy następujący przykład. Załóżmy, że interesuje nas czas trwania podróży koleją. Najszybsze połączenie z Katowic do Białegostoku wiedzie przez Warszawę. Czas trwania tej podróży traktujemy w tym przypadku jako 'odległość' z Katowic do Białegostoku. Sformułowanie problemu. Zbudujmy graf ważony, którego wierzchołki są miastami. Każda parę miast połączmy krawędziami. Każdej krawędzi nadajemy wagę równa 'odległości' miedzy miastami odpowiadającymi wierzchołkom, które są końcami tej krawędzi. Otrzymujemy w ten sposób graf pełny, który ma tyle wierzchołków, ile miast musi odwiedzić komiwojażer (wliczając w to miasto, z którego wyrusza). Odwiedzenie wszystkich miast odpowiada cyklowi, który przechodzi przez każdy wierzchołek danego grafu dokładnie raz. Cykl taki nazywamy cyklem Hamiltona. Poszukujemy więc w grafie pełnym cyklu Hamiltona o minimalnej sumie wag krawędzi. Przykład Na rysunku pokazano graf ważony o wierzchołkach odpowiadających pięciu miastom polskim. Wagami krawędzi są odległości podane w kilometrach. Poszukujemy rozwiązania następującego problemu: Komiwojażer wyrusza z Warszawy i chce odwiedzić wszystkie pozostałe cztery miasta a następnie wrócić do Warszawy. Jak powinien zaplanować podróż, aby przebył możliwie najmniejsza liczbę kilometrów? 1 4! Już przy pięciu miastach wszystkich możliwych tras podróży komiwojażera jest 4 3 2 1 =. Można 2 2 zauważyć, że przy większej liczbie miast rozważanie wszystkich możliwości nie jest najlepszym pomysłem. 55

Dlaczego rozwiązanie tego problemu zawsze istnieje? Dowolny graf pełny posiada co najmniej jeden cykl Hamiltona. Ponieważ graf ma skończona liczbę wierzchołków, to w zbiorze cykli Hamiltona istnieje taki (niekoniecznie jedyny), który posiada minimalna sumę wag krawędzi. Algorytmy rozwiązujące problem komiwojażera. Istnieje wiele algorytmów rozwiązujących ten problem. Wszystkie mają jedną podstawową wadę. Wymagają rozważenia bardzo dużej liczby przypadków i czas ich działania może być bardzo długi. Niewielki przyrost liczby miast powoduje 'duży' wzrost ilości przypadków do rozważenia i tym samym czasu działania algorytmu. Jeden z możliwych algorytmów polega na obliczeniu całkowitej długości wszystkich istniejących w danym grafie cykli Hamiltona. Jest to jednak bardzo skomplikowane już dla liczby miast niewiele większej od pięciu. Na przykład dla 20 miast liczba możliwych tras Komiwojażera 19! wynosi wynosi = 60822550204416000 i cykli Hamiltona w grafie pełnym o 20 wierzchołkach istnieje 2 około 6000000. Algorytmy przybliżone Czas rozwiązywania problemu komiwojażera można zmniejszyć stosując jeden ze znanych algorytmów przybliżonych, które nie wymagają rozważania aż tak dużej liczby przypadków. Jednak algorytmy takie nie zawsze znajdują optymalne rozwiązanie. Stworzona przez nie trasa może być znacznie 'dłuższa' od najkrótszej. Stosowanie algorytmów przybliżonych wynika z konieczności wyboru pomiędzy szybkością znajdowania a 'jakością' znalezionego rozwiązań. Z reguły zakłada się, że wynik działania takiego algorytmu nie może być gorszy od optymalnego o więcej niż pewna ustaloną z góry wartość. Znajdowanie cyklu Hamiltona. Znajdowanie cyklu Hamiltona w dowolnym grafie. W grafie pełnym cykl Hamiltona zawsze istnieje. W dowolnym grafie może jednak nie istnieć. Problem polegający na znalezieniu cyklu Hamiltona jest podobnie jak problem komiwojażera 'trudny' ze względu na długi czas działania znanych algorytmów. Do znalezienia takiego cyklu może wystarczyć 'trochę szczęścia'. Gorzej jest kiedy cykl Hamiltona w badanym grafie nie istnieje. W takim przypadku możemy nawet być zmuszeni do sprawdzenia wszystkich możliwych permutacji zbioru wierzchołków, aby uzyskać pewność, że cykl taki nie istnieje. 56

Problem niezawodności sieci Wyobraźmy sobie, że chcemy zaprojektować sieć komunikacyjną (np. telekomunikacyjną, drogową, komputerową). Składa się ona z pewnej liczby punktów węzłowych (np. terminali komputerowych) i bezpośrednich połączeń (linii) między niektórymi z nich. Jak przedstawić sieć komunikacyjną przy pomocy grafu? Sieć taką możemy przedstawić za pomocą grafu, którego wierzchołki odpowiadają punktom węzłowym a krawędź miedzy dwoma wierzchołkami oznacza bezpośrednie połączenie linią danych dwóch punktów węzłowych. Oto przykładowa sieć składająca się z 6 punktów węzłowych oznaczonych literami A,B,C,D,E,F i pewnych krawędzi między nimi Wiemy, że nic nie jest doskonałe i sieć narażona jest na awarie. Spójność wierzchołkowa takiego grafu jest równa minimalnej liczbie awarii w punktach węzłowych sieci, które spowodują awarię całej sieci (to znaczy między niektórymi węzłami zostanie zerwane połączenie). Natomiast spójność krawędziowa oznacza minimalną liczbę awarii łączy między węzłami, które spowodują awarię sieci. Przez niezawodność sieci możemy rozumieć maksymalną liczbę awarii, których wystąpienie nie spowoduje awarii całej sieci. Im większa spójność grafu tym większa niezawodność sieci Jak skonstruować sieć by jej niezawodność była możliwie największa? Oczywiście im więcej linii (krawędzi) tym niezawodność większa. Z drugiej jednak strony zbudowanie każdego połączenia kosztuje. Możemy założyć, że szukamy możliwie najtańszej sieci o z góry założonej niezawodności, bądź szukamy sieci o ustalonym koszcie i możliwie największej niezawodności. Jedno z możliwych sformułowań tego problemu wygląda następująco: Sformułowanie problemu. Załóżmy, że dana jest liczba punktów węzłowych oraz żądana niezawodność sieci k (liczba 'dopuszczalnych' awarii). Zbudowanie każdego bezpośredniego połączenia obarczone jest pewnym kosztem jednostkowym (jest to pewne uproszczenie - w rzeczywistości koszty budowy połączeń są różne). Chcemy zaprojektować sieć o żądanej niezawodności, której koszt budowy będzie możliwie najmniejszy. Poszukujemy więc grafu o n wierzchołkach i możliwie najmniejszej liczbie krawędzi, którego spójność wierzchołkowa lub spójność krawędziowa wynosi k. 57

Znajdowanie liczby chromatycznej grafu Sformułowanie problemu. Dane: graf G Szukane: liczba chromatyczna grafu G Przykładowe zastosowanie Przy pomocy algorytmów znajdujących optymalne pokolorowanie wierzchołków grafu można rozwiązać następujący problem dotyczący składowania substancji chemicznych: Zakłady chemiczne wykorzystują przy produkcji n surowców chemicznych. Wiadomo, że niektóre substancje nie mogą być przechowywane razem, gdyż zetknięcie ich ze sobą spowodowałoby 'zniszczenie magazynu', ewentualnie katastrofę ekologiczną. Jaka jest minimalna liczba magazynów potrzebna do przechowywania wszystkich surowców chemicznych używanych w tej fabryce? Jak to zrobić używając grafu? Tworzymy graf, którego n wierzchołków odpowiada poszczególnym surowcom chemicznym. Dwa wierzchołki łączymy krawędzią jeśli nie mogą być przechowywane razem. Minimalna liczba magazynów potrzebnych do składowania tych surowców jest równa liczbie chromatycznej tego grafu. Dlaczego tak jest? Rozważmy dowolne pokolorowanie wierzchołków tego grafu, w którym każde dwa wierzchołki połączone krawędzią są pokolorowane innym kolorem. Surowce odpowiadające wierzchołkom pokolorowanym tym samym kolorem mogą być składowane w jednym magazynie! Znajdowanie indeksu chromatycznego grafu Sformułowanie problemu. Dane: graf G Szukane: indeks chromatyczny grafu G Przykładowe zastosowanie. Rozważmy następujący problem. Dany jest zbiór m nauczycieli oraz zbiór n klas. Podane są także liczby godzin zajęć jakie musi odbyć w ciągu tygodnia dany nauczyciel z każdą z klas. Szukana jest minimalna liczba godzin w tygodniu, w czasie których mogą odbyć się wszystkie zajęcia. Wiadomo, że w danym momencie czasu nauczyciel może uczyć tylko jedną klasę i każda klasa może być uczona przez tylko jednego nauczyciela. Jak rozwiązać problem układania planu zajęć przy pomocy grafów? Stwórzmy graf dwudzielny, którego zbiór wierzchołków można podzielić na dwa rozłączne zbiory odpowiadające nauczycielom oraz klasom. W grafie tym dopuszczamy istnienie wielu krawędzi między każdą parą wierzchołków. Wierzchołek odpowiadający nauczycielowi łączymy tyloma krawędziami z wierzchołkiem odpowiadającym klasie ile godzin zajęć musi on odbyć z tą klasą w ciągu tygodnia. Zauważmy, że jeśli pokolorujemy krawędzie tego grafu tak, aby każde dwie mające wspólny koniec były różnych kolorów, to krawędzie pokolorowane tym samym kolorem odpowiadają zajęciom, które mogą odbywać się równocześnie. Poszukujemy więc minimalnej liczby kolorów potrzebnych do pokolorowania w ten sposób wszystkich krawędzi. Innymi słowy, poszukiwany jest indeks chromatyczny tego grafu. Problem ten można oczywiście skomplikować dodając założenia dotyczące sal, w których zajęcia mogą się odbywać, bądź narzucając pewne terminy, w których dane zajęcia muszą się odbyć. 58

Przykład Przypuśćmy, że mamy 5 nauczycieli: profesorów Mroza, Nowaka, Pawlaka, Cicho i Lisa oraz 4 klasy maturalne. Na poniższym rysunku pokazany jest graf stworzony na podstawie informacji o tym ile godzin zajęć w tygodniu z daną klasą ma poprowadzić każdy z nauczycieli. Dla przykładu: profesor Mróz ma 2 godziny z IVa i 1 godzinę z IVb a profesor Nowak po 1 godzinie z IVa i IVc. Indeks chromatyczny tego grafu wynosi 4. Czyli w ciągu 4 godzin uda się przeprowadzić wszystkie zajęcia. Widać, że mniejsza liczba godzin nie wystarczy ponieważ profesor Lis musi przeprowadzić 4 godziny zajęć. Również klasy IVa, IVc oraz IVd mają zaplanowane po 4 godziny. A oto jak wygląda pokolorowanie krawędzi tego grafu na 4 kolory, w którym żadne dwie krawędzie o wspólnym wierzchołku nie mają tego samego koloru. Jeżeli przyjmiemy, że każdy kolor oznacza pewien 45 minutowy okres czasu (np. 8.15-9.00), to w prosty sposób tak pokolorowany graf można przekształcić w poniższą tabelę. ---- ---- ---- ---- prof. Mróz IVa IVb IVa prof. Nowak IVc IVa prof. Pawlak IVd IVa IVc prof. Cicho IVc IVd IVb prof. Lis IVb IVd IVc IVd 59

W wierszach odpowiadających poszczególnym nauczycielom wypisane są klasy, które profesor powinien uczyć o danej godzinie (przy czym u góry każdej kolumny zamiast godziny jest kolor). Profesor Mróz ma najpierw godzinę z IVa potem godzinę z IVb, znowu 1 lekcję z IVa i na koniec godzinę wolną. Kolejność terminów (kolorów) możemy ustawić w dowolny sposób. Czyli profesor Mróz może mieć wpierw 2 godziny z IVa a potem 1 lekcję z IVb. Wymaga to tylko zamiany miejscami 2-ej i 3-ej kolumny w tabeli. Znajdowanie grubości grafu Sformułowanie problemu. Dane: graf G Szukane: grubość grafu G Przykładowe zastosowanie. Przewodzącą płytkę, na której jednej stronie drukowane są części układu elektronicznego oraz przewody je łączące, nazywamy obwodem drukowanym. Projektując obwód drukowany należy pamiętać o tym, że przewody nie mogą się przecinać ponieważ nie są izolowane. Wynika stąd, że graf odpowiadający obwodowi drukowanemu musi być płaski. Rozpatrzmy graf, który odpowiada całemu układowi elektronicznemu. Wierzchołkami są elementy tego układu a krawędziami przewody. Grubość takiego grafu jest to minimalna liczba obwodów drukowanych potrzebnych do złożenia całego układu. Mózg człowieka (przykład grafu), a procesor komputera Mówiąc o sieciach neuronowych często zamiennie używamy nazwy neurokomputery mając na myśli urządzenia, których budowa podobna jest do biologicznej struktury mózgu ludzkiego. Stąd też wywodzi się nazwa, podkreślająca, iż pierwowzorem podstawowego elementu sieci neuronowej jest właśnie neuron biologiczny - elementarny składnik mózgu. Oto przykład sieci - jej węzły to neurony, ich zadaniem jest przetwarzanie informacji wejściowej i przesyłanie wynikowego sygnału. To oczywiście tylko jeden z przykładów wielu modeli sieci. Mózg człowieka ciągle jest najpotężniejszym z istniejących obecnie urządzeń stosowanych do przetwarzania informacji w czasie rzeczywistym. Mózg i komputer : jakie są podobieństwa i jakie różnice. Wyobraźmy sobie komputer, który rozwiązując pewien problem sam się uczy. Najpierw wprowadzamy do niego informacje o postawionym zadaniu, dane wejściowe problemu oraz wybrane przykłady wraz z poprawnymi ich rozwiązaniami. Następnie komputer analizuje wprowadzone informacje i ucząc się na swoich błędach osiąga w końcu taki stan, w którym postawiony problem może być rozwiązany. W takiej działalności można zauważyć wiele podobieństw do działania człowieka. 60

Tu pojawia się pytanie: czy potrafimy skonstruować urządzenie techniczne o podobnych właściwościach??? Badania ostatnich lat sugerują odpowiedź twierdzącą - a właśnie sieci neuronowe wydają się być jedną z dróg prowadzących do tego celu. Z punktu widzenia informatyki interesującym jest porównanie zalet komputera z mocnymi stronami mózgu. Oto ono: Mózg Komputer rozpoznawanie kojarzenie klasyfikacja danych informacji obliczenia arytmetyczne równoległe przetwarzanie danych - wiele neuronów działających w tym samym czasie bardzo krótki czas przetwarzania jednego polecenia zdolność do rekonstrukcji i odtwarzania sygnałów odporność na uszkodzenia zdolność przetwarzania informacji niepełnej i obarczonej błędami wysoka precyzja obliczeń Podsumowując: sieci nerwowe mogą przeprowadzać niezwykłe obliczenia i działania, chociaż jest rzeczą oczywistą, że w na przykład obliczeniach arytmetycznych mózg nie jest tak dobrym urządzeniem (tak szybkim, wydajnym i dokładnym) jak komputer. z drugiej strony - gdy mamy do czynienia z zadaniami takimi jak rozpoznawanie, skojarzenia czy klasyfikacja - mózg może pokonać nawet najszybszy superkomputer, pomimo że w tym procesie neurony jako jednostki przetwarzające są o wiele rzędów wielkości wolniejsze od swoich elektronicznych odpowiedników. Analizując metody przetwarzania i selekcji informacji oraz sposoby podejmowania decyzji w systemie nerwowym łatwo można dojść do wniosku, że jest on przykładem rozwiązania wielu problemów, z którymi od wielu lat boryka się informatyka czy też teoria przetwarzania informacji. Wiele istotnych problemów kombinatorycznych, zagadnień klasyfikacji danych czy też zagadnień świata rzeczywistego wymyka się bowiem ścisłym regułom i algorytmom postępowania. Z takimi problemami sieci neuronowe radzą sobie znacznie lepiej. 61

Modele sieci Zadaniem każdego neuronu w sieci jest odbieranie sygnałów otrzymywanych od innych (nie koniecznie wszystkich) neuronów, sumowanie tych sygnałów, przetworzenie sumarycznego sygnału przy zastosowaniu tzw. funkcji aktywacji nazywanej również funkcją transferu albo funkcją przejścia, a następnie wysłanie przetworzonego sygnału do innych neuronów. W literaturze opisanych jest wiele modeli neuronów różniących się między sobą przede wszystkim sposobem kolekcjonowania (sumowania)otrzymywanych sygnałów oraz stosowaną funkcją aktywacji decydującą o tym, w którym momencie wysyłany jest sygnał zwrotny i jaka jest moc tego sygnału. Jednym z fundamentalnych modeli neuronu jest zaproponowany w 1943 roku model McCullocha-Pittsa (ozn. MP). Neuron MP, będący pierwszym formalnie zdefiniowanym modelem neuronu, pomimo zastosowania bardzo dużych uproszczeń w stosunku do modelu neuronu biologicznego (a po części właśnie z tego powodu) znalazł zastosowanie w wielu modelach sieci neuronowych. Schemat neuronu MP przedstawiony jest na rysunku powyżej. Neurony w sieci komunikują się ze sobą poprzez tzw. połączenia synaptyczne, których wielkość określa waga synaptyczna, nazywana w skrócie wagą. Waga połączenia neuronu z neuronem, oznaczana przez, może mieć wartość dodatnią, wtedy reprezentuje połączenie wzmacniające, ujemną wtedy jest połączeniem hamującym bądź zerową co oznacza brak połączenia pomiędzy neuronami. Suma przemnożonych przez wagi sygnałów docierających do neuronu w jednostce czasu stanowi potencjał wejściowy (potencjał wewnętrzny) tego neuronu, oznaczany przez. Odpowiedź neuronu na zadany potencjał wejściowy nazywana jest potencjałem wyjściowym (albo potencjałem zewnętrznym) tego neuronu. Charakterystykę odpowiedzi określa funkcja aktywacji, oznaczana przez. Podstawowym układem równań opisującym działanie neuronu jest w związku z tym: 62

Znak sumy znak oznacza sumę elementów od do, tzn. Modele sieci neuronowych różnią się zarówno rodzajem neuronów, z których są zbudowane jak i topologią (układem) połączeń międzyneuronalnych oraz sposobem przesyłania sygnałów w obrębie sieci. Najpopularniejszą grupę stanowią tzw. sieci jednokierunkowe. Sieci te zbudowane są z jednej bądź kilku warstw. Przepływ sygnału w tego typu sieciach przebiega zawsze w ściśle określonym kierunku: od warstwy wejściowej do warstwy wyjściowej. Każda dodatkowa warstwa pomiędzy warstwami wejściową i wyjściową nazywana jest warstwą ukrytą z uwagi na to, że jej działalność nie może być obserwowana bezpośrednio ani na wejściu sieci ani na jej wyjściu. Na przedstawionym rysunku warstwa wejściowa składa się z 5 neuronów, warstwa ukryta i wyjściowa mają po 3 neurony. Zwykle wszystkie neurony warstwy poprzedniej połączone są ze wszystkimi neuronami warstwy następnej. Połączenia wsteczne, połączenia omijające warstwę oraz połączenia typu sprzężenia zwrotnego (tzn. połączenia neuronu ze sobą) nie występują w sieciach jednokierunkowych. Kierunek przepływu sygnałów zaznaczony jest strzałkami. Najpopularniejszym przykładem sieci jednokierunkowej jest perceptron. Osobnym problemem jest kwestia zastosowania sieci neuronowej do wykonania określonego zadania. W tym celu stosowane są odpowiednie algorytmy uczące. 63

Drugą istotną grupę sieci neuronowych stanowią tzw. sieci rekurencyjne. W sieciach rekurencyjnych sygnał propagowany jest w obiegu zamkniętym, tzn. sygnały z warstwy wyjściowej sieci podawane są z powrotem do warstwy wejściowej. Często, z uwagi na skomplikowaną topologię pojęcie warstwy w sieci rekurencyjnej jest w dużym stopniu umowne. Wejścia sieci mogą być dodatkowo pobudzane wymuszeniem zewnętrznym, które jest sumowane z aktualnym sygnałem w sieci. Jeżeli wymuszenie zewnętrzne jest równe zero, to układ nazywamy autonomicznym. Najprostsza sieć rekurencyjna składa się z jednej warstwy, w której wyjścia połączone są z wejściami poprzez pętle sprzężeń zwrotnych. Sieć taka, po spełnieniu dodatkowych założeń nazywana jest siecią Hopfielda (por. rysunek powyżej). Sieć dyskretna Hopfielda, tzn. sieć z funkcją przejścia postaci służy przede wszystkim do konstruowania tzw. pamięci skojarzeniowych. W sieci ciągłej stosowana jest sigmoidalna funkcja przejścia: 64

Sieć ciągła wykorzystywana jest do rozwiązywania kombinatorycznych problemów optymalizacyjnych. Tangens hiperboliczny jest to funkcja tangens hiperboliczny, opisana wzorem Zasady uczenia się Poprzez uczenie sieci rozumiemy działanie, które ma doprowadzić do określonej reakcji na sygnały wejściowe. Uczenie to proces jednorazowy lub składający się z wielu etapów. Co najważniejsze, uczenie sieci może przebiegać zarówno z nauczycielem, jak i bez nauczyciela. W przypadku uczenia z nauczycielem znana jest odpowiedź prawidłowa na zadane sieci pytanie, możemy ją więc wykorzystać do skorygowania błędnej decyzji sieci. Przykładem tego typu problemu jest uczenie sieci klasyfikacji danych np. określania czy klient banku o podanej charakterystyce spłaci kredyt. Podstawowa zasada tej metody uczenia polega na tym, iż dla zadanych danych wejściowych, znana jest pożądana odpowiedź sieci. Wykonanie porównania z odpowiedzią udzieloną przez sieć pozwala ustalić błąd. Jego wielkość może zostać wykorzystana do korekty działania sieci. Nazwa metody oddaje jej charakter - kluczowym założeniem jest możliwość skorzystania z gotowych, poprawnych odpowiedzi - a więc wiedzy "nauczyciela". Ciekawym rodzajem uczenia jest uczenie bez nauczyciela. W takim przypadku nie znamy prawidłowej odpowiedzi, a zadaniem sieci jest jej ustalenie. Przykładem może być ustalanie granic pomiędzy różnymi klasami obrazów. W tym przypadku sieć musi posiadać mechanizm autoadaptacyjny. Taka metoda uczenia jest również nieobca człowiekowi. Każda próba samodzielnej analizy nowych zjawisk i ustalenia reguł nimi rządzących to uczenie bez nauczyciela - czyli bez znanych rozwiązań. Prosty, a jednocześnie ważny przykład uczenia z nauczycielem to uczenie dla problemu klasyfikacji danych chorobowych: niech zbiór danych na temat 250 pacjentów zawiera dane potrzebne do postawienia diagnozy i prawidłową diagnozę wtedy ucząc sieć stawiania diagnozy możemy postępować następująco: o o o sprawdzamy diagnozę wyliczoną przez sieć porównujemy ją ze znaną prawidłową diagnozą wyznaczony błąd diagnozy wykorzystujemy do skorygowania działania sieci i kontynuujemy uczenie dla kolejnych danych Zastosowania Omówione w cześci teoretycznej cechy sieci neuronowych, jak również znane z literatury różnorodne modele struktur sieciowych pozwalają na scharakteryzowanie ich możliwości oraz obszarów ich potencjalnych zastosowań. 65

Sieci nie uczą się algorytmów, lecz uczą się przez przykłady. W przeciwieństwie do konwencjonalnych komputerów są słabymi maszynami matematycznymi i słabo nadają się do typowego przetwarzania wykorzystującego algorytmy. Bardzo dobrze natomiast nadają się do zadań związanych z: rozpoznawaniem obrazów (nawet w przypadku niepełnej bądź zafałszowanej informacji), problemami optymalizacyjno - decyzyjnymi, szybkim przeszukiwaniem dużych baz danych, klasyfikacją i kompresją danych. Zastosowania praktyczne Wśród wielu zastosowań sieci neuronowych istotne miejsce zajmują również te o bardzo istotnym aspekcie praktycznym. Sieci neuronowe to nie tylko teoria i naukowe zastosowania! Wśród zastosowań praktycznych, istotne są również zastosowania do wspomagania decyzji - w tym decyzji jak inwestować na giełdzie. W tym przypadku istnieją i wciąż są rozwijane pakiety komercyjne, których głównym celem jest pomoc w pomnażaniu zysków inwestora. Przykład takiego oprogramowania to pakiet Profit firmy BioComp Systems, Inc. Jak tego typu systemy działają? Podstawą ich działania są sieci neuronowe - zadaniem systemu jest dostarczanie prognoz rozwoju sytuacji na rynku oraz sugerowanie decyzji (ang. buy,sell or hold). Danymi wejściowymi sieci stają się odpowiednio przetworzone dane opisujące aktualny stan rynku - np. notowania giełdowe. Często do konstruowania sieci neuronowych wykorzystywane są inne techniki sztucznej inteligencji i optymalizacji, a w szczególności algorytmy genetyczne. Ciekawym rozwiązaniem jest korzystanie z grupy sieci neuronowych o najlepszej efektywności prognozowania do wyznaczania ostatecznej prognozy. Przypomina to korzystanie z grupy ekspertów. Wtedy sugestie sieci są uśredniane, odrzuceniu mogą przy tym podlegać skrajne reakcje sieci. Problem n-hetmanów Interesującym przykładem problemu kombinatorycznego jest zagadnienie N - hetmanów polegające na ustawieniu na szachownicy N x N, N hetmanów w taki sposób, żeby się wzajemnie nie atakowały. Wydaje się, że problem jest stosunkowo prosty, ale... w istocie tak nie jest. Kto nie wierzy - niech spróbuje rozwiązać go np. dla N=20! Sieci neuronowe można bardzo efektywnie wykorzystać do rozwiązania tego problemu i problemów podobnych. Odpowiednio zdefiniowana sieć (w omawianym wypadku jest to sieć dyskretna Hopfielda) w kolejnych etapach proponuje przybliżone rozwiązania problemu, by po pewnym czasie osiągnąć rozwiązanie końcowe. 66

Sieć poszukując rozwiązania często próbuje ustawić inną liczbę hetmanów - np. N-1, ale jest za to karana, co w konsekwencji zmusza ją do przekonfigurowania bieżącego ustawienia w kierunku rozwiązania z N niekolidującymi hetmanami. W przypadku N=8 sieć w 1000 prób znajduje rozwiązanie problemu średnio 998 razy!. Oto przykład funkcjonowania sieci w przypadku standardowej szachownicy 8x8 czyli o 64-ech polach: Etap Zaproponowane rozwiązanie Liczba kolizji Liczba hetmanów 1 4 8 2 2 7 67

3 3 8 4 1 7 Wreszcie sieć uzyskuje rozwiązanie, spełniające wszystkie założenia, a więc 8 hetmanów ustawionych bez żadnej kolizji: Jak sprawdzić poprawność rozwiązania? Po prostu: w każdym wierszu i w każdej kolumnie musi znajdować się dokładnie jeden hetman, przy czym na żadnej przekątnej nie może być ustawiony więcej niż jeden z nich. Na koniec zapraszamy do obejrzenia w akcji sieci rozwiązującej problem N-hetmanów w postaci miniprogramu napisanego w języku Java. Pamięci skojarzeniowe Wiodącym obszarem zastosowań modelu dyskretnego jest konstruowanie pamięci skojarzeniowych, w których komórki pamięci adresowane są poprzez swoją zawartość a nie za pomocą określonego adresu. Zadaniem pamięci skojarzeniowej jest zapamiętanie danego zbioru wzorców binarnych w taki sposób, 68

żeby po otrzymaniu na wejściu nieznanego wzorca binarnego pamięć odtwarzała na wyjściu ten z zapamiętanych wzorców, który jest najbliższy w sensie odległości Hamminga (patrz ramka). Inaczej formułując problem, pamięć skojarzeniowa powinna spełniać dwa warunki: pokazanie na jej wejściu dowolnego z zapamiętanych wektorów (wzorców) powoduje odtworzenie na wyjściu tego samego wektora, pokazanie na wejściu zaburzonej (,,w rozsądnych granicach'') wersji dowolnego z zapamiętanych wektorów powoduje odtworzenie na wyjściu poprawnej postaci tego wektora. Odległość Hamminga dla dwóch wektorów binarnych i (ozn. jest równa liczbie bitów, które są różne w tych wzorcach. Na przykład dla,,, ponieważ wzorce i różnią sie na dwóch pozycjach (drugiej i trzeciej). Metodę konstruowania pamięci skojarzeniowej podaną przez Hopfielda można streścić następująco. Rozważmy zbiór złożony z wzorców bipolarnych (tzn. o elementach należących do zbioru ) o długości każdy. Transponowanie macierzy Jeżeli jest macierzą, to przez oznacza się zwykle macierz transponowaną, której kolejnymi wierszami są kolejne kolumny macierzy, tzn. jeżeli przez oznaczymy element macierzy, a przez element macierzy, to zachodzi zależność: Pamięć skojarzeniowa służąca do zapamiętania wzorców ze zbioru zbudowana jest z neuronów. Kwadratowa macierz połączeń o wymiarach obliczana jest za pomocą tzw. reguły Hebba: 69

(1) gdzie jest wagą połączenia wyjścia neuronu z wejściem neuronu. W zapisie macierzowym powyższa zależność ma postać: (2) gdzie jest macierzą jednostkową wymiaru, a składa się z wektorów bazowych zapisanych kolumnowo -. Mnożenie macierzy Mnożenie dwóch macierzy: (o wierszach i kolumnach) i (o wierszach i kolumnach) - zauważcie, że liczba kolumn macierzy musi być równa liczbie wierszy macierzy - np. Wynikiem mnożenia jest macierz, postaci której elementy powstają przez pomożenie wiersza macierzy przez kolumnę 70

macierzy. W związku z tym, elementy spełniają zależność: Przykładowo, dla zbioru wzorców otrzymana przy zastosowaniu reguły Hebba wygląda następująco: macierz Rozpoznawanie wzorców z wykorzystaniem nauczonej sieci przebiega w następujący sposób. Nieznany wzorzec jest pokazywany w warstwie wejściowej sieci, a następnie wszystkie neurony obliczają swoje potencjały wejściowe, oraz wyjściowe, tj. (3) Następnie, otrzymany w powyższym układzie równań wektor potencjałów wyjściowych neuronów jest podawany ponownie na wejście, liczone są potencjały wejściowe i wyjściowe neuronów, itd. Iteracyjny proces rozpoznawania może zakończyć sie dwojako. Po pierwsze wtedy, gdy sieć osiąga stan stabilny, tj. w dwóch kolejnych iteracjach wygenerowany jest taki sam wektor wyjściowy. Wektor ten jest odpowiedzią sieci na wektor wejściowy. Drugi sposób zakończenia działania sieci ma miejsce wtedy, gdy zostanie przekroczona zadana z góry liczba iteracji. Sytuacja taka może mieć miejsce w przypadku, gdy sieć oscyluje pomiędzy dwoma stanami, tj. na zmianę generuje na wyjściu dwa różne wektory i według schematu: (4) 71