Możliwości i ograniczenia komputerów Komputer potrafi

Transkrypt

Możliwości i ograniczenia komputerów Komputer potrafi
Możliwości i ograniczenia komputerów
Komputer potrafi przeanalizować ogromną ilość danych pochodzących np. z
wielu zdjęć rentgenowskich ludzkiego mózgu wykonanych pod stopniowo
zwiększanym kątem, tworząc obraz przekroju mózgu. Obraz taki może być użyty
do umiejscawiania nieregularności, np. guzów.
Komputer nie potrafi przeanalizować pojedynczego zdjęcia twarzy tego samego
pacjenta i określić jego wieku z dokładnością do np. 5 lat, a nawet dzieci to
potrafią.
Komputery są zdolne sterować w najbardziej sprawny i precyzyjny sposób
dowolnie skomplikowanymi robotami przemysłowymi używanymi do
konstruowania dowolnie skomplikowanych części maszyn.
Komputer nie potrafi jednak pokierować robotem tak, aby z kupki gałązek
utworzył ptasie gniazdo. Potrafi to każdy, nawet młody ptak.
1
Komputery potrafią grać w szachy na poziomie mistrzów międzynarodowych –
pokonują znakomitą większość ludzi.
Komputery nie potrafią dostosować się jednak do nagłej zmiany reguł – np.
reguły dopuszczającej podwójny ruch skoczka lub ograniczenie ruchu hetmanem
do 5 pól. Kilkunastoletni szachista-amator będzie bardzo szybko w stanie grać
dobrze przy nowych regułach.
Rozdźwięk ten związany jest z różnicą między inteligencją ludzką a
komputerową (tzw. paradoks Moraveca).
2
Oprogramowanie (ang. software)
Całość informacji w postaci zestawu instrukcji, zaimplementowanych interfejsów
i zintegrowanych danych przeznaczonych dla komputera do realizacji
wyznaczonych celów.
Celem oprogramowania jest przetwarzanie danych w określonym przez twórcę
zakresie. Oprogramowanie jest synonimem terminów program komputerowy
oraz aplikacja, przy czym stosuje się go zazwyczaj do określania większych
programów oraz ich zbiorów.
Oprogramowanie tworzą programiści w procesie programowania.
Dział informatyki, zajmujący się całościowym badaniem procesu tworzenia
oprogramowania (analiza wymagań, projekt systemu, implementacja, testowanie,
wdrożenie, konserwacja), to inżynieria oprogramowania.
3
Komunikaty i informacje
Komunikat i informacja to dwa ważne pojęcia informatyki, których znaczenie
nieco odbiega od przyjętego w języku codziennym.
Komunikat – wyodrębniony zbiór znaków danego kodu, przekazywany
pomiędzy komunikującymi się obiektami w określonym porządku czasowym i
przestrzennym.
Relacje pomiędzy komunikatem i informacją nie są wzajemnie jednoznaczne:
● tę samą informację mogą przenosić różne komunikaty, np. komunikaty
sformułowane w różnych językach, lub komunikaty z uzupełnieniami/wstawkami
nie zawierającymi dodatkowej informacji (np. kolejne przemówienia na ten sam
temat wypełnione tzw. watą słowną).
● dany komunikat może przenosić informację odmienną w stosunku do różnych
adresatów (np. ten sam artykuł w gazecie w stosunku do różnych czytelników).
4
Zatem: ten sam komunikat, różnie interpretowany, może przenosić odmienną
informację; w wielu aspektach można traktować informację jako wynik
interpretacji komunikatu.
Zapis symboliczny reguły interpretacji:
α
K →I
K – komunikat, I – informacja, α – reguła interpretacyjna.
Reguła interpretacyjna może być szczególnym przypadkiem reguły ogólniejszej,
stosowanej do zbioru komunikatów zbudowanych zgodnie z jednakowymi
prawidłami (np. znajomość danego języka oznacza, że w większości przypadków
poprawnie interpretowane są komunikaty sformułowane w tym języku).
Komunikaty mogące być interpretowane w ten sposób, że różne interpretacje
oparte są na sobie, są to komunikaty o różnym stopniu abstrakcji (np. komunikat
„pada” nie tylko informuje o deszczu, lecz także dodatkowo o tym, że warto
wziąć parasol).
5
Niekiedy język znany jest tylko niewielu wtajemniczonym – nikt postronny nie
powinien być w stanie uzyskać informacji z zaszyfrowanego przekazu – jest ona
jednak łatwo dostępna dla posiadaczy klucza.
H2SO4
<script language=”JavaScript”>
see you tomorrow
jeśli uzna, to raczej opornie
jeśli uzna, to raczej opornie
JDOOLD HVW RPQLV GLYLVD… (“Gallia est omnia divisa in partes tres”
w kodzie Cezara: A–>D, B–>E,..., itd.)
Najlepsze kasztany są na placu Pigalle (agent J23; Stawka większa niż życie)
6
Programowanie to modyfikowanie, rozszerzanie, naprawianie, ale przede
wszystkim tworzenie oprogramowania.
Język programowania to usystematyzowany sposób przekazywania
komputerowi poleceń do wykonania.
Język programowania pozwala programiście na precyzyjne przekazanie
maszynie, jakie dane mają ulec obróbce i jakie czynności należy podjąć w
określonych warunkach.
Języki programowania wiążą się zwykle ze sztywną składnią, dopuszczającą
używanie jedynie specjalnych kombinacji wybranych symboli i słów
kluczowych.
Języki programowania mogą być kompilowane lub interpretowane.
Formalna składnia typowego języka programowania zawiera zwykle różne
warianty struktur sterujących, wzorce podstawowych instrukcji, sposoby
definiowania struktur danych.
7
Struktury sterujące
bezpośrednie następstwo
wykonaj A, potem B, następnie C
pseudokod
C
C++
wczytaj a
a = a + 1
wypisz a
scanf(”%d”,&a); cin >> a;
a++;
a++;
printf(”%d”,a); cout << a;
Fortran77
Python
read(*,*) a
a=input()
a = a + 1
a=a+1
write(*,*) a print a
8
wybór warunkowy (rozgałęzienie warunkowe)
jeżeli Q to wykonaj A, w przeciwnym razie wykonaj B
Q – warunek logiczny, np. a>0
pseudokod
C/C++
Fortran77
Python
jeżeli a > 0
a = a + 1
c = a * 3
w przeciwnym
wypadku
a = a – 1
c = a / 3
if (a>0)
{a++;
c=a*3;}
else
{a--;
c = a/3;}
if (a .gt. 0) then if a > 0:
a=a+1
a=a+1
c=a*3
c=a*3
else
else:
a=a-1
a=a-1
c=a/3
c=a/3
endif
9
Iteracja (pętla) ograniczona
wykonaj A dokładnie n razy
pseudokod
C++
Fortran77
Python
pętla od i = 1
do n
wypisz i
wypisz i*i
for (i=1; i<=n;
i++)
{cout << i;
cout << i*i;}
do 55 i=1, n
write(*,*) i
write(*,*)
i*i
55 continue
for i in
range(1:n+1):
print i
print i*i
10
Iteracja (pętla) ograniczona
dopóki Q, wykonuj A
pseudokod
C++
Fortran77
Python
i = 1
dopóki i ≤ n
wypisz i
wypisz i*i
i = i + 1
i=1;
do while (i<=n)
{cout << i;
cout << i*i;
i++;}
i=1
i=1
15 if (i .ls. n) while i<=n:
then
print i
print i*i
write(*,*) i
write(*,*) i*i
i=i+1
i=i+1
goto 15
endif
wykonuj A, dopóki Q
i = 1
wykonuj
wypisz i
wypisz i*i
i = i + 1
dopóki i ≤ n+1
11
pętle zagnieżdżone
pętla od i = 1 do n
pętla od j = 1 do i
wypisz i + j
dopóki i ≤ n
pętla od j = 1 do i
wypisz i + j
i = i + 1
instrukcja skoku
skocz do oznaczonego miejsca w programie
i = 1
#G
wypisz i
wypisz i*i
i = i + 1
jeżeli i ≤ n
skocz do G
12
podprogramy
fragment algorytmu zapisany w formie osobnej procedury lub funkcji, np. w celu
umożliwienia jego wywoływania dla różnych wartości parametrów.
silnia(n):
jeżeli n == 0
silnia = 1
w przeciwnym wypadku
silnia = n * silnia(n-1)
dodaj_i_wypisz(a, b):
wypisz a + b
wywołanie podprogramu
wynik = silnia(10) + 1
pętla od n = 1 do 100
wypisz silnia(n)
pętla od i = 1 do 100
pętla od j = 1 do 100
dodaj_i_wypisz(i, j)
Przykład w C++
#include <iostream>
using namespace std;
int silnia(int n){
if (n == 0)
return 1;
else
return n * silnia(n-1);
}
int main(){
int n;
cin >> n;
cout << silnia(n);
}
13
Typy danych
obiekty, na których operują algorytmy:
● liczby (całkowite, zmiennoprzecinkowe, zespolone, dwójkowe itp.)
● typ znakowy (pojedynczy znak)
● typ tekstowy (ciąg znaków)
● typ logiczny (prawda/fałsz)
● wskaźnik
Zmienna
Obszar pamięci przechowujący dane. O rodzaju i sposobie przechowywania
decyduje typ zmiennej.
14
Struktury danych
Tablica jednowymiarowa (wektor)
Poszczególne komórki dostępne są za pomocą kluczy, które najczęściej
przyjmują wartości numeryczne. W komórkach można przechowywać zmienne
różnego typu.
T1 = {1, 4, 5, 12, 24, 10, 0, -4, 12, 15}
T1[2] = 4, T1[6] = 10 itp.
wypisz (T1[3] + T1[4]) / 2
T2 = {"poniedziałek", "wtorek", ..., "niedziela"}
T2[1]= "poniedziałek"
wypisz "Dzisiaj jest: ", T2[6]
15
Tablica wielowymiarowa
Tablica 3x3:
T11 T12 T13  1 2 3

 

T
21
T
22
T
23  = 4
5
6


T =
T 31 T 32 T 33 7 8 9

 

T = {{1,2,3},{4,5,6},{7,8,9}}
T12 = T[1][2] = 2
itp.
pętla od i = 1 do 3
pętla od j = 1 do 3
T[i][j] = 10 // przypisanie liczby 10 wszystkim elementom
Kolejka
Liniowa struktura danych FIFO (First In, First Out; pierwszy na wejściu,
pierwszy na wyjściu). Znaczeniowo odpowiadająca nazwie: nowe dane
dopisywane są na końcu kolejki, a jako pierwsze obsługiwane są dane z
początku.
16
Stos
Liniowa struktura danych LIFO (Last In, First Out; ostatni na wejściu, pierwszy
na wyjściu), znaczeniowo odpowiadająca nazwie: dane dokładane są na wierzch
stosu, również z wierzchołka są ściągane).
Drzewo
Hierarchiczna struktura danych:
korzeń (ojciec) – węzeł (potomstwo); potomstwo potomstwa itp., liście na
najniższym poziomie
17
Przykłady
Generowanie podciągu
Wejście: dwie liczby całkowite m i n, gdzie m <= n.
Wyjście: posortowana lista m losowych liczb całkowitych z przedziału 1...n,
wśród których żadna nie powtarza się dwukrotnie.
test losowy
Wykorzystujemy algorytm, który analizuje kolejno liczby całkowite 1, 2,
..., n i na podstawie odpowiedniego testu losowego decyduje, czy wybrać,
czy też odrzucić każdą z nich. Ogólnie, aby wylosować w liczb spośród p
pozostałych, należy następną liczbę wybierać z prawdopodobieństwem w/p.
18
szkielet algorytmu:
wczytaj m, n
wybierz = m
pozostało = n
pętla od i=1 do n
oblicz prawdopodobieństwo wylosowania i-tej liczby,
prawd = wybierz / pozostało
wygeneruj liczbę losową los z przedziału [0,1)
jeżeli los < prawd
wypisz i
wybierz = wybierz – 1
pozostało = pozostało - 1
koniec
19
wybieranie
Cel realizujemy wybierając m elementów wejściowej tablicy n-elementowej. Po
każdym losowaniu sprawdzamy, czy liczba się nie powtórzyła a następnie
sortujemy wybrane elementy.
20
szkielet algorytmu:
wczytaj m, n
pętla od i=1 do m
powtarzaj
wygeneruj liczbę losową los z przedziału [1,n]
jeżeli i > 1 to sprawdź, czy liczba los już wcześniej wystąpiła:
wystąpiła = false
pętla od j=1 do i-1
jeżeli W[j] == los
wystąpiła = true
zakończ pętlę
jeżeli wystąpiła == true
zapisz los do wyniku: W[i] = los
posortuj tablicę W
koniec
21
przemieszanie
Cel realizujemy mieszając (czyli zamieniając) pierwszych m elementów
wejściowej tablicy n-elementowej (czyli liczby z przedziału 1...m) z
elementami 1...n tej samej tablicy (oczywiście, w szczególnym przypadku
taka zamiana może nie nastąpić, gdy chcąc przemieszać i-tą liczbę wylosujemy
właśnie liczbę i). Wynikiem (po posortowaniu) jest tablica złożona z pierwszych
m przemieszanych elementów.
22
szkielet algorytmu:
wczytaj m, n
utwórz tablicę T=[1,2,...,n]:
pętla od i=1 do n
T[i] = i
pętla od i=1 do m
wygeneruj liczbę losową los z przedziału [1,n]
zamień T[i] z T[los]
Utwórz tablicę wynikową: W = T[1,2,...,m]:
pętla od i=1 do m
W[i] = T[i]
posortuj tablicę W
koniec
23
Wyszukiwanie
Wejście: posortowana, n-elementowa tablica liczbowa T oraz liczba p.
Wyjście: liczba naturalna, określająca pozycję elementu p w tablicy T, bądź
–1, jeżeli element w tablicy nie występuje.
wyszukiwanie binarne
Wyszukiwanie binarne polega na tropieniu fragmentu tablicy, o którym wiemy,
że musi zawierać element p, o ile element ten znajduje się w tablicy T.
Początkowo tym fragmentem jest cała tablica. Przedział kurczy się po
porównaniu środkowego elementu ze zmienną p i odrzuceniu odpowiedniej
połowy tego przedziału. Proces trwa do chwili odnalezienia p w tablicy lub do
momentu, gdy wiadomo, że przedział w którym musiałby się on znajdować, jest
pusty. Złożoność obliczeniowa: O(log n).
(Na analogicznej zasadzie działa np. algorytm bisekcji, służący do znajdywania
miejsc zerowych funkcji.)
24
Sformułowanie algorytmu
Wiemy, że jeżeli p znajduje się w gdziekolwiek w tablicy T, to musi być w
określonym przedziale. Oznaczmy ten fakt skrótem:
musi_być(przedział).
Szkic programu:
zainicjuj przedział jako 1...n
pętla
// niezmiennik: musi_być(przedział)
jeżeli przedział pusty
elementu p nie ma w tablicy T
koniec
oblicz wartość środek, środka przedziału
użyj środek do testów, aby zmniejszyć przedział
jeżeli p znaleziono podczas testu
wynik = pozycja p w tablicy
koniec
25
Przedział reprezentujemy przy pomocy dwóch indeksów: dół, góra.
Oznaczenie musi_być(dół, góra) oznacza, że jeżeli element znajduje się
gdzieś w tablicy, to znajduje się w przedziale domkniętym
T[dół ... góra].
Inicjowanie: jakie wartości muszą mieć zmienne góra i dół, aby warunek
musi_być(dół, góra) był spełniony? Oczywiście 1 oraz n. A zatem:
dół = 1
góra = n
Następnie sprawdzamy, czy przedział jest pusty:
jeżeli dół > góra
wynik = -1
// elementu nie ma w tablicy
koniec
26
Obliczamy wartość środka przedziału:
środek = (dół + góra) div 2
// Dzielenie całkowite
Szkic programu wygląda teraz tak:
dół = 1
góra = n
pętla
// niezmiennik: musi_być(dół, góra)
jeżeli dół > góra
wynik = -1
koniec
środek = (dół + góra) div 2
użyj środek do testów, aby zmniejszyć przedział [dół ... góra]
jeżeli p znaleziono podczas testu
wynik = pozycja p w tablicy
koniec
27
Ostatnie 4 wiersze wiążą się z porównaniem p i T[środek] oraz podjęciem
odpowiednich działań w celu zachowania niezmiennika. Piszemy:
jeżeli T[środek] < p: przypadek A
jeżeli T[środek] == p: przypadek B
jeżeli T[środek] > p: przypadek C
Przypadek B oznacza, że znaleziono element: przypisujemy wynik = środek
i kończymy program.
Analiza przypadku A: Jeżeli T[środek] < p, to T[1] ≤ T[2] ≤ ...
≤ T[środek] < p. Innymi słowy, p nie może znajdować się w przedziale
T[1... środek]. Zatem, jeżeli p znajduje się w tablicy T, to znajduje się w
przedziale [środek +1 ... góra], co zapisujemy: musi_być(środek
+1, góra). Przywracamy zatem prawdziwość niezmiennika musi_być(dół,
góra) poprzez podstawienie:
dół = środek + 1.
28
Analogicznie, w przypadku C przywracamy niezmiennik podstawiając góra =
środek – 1.
dół = 1
góra = n
pętla
// niezmiennik: musi_być(dół, góra)
jeżeli dół > góra
wynik = -1
koniec
środek = (dół + góra) div 2
jeżeli T[środek] < p: dół
= środek + 1
jeżeli T[środek] == p: wynik = środek; koniec
jeżeli T[środek] > p: góra = środek -1
29
Sortowanie
Wejście: tablica T zawierająca n elementów (a1, a2, . . . , an) typu
porządkowego.
Wyjście: tablica o tych samych elementach, ale uporządkowana niemalejąco.
metoda przez wstawianie
Przypomnienie: Algorytm polega na usuwaniu pewnego elementu z danych
wejściowych i wstawianiu go na odpowiednie miejsce w wynikach. Wybór
następnego elementu z danych jest dowolny. Szybkość tego algorytmu zależy od
struktury danych wyjściowych i implementacji operacji wstawiania. Złożoność
obliczeniowa: O(n2)
30
Schemat działania algorytmu:
1. Utwórz zbiór elementów posortowanych i przenieś do niego dowolny element
ze zbioru nieposortowanego.
2. Weź dowolny element ze zbioru nieposortowanego.
3. Wyciągnięty element porównuj z kolejnymi elementami zbioru posortowanego
póki nie napotkasz elementu równego lub elementu mniejszego, lub nie
znajdziesz się na początku zbioru uporządkowanego.
4. Wyciągnięty element wstaw w miejsce gdzie skończyłeś porównywać.
5. Jeśli zbiór elementów nieuporządkowanych jest niepusty, wróć do punktu 2.
pętla od i = 2 do n
// niezmiennik: fragment T[1... i-1] jest posortowany
// cel: przesunąć element T[i] w dół na właściwe miejsce
pętla od j = i do 2
jeżeli T[j] < T[j-1]
zamień T[j] z T[j-1]
31
sortowanie stogowe (przez kopcowanie)
Kopiec (stóg, sterta), ang. heap: struktura danych – reprezentacja zbioru
elementów (np. liczb), mająca postać tzw. drzewa binarnego.
Zastosowania:
● sortowanie przez kopcowanie porządkuje n-elementową tablicę
w czasie Θ(n log n),
● kolejki priorytetowe: określanie operacji w zbiorze, służących do dodawania
nowego elementu oraz usuwania elementu najmniejszego w czasie O(log n).
Przykład kopca:
32
Własności kopca:
1. Uporządkowanie.
Wartość każdego wierzchołka (ojca) jest nie większa niż wartości jego synów.
Wniosek: najmniejszy element zbioru znajduje się w korzeniu drzewa. Nic
jednak nie wiemy o wzajemnym uporządkowaniu lewego i prawego syna.
2. Kształt
Synowie znajdują się na jednym lub więcej poziomach, a te na najniższym
poziomie (liście) są przesunięte jak najbardziej w lewo.
Wniosek: jeżeli drzewo zawiera n wierzchołków, to żaden z nich nie jest bardziej
oddalony od korzenia niż o (log n) węzłów.
Własności 1 i 2 są warunkami na tyle silnymi, żeby umożliwić szybkie
odnalezienie elementu najmniejszego w zbiorze a jednocześnie umożliwiają
szybką reorganizację struktury kopca po dodaniu lub usunięciu z niego elementu.
33
Realizacja kopca za pomocą tablicy:
korzeń
wartość(i)
lewysyn(i)
prawysyn(i)
ojciec(i)
=
=
=
=
=
1
x[i]
2*i
2*i+1
i div 2
Tablica x={12,20,15,29,23,17,22,35,40,26,51,19}
Uwaga: w C/C++ tablice indeksujemy od zera a nie od jedynki!
Ściśle:
Tablica x[1...n] jest kopcem, jeżeli ∀2≤i≤n x[i div 2]≤x[i]. Mówimy,
że zachodzi kopiec(1,n).
Fragment tablicy x[d...g] jest kopcem (czyli zachodzi kopiec(d,g)),
jeżeli ∀2d≤i≤g x[i div 2]≤x[i].
34
Procedury porządkowania kopca
1. Załóżmy, że x[1...n-1] jest kopcem i dodajmy nowy element x[n].
Prawdopodobnie x[1...n] nie jest już kopcem.
Procedura przywracania własności kopca dla tablicy x[1...n]:
Procedura doGóry(n):
– Przemieszczamy nowy element w górę drzewa tak daleko, jak powinien
dotrzeć, zamieniając go po drodze z ojcem. Kończymy, gdy przemieszczany
element stanie się większy lub równy ojcu.
– Uwaga: droga w górę drzewa to malejące indeksy w tablicy.
– Koszt operacji: O(log n).
35
2. Jeżeli w kopcu x[1...n] na pozycji x[1] przypiszemy nową wartość, to
warunek kopiec(2,n) pozostanie prawdziwy. Procedura przywracania
własności kopiec(1,n):
Procedura naDół(n):
● Przemieszczamy element x[1] w dół drzewa (indeksy rosną!), zamieniając go
po drodze z mniejszym synem, aż do chwili kiedy nie ma on już żadnych synów
albo jest od nich mniejszy lub równy.
● Koszt operacji: O(log n).
36
Kolejki priorytetowe
Kolejka umożliwia operację dodania i usunięcia elementu z pewnej ich
sekwencji, w naszym przypadku struktury kopca.
Początkowo kolejkę stanowi pusty zbiór S.
Procedura wstaw(t) wstawia do kolejki nowy element t:
wstaw(t):
S = S ∪ {t}
n++
doGóry(n)
37
Procedura usunMin() usuwa najmniejszy element zbioru:
usunMin():
S = S \ {t} i t = min(S)
S[1] = S[n]
n-naDół(n)
Ostateczna postać algorytmu sortowania przez kopcowanie:
pętla od i=1 do n
wstaw(x[i]) // Utworzenie kopca
pętla od i=1 do n
usunMin()
// Zdejmowanie z kopca el. min.
Pesymistyczny i średni koszt operacji: Θ(n log n) .
38
sortowanie szybkie
Wykorzystuje metodę „dziel i zwyciężaj” (rekurencyjna)
Złożoność obliczeniowa: O(n log n)
Aby posortować tablicę, dzielimy ją na dwie części ze względu na wybrany
element tablicy tak, żeby wszystkie elementy mniejsze od tego wybranego
znalazły się po lewej stronie a większe po prawej.
Następnie sortujemy rekurencyjnie każdą z części. Rekurencja kończy się, gdy
przedział ma mniej niż 2 elementy.
39
Szkielet kodu:
qsort(dół, góra):
jeżeli dół ≥ góra
// nie więcej niż jeden element – nie rób nic
koniec
// cel: podzielić tablicę ze względu na pewną wartość,
// która jest na koniec wstawiana na właściwe miejsce oznaczone
środek.
qsort(dół, środek-1)
qsort(środek+1, góra)
40
Sortowanie za pomocą porównań
Porządek wyjściowy jest wyznaczany jedynie na podstawie wyników porównań
między elementami.
● sortowanie przez wstawianie
● sortowanie przez scalanie
● sortowanie przez kopcowanie
● sortowanie szybkie (quicksort)
Dolne ograniczenie sortowania za pomocą porównań
Twierdzenie:
Dolne ograniczenie sortowania n elementów za pomocą porównań,
wynosi Ω(n log n).
Wniosek:
Sortowanie przez kopcowanie, scalanie oraz quicksort są asymptotycznie
optymalnymi algorytmami sortującymi za pomocą porównań.
41
Sortowanie przez zliczanie
Założenie: każdy z n sortowanych elementów jest liczbą całkowitą z przedziału
od 1 do k dla pewnego ustalonego k.
Idea: dla każdego elementu wejściowego x należy wyznaczyć ile elementów jest
mniejszych od x. Znając te liczbę, znamy jednocześnie dokładną pozycję liczby
x w ciągu posortowanym.
Przykład: jeżeli od x jest mniejszych 17 elementów, to x powinien się znaleźć na
miejscu 18 w ciągu posortowanym (przy założeniu, że elementy nie mogą się
powtarzać).
42
Implementacja
Tablica A – elementy wejściowe; B – elementy wyjściowe (posortowane); C –
dane pomocnicze
Counting-Sort(A, B, k):
pętla od i=1 do k
C[i]=0
pętla od j=1 do n
C[A[j]] = C[A[j]]+1
// C[i] zawiera teraz liczbę elementów równych i
pętla od i=2 do k
C[i] = C[i] + C[i-1]
// C[i] zawiera liczbę elementów mniejszych lub równych i
pętla od i=n do 1
B[C[A[i]]] = A[i]
C[A[i]] = C[A[i]]-1
Pętla 1: inicjalizacja. Pętla 2: zliczenie elementów równych indeksowi tablicy. Pętla 3:
zliczenie elementów mniejszych lub równych indeksowi tablicy. Pętla 4: zapisanie
wyników do wyjściowej tablicy; zmniejszenie zawartości tablicy C w celu uniknięcia
konfliktu przy powtarzających się liczbach.
43
Ilustracja działania procedury
a) stan po wykonaniu drugiej pętli,
b) stan po wykonaniu 3 pętli,
c) d) e) stan po wykonaniu odpowiednio 1, 2, 3 iteracji w czwartej pętli,
f) ostateczna zawartość tablicy wyjściowej.
44
Czas działania
W algorytmie nie występują porównania elementów, zatem nie ma tu
zastosowania twierdzenie dotyczące dolnego ograniczenia dla metod sortowania
przez porównanie.
Pierwsza pętla:
Druga pętla:
Trzecia pętla:
Czwarta pętla:
k wykonań,
n wykonań,
k wykonań,
n wykonań,
Razem liczba operacji: O(n+k). W praktyce najczęściej k=O(n), zatem czas
działania procedury wynosi w takim przypadku O(n) – mniej niż czas Ω(n
log n).
Algorytm jest stabilny (nie zmienia kolejności takich samych liczb w tablicy
wynikowej).
45
Sortowanie pozycyjne
– polega na sortowaniu liczby według najmniej znaczącej cyfry, proces jest
powtarzany dla wszystkich cyfr
329
457
657
839
436
720
355
→
720
355
436
457
657
329
839
↑
720
329
436
→ 839
355
457
657
↑
→
329
355
436
457
657
720
839
↑
– dla pewnych długości elementów do posortowania oraz ich liczby, algorytm
działa w czasie liniowym (oczywiście pod warunkiem odpowiedniego wyboru
algorytmu sortującego wg kolejnej cyfry – najczęściej stosuje się zliczanie)
46
Sortowanie kubełkowe
● polega na utworzeniu „kubełków” – pojemników, w których są
przechowywane liczby przeznaczone do posortowania
● kubełki tworzymy poprzez podzielenie przedziału, do jakiego należą sortowane
liczby na szereg podprzedziałów
● liczby wrzucamy do odpowiedniego kubełka, sortujemy w każdym z nich i
wypisujemy od kubełka pierwszego do ostatniego
a) tablica do posortowania
b) 10 kubełków i liczby, które do nich należą;
liczby (po posortowaniu np. przez wstawianie) są
wyświetlane od kubełka o najniższym numerze
(B[0]) do tego o najwyższym. Operacja ta
wykonywana jest w czasie liniowym O(n).
47