Professional Documents
Culture Documents
Sedgewick R. Wayne K. - Algorytmy Wydanie 4
Sedgewick R. Wayne K. - Algorytmy Wydanie 4
R O B E R T S E D G E W I C K K E V I N W A Y N E
b . 7 ii
Helion
SPIS TREŚCI
P rzedm ow a .................................................................................................... 8
1 P odstaw y................................................................................................. 14
1.1 Podstawowy m odel program ow ania 20
1.2 Abstrakcja danych 76
1.3 Wielozbiory, kolejki i stosy 132
1.4 Analizy algorytm ów 184
1.5 Studium przypadku — problem U nion-F ind 228
2 Sortowanie............................................................................................254
2.1 Podstawowe m etody sortow ania 256
3 W yszukiw anie......................................................................................372
3.1 Tablice sym boli 374
3.2 Drzewa wyszukiwań binarnych 408
3.3 Zbalansow ane drzewa wyszukiwań 436
3.4 Tablice z haszow aniem 470
6 K o n te k s t................................................................................................864
Skorowidz...................................................................................................946
7
puzedm ow a
nauk komputerowych, jednak dziedzina ta jest przeznaczona nie tylko dla progra
mistów i studentów nauk komputerowych. Każdy, kto używa komputera, chce, aby
maszyna działała szybciej lub rozwiązywała większe problemy. Algorytmy w książce
reprezentują niezbędną wiedzę opracowaną w ciągu ostatnich 50 lat. Od symulacji fi
zycznych problemów ruchu N ciał po problemy sekwencjonowania genomu z biologii
molekularnej — opisane tu podstawowe metody stały się kluczowe w badaniach na
ukowych. W obszarach od systemów modelowania architektonicznego po symulacje
lotu stały się niezbędnymi narzędziami dla inżynierów. W dziedzinach od systemów
baz danych do wyszukiwarek internetowych stały się podstawowymi elementami
współczesnych systemów oprogramowania. A to dopiero kilka przykładów. Wraz ze
zwiększaniem się zakresu zastosowań komputerów rośnie też wpływ podstawowych
m etod omówionych w książce.
Przed przedstawieniem podstawowego sposobu badania algorytmów opracowano
typy danych dla stosów, kolejek i innych niskopoziomowych abstrakcji używanych
w książce. Następnie omówiono podstawowe algorytmy sortowania i wyszukiwa
nia oraz do przetwarzania grafów i łańcuchów znaków. Ostatni rozdział to przegląd,
w którym pozostały materiał z książki przedstawiono w szerszym kontekście.
Cechy charakterystyczne Książka ma pozwolić poznać algorytmy często sto
sowane w praktyce. Przedstawiono tu bardzo zróżnicowane algorytmy i struktury
danych oraz wystarczającą ilość wiedzy na ich temat, aby m ożna było je swobodnie
zaimplementować i zdiagnozować, a także zastosować w dowolnym środowisku
obliczeniowym. Oto zastosowane podejścia:
A lg o rytm y Opisy algorytmów oparte są na kompletnych implementacjach i om ó
wieniu działania programów na podstawie spójnego zbioru przykładów. Zamiast
przedstawiać pseudokod, zaprezentowano rzeczywisty kod, dlatego programy można
szybko wykorzystać w praktyce. Programy napisano w Javie, jednak w taki sposób,
że większość kodu m ożna ponownie wykorzystać do opracowania implementacji
w innych współczesnych językach programowania.
Typy danych Zastosowano współczesny styl programowania oparty na abstrakcji
danych, dlatego algorytmy i ich struktury danych są hermetyzowane razem.
Z astosow ania Każdy rozdział obejmuje szczegółowy opis zastosowań, w których
algorytmy odgrywają kluczową rolę. Są to zastosowania od dziedzin fizyki i biologii
molekularnej przez obszary inżynierii komputerów i systemów po popularne zada
nia, takie jak kompresja danych i wyszukiwanie informacji w internecie.
Podejście naukow e W książce położono nacisk na opracowywanie modeli m atem a
tycznych do opisu wydajności algorytmów, używanie modeli do tworzenia hipotez
na tem at wydajności i testowanie hipotez przez urucham ianie algorytmów w reali
stycznym kontekście.
Szeroki zakres Uwzględniono podstawowe abstrakcyjne typy danych, algorytmy
sortowania, algorytmy wyszukiwania, przetwarzanie grafów i przetwarzanie łań
cuchów znaków. Materiał opisywany jest w kontekście algorytmów — omówiono
struktury danych, paradygmaty projektowania algorytmów, redukcję i modele roz
wiązywania problemów. Przedstawiono klasyczne metody, uczone od lat 60. ubiegłe
go wieku, a także nowe rozwiązania, wynalezione w ostatnich latach.
10
Wykorzystanie w programie nauczania Książka ma być podręcznikiem
do drugiego kursu w programie nauk komputerowych. Obejmuje cały podstawowy
materiał i jest doskonałym narzędziem umożliwiającym studentom zyskanie do
świadczenia oraz dojrzałości w programowaniu, wnioskowaniu ilościowym i rozwią
zywaniu problemów. Zwykle wystarczającym wymogiem wstępnym jest ukończenie
jednego kursu z nauk komputerowych. Książka jest przeznaczona dla każdego, kto
zna jeden ze współczesnych języków programowania i podstawowe funkcje współ
czesnych systemów komputerowych.
Algorytmy i struktury danych są zapisane w Javie, ale w stylu przystępnym dla
osób znających inne współczesne języki. Zastosowano nowoczesne abstrakcje z Javy
(w tym typy generyczne), ale pominięto wyrafinowane mechanizmy języka.
Materiały matematyczne związane z analizami można przeważnie zrozumieć bez
dodatkowej wiedzy (w przeciwnym razie opisano je jako wykraczające poza zakres
książki), dlatego w większości książki specyficzne przygotowanie matematyczne jest
potrzebne w niewielkim zakresie, choć — oczywiście — bywa pomocne. Opisane
zastosowania oparto na materiałach dla początkujących z dziedziny nauk przyrodni
czych i też nie wymagają dodatkowej wiedzy.
Przedstawiony m ateriał stanowi niezbędną podstawę dla każdego studenta nauk
komputerowych, inżynierii elektrycznej lub badań operacyjnych. Jest też w artoś
ciowy dla studentów interesujących się naukam i przyrodniczymi, matem atyką lub
inżynierią.
11
P o d z i ę k o w a n ia Książka ta jest wydawana prawie od 40 lat, dlatego wymienienie
wszystkich osób, które się do tego przyczyniły, jest po prostu niemożliwe. We wcześ
niejszych wydaniach wymieniono dziesiątki osób. Oto niektóre z nich (w porząd
ku alfabetycznym): Andrew Appel, Trina Avery, Marc Brown, Lyn Dupre, Philippe
Flajolet, Tom Freeman, Dave Hanson, Janet Incerpi, Mike Schidlowsky, Steve Summit
i Chris Van Wyk. Wszystkie te osoby zasługują na podziękowania, nawet jeśli wnio
sły wkład w książkę kilkadziesiąt lat temu. W czwartym wydaniu dziękujemy set
kom studentów z Princeton i kilku innych jednostek, którzy musieli „zmagać się”
ze wstępnymi wersjami książki, a także czytelnikom z całego świata za nadsyłanie
komentarzy i poprawek przez witrynę.
Jesteśmy wdzięczni Uniwersytetowi Princeton za wsparcie oraz niezachwiane za
angażowanie w doskonalenie nauczania i uczenia się, co zapewniło podstawy do po
wstania tej książki.
Peter Gordon służył nam m ądrym i radam i niemal od początku prac. Między in
nymi delikatnie zaproponował podejście „powrót do podstaw”, na którym oparliśmy
to wydanie. W kontekście czwartego wydania jesteśmy wdzięczni Barbarze Wood
za staranną i profesjonalną edycję, Julie Nahil za zarządzanie produkcją oraz wielu
innym osobom z wydawnictwa Pearson za ich pracę przy powstawaniu i marketingu
książki. Wszystkie te osoby doskonale dostosowały się do dość wymagającego har
monogramu, nie idąc przy tym na żadne kompromisy w obszarze jakości.
Robert Sedgewick
Kevin Wayne
Princeton, NJ
Styczeń 2011
ROZDZIAŁ 1
mli Podstawy
1.1 Podstawowy model p ro g ra m o w a n ia ......................... 20
15
RO ZD ZIA Ł 1 n Podstawy
20
1.1 a Podstawowy model program owania
Wiersz poleceń
(strona 48 ) ' N azw ap lik u fa rgs [0 ] J
ł
% j a v a B in a r y S e a r c h l a r g e w . t x t < l a r g e T . t x t
Standardowe
wyjście - -499569 t
(strona 49) 984875 PM przekierowany
ze standardowego
wejścia (strona 52)
należy wpisać polecenie java Bi narySearch (po którym następuje nazwa pliku z białą
listą), aby przekazać sterowanie do wersji programu w kodzie bajtowym. Aby zrozu
mieć skutki tych działań, warto szczegółowo rozważyć proste typy danych, wyrażenia,
różnego rodzaju instrukcje Javy, tablice, metody statyczne, łańcuchy znaków i operacje
wejścia-wyjścia.
Aby zdefiniować typ danych, trzeba tylko określić wartości i zbiór wykonywanych
na nich operacji. Informacje te podsumowano w tabeli dla typów i nt, doubl e, bool ean
i char Javy. Typy te są podobne do prostych typów danych z wielu języków progra
mowania. Dla typów i nt i doubl e operacjami są standardowe operacje arytmetyczne.
Dla typu bool ean są to znane operacje logiczne. Należy zauważyć, że operatory +, -,
* i / są przeciążone. Ten sam symbol, w zależności od kontekstu, określa operacje
dla wielu różnych typów. Kluczową cechą podstawowych operacji jest to, że operacja
obejmująca wartości danego typu daje wartość tego samego typu. Reguła ta podkreśla
fakt, że często używane są wartości przybliżone, ponieważ dokładna wartość wyraże
nia nie należy do danego typu. Na przykład 5/3 ma wartość 1, a 5 .0 /3 .0 — wartość
bardzo zbliżoną do 1.66666666666667, natomiast żadne z tych wyrażeń nie jest rów
ne 5/3. Tabela jest bardzo niekompletna. W pytaniach i odpowiedziach w końcowej
części podrozdziału przedstawiono pewne dodatkowe operatory i różne wyjątkowe
sytuacje, które czasem trzeba uwzględnić.
Typowe wyrażenia
Typ Zbiór wartości Operatory
Wyrażenie Wartość
Inne typ y proste Typ i nt w Javie ma 232 wartości, dlatego można go reprezentować
w maszynach ze słowami 32-bitowymi (wiele maszyn ma obecnie słowa 64-bitowe,
jednak wciąż stosuje się 32-bitowy typ i nt). Standardowo typ doubl e ma reprezen
tację 64-bitową. Rozmiary tych typów danych są odpowiednie dla typowych aplika
cji korzystających z liczb całkowitych i rzeczywistych. Aby zapewnić elastyczność,
w Javie udostępniono pięć dodatkowych prostych typów danych:
■ 64-bitowe liczby całkowite z operacjami arytmetycznymi (1 ong),
■ 16-bitowe liczby całkowite z operacjami arytmetycznymi (short),
■ 16-bitowe znaki z operacjami arytmetycznymi (char),
■ 8 -bitowe liczby całkowite z operacjami arytmetycznymi (byte),
■ 32-bitowe liczby rzeczywiste o pojedynczej precyzji z operacjami arytmetycz
nymi (float).
W książce najczęściej używane są operacje arytmetyczne typów i nt i doubl e, dlatego
nie omówiono szczegółowo pozostałych (bardzo podobnych) typów.
RO ZD ZIA Ł 1 o Podstawy
Deklaracje Deklaracja łączy nazwę zmiennej z typem na etapie kompilacji. Java wy
maga stosowania deklaracji do określania nazw i typów zmiennych. W ten sposób
można bezpośrednio opisać obliczenia. Java jest językiem ze ścisłą kontrolą typów,
ponieważ kompilator Javy sprawdza ich zgodność (na przykład nie zezwala na p o
mnożenie przez siebie wartości typów bool ean i doubl e). Deklaracje mogą występo
wać w dowolnym miejscu przed pierwszym użyciem zmiennej. Najczęściej podaje się
je w miejscu pierwszego użycia. Zasięg zmiennej to fragment programu, w którym
zmienna jest zdefiniowana. Ogólnie zasięg zmiennej to instrukcje następujące po jej
deklaracji w bloku z tą deklaracją.
Przypisania Instrukcja przypisania łączy wartość typu danych (zdefiniowaną za
pomocą wyrażenia) ze zmienną. Zapis c = a + b w Javie nie oznacza równości m a
tematycznej, ale działanie — ustawienie wartości zmiennej c na wartość zmiennej
a plus wartość zmiennej b. To prawda, że c bezpośrednio po wykonaniu przypisa
nia matematycznie równa się a + b, jednak celem instrukcji jest zmiana wartości c
(jeśli jest to konieczne). Po lewej stronie instrukcji przypisania musi znajdować się
pojedyncza zmienna. Po prawej stronie m ożna umieścić dowolne wyrażenie dające
wartość danego typu.
1.1 0 Podstawowy model program owania
Przypisanie i++; i = i + 1;
niejawne i += 1;
Tablice Tablica przechowuje kolekcję wartości tego samego typu. Służy nie tylko
do przechowywania wartości, ale też zapewnia dostęp do każdej z nich. Aby możliwe
było wskazywanie poszczególnych wartości w tablicy, są one numerowane. Następnie
można użyć ich indeksu. Jeśli istnieje N wartości, m ożna przyjąć, że mają num ery od
0 do N - 1. W kodzie Javy można jednoznacznie określić dowolną wartość, używając
zapisu a [i] , aby wskazać i-tą wartość dla dowolnego i z przedziału od 0 do N-l.
Ta konstrukcja Javy to tablica jednowymiarowa.
a [ i ] = 1234;
i nt [] b = a;
d o u b l e j ] [] a = new d o u b le [M ] [ N ] ;
RO ZD ZIA Ł 1 s Podstawy
doublet] [] a;
a = new double[M] [N];
fo r (in t i = 0 ; i < M; i++)
fo r (in t j = 0; j < N; j++)
a [ i] [ j ] = 0.0;
Ten kod jest zbędny przy inicjowaniu zerem, zagnieżdżone pętle fo r są jednak p o
trzebne przy inicjowaniu innymi wartościami.
1.1 ° Podstawowy m odel program owania
Metody statyczne Każdy program Javy w tej książce jest albo definicję typu da
nych (opisano je szczegółowo w p o d r o z d z ia l e 1 .2 ), albo bibliotekę metod statycz
nych (przedstawionych w tym miejscu). Metody statyczne w wielu językach są nazy
wane funkcjami, ponieważ mogą działać jak funkcje matematyczne, co omówiono
dalej. Każda metoda statyczna to ciąg instrukcji wykonywanych jedna po drugiej po
wywołaniu metody statycznej. Określenie statyczne pozwala odróżnić te metody od
metod egzemplarza (ang. instance method), przedstawionych w p o d r o z d z ia l e 1 .2 .
Słowo metoda bez dookreślenia jest używane przy opisie cech wspólnych dla metod
obu rodzajów.
Definiowanie m etody statycznej W metodzie ukrywa się obliczenia zdefiniowane za
pomocą ciągu instrukcji. Metoda przyjmuje argumenty (wartości określonych typów
danych) i na ich podstawie albo oblicza wartość zwracaną (przypomina to obliczanie
wartości za pomocą funkcji matematycznej), albo powoduje efekty uboczne (na przykład
wyświetla wartość). Metoda statyczna
Svanatura Typ zwracanej Argument rank() w programie BinarySearch to
Sygnatura wartości Nazwa Typ a 1
metody argumentu ^ przykład metody pierwszego rodzaju;
mai n () to metoda drugiego typu. Każda
p u b l i c s t a t i c Id o u b le lls a r t l ( Id o u b le cTTj metoda statyczna składa się z sygnatury
{ _____________________________ (słów kluczowych public s ta tic , po
i f (c < 0) return Double.NaN;
Zmienne których następuje typ zwracanej war
d ou b le e r r = le - 1 5 ;
lokalne — —------- , tości, nazwa metody i ciąg argumentów
I d o u b l e 1 1= c;_______________
C ia ło w h i l e | ( M a t h . a b s ( t - c/ t)| > e r r * t ) — każdy z zadeklarowanym typem)
metody t = (c/ t + t ) / 2 .0; i ciała (bloku z instrukcjami — ciągu
r e t u r n tT] \
instrukcji umieszczonych w nawiasach
} Wywołanie innej metody
Instrukcja return
klamrowych). Przykładowe metody
statyczne przedstawiono w tabeli na
Anatomia metody statycznej
stronie obok.
W yw oływ anie m etod statycznych Wywołanie m etody wymaga podania jej nazwy
i wyrażeń z rozdzielonymi przecinkami wartościami argumentów w nawiasach. Jeśli
wywołana metoda jest częścią wyrażenia, oblicza wartość, która jest używana w wy
rażeniu w miejscu wywołania. Na przykład wywołanie m etody rank() w programie
BinarySearch powoduje zwrócenie wartości typu in t. Samo wywołanie metody,
po którym następuje średnik, to instrukcja. Zwykle powoduje ona efekty uboczne.
Na przykład wywołanie A rray s.so rt() w metodzie main() programu BinarySearch
to wywołanie m etody systemowej A rrays. s o rt (), której efektem ubocznym jest po
sortowanie elementów tablicy. Przy wywoływaniu m etody argumenty są inicjowa
ne wartościami z odpowiedniego wyrażenia z wywołania. Instrukcja return kończy
działanie metody statycznej i powoduje zwrócenie sterowania do jednostki wywołu
jącej. Jeśli m etoda statyczna oblicza wartość, trzeba ją podać w instrukcji retu rn
(jeżeli metoda statyczna może dojść do końca ciągu instrukcji bez napotkania in
strukcji return, kompilator zgłasza błąd).
1.1 a Podstawowy model program owania
Zadanie Implementacja
Cechy m etod Kompletny, szczegółowy opis cech m etod wykracza poza zakres książ
ki, warto jednak zwrócić uwagę na następujące kwestie:
■ Argumenty są przekazywane przez wartość. Argumentów m ożna używać w do
wolnym miejscu w ciele m etody w taki sam sposób, jak zmiennych lokalnych.
Jedyna różnica między argumentami a zmiennymi lokalnymi polega na tym, że
argumenty są inicjowane wartościami podanymi w wywołaniu. M etoda działa
na podstawie wartości argumentów, a nie przy użyciu ich samych. Jedną z kon
sekwencji takiego stanu rzeczy jest to, że zmiana wartości argumentu w m eto
dzie statycznej nie m a wpływu na kod wywołujący metodę. Ogólnie w kodzie
w książce wartość argumentów nie jest modyfikowana. Przekazywanie przez
wartość powoduje, że argumenty w postaci tablicy są używane jak nazwa za
stępcza (strona 31). M etoda używa argumentu do wskazywania tablicy z miejsca
wywołania i może zmienić jej zawartość (choć nie modyfikuje samej tablicy).
Na przykład wywołanie A rrays. s o rt () zmienia zawartość tablicy przekazanej
jako argument — powoduje uporządkowanie jej elementów.
■ Nazwy metod można przeciążać. W bibliotece Math Javy podejście to zastoso
wano do udostępnienia implementacji metod M ath.abs(), Math.min() i Math,
max () dla wszystkich prostych typów liczbowych. Innym częstym zastosowa
niem przeciążania jest definiowanie dwóch różnych wersji funkcji, z których
jedna przyjmuje argument, a druga używa wartości domyślnej argumentu.
■ Metoda zwraca jedną wartość, ale może zawierać wiele instrukcji return. Metoda
Javy może zwracać tylko jedną wartość, o typie zadeklarowanym w sygnatu
rze. Sterowanie wraca do programu wywołującego bezpośrednio po dojściu do
pierwszej instrukcji return w metodzie statycznej. Instrukcje retu rn można
umieścić w dowolnym miejscu. Choć czasem istnieje wiele instrukcji return,
każda metoda statyczna w każdym wywołaniu zwraca tylko jedną wartość —
podaną po pierwszej napotkanej instrukcji return.
■ Metody mogą powodować efekty uboczne. W metodzie można podać słowo
kluczowe void jako typ zwracanej wartości. Oznacza to, że m etoda nie zwraca
wartości. W metodach statycznych tego rodzaju nie trzeba bezpośrednio uży
wać instrukcji return. Sterowanie wraca do miejsca wywołania po ostatniej in
strukcji. Metoda statyczna typu voi d powoduje efekty uboczne (przyjmuje dane
wejściowe, generuje dane wyjściowe, modyfikuje elementy tablicy lub w inny
sposób zmienia stan systemu). Na przykład typ zwracanej wartości w metodzie
statycznej mai n () w programach w książce to voi d, ponieważ jej zadaniem jest
generowanie danych wyjściowych. Technicznie m etody typu void nie działają
jak funkcje matematyczne (to samo dotyczy funkcji Math.random(), która nie
przyjmuje argumentów, ale generuje zwracaną wartość).
Metody egzemplarza, będące tematem p o d r o z d z i a ł u 2 . 1 , też mają te cechy, choć
znacznie różnią się w obszarze efektów ubocznych.
1.1 E Podstawowy m odel program owania
Rekurencja Metoda może wywoływać samą siebie (jeśli nie znasz dobrze techniki
rekurencji, zachęcamy do wykonania ć w i c z e ń od 1 . 1.16 do 1 . 1 .22 ). Kod w dolnej czę
ści tej strony to odm ienna implementacja metody rank() z program u Bi narySearch.
Często stosujemy rekurencyjne implementacje metod, ponieważ pozwala to tworzyć
zwięzły, elegancki kod, łatwiejszy do zrozumienia niż równoważna implementacja,
w której nie wykorzystano rekurencji. W komentarzach do wspomnianej implemen
tacji znajduje się krótki opis działania kodu. Na podstawie tego komentarza m oż
na — za pom ocą indukcji matematycznej — udowodnić, że kod działa poprawnie.
W p o d r o z d z i a l e 3.1 zagadnienie to rozwinięto i przedstawiono odpowiedni dowód
dla wyszukiwania binarnego. Istnieją trzy ważne reguły rozwijania programów reku-
rencyjnych:
■ W rekurencji występuje przypadek podstawowy. Jako pierwsza w programie za
wsze występuje instrukcja warunkowa z instrukcją return.
■ Wywołania rekurencyjne muszą rozwiązywać podproblemy, które w pewnym
sensie są mniejsze, dlatego wywołania rekurencyjne prowadzą do przypadku
podstawowego. W przedstawionym dalej kodzie różnica między wartościami
czwartego i trzeciego argumentu zawsze się zmniejsza.
■ Wywołania rekurencyjne nie powinny rozwiązywać nakładających się pod-
problemów. W kodzie fragmenty tablicy uwzględniane w obu podproblemach
są rozłączne.
Naruszenie jednej z reguł często prowadzi do uzyskania nieprawidłowych wyni
ków lub powstania niezwykle niewydajnych programów (zobacz ć w i c z e n i a 1 . 1.19
i 1 .1 .2 7 ). Przestrzeganie zasad zwykle pozwala utworzyć przejrzysty i poprawny pro
gram, którego wydajność łatwo jest określić. Innym powodem stosowania metod
rekurencyjnych jest to, że prowadzą do modeli matematycznych, które można wyko
rzystać do zrozumienia wydajności. Zagadnienie to omówiono w p o d r o z d z i a l e 3.2
(dla wyszukiwania binarnego) i w kilku innych miejscach książki.
m etody mai n (), aby pomóc lepiej zrozumieć przeznaczenie każdego modułu, i pozo
stawiamy opracowanie klientów testowych jako ćwiczenia.
Biblioteki zew nętrzne Można używać m etod statycznych z czterech rodzajów bi
bliotek, z których każda wymaga (nieco) innych procedur w celu wielokrotnego wy
korzystania kodu. Większość bibliotek obejmuje m etody statyczne, przy czym nie
które biblioteki to definicje typów danych, które też zawierają m etody statyczne. Oto
rodzaje bibliotek:
" Standardowe biblioteki systemowe j ava. 1ang. *. Należą do nich: Standardowe
biblioteki systemowe
Math, zawierająca metody dla często używanych funkcji matem a
Math
tycznych; Integer i Doubl e, które służą do przekształcania między
Int e g e r'
łańcuchami znaków a wartościami typów in t i double; String
Double*
i S tringB uilder, omówione szczegółowo w dalszej części p o d
S t r i ng'
rozdziału i w r o z d z ia l e 5 .; a także dziesiątki innych bibliotek,
StringBuilder
których nie wykorzystano w tej książce.
■ Im portowane biblioteki systemowe, na przykład j a v a .u t i l . System
modular
b ib l io t e k i m eto d z a im p le m e n t o w a n e s a m o d z ie l n ie i p r z e z in n y c h w
nym środowisku programowania pozwalają znacznie rozwinąć model programowania.
Oprócz wszystkich bibliotek dostępnych w standardowych wydaniach Javy w inter-
necie można znaleźć tysiące innych bibliotek przeznaczonych do rozmaitych zastoso
wań. Aby ograniczyć zakres modelu programowania do akceptowalnego rozmiaru, co
pozwoli skoncentrować się na algorytmach, używamy tylko bibliotek wymienionych
w tabeli po prawej stronie i podzbioru ich metod wymienionych w interfejsach API.
RO ZD ZIA Ł 1 Q Podstawy
p ub lic c l a s s M a t h
Biblioteka Arrays nie należy do pakietu j ava. 1ang, dlatego korzystanie z niej wymaga
użycia instrukcji import, tak jak zrobiono to w programie Bi narySearch. r o z d z ia ł 2.
książki dotyczy implementacji metody so rt () dla tablic. Opisano między innymi al
gorytmy sortowania przez scalanie i sortowania szybkiego zaimplementowane w m e
todzie A rrays. s o rt (). Wiele podstawowych algorytmów omówionych w książce jest
zaimplementowanych w Javie i licznych innych środowiskach programistycznych.
Biblioteka Arrays obejmuje też na przykład implementację wyszukiwania binarnego.
Aby uniknąć niejasności, zwykle korzystamy z opracowanych przez nas implemen
tacji, natomiast nie m a niczego złego w stosowaniu dopracowanych implementacji
z bibliotek, jeśli programista rozumie dany algorytm.
RO ZD ZIA Ł 1 o Podstaw y
p ub lic c l a s s StdRandom
static void i n i t i a l i z e ( l o n g seed) Inicjowanie
static double random() Liczba rzeczywista z przedziału 0 - 1
static i n t uniform (int N) Liczba całkowita z przedziału 0 - N-l
static in t uniform (int lo , i n t hi) Liczba całkowita z przedziału! o — h i -1
static double uniform(double lo, double hi) Liczba rzeczywista z przedziału 1o - hi
s t a t i c boolean b e r n o u lli( d o u b le p) Zwraca true z prawdopodobieństwem p
static double g a ussian () Rozkład normalny, średnia 0, odch. st. 1
static double ga ussian(d ouble m, double s) Rozkład normalny, średnia m, odch. st. s
static in t dis c re te (d o u b le [ ] a) Zwraca i z prawdopodobieństwem a [ i ]
static void shuffle (doublet] a) Losowo porządkuje elementy tablicy a []
Uwaga: dostępne są przeciążone implementacje metody shuffle() dla innych typów prostych i dla
typu Object.
p ub lic c l a s s StdRandom
s t a t i c double max (doublet] a) Największa wartość
s t a t i c double min(double[] a) Najmniejsza wartość
s t a t i c double var (d ou ble[] a) Wariancja dla próbki
s t a t i c double stddev(double[] a) Odchylenie standardowe dla próbki
s t a t i c double median (doublet] a) Mediana
Interfejs API dla opracowanej przez nas biblioteki metod statycznych do analizy danych
1.1 B Podstawowy m odel program owania
M etoda I n itia l ize() z biblioteki StdRandom umożliwia określenie ziarna dla gene
ratora liczb losowych, dzięki czemu można powtórzyć eksperymenty z wykorzysta
niem takich liczb. Z implementacjami wielu spośród tych m etod m ożna zapoznać się
na stronie 44. Niektóre m etody są bardzo proste w implementacji. Po co umieszczać
je w bibliotece? Oto standardowe odpowiedzi dotyczące dobrze zaprojektowanych
bibliotek:
n Takie m etody zapewniają poziom abstrakcji, co pozwala skoncentrować się na
implementowaniu i testowaniu omawianych w książce algorytmów zamiast na
generowaniu losowych obiektów lub obliczaniu statystyk. Kod kliencki, w któ
rym wykorzystano te metody, jest bardziej przejrzysty i łatwiejszy do zrozumie
nia niż samodzielnie napisany kod, wykonujący te same obliczenia.
■ Implementacje z biblioteki wykrywają wyjątkowe warunki, uwzględniają rzad
kie sytuacje i są gruntownie przetestowane, dlatego m ożna zakładać, że działa
ją w oczekiwany sposób. Implementacje tego rodzaju mogą obejmować dużą
ilość kodu. Często potrzebne są implementacje dla różnych typów danych.
Na przykład biblioteka Arrays Javy obejmuje wiele przeciążonych implementa
cji m etody s o rt () — po jednej dla każdego typu danych, który może wymagać
sortowania.
Są to podstawy programowania m odularnego w Javie, jednak w tym kontekście
stwierdzenia te m ożna uznać za nieco przesadne. Choć metody w obu bibliotekach są
samodokumentujące się, a implementacja wielu z nich jest prosta, niektóre stanowią
ciekawe ćwiczenia algorytmiczne. Dlatego zachęcamy do tego, aby zarówno przeana
lizować kod z bibliotek StdRandom. java i S td S ta ts . java z witryny, jak i korzystać
z tych sprawdzonych implementacji. Najłatwiejszy sposób na wykorzystanie biblio
tek (i sprawdzenie kodu) polega na pobraniu kodu źródłowego z witryny i umiesz
czeniu go w katalogu roboczym. W witrynie opisano też różne zależne od systemu
mechanizmy używania bibliotek bez konieczności tworzenia wielu kopii.
W łasne biblioteki Warto traktować każdy napisany przez siebie program jak imple
mentację biblioteki do powtórnego użytku w przyszłości. W tym celu należy:
° Napisać kod klienta — implementację wysokiego poziomu, dzielącą obliczenia
na części o rozsądnej wielkości.
D Określić interfejs API dla biblioteki (lub kilka interfejsów API dla kilku biblio
tek) metod statycznych i uwzględnić w nim każdą część.
■ Opracować implementację interfejsu API, obejmującą metodę mai n (), która te
stuje metody niezależnie od klienta.
To podejście nie tylko prowadzi do powstania wartościowego oprogramowania, które
można powtórnie zastosować; wykorzystanie programowania modularnego w ten spo
sób jest też kluczem do udanego rozwiązywania złożonych zadań programistycznych.
RO ZD ZIA Ł 1 □ Podstawy
p ub lic s t a t i c in t d i s c re te (d ou ble[] a)
{ // Elementy z a[] muszą s i ę sumować do 1.
double r = StdRandom.random();
double sum = 0.0;
Losowa wartość typu i nt for (int i = 0; i < a.le ngth; i++)
z rozkładu dyskretnego (
(i z prawdopodobieństwem a [ i ] j sum = sum + a [ i ] ;
i f (sum >= r) return i ;
}
return -1;
nie powinien posiadać na tem at implementacji żadnych informacji oprócz tych udo
stępnionych w interfejsie API, a w implementacji nie należy uwzględniać cech żad
nego konkretnego klienta. Interfejsy API umożliwiają niezależne rozwijanie kodu
o różnym przeznaczeniu i jego późniejszy wielokrotny użytek na dużą skalę. Żadna
biblioteka Javy nie może zawierać wszystkich m etod potrzebnych w danych oblicze
niach, dlatego opisane podejście to kluczowy krok w pracy nad skomplikowanymi
aplikacjami. Programiści zwykle traktują interfejs API jak kontrakt między klientem
a implementacją, będący dokładną specyfikacją tego, co każda metoda ma robić.
Często zadanie można wykonać na wiele sposobów, a oddzielenie kodu klienta od
kodu implementacji pozwala zastosować nowe i ulepszone implementacje. W dzie
dzinie badań nad algorytmami omawiane podejście jest ważnym czynnikiem um oż
liwiającym zrozumienie wpływu opracowanych usprawnień algorytmu.
RO ZD ZIA Ł 1 h Podstawy
Łańcuchy znaków Typ S tri ng to ciąg znaków (wartości typu char). Literał typu
S tri ng to ciąg znaków w cudzysłowach, na przykład "Wi t a j , świ eci e ! St ri ng to
typ danych Javy, jednak nie jest typem prostym. Opisano go w tym miejscu, ponieważ
jest kluczowym typem danych, używanym w niemal każdym programie Javy.
Z łączanie Java ma wbudowany operator złączania (+) dla typu S tri ng, podobny do
operatorów wbudowanych dla typów prostych. Pozwala to dodać wiersz z poniższej
tabeli do tabeli typów prostych ze strony 24. Wynik złączenia dwóch wartości typu
S tring to jedna taka wartość, w której po pierwszym łańcuchu znaków następuje
drugi.
p ub lic c l a s s Integer______________________________________________________________
static in t p a rs e ln t ( S t r i n g s) Przekształcanie s na wartość typu i nt
s ta tic Strin g t o S tr in g (in t i) Przekształcanie i na wartość typu S t r i n g
Konwersja autom atyczna Opisane metody to S tri ng () rzadko stosuje się w bezpo
średni sposób, ponieważ Java posiada wbudowany mechanizm umożliwiający prze
kształcenie wartości dowolnego typu na typ S tring za pom ocą złączania. Jeśli jednym
z argumentów operatora + jest wartość typu S tri ng, Java automatycznie przekształca
drugi argument na ten typ (jeśli nie jest to wartość tego typu). Oprócz zastosowań
w rodzaju "Pierw iastek kwadratowy z 2.0 to " + M ath.sqrt(2.0) mechanizm
ten umożliwia przekształcanie wartości dowolnego typu danych na typ S tri ng przez
złączenie wartości z pustym łańcuchem znaków "".
p ub lic c l a s s StdOut
s t a t i c void p r i n t ( S t r i n g s) Wyświetla s
s t a t i c void p r i n t l n ( S t r i n g s) Wyświetla s i nowy wiersz
s t a t i c void p r i n t l n () Wyświetla nowy wiersz
s t a t i c void p r in tf (String f, ...) Wyświetla sformatowane dane
Uwaga: istnieją też przeciążone implementacje dla typów podstawowych i typu Object.
typu S tri ng). Między % a kodem konwersji znajduje się wartość całkowitoliczbowa,
określająca szerokość pola z przekształconą wartością (liczbę znaków w przekształco
nym łańcuchu wyjściowym). Domyślnie po lewej dodawane są odstępy, przez co dłu
gość przekształconych danych wyjściowych jest równa szerokości pola. Aby odstępy
pojawiły się po prawej stronie, należy wstawić znak minus przed szerokością pola. Jeśli
przekształcony łańcuch wyjściowy jest dłuższy niż szerokość pola, jej wartość jest ig
norowana. Po szerokości można podać kropkę i liczbę cyfr podawanych po kropce
dla wartości typu double (precyzję) albo liczbę znaków pobieranych z początku łań
cucha dla wartości typu S tri ng. Najważniejszą rzeczą do zapamiętania na temat meto
dy p rin tf () jest to, że kod konwersji w łańcuchu formatującym musi pasować do typu
powiązanego argumentu. Java musi móc przekształcić typ argumentu na typ żądany
w kodzie konwersji. Pierwszy argument metody pri n tf () ma typ S tri ng i może obej
mować znaki, które nie należą do formatującego łańcucha znaków. Każda część argu
mentu, która nie wchodzi w skład formatującego łańcucha znaków, trafia do danych
wyjściowych, natomiast łańcuch formatujący jest zastępowany wartością argumentu
(przekształconą w odpowiedni sposób na typ S tri ng). Na przykład instrukcja:
wyświetla wiersz:
PI to około 3.14
Zauważmy, że trzeba bezpośrednio dodać symbol nowego wiersza, \n, aby za pom o
cą metody pri n tf () wyświetlić nowy wiersz. Metoda ta może przyjmować więcej niż
dwa argumenty. Wtedy łańcuch formatujący obejmuje określenia sposobu formato
wania dla każdego dodatkowego argumentu. Czasem kody rozdzielone są innymi
znakami przekazywanymi na wyjście. Można też użyć metody statycznej S trin g ,
format () z argumentami opisanymi dla m etody pri n tf (), aby uzyskać sformatowa
ny łańcuch znaków bez wyświetlania go. Wyświetlanie sformatowanych danych to
wygodny mechanizm, umożliwiający pisanie zwięzłego kodu, który generuje dane
z eksperymentów uporządkowane w formie tabeli (jest to podstawowe zastosowanie
tego mechanizmu w książce).
512
in t d 512
512
% 14.2f 1595.17
f
double 1595.1680010754388 "% .7 f" 1595.168001111
e
'% 14.4e 1.5952e+03
p ub lic c l a s s Stdln
Potokowe przekazywanie wyjścia z jednego programu do wejścia drugiego % java Average < d a ta .tx t
% j a v a RandomSeq 1000 1 0 0 .0 2 0 0 .0 | ja v a A ve ra ge To polecenie wczytuje ciąg
liczb z pliku data.txt i ob
licza ich średnią wartość.
Symbol < to dyrektywa, któ
ra nakazuje systemowi ope
racyjnemu zastosowanie
standardowego strumienia
Przekierowywanie ¡ potokowe przekazywanie w wierszu poleceń wejścia przez wczytanie
danych z pliku tekstowego
data.txt zamiast oczekiwania na wpisanie przez użytkownika danych w oknie ter
minala. Kiedy program wywołuje metodę StdIn.readD ouble(), system operacyjny
wczytuje wartość z pliku. Połączenie obu technik w celu przekierowania wyjścia
z jednego programu do wejścia drugiego to przekazywanie potokowe:
p u b lic c l a s s In
static i n t [ ] r e a d l n t s ( S t r i n g name) Wczytuje wartości typu i nt
static double[] re adDouble s(Str ing name) Wczytuje wartości typu doubl e
static S t r i n g [ ] r e a d S t r i n g ( S t r i n g name) Wczytuje wartości typu S t r i n g
p u b lic c l a s s Out
sta tic void w r i t e ( i n t [ ] a, S t r i n g name) Zapisuje wartości typu in t
static void w rite(double[] a, S t r i n g name) Zapisuje wartości typu double
sta tic void w r i t e ( S t r i n g [ ] a, S t r i n g name) Zapisuje wartości typu S t r i n g
Uwaga 1. Obsługiwane są też inne typy proste.
Uwaga 2. Obsługiwane są też Stdln i StdOut (należy pominąć argument name).
Interfejs API opracowanych przez nas metod statycznych do odczytu i zapisu tablic
RO ZD ZIA Ł 1 ■ Podstawy
Przykłady zastosowania
biblioteki StdDraw
1.1 ■ Podstawowy m odel program owania
p ub lic c l a s s StdDraw
s t a t i c void f i lle d E l1ip se(d ouble x, double y, double rw, double rh)
Standardowe rysow anie (m etody pom ocnicze) Biblioteka obejmuje też m etody do
zmiany skali i rozmiaru płótna, koloru i szerokości linii, czcionki tekstu oraz czasu
rysowania (do wykorzystania w animacjach). Jako argument m etody setPenColor()
można zastosować jeden ze zdefiniowanych kolorów: BLACK, BLUE, CYAN, DARK_GRAY,
GRAY, GREEN, LIGHT_GRAY, MAGENTA, ORANGE, PINK, RED, B00K_RED, WHITE i YELLOW. Są one
zdefiniowane jako stałe w bibliotece StdDraw (dlatego do ich określania służy kod
w rodzaju StdDraw.RED). Okno obejmuje też opcje m enu służące do zapisywania
rysunku w pliku w formacie odpowiednim do publikowania w internecie.
p ub lic c l a s s StdDraw
i n t N = 100;
StdDraw.set Xscale(0, N) ;
StdDra w.set Ysc ale(0 , N*N);
StdDraw.setPenRadius(.Ol) ;
Wartości f o r ( i n t i = 1; i <= N; i++)
funkcji {
S t d D ra w .p o in t( i, i ) ;
S t d D ra w .p o in t( i, i * i ) ;
S t d D ra w .p o in t( i, i * M a t h . l o g ( i )) ;
}
i n t N = 50;
double[] a = new double[N] ;
f o r (i n t i = 0 ; i < N; i++)
a [ i ] = StdRandom.randomO;
f o r ( i n t 1 = 0; i < N; i++ )
Tablica
losowych
{
double x = 1.0*i/N;
wartości double y = a [ i ] / 2 . 0 ;
double rw = 0.5/N;
double rh = a [ i ] / 2 . 0 ;
StdDraw.filledRectangle(x, y, rw, rh);
)
i n t N = 50;
double[] a = new double[N] ;
f o r (i n t i = 0; i < N; i++ )
a [i ] = StdRandom.randomO;
A rrays.s o r t ( a ) ;
Posortowana f o r ( i n t i = 0; i < N; i++ )
tablica losowych (
wartości double x = 1.0*i/N;
double y = a [ i ] / 2 . 0 ;
double rw = 0.5/N;
double rh = a [ i ] / 2 . 0 ;
StdDraw.filledRectangle(x, y, rw, rh);
Wyszukiwanie binarne
import ja v a . u t il . A r r a y s ;
public c la s s BinarySearch
{
public s t a t ic in t ran k(in t key, in t [] a)
{ // Tablica, musi być posortowana,
in t lo = 0;
in t hi = a.length - 1;
while (lo <= hi)
{ // Klucz znajduje s ię wa [ l o . . h i ] lub nie ma go w ta b lic y ,
in t mid = lo + (hi - lo) / 2;
if (key < a [mid]) hi = mid - 1;
else i f (key > a [mid]) lo = mid + 1;
else return mid;
}
return -1;
}
Program przyjmuje jako argument nazwę pliku z białą listą (z ciągiem liczb całkowitych)
i odfiltrowuje wszystkie wartości ze standardowego wejścia, które znajdują się na białej liście.
Pozostawia tylko liczby nieznajdujące się na liście. W celu wydajnego wykonania zadania
wykorzystano algorytm wyszukiwania binarnego zaimplementowany w metodzie statycznej
rank (). Pełne omówienie, dowód popraw
ności, analizy wydajności i zastosowania Java B in arySearch tinyW .txt < t in y T . t x t
la r g e w . t x t largeT .txt
489910
18940
774392
490636
125544
407391
115771
992663
923282
176914
217904
571222
519039
395667
Nie występują
w la r g e w .t x t
1 000 000
wartości
typu i n t
10 000 000
wartości
typu i n t
t
3 675 966
wartości
typu i n t
Duże pliki dla klienta testowego
programu B in a r y S e a r c h
RO ZD ZIA Ł 1 * Podstawy
^ Pytania i odpowiedzi
O. Kwestia ta rodzi spory wśród programistów. Oto krótka odpowiedź — brak spraw
dzania to jeden z powodów, dla których pewne typy danych są nazywane prostymi.
Wiedza pozwala w dużym stopniu uniknąć takich problemów. Należy stosować typ
in t dla małych liczb (mających mniej niż 10 cyfr w zapisie dziesiętnym), a typ long
dla wartości na poziomie miliardów lub większych.
P. Czy można używać symboli < i > do porównywania zmiennych typu S tri ng?
O. Nie. Operatory te są zdefiniowane tylko dla typów prostych. Zobacz tekst na stro
nie 92.
O. Kod w nagłówku pętli fo r jest traktowany tak, jakby znajdował się w tym samym
bloku, co ciało pętli. W typowej pętli fo r zmienna używana do inkrementacji nie jest
dostępna w instrukcjach poza pętlą, natomiast w pętli while jest. To rozróżnienie
często prowadzi do stosowania pętli whi 1 e zamiast for.
P. Jeśli a [] to tablica, dlaczego instrukcja StdOut. pri ntl n (a) wyświetla szesnastko
wą liczbę całkowitą, na przykład @f62373, zamiast elementów tablicy?
P. Dlaczego w książce nie są używane standardowe biblioteki Javy dla wejścia i gra
fiki?
O. Nie. Dane można wczytać tylko raz (podobnie nie można wycofać instrukcji
p rin tln (J).
P. Czy w Javie m etoda statyczna może przyjmować inną m etodę statyczną jako
argument?
| ĆWICZENIA
b. 2.0e-6 * 100000000.1
c. 4.1 >= 4
d. 1 + 2 + "3"
1.1.5. Napisz fragment kodu, który wyświetla wartość true, jeśli zmienne x i y typu
doubl e znajdują się w przedziale od 0 do 1, a w przeciwnym razie wyświetla wartość
fal se.
1.1.9. Napisz fragment kodu, który umieszcza binarną reprezentację dodatniej licz
by całkowitej Nw zmiennej s typu S t r i ng.
Rozwiązanie: nie przydzielono tu pamięci dla a[] za pom ocą new. Kod ten prowadzi
do błędu czasu kompilacji v a riab le a might not have been i n i t i a l i z e d (zmienna
a mogła nie zostać zainicjowana).
1.1.11. Napisz fragment kodu, który wyświetla zawartość dwuwymiarowej tablicy
wartości logicznych. Użyj * do reprezentowania wartości tru e i odstępu do reprezen
towania fal se. Dodaj num ery wierszy i kolumn.
1.1.14. Napisz metodę statyczną lg (), która przyjmuje jako argument wartość N
typu i nt i zwraca największą wartość typu i nt nie większą niż logarytm o podstawie
2 dla N. Nie używaj biblioteki Math.
1.1.15. Napisz m etodę statyczną histogram(), przyjmującą jako argumenty tablicę
a [] wartości typu i nt i liczbę całkowitą Moraz zwracającą tablicę o długości M, której
i-ty element to liczba wystąpień liczby całkowitej i w tablicy podanej jako argument.
Jeśli wszystkie wartości w a[] znajdują się w przedziale od 0 do M-l, suma wartości
w zwróconej tablicy powinna być równa a . 1ength.
public s t a t ic in t mystery(int a, in t b)
{
i f (b == 0) return 0;
i f (b % 2 == 0) return mystery(a+a, b/2);
return mystery(a+a, b/2) + a;
}
Jakie wartości mają wywołania mystery (2, 25) imystery(3, 11)? Opisz, jaką wartość
obliczy funkcja mystery (a, b) dla dodatnich liczb całkowitych a i b. Odpowiedz na to
samo pytanie, ale zastąp + znakiem *, a instrukcję return 0 — wywołaniem return 1.
1.1.19. Uruchom na komputerze następujący program:
public c lass Fibonacci
{
public s t a t ic long F (in t N)
{
i f (N == 0) return 0;
i f (N == 1) return 1;
return F(N-l) + F (N -2 );
}
public s t a t i c void m ain(String[] args)
{
for (in t N = 0; N < 100; N++)
S td 0 u t.prin tln (N + " " + F(N));
}
R O ZD ZIA Ł 1 ■ Podstawy
Jaka jest największa wartość N, przy której program obliczy wartość F(N) w mniej
niż godzinę? Opracuj lepszą implementację F(N), która zapisuje obliczone wartości
w tablicy.
1.1.20. Napisz rekurencyjną metodę statyczną obliczającą wartość ln(Nl).
1.1.21. Napisz program, który wczytuje wiersze z wejścia standardowego, przy czym
każdy wiersz obejmuje nazwisko i dwie liczby całkowite. Program ma następnie za
pomocą m etody pri n tf () wyświetlać tabelę z kolum ną z nazwiskiem, liczbami cał
kowitymi i wynikiem dzielenia pierwszej liczby przez drugą z dokładnością do trzech
miejsc po przecinku. Programu tego typu można użyć do wyświetlenia w tabeli śred
nich wybić dla baseballistów lub średnich ocen dla studentów.
[j p r o b l e m y d o r o z w ią z a n ia
1.1.26. Sortow anie trzech liczb. Załóżmy, że zmienne a, b, c i t są tego samego licz
bowego typu prostego. Wykaż, że poniższy kod porządkuje a, b i c w kolejności ros
nącej:
if (a > b) { t = a; a = b; b = t ; }
if (a > c) { t = a; a = c ; c=t; }
if (b>c) { t = b ; b=c; c=t; }
1.1.30. Ć w iczenie dotyczące tablic. Napisz fragment kodu, który tworzy tablicę war
tości logicznych, a [] [], o wymiarach N n a N. W tablicy element a [i] [j] ma wartość
true, jeśli i oraz j to liczby względnie pierwsze (nie mają wspólnych dzielników).
W przeciwnym razie element ma wartość fal se.
1.1.31. Losowe połączen ia. Napisz program, który przyjmuje argumenty z wiersza
poleceń (liczbę całkowitą N i wartość p typu double mieszczącą się w przedziale
0 - 1), rysuje w równomiernych odstępach N kropek o wielkości .05 na obwodzie
okręgu, a następnie, z prawdopodobieństwem p dla każdej pary punktów, łączy je
szarą linią.
R O ZD ZIA Ł 1 a Podstaw y
1.1.32. H istogram . Załóżmy, że standardowy strum ień wejścia zawiera ciąg wartości
typu doubl e. Napisz program, który pobiera z wiersza poleceń liczbę całkowitą N
i dwie wartości typu doubl e, l oraz r, a następnie używa biblioteki StdDraw do naryso
wania histogramu z liczbą wartości ze standardowego strumienia wejścia mieszczą
cych się w każdym z N przedziałów wyznaczonych przez podział zbioru (/, r) na N
fragmentów o równej wielkości.
1.1.33. Biblioteka Matri x. Napisz bibliotekę Matri x z implementacją poniższego in
terfejsu API:
p ub lic c l a s s M atr ix
[ eksperym en ty
1.1.35. Sym ulowanie rzutu kostką. Poniższy kod oblicza rozkład prawdopodobień
stwa sumy oczek na dwóch kostkach:
in t SIDES = 6;
double[] d is t = new double[2*SIDES+1];
fo r (in t i = 1; i <= SIDES; i++)
fo r (in t j = 1; j <= SIDES; j++)
d ist[i+ j] += 1.0;
1.1.38. W yszukiwanie binarne a w yszu kiw an ie m eto d ę ataku siłowego. Napisz pro
gram BruteForceSearch z wykorzystaniem wyszukiwania metodą ataku siłowego, co
opisano na stronie 60. Porównaj czas działania tego program u na plikach largeW .txt
i largeT.txt z czasem pracy programu Bi narySearch.
R O ZD ZIA Ł 1 a Podstawy
p ub lic c l a s s Counter
Choć podstawą definicji typu danych jest zbiór wartości, jego rola nie jest widoczna
w interfejsie API. Interfejs API obejmuje tylko operacje na wartościach. Dlatego de
finicja typu ADT pod wieloma względami przypomina bibliotekę m etod statycznych
(zobacz stronę 36).
n Oba elementy są implementowane jako klasy Javy.
■ Metody egzemplarza przyjmują zero lub więcej argumentów określonego typu,
rozdzielonych przecinkami i umieszczonych w nawiasach.
° Metody mogą zwracać wartość określonego typu lub w ogóle jej nie zwracać
(metody typu void).
Istnieją też trzy ważne różnice:
0 Niektóre elementy w interfejsach API mają nazwę taką samą jak klasa, ale nie
mają typu zwracanej wartości. Są to tak zwane konstruktory. Odgrywają one
specjalną rolę. Tu typ Counter ma konstruktor przyjmujący argument typu
S t r i ng.
RO ZD ZIA Ł 1 0 Podstaw y
Obiekty Zm ienną heads powiązaną z typem danych Counter można, oczywiście, za
deklarować za pom ocą kodu:
Counter heads;
Jak jednak można przypisywać wartości lub urucham iać operacje? Odpowiedź
na to pytanie związana jest z zagadnieniami podstawowymi w abstrakcji danych.
Obiekt jest jednostką, która przyjmuje wartość typu danych. Obiekty mają trzy klu
czowe cechy: stan, tożsamość i działanie. Stan obiektu to wartość jego typu danych.
Za tożsamość obiektu m ożna uznać miejsce, w którym wartość jest przechowywana
w pamięci. Działanie obiektu zależy od operacji typu da Jed e n o b ie k t ty p u C o u n te r
nych. Implementacja odpowiada za zachowanie tożsamości
obiektu, dlatego kod klienta może korzystać z typu danych
niezależnie od reprezentacji jego stanu, zachowując zgod
ność z interfejsem API opisującym działanie obiektu. Stan heads
tworzy dwa różne obiekty typu Counter. W abstrakcyjnym typie danych szczegó
ły reprezentacji wartości są ukryte przed kodem klienta. Można założyć, że wartość
powiązana z każdym obiektem Counter to nazwa typu S t r i ng i licznik typu i nt, nie
można jednak pisać kodu zależnego od konkretnej reprezentacji (a nawet stwierdzić,
czy założenie jest prawdziwe — możliwe, że licznik to wartość typu long).
W ywoływ anie m etod egzemplarza Metody egzemplarza służą do działania na war
tościach typu danych, dlatego język Java udostępnia specjalny mechanizm do wywo
ływania takich metod, w którym podkreślone jest powiązanie z obiektem. Metodę eg
zemplarza można wywołać, podając nazwę zmiennej powiązanej z obiektem, kropkę,
nazwę metody egzemplarza i 0 lub więcej argumentów umieszczonych w nawiasach
•Deklaracja
i rozdzielonych przecinkami. Metoda egzem
c o u n t e r h e a d s; ' plarza może zmieniać wartość typu danych lub
Za p o m o cą new (konstruktor) tylko ją sprawdzać. Metody egzemplarza mają
he ad s = new c o u n t e r ( " h e a d s " ) ; wszystkie cechy metod statycznych wymienio
t ne na stronie 36. Argumenty są przekazywane
Wywołanie konstruktora (tworzenie obiektu) przez wartość, nazwy metod można przeciążać,
Jako instrukcja (w artość zw racana v o id ) metody mogą mieć wartość zwracaną i powodo
|heads|. i n c r e m e n t Q : wać efekty uboczne. Mają też dodatkową, cha
T
Nazwa obiektu \ rakterystyczną dla nich cechę: każde wywołanie
Wywołanie metody egzemplarza jest powiązane z obiektem. Na przykład kod:
zmieniającej wartość obiektu
heads.increm entO ;
Jako w yrażenie
w kodzie klienta można używać wywołań metod egzemplarza w taki sam sposób, jak
wywołań metod statycznych — jako instrukcji (metody voi d) lub wartości w wyraże
niach (metody zwracające wartość). Metody statyczne to przede wszystkim implemen
tacje funkcji. Metody niestatyczne (egzemplarza) służą głównie do implementowania
operacji typów danych. W kodzie klienta mogą występować metody obu rodzajów,
przy czym łatwo je odróżnić
Metoda egzemplarza Metoda statyczna
od siebie, ponieważ wywołania
metod statycznych rozpoczy Przykładowe
head.increm ent() M a th .sq rt(2 .0 )
wywołanie
nają się od nazwy klasy (trady
cyjnie zaczynającej się wielką Wywoływana
Nazwa obiektu Nazwa klasy
za pomocą
literą), a wywołania metod
niestatycznych — od nazwy Referencja do obiektu
Parametry Argumenty
i argumenty
obiektu (tradycyjnie zaczyna
Główne Sprawdzanie lub Obliczanie
jącej się małą literą). Różnice
przeznaczenie zmienianie wartości obiektu zwracanej wartości
te podsumowano w tabeli po
Metody egzem plarza a metody statyczne
prawej stronie.
Korzystanie z obiektów Deklaracje tworzą nazwy zmiennych powiązanych z obiek
tami. Nazw można używać w kodzie nie tylko do tworzenia obiektów i wywoływania
metod egzemplarza, ale też w taki sam sposób, jak nazw zmiennych dla liczb całkowi
tych, liczb zmiennoprzecinkowych i innych typów prostych. Aby utworzyć kod klienta
z wykorzystaniem określonego typu danych, należy:
° Zadeklarować zmienne tego typu, służące do wskazywania obiektów.
° Użyć słowa kluczowego new, aby wywołać konstruktor tworzący obiekty tego typu.
° Użyć nazwy obiektu w celu wywołania m etod egzemplarza — albo jako instruk
cji, albo w wyrażeniach.
Na przykład klasa FI i ps przedstawiona na początku następnej strony to klient typu
danych Counter, pobierający argument T z wiersza poleceń i symulujący T rzutów
monetą (FI ips jest też klientem biblioteki StdRandom). Oprócz takich bezpośrednich
zastosowań zmienne powiązane z obiektami m ożna wykorzystać w taki sam sposób,
jak zmienne powiązane z wartościami typów prostych:
■ w instrukcjach przypisania;
° do przekazywania lub zwracania obiektów w metodach;
D do tworzenia i używania tablic obiektów.
Zrozumienie każdego rodzaju zastosowania wymaga myślenia w kategoriach refe
rencji, a nie wartości. Jest to wyraźnie widoczne w dalszym omówieniu wszystkich
rodzajów zastosowań.
p ub lic c l a s s F l i p s
{
p ub lic s t a t i c void main(String[] args)
I % ja va F I i p s 10
i n t T = In t e g e r . p a r s e ln t ( a r g s [ 0 ] ); 5 o r íy
Counter heads = new C o u n t e r ( " o r t y " ) ; 5 reszki
Counter t a i l s = new C o u n t e r ( " r e s z k i '') ; różn ica: 0
f o r ( i n t t = 0; t < T; t++)
i f (StdRandom.bernoulli(0.5)) % java FI i p s 10
heads.increment(); 8 orty
e lse t a i l s . i n c r e m e n t ( ) ; 2 re szki
S td O u t. p rin t ln (h e a d s); ró żn ica: 6
Std O u t.p rintln (tails);
i n t d = h e a d s .t a lly ( ) - tai 1s . t a l l y (); % java FI ip s 1000000
S t d O u t . p r i n t ln ( “ro znica: " + M a th .a b s (d )); 499710 o r ty
} 500290 resz ki
ró żnica: 580
>
Referencje do tego
c l . in c re m e n to ;
samego obiektu
Counter c2 = c l;
c2.increment();
S td O u t .p rin t ln (c l) ;
Obiekty ja ko zwracane wartości Oczywiście, można też używać obiektów jako war
tości zwracanych przez metodę. Metoda może zwrócić obiekt przekazany do niej jako
argument, tak jak w dalszym przykładzie, lub utworzyć obiekt i zwrócić referencję do
niego. Jest to ważna możli
wość, ponieważ Java dopusz p ub lic c l a s s FlipsMax
cza tylko jedną zwracaną war {
p ub lic s t a t i c Counter max(Counter x, Counter y)
tość. Zastosowanie obiektów
{
pozwala napisać kod, który i f ( x . t a l l y ( ) > y . t a l l y O ) return x;
zwraca kilka wartości. e lse return y;
1
i f (hea ds.tal 1y () == t a i l s . t a l l y O )
S t d O u t . p r i n t l n C 'R e m i s " ) ;
e lse St dOut. println(max(hea ds, t a i l s ) + " wygrywają");
% java FlipsMax 1000000
500281 resz ki wygrywają
Tablice to obiekty W Javie każda wartość typu innego niż typ prosty jest obiek
tem. Obiektami są na przykład tablice. Podobnie jak dla łańcuchów znaków, język
zapewnia specjalną obsługę pewnych operacji na tablicach: deklarowania, inicjowa
nia i indeksowania. Podobnie jak w przypadku innych obiektów, przekazanie tablicy
do m etody lub użycie reprezentującej tablicę zmiennej po prawej stronie instrukcji
przypisania powoduje utworzenie kopii referencji do tablicy, a nie kopii samej tabli
cy. To podejście dobrze nadaje się dla typowego przypadku, kiedy programista ocze
kuje, że m etoda będzie mogła zmodyfikować tablicę, zmieniając uporządkowanie jej
elementów (za pom ocą m etody jav a, ú til .A rra y s.so rt() lub opisanej na stronie 44
metody shuffle()).
Tablice obiektów Elementy tablicy mogą być dowolnego typu, jak już to przedsta
wiono. Parametr args [] w napisanej przez nas implementacji m etody mai n () to tab
lica obiektów typu S tri ng. Tablicę obiektów można utworzyć w dwóch krokach:
■ utworzenie tablicy za pomocą składni z nawiasami kwadratowymi używanej
dla konstruktorów tablic;
■ utworzenie każdego obiektu w tablicy przy użyciu standardowego konstruktora
dla poszczególnych obiektów.
Poniższy kod to symulacja rzutu kostką. Wykorzystano tu tablicę obiektów typu
Counter do rejestrowania liczby wystąpień każdej możliwej wartości. Tablica obiek
tów w Javie to tablica referencji do nich — nie zawiera samych obiektów. Jeśli obiek
ty są duże, pozwala to zwiększyć wydajność, ponieważ nie trzeba przenosić samych
obiektów (wystarczy przenieść referencje). Przy małych obiektach może nastąpić
spadek wydajności z uwagi na konieczność podążania za referencją za każdym ra
zem, kiedy potrzebne są informacje.
p u b lic c la s s Point2D
Point2D(double x, double y) Tworzenie punktu
double x() Współrzędna x
double y () Współrzędna y
double r( ) Promień (współrzędne biegunowe)
double theta() Kąt (współrzędne biegunowe)
double distTo (Poin t2D that) Odległość euklidesowo od danego punktu do punktu that
void draw() Rysowanie punktu na StdDraw
public c l a s s I n t e r v a llD
IntervallD(double lo, double hi) Tworzenie przedziału
double length() Długość przedziału
boolean conta ins(d ouble x) Czy przedział obejmuje x?
boolean i n t e r s e c t s ( I n t e r v a l I D that) Czy przedział ma część wspólną z t ha t?
void draw() Rysowanie przedziału na StdDraw
public c l a s s Interval2 D
się nad zdefiniowaniem typu ADT, tak jak zrobiono to w przykładach. Rozwiązanie
to pomaga uporządkować dane i znacznie uprościć kod klienta w typowych zastoso
waniach, a także jest ważnym krokiem na drodze do abstrakcji danych.
1.2 h Abstrakcja danych
i n t month() Miesiąc
i n t day( ) Dzień
in t year() Rok
Łańcuchy znaków S tring to w Javie ważny i przydatny typ ADT. Typ String to
indeksowany ciąg wartości typu char, mający dziesiątki m etod egzemplarza, w tym
poniższe:
p u b lic c la s s S trin g
S t r i n g () Tworzenie pustego łańcucha znaków
i n t le n g t h () Długość łańcucha znaków
i n t c h a r A t (in t i ) i -ty znak
in t i ndexOf(S t r i n g p) Pierwsze wystąpienie p (-1, jeśli nie ma p)
i n t indexO f( S t r i n g p, i n t i ) Pierwsze wystąpienie p po i -tym znaku (-1, jeśli nie ma p)
S t r i n g co n c a t( S t rin g t) Łańcuch z dołączonym t
S t r i n g s u b s t r i n g ( i n t i , i n t j) Podłańcuch danego łańcucha (znaki od i-tego do j - 1 )
S t r i ng [] s p l i t ( S t r i n g del im) Łańcuchy znaków między wystąpieniami delim
in t compareTo(String t) Porównywanie łańcuchów znaków
boolean e q u a l s ( S t r i n g t) Czy wartość danego łańcucha jest taka sama ja k t.?
i n t hashCode() Kod skrótu
Wartości typu S tri ng przypominają tablice znaków, jednak nie są nimi. Tablice mają
wbudowaną składnię Javy umożliwiającą dostęp do znaku. Typ S tri ng posiada metody
egzemplarza zapewniające dostęp indeksowany, określające długość itd. Dla tego typu
język udostępnia specjalną obsługę inicjowania i złączania. Zamiast tworzyć i inicjo
wać łańcuch znaków za pomocą konstruktora, można użyć literału. Zamiast wywoły
wać metodę concat (), wystarczy użyć operatora +. Omawianie szczegółów implemen
tacji nie jest tu ważne, jednak — co okaże się w r o z d z i a l e 5 . — przy rozwijaniu algo
rytmów przetwarzających łańcuchy warto zrozumieć aspekty związane z wydajnością
niektórych metod. Dlaczego nie używamy prostych tablic znaków zamiast wartości
typu String? Odpowiedź jest taka sama jak dla innych typów ADT — aby uprościć
kod klienta i zwiększyć jego przejrzystość. Za po
S t r i n g a = " i mamy "
mocą typu S tri ng można pisać przejrzysty i prosty S t r i n g b = " j u ż cz as 5
kod klienta z wykorzystaniem wielu wygodnych S t r i n g c = "n a "
metod egzemplarza bez uwzględniania sposobu
Wywołanie Wartość
reprezentowania łańcuchów znaków (zobacz na
a.leng th() 7
stępną stronę). Nawet ta krótka lista obejmuje roz
a.charAt(4) m
budowane operacje, wymagające zaawansowanych
a.concat(c) " i mamy na"
algorytmów, takich jak opisane w r o z d z i a l e 5 . Na
a. in d e x O f( "m a m y") 2
przykład argumentem metody spl i t () może być
a . s u b s t r i n g ( 2 , 5) "mam"
wyrażenie regularne (zobacz p o d r o z d z i a ł 5 .4 ).
a . s p l i t ( " " ) [ 0]
II.j I
W przykładzie zastosowania metody s p l i t( ) na
stronie 93 użyto argumentu "\\s+ ", co oznacza a . s p l i t ( " " ) [ 1 ] "mamy"
Zadanie Implementacja
p u b l i c s t a t i c boolean i s P a l i n d r o m e ( S t r i n g s)
{
int N = s .le n g t h ( ) ;
Czy łańcuch znaków f o r ( i n t i = 0; i < N/2; i+ + )
jest palindromem? i f ( s . c h a r A t ( i ) != s . c h a r A t ( N - l - i ) )
return f a l s e ;
return true;
Strin g s = args[0];
Pobieranie nazwy pliku
in t dot = s .in d e x O f ( " . " ) ;
i rozszerzenia z argumentu
S t r i n g ba se = s . s u b s t r i n g ( 0 , dot);
z wiersza poleceń S t r i n g exten sio n = s . s u b s t r i n g ( d o t + 1, s . l e n g t h ( ) ) ;
Wyświetlanie wszystkich S t r i n g q ue ry = a r g s [ 0 ] ;
w hile ( IS t d l n . is E m p t y O )
wierszy ze standardowego
wejścia, zawierających
(
Strin g s = Std ln .re ad Lin e();
łańcuch znaków podany i f (s .c o n t a in s (q u e ry ) ) S t d O u t . p r i n t l n ( s ) ;
w wierszu poleceń )
Tworzenie tablicy
łańcuchów znaków S t r i n g in p u t = S t d l n . r e a d A l 1 ( ) ;
ze S t d l n ograniczonych Strin g[] words = i n p u t . s p l i t ( " \ \ s + " ) ;
białymi znakam i
p u b l i c bo ole an i s S o r t e d ( S t r i n g [ ] a)
(
f o r ( i n t i = 1; i < a . l e n g t h ; i++)
Sprawdzanie,
czy łańcuchy znaków {
if ( a [ f -1] . c o m p a r e T o ( a [ i ] ) > 0)
w tablicy są uporządkowane return f a ls e ;
alfabetycznie }
return true;
p u b lic c la s s In
public c l a s s Out
Interfejs API opracowanego przez nas typu danych dla strumieni wyjścia
public c l a s s Draw
Draw()
Uwaga: wszystkie operacje obsługiwane przez StdDraw są też obsługiwane dla obiektów typu Draw
p u b lic c l a s s Counter
{
Nazwa klasy
Zmienne p r i v a t e f i n a l S t r i n g name;|
egzemplarza p r i v a t e i n t count;
p u b l i c c o u n t e r ( S t r i ng i d )
Konstruktor -
{ name = i d ; }
p u b lic vo id in c re m e n to
{ count++; }
Metody p u b lic in t t a l l y O
egzemplarza { re tu rn count; }
Nazwa zmiennej
egzemplarza
p u b lic S t rin g t o S t r in g O
{ r e t u r n c o u n t + " " + name; }
sygnaturach (tak samo, jak w przypadku metod). Jeśli programista nie zdefiniuje żad
nego innego konstruktora, automatycznie używany jest konstruktor domyślny. Nie ma
on argumentów i inicjuje zmienne egzemplarza domyślnymi wartościami. Wartości do
myślne zmiennych egzemplarza to 0 dla prostych typów liczbowych, f al se dla typu boo-
1ean i nul 1 dla typów referencyjnych. Wartości te można zmienić, używając deklaracji
inicjującej dla zmiennych egzem
plarza. Java automatycznie wywo- p u b li c c l a s s Counter
luje konstruktor, kiedy w kliencie
private f in a l s t r in g
występuje słowo new. Przeciążone p r i v a t e i n t count;
konstruktory zwykle służą do ini
cjowania zmiennych egzemplarza BEZ typu
Modyfikator zwracanej Nazwa konstruktora (taka
podanymi przez klienta wartoś widoczności wartości sama, jak nazwa klasy) / Parametr
ciami innymi niż domyślne. Na
przykład typ Counter posiada
\
p u b !i c | Counter| ( | s t r i n g id )
konstruktor jednoargumentowy, { name == “id; |} \
który inicjuje zmienną egzempla Sygnatura
rza name wartością podaną jako Kod inicjujący zmienne egzemplarza (zmienna
count jest domyślnie inicjowana wartością 0)
argument (zmienna egzemplarza
count jest inicjowana wartością
S tru k tu ra k o n s tru k to ra
domyślną 0).
M etody egzem plarza Aby zaimplementować metody egzemplarza typu danych
(określić działanie każdego obiektu), należy umieścić w metodach egzemplarza kod
taki sam, jaki poznano w p o d r o z d z i a l e i . i przy implementowaniu metod statycz
nych (funkcji). Każda metoda egzemplarza ma typ zwracanej wartości, sygnaturę
(określającą nazwę metody oraz typy i nazwy parametrów) i ciało (składające się
z ciągu instrukcji, w tym instruk-
cji return z wartością zwracaną Modyfikatorwidoczności
Typ zwracanej Nazwa
wartości metody
- Sygnatura
do klienta). Kiedy klient wywołuje _ ł _______ ł _ __ ł _
metodę, wartości parametrów (je |pub!i c||void ||lncrement()|
śli te ostatnie istnieją) są inicjowa { |count|r+; }
ne wartościami podanym i przez \
klienta, instrukcje są wykonywane Nazwa zmiennej egzemplarza
wołania metody. W wywołaniu heads.increm ento kod metody increm ento używa
zmiennych egzemplarza obiektu heads. Ujmijmy to inaczej — programowanie obiek
towe wzbogaca używanie zmiennych w programach Javy o niezwykle istotny aspekt:
■ wywoływanie metod egzemplarza działających na wartościach danego obiektu.
Różnica w porównaniu z używaniem samych m etod statycznych jest czysto sem an
tyczna (zobacz „Pytania i odpowiedzi”), jednak w wielu sytuacjach zmienia sposób
myślenia o rozwijaniu kodu przez współczesnych programistów. Jak się okaże, podej
ście to dobrze nadaje się do badania algorytmów i struktur danych.
Zasięg W skrócie m ożna stwierdzić, że w kodzie Javy pisanym przy implementowa
niu metod egzemplarza używane są trzy rodzaje zmiennych:
» zmienne dla parametrów,
° zmienne lokalne,
B zmienne egzemplarza.
Pierwsze dwa rodzaje są takie same jak dla m etod statycznych. Zmienne dla param e
trów są określane w sygnaturze metody i inicjowane wartościami podanym i w wywo
łaniu metody w kliencie. Zmienne lokalne są deklarowane i inicjowane w ciele m e
tody. Zasięgiem zmiennych parametrów jest cała metoda. Dla zmiennych lokalnych
zasięg to dalsze instrukcje w bloku z definicją zmiennej. Zmienne egzemplarza są zu
pełnie odmienne. Przechowują wartości typu danych dla obiektów określonej klasy,
a zasięgiem jest cała klasa (jeśli występuje wieloznaczność, można użyć przedrostka
thi s do wskazania zmiennych egzemplarza). Zrozumienie różnic między trzema ro
dzajami zmiennych w metodach egzemplarza to klucz do skutecznego program owa
nia obiektowego.
p r iv a t e in t v a r;
p r i v a t e v o id m e t h o d l( )
{
Zmienna ^ in t v a r; Dotj
Dotyczy zmiennej lokalnej,
lokalna a NIE zmiennej egzemplarza
var
t h is .v a r
Dotyczy zmiennej egzemplarza
}
p r i v a t e v o id m e tho d2 ( )
{
var
} Dotyczy zmiennej egzemplarza
}
Z a się g z m ie n n y c h e g z e m p la rz a i lo k a ln y c h w m e to d z ie e g z e m p la rz a
100 R O Z D Z IA Ł ! a Podstawy
Interfejs API, klienty i im plem entacje Są to podstawowe elementy, które trzeba zro
zumieć, aby móc tworzyć i stosować abstrakcyjne typy danych w Javie. Implementacja
każdego omawianego typu ADT to klasa Javy z prywatnymi zmiennymi egzemplarza,
konstruktorami, m etodam i egzemplarza i klientem. Do pełnego zrozumienia typu
danych potrzeba interfejsu API, kodu typowego klienta i implementacji. Informacje
te dla typu Counter pokazano na następnej stronie. Aby podkreślić oddzielenie klien
ta od implementacji, zwykle każdego klienta przedstawiamy jako odrębną klasę
z metodą statyczną main(), a metodę main() klienta testowego w definicji typu da
nych rezerwujemy na podstawowe testy jednostkowe i na potrzeby programowania
(każda m etoda egzemplarza wywoływana jest w niej przynajmniej raz). W każdym
rozwijanym typie danych wykonujemy te same zadania. Zamiast myśleć o operacjach
potrzebnych do zrealizowania zadania obliczeniowego (jak postępowano przy nauce
programowania), zastanawiamy się nad potrzebami klienta, a następnie uwzględnia
my je w typie ADT, wykonując trzy poniższe kroki:
■ Określanie interfejsu API. Interfejs API służy do oddzielania klientów od imple
mentacji, co umożliwia programowanie modularne. Przy określaniu interfejsu
API ważne są dwa cele. Po pierwsze, należy umożliwić pisanie przejrzystego
i poprawnego kodu klienta. Dobrym pomysłem jest napisanie kodu klien
ta przed zakończeniem tworzenia interfejsu API. Pozwala to upewnić się, że
określone operacje typu danych to te potrzebne klientom. Po drugie, możliwe
powinno być zaimplementowanie operacji. Nie ma sensu określać operacji, jeśli
nie wiadomo, jak je zaimplementować.
■ Implementowanie klasy Javy zgodnej ze specyfikacją interfejsu API. Najpierw
należy wybrać zmienne egzemplarza, a następnie napisać konstruktory i m eto
dy egzemplarza.
■ Opracowanie wielu klientów testowych w celu potwierdzenia poprawności de
cyzji projektowych podjętych w dwóch pierwszych krokach.
Jakie operacje są potrzebne klientom i jakie wartości typu danych w największym
stopniu ułatwiają wykonywanie tych operacji? Podejmowanie takich podstawowych
decyzji jest istotą rozwijania każdej implementacji.
1.2 a Abstrakcja danych 101
f o r ( i n t t = 0; t < T; t++ )
if (StdR and om .bernoulli(0.5))
heads.in c r e m e n t o ;
e ls e t a i l s.in c re m e n t();
Zastoso w an ie Im p le m e n ta c ja
p u b l i c c l a s s C o un te r % j a v a F l i p s 1000000
{ 500172 o r t y
p r i v a t e final S t r i n g name; 499828 r e s z k i
p r i v a t e i n t count; r ó ż n i c a : 344
p u b l i c C o u n t e r ( S t r i n g id )
{ ñame = id; }
p u b l i c v o id i n c r e m e n t o
{ co un t++; }
pub lic in t t a l l y ( )
{ r e t u r n co unt; )
pub lic S t r in g t o S t r in g O
{ r e t u r n count + 11 11 + name; )
Więcej implementacji typów ADT Tak jak przy każdym zagadnieniu pro
gramistycznym, tak i tu najlepszym sposobem na zrozumienie wartości oraz przy
datności typów ADT jest staranne przyjrzenie się większej liczbie przykładów i im
plementacji. Będzie to możliwe, ponieważ duża część książki dotyczy implementacji
typów ADT, natomiast kilka dodatkowych przykładów w tym miejscu pozwala uzy
skać podstawową wiedzę.
D a te Na następnej stronie przedstawiono dwie implementacje typu ADT Date opisa
nego na stronie 91. Aby zwiększyć przejrzystość, pominięto konstruktor przetwarzają
cy dane (opisany w ć w i c z e n i u 1 .2 . 1 9 ) i odziedziczone metody equal s ( ) (zobacz stro
nę 115), compareTo() (zobacz stronę 259) i hashCodeQ (zobacz ć w i c z e n i e 3 .4 .2 2 ).
W prostej implementacji widocznej po lewej stronie dzień, miesiąc i rok przecho
wywane są jako zmienne egzemplarza, dlatego metody egzemplarza jedynie zwracają
odpowiednią wartość. Wydajniejsza ze względu na pamięć implementacja przedsta
wiona po prawej stronie wykorzystuje jedną wartość typu i nt do zapisu daty. Użyto tu
wartości o mieszanej podstawie, która reprezentuje datę dla dnia d, miesiąca m i roku
y jako 512y + 32m + d. Dla klienta jedną z istotnych różnic między tymi implemen
tacjami jest możliwość naruszenia niejawnych założeń. Kod oparty na drugiej imple
mentacji wymaga do poprawnego działania, aby dzień zawierał się w przedziale 0 -3 1 ,
miesiąc w przedziale 0 - 1 2 , a rok był dodatni (w praktyce w obu implementacjach
należy sprawdzać, czy miesiące znajdują się w przedziale 1 - 1 2 , a dni w przedziale
1 - 3 1 ; ponadto daty w rodzaju 31 czerwca 2009 i 29 lutego 2009 są niedozwolone,
choć sprawdzenie tego wymaga więcej pracy). Przykład ten podkreśla fakt, że w in
terfejsie API rzadko w pełni określamy wymagania dotyczące implementacji (zwykle
staramy się, jak możemy; tu mogliśmy postarać się bardziej). W kliencie można też
odczuć różnicę między implementacjami w obszarze wydajności. Implementacja po
prawej stronie wymaga mniej pamięci do przechowywania wartości typu danych, jed
nak dzieje się to kosztem dłuższego czasu udostępniania ich klientowi w uzgodnionej
postaci (potrzeba jednej lub dwóch operacji arytmetycznych). Różne zyski i koszty
tego rodzaju są czymś powszechnym. W jednym kliencie preferowana może być jedna
implementacja, a w innym — druga, dlatego należy uwzględnić obie. Jednym z po
wtarzających się zagadnień poruszanych w książce jest konieczność zrozumienia wy
mogów z zakresu pamięci i czasu specyficznych dla różnych implementacji. Trzeba
też uwzględnić dopasowanie implementacji do różnych ldientów. Jedną z kluczowych
zalet stosowania w implementacjach abstrakcji danych jest to, że zwykle można zmie
nić jedną implementację na inną bez modyfikowania kodu klientów.
U trzy m y w a n ie w ielu im p le m e n ta c ji Duża liczba implementacji tego samego in
terfejsu API może prowadzić do problemów z konserwacją i nazwami. W niektórych
przypadkach korzystne jest zastąpienie dawnej implementacji nową, usprawnioną.
W innych sytuacjach potrzebne może być utrzymywanie dwóch implementacji —
jednej odpowiedniej dla jednych ldientów i drugiej, właściwej dla innych. Głównym
celem tej książki jest szczegółowe omówienie kilku implementacji każdego z wie
lu podstawowych typów ADT. Implementacje te zwykle mają inne cechy związane
1.2 0 Abstrakcja danych 103
Dwie im plem entacje abstrakcyjnego typu danych służącego do herm etyzacji dat
104 R O ZD ZIA Ł 1 » Podstawy
Zastosowanie
% java TestAccumulator 1000
Średnia (l i c z b a wartości = 1000): 0.51829
% java TestAccumulator 1000000
Średnia (l i c z b a wartości = 1000000): 0.49948
% java TestAccumulator 1000000
Średnia (l i c z b a wartości = 1000000): 0.50014
Implementacja
pub lic c l a s s Accumulator
{
p riv a t e double t o t a l ;
p riv a t e in t N;
p ub lic S t r i n g t o S t r i n g O
( return “Średnia (l i c z b a wartości = " + N + " ) : 11
+ S t r i n g . f o r m a t ( " % 7 . 5 f " , mean()); }
Zastosowanie
% j a v a T e s t V i s u a l A c c u m u l a t o r 2000
Ś r e d n i a ( l i c z b a w a r t o ś c i = 2 0 0 0 ) : 0 .5 0 9 7 8 9
1.2 b Abstrakcja danych 1 07
Im p le m e n ta c ja import j a v a . u t i l . A r r a y s ;
p ub lic c l a s s S t a t i c S E T o f ln t s
(
p riv ate i n t [] a;
p ub lic S t a t i c S E T o f I n t s ( i n t [ ] keys)
{
a = new i n t [ k e y s . l e n g t h ] ;
fo r ( i n t i = 0; i < k eys.length; i++)
a [ i ] = k e y s [ i ] ; // Kopia zabezpieczająca.
A rrays.sort(a);
)
p ub lic boolean c o n t a i n s ( i n t key)
{ return rank(key) != -1 ; )
Kompilator Javy sprawdzi wtedy, czy kod pasuje do interfejsu. Dodanie fragmen
tu implements Datable do klasy z implementacją operacji month(), day() i year()
stanowi gwarancję dla klientów, że obiekt danej klasy pozwala wywołać te metody.
Ta technika to dziedziczenie interfejsu. Klasa z implementacją dziedziczy interfejs.
Dziedziczenie interfejsu pozwala pisać programy klienckie, które mogą manipulować
obiektami dowolnego typu z implementacją danego interfejsu (nawet typu, który jesz
cze nie istnieje), wywołując m etody z interfejsu. Mogliśmy zastosować dziedziczenie
interfejsów zamiast mniej formalnych interfejsów API, jednak zdecydowaliśmy się
na inne rozwiązanie, aby uniknąć zależności od specyficznych, wysokopoziomowych
mechanizmów języka, które
Interfejs Metody Podrozdział nie są kluczowe do zrozumie
nia algorytmów. Zastosowane
j ava.1ang.Comparable compareToO 2.1
Porównywanie podejście pozwala też unik
ja va .u til.C om p a ra tor compare() 2.5 nąć dodatkowego obciążenia
java.lang.Iterable iterator() 1.3 w postaci plików interfejsu.
Jednak w niektórych sytu
Iterowanie hasNext()
acjach mechanizmy Javy
j a v a . u t i l . Ite ra to r next() 1.3
remove() sprawiły, że uznaliśmy, iż
warto wykorzystać interfejsy.
Interfejsy Javy używane 1w książce
1.2 s Abstrakcja danych 1 13
N iezm ienność Niezmienny (ang. immutable) typ danych, taki jak Date, cechuje się
tym, że wartość obiektu po jego utworzeniu nigdy się nie zmienia. Z kolei zmienne
typy danych, na przykład Counter lub Accumul ator, manipulują wartościami obiektu
przeznaczonymi do modyfikowania. W Javie do wymuszania niezmienności służy
modyfikator finał. Zadeklarowanie zmiennej przy jego użyciu oznacza, że wartość
zostanie przypisana do niej tylko raz — albo przy inicjowaniu, albo w konstruktorze.
Kod modyfikujący wartość zmiennej z modyfikatorem finał powoduje błąd czasu
kompilacji. W przedstawionym kodzie użyto modyfikatora finał dla zmiennych eg
zemplarza, których wartość nigdy się nie zmienia. To podejście pozwala udokum en
tować, że wartość się nie zmienia, zapobiega przypadkowym modyfikacjom i ułatwia
diagnozowanie programów. Przykładowo, wartości z modyfikatorem finał nie trzeba
dodawać do śladu, ponieważ wiadomo, że jest niezmienna. Typ danych w rodzaju
typu Data, w którym wszystkie zmienne egzemplarza są typu prostego i mają m ody
fikator finał, to typ niezmienny (w kodzie, w którym — tak jak w tej książce — nie
stosuje się dziedziczenia implementacji). Ustalenie, czy typ danych ma być niezm ien
ny, jest ważną decyzją projektową, specyficzną dla aplikacji. Abstrakcja w typach da
nych w rodzaju typu Date ma służyć ukrywaniu wartości, które się nie zmieniają, co
pozwala stosować je w instrukcjach przypisania, jako argumenty i wartości zwracane
przez funkcje w taki sam sposób, jak używa się typów prostych (bez obaw o możli
wość zmiany wartości). Programista implementujący klienta
_ , , , , , ,, j , , , Zm ienne Niezmienne
typu Date może napisać kod d = dO dla dwóch zmiennych
typu Date, podobnie jak dla wartości typu double lub in t. Counter Data
Jednak gdyby typ Date był zmienny, a wartość d zmieniłaby się Tablice Javy S t r i ng
po przypisaniu d = dO, modyfikacji uległaby także wartość dO Przykłady typów
(obie zmienne to referencje do tego samego obiektu)! Z dru- zm iennych i niezmiennych
giej strony, w typach danych w rodzaju Counter i Accumuł a to r
celem abstrakcji jest ukrywanie modyfikowanych wartości. Zetknąłeś się już z tą
różnicą jako programista programów klienckich przy stosowaniu tablic Javy (typ
zmienny) i typu danych S tri ng Javy (typ niezmienny). Przy przekazywaniu wartości
typu S tri ng do m etody nie trzeba martwić się tym, że m etoda zmieni układ znaków
w łańcuchu. M etoda może natomiast zmodyfikować zawartość tablicy. Obiekty typu
String są niezmienne, ponieważ zwykle nie chcemy, aby ich wartość się zmieniała.
Tablice Javy są zmienne, gdyż zazwyczaj chcemy modyfikować ich wartość. W pew
nych sytuacjach przydatne są zmienne łańcuchy znaków (do ich tworzenia służy klasa
S tri ngBui 1der Javy) i niezmienne tablice (tak działa klasa Vector opisana w dalszej
części podrozdziału). Ogólnie typy niezmienne są łatwiejsze w użyciu i trudniej po
pełnić błąd przy ich stosowaniu niż przy korzystaniu z typów zmiennych, ponieważ
zasięg kodu, w którym m ożna modyfikować te pierwsze, jest dużo mniejszy. Łatwiej
jest diagnozować kod, w którym używane są typy niezmienne, ponieważ prościej jest
zagwarantować, że zmienne tego typu w kodzie klienta zachowają spójny stan. Przy
korzystaniu z typów zmiennych zawsze trzeba uważać, gdzie i kiedy modyfikowa
ne są wartości. Wadą niezmienności jest konieczność tworzenia nowego obiektu dla
118 RO ZDZIAŁ 1 B Podstawy
public Vector(double[] a)
{ coords = a; }
}
Program kliencki może utworzyć obiekt typu Vector, podając elementy tablicy, a na
stępnie (z pominięciem interfejsu API) zmodyfikować je po utworzeniu:
Zmienna egzemplarza coords [] ma modyfikatory pri vate i final, jednak typ Vector
jest zmienny, ponieważ klient przechowuje referencję do danych. Nad niezm iennoś
cią należy zastanowić się przy projektowaniu każdego typu danych. W interfejsie API
należy też określić, czy typ danych jest niezmienny, tak aby programiści klientów
wiedzieli, że wartości obiektu się nie zmieniają. W tej książce niezmienność jest przy
datna głównie do sprawdzania poprawności algorytmów. Na przykład gdyby typ da
nych używany w algorytmie wyszukiwania binarnego był zmienny, działanie klien
tów mogłoby być niezgodne z założeniem, że tablica jest posortowana pod kątem
tego algorytmu.
1.2 □ Abstrakcja danych
danych określa warunek wstępny (warunek, który klient musi spełniać w momencie
wywołania metody), warunek końcowy (implementacja gwarantuje jego spełnienie
po zwróceniu sterowania z metody) i efekty uboczne (inne zmiany stanu, które m e
toda może powodować). W czasie programowania warunki te m ożna sprawdzać za
pomocą asercji.
Podsum ow anie Mechanizmy języka opisane w tym podrozdziale pokazują, że pro
jektowanie efektywnych typów danych związane jest z niebanalnymi problemami,
które niełatwo jest rozwiązać. Eksperci wciąż dyskutują na temat najlepszych spo
sobów radzenia sobie z pewnymi omówionymi tu zagadnieniami projektowymi.
Dlaczego Java nie dopuszcza stosowania funkcji jako argumentów? Dlaczego Matlab
kopiuje tablice przekazywane jako argumenty do funkcji? Na początku r o z d z i a ł u i .
wspomniano, że narzekanie na mechanizmy języka programowania często prowadzi
do wejścia na trudną drogę projektowania języków programowania. Jeśli nie planujesz
tego robić, najlepszym podejściem jest stosowanie popularnych języków. Większość
systemów udostępnia bogate biblioteld, z których, oczywiście, należy korzystać w od
powiednich sytuacjach. Często jednak m ożna uprościć kod klientów i zabezpieczyć
się, budując abstrakcje, które można łatwo przenieść do innych języków. Głównym
celem jest utworzenie typów danych w taki sposób, aby większość zadań można było
wykonać na poziomie abstrakcji odpowiednim do rozwiązywanego problemu.
Tabela na następnej stronie zawiera podsumowanie różnych rodzajów omówio
nych klas Javy.
1.2 h Abstrakcja danych 121
| Pytania i odpowiedzi
P. Po co stosować podział na typy proste i referencyjne? Czy nie lepiej używać sa
mych typów referencyjnych?
O. Ważna jest wydajność. Java udostępnia odpowiadające typom prostym typy re
ferencyjne Integer, Doubl e itd. Mogą z nich korzystać programiści, którzy chcą zig
norować wspomniany podział. Typy proste są bliższe typom danych obsługiwanym
przez sprzęt komputera, dlatego używające ich programy zwykle działają szybciej niż
programy, w których wykorzystano powiązane typy referencyjne.
P. Co się stanie, jeśli zapomnę użyć słowa new przy tworzeniu obiektu?
O. Java potraktuje to tak, jakbyś chciał wywołać metodę statyczną, która zwraca
wartość o typie danego obiektu. Ponieważ nie zdefiniowano takiej metody, kom uni
kat o błędzie będzie taki sam, jak przy każdym użyciu niezdefiniowanego symbolu.
Próba kompilacji poniższego kodu:
Counter c = C o u n te r(" te st");
Ten sam komunikat o błędzie pojawi się po podaniu złej liczby argumentów w kon
struktorze.
1.2 ■ Abstrakcja danych 123
P. Co się stanie, kiedy zapomnę użyć słowa new przy tworzeniu tablicy obiektów?
O. Słowo new trzeba podać przy tworzeniu każdego obiektu, dlatego tworząc tablicę
N obiektów, należy użyć go N +1 razy — raz dla tablicy i po jednym razie dla każdego
obiektu. Jeśli zapomnisz utworzyć tablicę:
CounterJ] a;
a [0] = new C o u n t e r ( " t e s t " ) ;
zobaczysz ten sam kom unikat o błędzie, co przy próbie przypisania wartości do nie-
zainicjowanej zmiennej:
v a riab le a might not have been i n i t i a l i z e d
a [0] = new C o u n t e r ( " t e s t " ) ;
/\
Jeżeli jednak zapomnisz słowa new przy tworzeniu obiektu w tablicy, a następnie
spróbujesz użyć obiektu do wywołania metody:
CounterJ] a = new Counter[2];
a [0] .in c re m e n t o ;
O. Ten kod działa poprawnie, jednak Java pozwala pom inąć jego fragment, gdyż au
tomatycznie wywołuje metodę to S tri ng () dla każdego obiektu, ponieważ pri ntl n ()
ma wersję przyjmującą argument typu Object.
O. Dobre pytanie. Podany wcześniej wyjątek, Nul 1 Poi nterExcepti on (czyli wyjątek
pustego wskaźnika), powinien nosić nazwę NullReferenceException (czyli wyją
tek pustej referencji). Wskaźnik, podobnie jak referencję Javy, m ożna traktować jak
adres w pamięci. W wielu językach programowania wskaźnik to prosty typ danych,
którym programiści mogą manipulować na wiele sposobów. Jednak programowanie
z wykorzystaniem wskaźników jest narażone na błędy, dlatego operacje na wskaź
nikach trzeba starannie projektować, aby pom óc program istom uniknąć błędów.
W Javie podejście to zastosowano w skrajnym stopniu (jest to rozwiązanie prefero
wane przez wielu współczesnych projektantów języków programowania). Istnieje tu
tylko jeden sposób na utworzenie referencji (new) i tylko jeden sposób na jej zm o
dyfikowanie (za pom ocą instrukcji przypisania). Oznacza to, że jedyną rzeczą, jaką
programista może zrobić z referencją, jest jej utworzenie i skopiowanie. W żargo
nie związanym z językami programowania referencje Javy to tak zwane bezpieczne
124 R O Z D Z IA L I a Podstawy
P. Gdzie można znaleźć więcej informacji o tym, w jaki sposób w Javie zaimplemen
towane są referencje i jak język obsługuje przywracanie pamięci?
O. Jeden system Javy może zupełnie różnić się od drugiego. Przykładowo, natural
nym rozwiązaniem jest używanie wskaźników (adresów w pamięci) lub uchwytów
(wskaźników do wskaźników). Pierwsze podejście zapewnia szybszy dostęp do da
nych; drugie — lepsze przywracanie pamięci.
O. Jest to literał oznaczający brak obiektu. Wywołanie m etody przy użyciu referen
cji nul l nie m a sensu i prowadzi do zgłoszenia wyjątku Nul l Poi nterExcepti on. Jeśli
napotkasz taki komunikat o błędzie, upewnij się, czy konstruktor poprawnie inicjuje
wszystkie zmienne egzemplarza.
O. Jest to metoda, która nie jest w pełni obsługiwana, ale zachowano ją w interfejsie
API w celu zapewnienia zgodności. Java zawierała Medyś metodę C haracter. i sSpa-
ce(), a programiści pisali całe programy, wykorzystując działanie tej metody. Kiedy
programiści Javy chcieli później dodać obsługę innych białych znaków z kodowania
Unicode, nie mogli zmienić działania m etody i sSpace(), nie uszkadzając przy tym
programów Mientów, dlatego zamiast tego dodali nową metodę, C h ara cter.isWhi-
teSpace(), a dawną uznali za przestarzałą. Z czasem podejście to, oczywiście, kom
plikuje interfejsy API. Nieraz za przestarzałe zostają uznane całe Masy. PrzyMadowo,
w Javie uznano za przestarzałą Masę ja v a .u til .Date, aby zapewnić lepszą obsługę
umiędzynarodowiania.
R O ZD ZIA Ł 1 ■ Podstawy
| ĆWICZENIA
1.2.1. Napisz klienta typu Poi nt2D. Klient ma pobierać z wiersza poleceń liczbę cał
kowitą N, generować N losowych punktów w jednostce kwadratowej i obliczać odle
głość między parę najbliższych punktów.
1.2.2. Napisz klienta typu Interval ID. Klient ma pobierać z wiersza poleceń war
tość N typu i nt, wczytywać ze standardowego wejścia N przedziałów (każdy zdefi
niowany za pom ocą pary wartości typu doubl e) i wyświetlać wszystkie pary mające
część wspólną.
1.2.3. Napisz klienta typu Interval 2D. Klient ma pobierać z wiersza poleceń argu
m enty N, mi n i max oraz generować N losowych dwuwymiarowych przedziałów, któ
rych wysokość i szerokość podzielono na równe fragmenty między mi n i max w jed
nostce kwadratowej. Narysuj przedziały na StdDraw i wyświetl liczbę par przedzia
łów mających część wspólną oraz liczbę par przedziałów, z których jeden zawiera się
w drugim.
Odpowiedź: "W itaj, świecie". Obiekty typu S tring są niezmienne. Jego metody
zwracają nowy obiekt typu S tring o odpowiedniej wartości, jednak nie zmieniają
wartości obiektu, dla którego je wywołano. Powyższy kod pomija zwrócone obiekty
i wyświetla pierwotny łańcuch znaków. Aby wyświetlić słowo "ŚWIECIE", należy użyć
instrukcji s = s.toUpperCase() i s = s.su b s trin g (7 , 14).
1.2.6. Łańcuch znaków s jest przesunięciem cyklicznym (ang. circular rotation) łań
cucha t, jeśli pasuje do niego po cyklicznym przestawieniu znaków o dowolną liczbę
pozycji. Na przykład ACTGACGto przesunięcie cykliczne łańcucha TGACGAC i na odwrót.
1.2 o Abstrakcja danych 127
Wykrycie tego warunku jest ważne w badaniach nad sekwencjami genomu. Napisz
program, który sprawdza, czy dwa łańcuchy znaków s i t są dla siebie przesunięciem
cyklicznym. Wskazówka: rozwiązaniem jest jeden wiersz z m etodam i indexOf(),
1 ength () i łączeniem łańcuchów znaków.
in t[] t = a; a = b; b = t ;
Odpowiedź: kod zamienia zawartość tablic. Jest maksymalnie wydajny, ponieważ robi
to przez kopiowanie referencji, dlatego nie trzeba kopiować milionów elementów.
1.2.9. Zmodyfikuj program Bi narySearch (strona 59), tak aby używał klasy Counter
do zliczania kluczy sprawdzanych we wszystkich wyszukiwaniach i wyświetlał liczbę
kluczy po zakończeniu poszukiwań. Wskazówka: utwórz obiekt klasy Counter w m e
todzie mai n () i przekaż go jako argument do metody rank ().
1.2.10 Utwórz klasę Vi sual Counter z obsługą inkrementacji i dekrementacji.
Konstruktor m a przyjmować dwa argumenty — Ni max. Nto maksymalna liczba ope
racji, a max to maksymalna wartość bezwzględna licznika. Jako efekt uboczny obiekt
ma generować rysunek z wartością licznika po każdej jej zmianie.
1.2.11. Napisz implementację typu SmartDate na podstawie interfejsu API typu
Date. Implementacja ma zgłaszać wyjątek, jeśli data jest nieprawidłowa.
1.2.13. Napisz implementację typu Transact i on, biorąc za model opracowaną przez
nas implementację typu Date (strona 103).
1.2.14. Napisz implementację metody equals() dla typu Transaction, biorąc za
model opracowaną przez nas implementację metody equals() dla typu Date (stro
na 115).
1.2 a Abstrakcja danych 129
[I PROBLEMY DO ROZWIĄZANIA
1.2.15* Dane wejściowe z pliku. Napisz implementację m etody statycznej readl nts ()
z biblioteki I n (metody tej używamy w różnych klientach testowych, na przykład do
wyszukiwania binarnego na stronie 59). Implementacja ma być oparta na metodzie
s p lit( ) typu String.
Rozwiązanie-.
public s t a t i c i n t [] re ad In ts(S trin g name)
{
In in = new In(name);
S trin g input = S td ln .rea d A ll();
S t r i n g Q words = input.spl i t ( " \ \ s + " ) ;
i n t [] in t s = new int[w ords.length;
f o r in t i = 0 ; i < word.length; i++)
i n t s [ i ] = In t e g e r. p a rs e In t( w o rd s [i]);
return in t s ;
}
Inną implementację omówiono w p o d r o z d z ia l e 1.3 (zobacz stronę 138).
1.2.16. Liczby wymierne. Zaimplementuj niezmienny typ danych Rational dla liczb
wymiernych. Typ ma obsługiwać dodawanie, odejmowanie, mnożenie i dzielenie.
p ub lic c la s s Rational
R a t io n a l( in t numerator, in t denominator)
public c la s s Accumulator
{
private double m;
private double s;
private in t N;
}
Ta implementacja jest w mniejszym stopniu narażona na błędy przy zaokrąglaniu niż
prosta implementacja oparta na zapisywaniu sumy kwadratów liczb.
1.2 e Abstrakcja danych 131
Częściowe rozwiązanie:
public Date(String date)
{
S t r in g [ ] fields = date.spl i t ( " / " ) ;
month = In te g er.p arse ln t(fields[0 ]) ;
day = Integer.parselnt(fiel d s [1] ) ;
year = In t e g e r.p a rs e ln t(fie ld s [2 ]);
}
Liczby całkowite , ,
Date • 5/22/1939
oddzielone ukośnikami
Klient, data i wartość , ,
T ran sa ction Tu rin g 5/22/1939 11.99
rozdzielone odstępami
F o r m a ty u ż y w a n e p rz y p r z e tw a r z a n iu
1.3. W IE LO Z B IO R Y , K O L E JK I I S T O S Y
obiektów jest grupą wartości, a operacje dotyczą tu dodawania, usuwania lub spraw
dzania obiektów w kolekcji. W tym podrozdziale omawiamy trzy typy danych tego
rodzaju — wielozbiory, kolejki i stosy. Różnią się one tym, który obiekt ma być usu
wany lub sprawdzany jako następny.
Wielozbiory, kolejki i stosy to podstawowe typy o wielu zastosowaniach. Używamy
ich w implementacjach w całej książce. Ponadto kod klienta i implementacji typów
z tego podrozdziału stanowi wprowadzenie do ogólnego, stosowanego przez nas spo
sobu rozwijania struktur danych i algorytmów.
Jednym z celów w tym podrozdziale jest podkreślenie faktu, że sposób reprezen
towania obiektów w kolekcji bezpośrednio wpływa na wydajność różnych operacji.
Dla kolekcji projektujemy struktury danych reprezentujące grupy obiektów i umożli
wiające wydajne zaimplementowanie potrzebnych operacji.
Drugim celem jest przedstawienie typów generycznych i iteracji — podstawowych
elementów Javy, pozwalających znacznie uprościć kod klienta. Są to zaawansowane
mechanizmy języka programowania, które nie są niezbędne do zrozumienia algoryt
mów, natomiast pozwalają tworzyć kod klienta (i implementacje algorytmów) w bar
dziej przejrzysty, zwięzły i elegancki sposób.
Trzecim celem w podrozdziale jest wprowadzenie powiązanych struktur danych
i pokazanie ich znaczenia. Klasyczna struktura danych, lista powiązana, pozwala za
implementować wielozbiory, kolejki i stosy w bardzo wydajny sposób, nieosiągalny
innymi metodami. Zrozumienie list powiązanych to kluczowy pierwszy krok na dro
dze do poznawania algorytmów i struktur danych.
Dla każdego z trzech wymienionych typów omawiamy interfejsy API i przykła
dowe programy klienckie, a następnie analizujemy możliwe reprezentacje wartości
typu danych oraz implementacje operacji typu. Scenariusz ten (w kontekście bardziej
skomplikowanych struktur danych) powtarza się w książce. Implementacje z tego
miejsca są modelem dla późniejszych implementacji, dlatego warto je starannie prze
studiować.
132
-
1.3 * Wielozbiory, kolejki i stosy 133
W ie lo zb ió r
K olejka FIFO
S to s (k o le jk a LIFO)
Typy generyczne Kluczową cechą typów ADT dla kolekcji jest to, że możliwe po
winno być używanie ich dla dowolnego typu danych. Umożliwia to specyficzny m e
chanizm Javy — typy generyczne (inaczej typy sparametryzowane). Wpływ typów ge
nerycznych na język programowania jest na tyle duży, że w wielu językach (także we
wczesnych wersjach Javy) typy te nie występują. Jednak sposób, w jaki je stosujemy,
wymaga tylko niewielkiej ilości dodatkowej składni Javy i jest łatwy do zrozumienia.
Zapis <Item> po nazwie klasy w każdym interfejsie API określa, że nazwa Item to pa
rametr typu. Jest to symboliczny zastępnik, za który można podstawić konkretny typ
używany w kliencie. Kod Stack<Item> można przeczytać jako „stos elementów”. Przy
implementowaniu typu Stack konkretny typ podstawiany za Item nie jest znany, jed
nak w kliencie można użyć stosu dla dowolnego typu danych, w tym dla typów napi
sanych długo po opracowaniu implementacji stosu. Kod klienta określa konkretny typ
w momencie tworzenia stosu. Można zastąpić Item nazwą dowolnego typu referencyj
nego (należy zrobić to konsekwentnie, w miejscu każdego wystąpienia nazwy Item).
Jest to dokładnie to, czego potrzebujemy. Można na przykład napisać taki kod:
powoduje użycie kolejki dla obiektów Date. Próba dodania obiektu typu Date (lub da
nych dowolnego typu różnego od String) do stack lub obiektu typu String (lub da
nych dowolnego typu różnego od Date) do queue powoduje błąd czasu kompilacji.
Bez typów generycznych konieczne byłoby definiowanie (i implementowanie) róż
nych interfejsów API dla każdego typu danych, który trzeba umieszczać w kolekcji.
Typy generyczne pozwalają zastosować jeden interfejs API (i jedną implementację)
dla wszystkich typów danych — nawet dla typów implementowanych w przyszłości.
Jak się wkrótce okaże, typy generyczne prowadzą do tworzenia przejrzystego kodu
klienta. Kod ten jest łatwy do zrozumienia i w diagnozowaniu, dlatego używamy ta
kich typów w książce.
A utoboxing Jako param etr typu trzeba podać typ referencyjny, dlatego Java udostęp
nia specjalny mechanizm, umożliwiający stosowanie generycznego kodu dla typów
prostych. Przypomnijmy, że typy nakładkowe Javy to typy referencyjne odpowiada
jące typom prostym. Typy Boolean, Byte, Character, Double, Float, Integer, Long
i Short odpowiadają typom bool ean, byte, char, doubl e, float, i nt, 1ong i short. Java
automatycznie dokonuje konwersji między wymienionymi typami referencyjnymi
1.3 o Wielozbiory, kolejki i stosy 135
Technika ta jest też nazywana instrukcją foreach. Instrukcję fo r można czytać tak:
dla każdej transakcji t z kolekcji wykonaj następujący blok kodu. Kod klienta nie musi
znać reprezentacji ani implementacji kolekcji. Musi jedynie przetworzyć każdy z jej
elementów. Ta sama pętla fo r zadziała dla kolekcji Bag z transakcjami lub dowolnej
innej kolekcji z możliwością iterowania. Trudno wyobrazić sobie bardziej przejrzysty
i zwięzły kod klienta. Jak się okaże, zapewnienie obsługi tego mechanizmu wymaga
dodatkowej pracy przy implementowaniu, jednak efekt jest tego wart.
w arto zau w ażyć , że jedyne różnice między interfejsami API typów Stack i Queue to
ich nazwy oraz nazwy metod. To spostrzeżenie dowodzi, że nie można łatwo określić
wszystkich cech typu danych na liście sygnatur metod. Tu rzeczywista specyfikacja
obejmuje opisy w języku polskim, określające reguły wybierania elementu — usu
wanego lub przetwarzanego w instrukcji foreach. Różnice między tymi regułami są
znaczące, stanowią część interfejsu API i, oczywiście, mają kluczowe znaczenie przy
rozwijaniu kodu klienta.
136 R O Z D Z IA L I ■ Podstawy
sum = 0.0;
f o r (double x : numbers)
sum += (x - mean)*(x - mean);
double std = M a th .sq rt(su m / (N -1 ));
Z a s to s o w a n ie % java Sta ts
100
99
101
120
98
107
109
81
101
90
Kolejki FIFO Kolejka FIFO (lub po prostu kolejka) to kolekcja oparta na zasadzie
pierwszy na wejściu, pierwszy na wyjściu (ang. first-in-jirst-out — FIFO). Zasada wy
konywania zadań w kolejności ich nadcho
Serwer
Kolejka klientów dzenia jest często spotykana w codziennym
{
Tc
życiu — od osób czekających w kolejce po
m m m bilet do teatru, przez samochody stoją
N o w y element
ce przed budką poboru opłat, po zadania
Dodaw anie trafia na koniec oczekujące na wykonanie przez aplikację
do kolejki ł w komputerze. Podstawą wszystkich reguł
m Ta
mmmm obsługi jest uczciwość. Większość osób
N o w y element za uczciwe rozwiązanie uznaje to, że jed
trafia na koniec
Dodaw anie nostka oczekująca najdłużej powinna zo
do kolejki ł
CD Tm m m m m stać obsłużona jako pierwsza. Tak właśnie
działa kolejka FIFO. Kolejki są naturalnym
Pierwszy element modelem wielu codziennych zjawisk i od
opuszcza
Usuwanie kolejkę grywają kluczową rolę w wielu aplikacjach.
z kolejki I Kiedy klient przechodzi po elementach ko
m m Tmmmm lejki za pom ocą techniki fo r each, elementy
Następny element są przetwarzane w kolejności ich dodawa
opuszcza nia do kolejki. Kolejki w aplikacjach stosu
Usuwanie kolejkę
je się głównie po to, aby zapisać elementy
z kolejki ł
m CD Tm m m w kolekcji, zachowując przy tym ich względ
ną kolejność. Elementy opuszczają kolejkę
T ypow a kolejka FIFO w tej samej kolejności, w jakiej je do niej
dodano. Przykładowo, przedstawiony dalej
klient to implementacja m etody statycznej r e a d D o u b le s () z opracowanej przez nas
klasy In. M etoda ta pozwala klientowi pobierać liczby z pliku do tablicy, bez uprzed
niej znajomości rozmiaru pliku. Metoda dodaje do kolejki liczby z pliku, używa m eto
dy s i z e () typu Queue do określenia
rozmiaru tablicy, tworzy ją, a na p u b lic s t a t ic i n t [] r e a d ln t s ( S t r in g name)
stępnie usuwa z kolejki liczby, aby {
In in = new In(nam e);
przenieść je do tablicy. Kolejka jest
Queue<Integer> q = new Q ueu e< Intege r> ();
odpowiednia, ponieważ powoduje w hile (lin . is E m p t y O )
umieszczanie liczb w tablicy w ko q .e n q u e u e (in .re a d ln t ());
lejności, w jakiej występują w pliku
in t N = q . s i z e ( ) ;
(jeśli kolejność jest nieistotna, m oż i nt [] a = new i nt [N ];
na użyć typu Bag). W kodzie wyko f o r ( in t i = 0; i < N; i++)
rzystano autoboxing i autounboxing a [i ] = q.dequeued ;
re tu rn a;
do przekształcania między typem
prostym doubl e z kodu klienta a ty
pem nakładkowym D o u b le używa Przykładowy klient typu Queue
nym w kolejce.
1.3 Q Wielozbiory, kolejki i stosy 139
I
14 0 R O Z D Z IA L I ■ Podstawy
public c la s s Evaluate
{
p u b l i c s t a t i c void m a i n ( S t r i n g [ ] args)
Przedstawiony klient typu Stack używa dwóch stosów do obliczania wyrażeń arytmetycznych.
Jest to ilustracja podstawowego procesu z dziedziny przetwarzania — interpretowania łańcu
cha znaków jako programu i wykonywania go w celu obliczenia pożądanego wyniku. Dzięki
typom generycznym można użyć kodu z jednej implemen
tacji typu Stack do zaimplementowania stosu wartości %ja va Evaluate
typu S tri ng i stosu wartości typu Doubl e. Dla uproszczę- ( l + ( ( Z + 3 ) * ( 4 * 5 ) ) )
nia w kodzie przyjęto, że wyrażenie zapisano w notacji na
wiasowej, a liczby i znaki są oddzielone odstępami. % java Evaluate
( ( 1 + sq rt ( 5.0 ) ) / 2.0 )
1.618033988749895
142 ROZDZIALI a Podstawy
( 1+ ( ( 2 + 3 ) * ( 4 * 5 ) ) )
( 1+ ( 5 * ( 4 * 5 ) ) )
( 1+ ( 5 *20 ) )
( 1+100 )
101
( l + ( ( 2 + 3 ) * ( 4 * 5 ) ) )
y O perand - um ieszczany n a stosie operandów
Stos jI, . .,
operandów ~''x_ l + ( ( 2 + 3 ) * ( 4 * 5 ) ) )
Stos
fcd
p j ---------
^ Operator - um ieszczany na stosie operatorów
+ ( ( 2 + 3 ) * ( 4 * 5 ) ) )
operatorów ' sx | + 1
( ( 2 + 3) * ( 4 * 5 ) ) )
11
( 2 + 3 ) * ( 4 * 5 ) ) )
2 + 3 ) * (4 * 5 ) ) )
+ 3 ) * ( 4 * 5) ) )
1 2
++
3 ) * ( 4 * 5 ) ) )
Praw y naw ias - zdejm ow anie operatora
j / i operandów oraz um ieszczanie w yniku na stosie
) * C4 * 5 ) ) )
* ( 4 * 5 ) ) )
[U
( 4*5) ))
4*5)))
11 5 4
II + * 1
* 5) ) )
Il *5 *4
1+
5) ) )
II1 5* 4* 5:
II*
) ) )
u 5 20 ;
L+. A |
) )
11 100
1+
)
| 101 i
Z a s to s o w a n ie % more to b e .txt
to be or not t o - b e - - that - - - i s
A b s tra k c y jn y t y p d a n y c h d la s to s u o s ta łe j d łu g o ś c i
n a ła ń c u c h y z n a k ó w
146 R O Z D Z IA L I ■ Podstawy
Zastosowanie
% more to b e .txt
to be or not t o - b e - - that - - - i s
czwartej rozm iaru tablicy. Po zmniejszeniu wielkości tablicy o połowę będzie ona
w połowie pełna, co pozwoli wykonać wiele operacji push ( ) i pop ( ) , zanim niezbęd
na będzie ponowna zmiana rozmiaru.
public S t r in g pop()
{ // Zdejmowanie elementu z wierzchu stosu.
S t r in g i tern = a [--N ];
a[N] = n u li; // Likwidowanie zbędnych referencji
// (zobacz opis w te k ście ),
i f (N > 0 && N == a.length/4) r e s iz e ( a . le n g t h / 2 );
return i tern;
}
W tej implementacji stos nigdy nie zostaje przepełniony i nigdy nie jest zajęty w mniej
niż jednej czwartej (o ile nie jest pusty — wtedy rozmiar tablicy to 1). Szczegółowe
analizy dotyczące wydajności tego podejścia przedstawiono w p o d r o z d z i a l e 1 .4 .
Zbędne referencje Reguły przywracania pamięci w Javie powodują odzyskiwanie
pamięci powiązanej z obiektami, do których dostęp jest niemożliwy. W przedstawio
nych tu implementacjach m etody pop () referencja do pobranego elementu pozostaje
w tablicy. Element jest w zasadzie osierocony — kod nigdy już nie będzie z niego
korzystał — jednak mechanizm przywracania pamięci nie potrafi tego stwierdzić do
czasu nadpisania elementu. Nawet kiedy klient skończy korzystać z elementu, refe
rencja w tablicy może sprawić, że element nie zostanie usunięty. Przechowywanie
referencji do niepotrzebnego elementu prowadzi do powstawania zbędnych referencji
(ang. loitering). Tu można łatwo uniknąć tego zjawiska, ustawiając odpowiadający
zdjętemu elementowi wpis w tablicy na nuli. Powoduje to nadpisanie nieużywanej
referencji i umożliwia systemowi przywrócenie pamięci powiązanej ze zdjętym ele
mentem, kiedy klient skończy z niego korzystać.
Ślad procesu zmieniania wielkości tablicy przy wykonywaniu serii operacji push() i pop()
150 R O Z D Z IA L I a Podstawy
Ta instrukcja foreach jest skrótem dla struktury whi 1e (podobnie jak sama instrukcja
for); stanowi odpowiednik poniższej instrukcji whi 1 e:
Iterato r< S trin g > i = col l e c tio n .i t e r a t o r ( ) ;
while (i .hasNext())
{
S tring s = i .n e x t( ) ;
S td O u t.p rin tln (s);
}
Ten kod obejmuje elementy potrzebne do zaimplementowania dowolnej kolekcji
z możliwością iterowania:
■ Kolekcja musi obejmować implementację m etody ite r a to r ( ) zwracającej
obiekt typu Ite ra to r.
■ Klasa Ite r a to r musi obejmować dwie metody: hasNext () (zwracającą wartość
typu bool ean) i next () (zwraca element generyczny z kolekcji).
W Javie do określania, że klasa zawiera implementację konkretnej metody, służy sło
wo i n te rf ace (zobacz stronę 112). W kolekcjach z możliwością iterowania niezbędne
interfejsy są już zdefiniowane w Javie. Aby umożliwić iterowanie po klasie, najpierw
trzeba dodać do jej deklaracji fragment implements Iterable<Item >, odpowiadający
interfejsowi:
public in te rfa c e Iterable<Item >
{
Iterator<Item > i t e r a to r ( ) ;
}
Interfejs ten znajduje się w bibliotece jav a. 1an g .Ite ra b le . Do klasy trzeba też do
dać metodę ite r a to r ( ) zwracającą obiekt Iterator<Item >. Iteratory są generyczne,
dlatego można używać sparametryzowanego typu Item, aby umożliwić klientom ite
rowanie po obiektach niezależnie od podanego typu. W używanej tu reprezentacji
1.3 o Wielozbiory, kolejki i stosy 151
Czym jest iterator? Jest to obiekt klasy z implementacją m etod hasNext() i next(),
co określa poniższy interfejs (znajduje się on w bibliotece j ava. uti 1 . Iterator):
public interface Iterator<Item>
{
boolean hasNext();
Item next() ;
void remove();
}
Choć w interfejsie określono metodę remove(), w tej książce zawsze jest ona pusta,
ponieważ najlepiej jest unikać łączenia iteracji z operacjami modyfikującymi struktu
rę danych. W iteratorze ReverseArraylterator wszystkie metody mają jeden wiersz
i są zaimplementowane w klasie zagnieżdżonej w ldasie stosu:
private c la s s ReverseArraylterator implements Iterator<Item>
{
private in t i = N;
alg o rytm i . i to implementacja interfejsu API opracowanego przez nas typu Stack.
Implementacja zmienia wielkość tablicy, umożliwia klientom tworzenie stosów dla
dowolnego typu danych i pozwala na stosowanie instrukcji foreach do iterowania po
elementach stosu w porządku LIFO. Implementację oparto na mechanizmach Javy,
w tym interfejsach I te r a to r i Ite ra b le , nie trzeba jednak szczegółowo ich pozna
wać, ponieważ sam kod jest prosty i można go wykorzystać jako szablon dla innych
implementacji kolekcji.
Przykładowo, m ożna zaimplementować interfejs API typu Queue, przechowując
dwa indeksy jako zmienne egzemplarza, zmienną head określającą początek kolejki
i zmienną ta i 1 wyznaczającą jej koniec. Aby usunąć element, należy użyć zmiennej
head w celu uzyskania dostępu do elementu, a następnie zwiększyć wartość tej zm ien
nej. Aby wstawić element, należy wykorzystać zmienną ta i 1 do jego zapisania, a na
stępnie zwiększyć tę zmienną. Jeśli inkrementacja indeksu prowadzi do wyjścia poza
koniec tablicy, należy ustawić indeks na 0. Opracowanie szczegółów sprawdzania,
czy kolejka jest pusta i czy tablica jest pełna, co wymaga jej rozszerzenia, to ciekawe
i warte wykonania ćwiczenie programistyczne (zobacz ć w ic z e n ie 1 .3 . 1 4 ).
5 0 5 to be or not to
- to 4 1 5 to be or not to
be - 5 1 6 to be or not to be
- be 4 2 6 to be or not to be
_ or 3 3 6 to be or that to be
import j a v a . u t i l . I t e r a t o r ;
public c la s s ResizingArrayStack<Item> implements Iterable<Item>
{
p rivate Item [] a = ( Item [ ] ) new Object[1]; // E l e m e n t y s t o s u ,
private in t N = 0; // L i c z b a e l e m e n t ó w .
public Iterator<Item> it e r a t o r ( )
{ return new R e ve rs e A rra y It e ra to r(); )
Ta generyczna implementacja z możliwością iterowania dla interfejsu API typu Stack jest
modelem dla typów ADT kolekcji przechowujących elementy w tablicy. Implementacja
zmienia wielkość tablicy, aby była zależna liniowo od rozmiaru stosu.
R O ZD ZIA Ł 1 b Podstawy
Listy powiązane Rozważmy teraz podstawową strukturę danych, która jest od
powiednia do reprezentowania danych w implementacjach typów ADT dla kolekcji.
Jest to pierwszy przykład, w którym pokazano budowanie struktury danych nieob-
sługiwanej bezpośrednio przez Javę. Przedstawiona implementacja jest modelem
kodu używanego do budowania w książce bardziej skomplikowanych struktur da
nych, dlatego powinieneś starannie zapoznać się z tym fragmentem, nawet jeśli masz
doświadczenie w stosowaniu list powiązanych.
Definicja. Lista powiązana to rekurencyjna struktura danych, która jest albo pusta
(nul 1 ), albo jest referencją do węzła zawierającego generyczny element i referencję
do listy powiązanej.
Węzeł w tej definicji to abstrakcyjna jednostka, która może przechowywać dane dowol
nego rodzaju, a także referencję do węzła, określającą rolę elementu w liście powiązanej.
Rekurencyjna struktura danych, podobnie jak program rekurencyjny, początkowo może
być trudna do zrozumienia, ma jednak bardzo dużą wartość z uwagi na jej prostotę.
Rekord w ęzła W programowaniu obiektowym implementowanie list powiązanych
nie jest trudne. Zaczynamy od klasy zagnieżdżonej z definicją abstrakcyjnego węzła:
private c la s s Node
{
Item item;
Node next;
}
Klasa Node ma dwie zmienne egzemplarza — Item (typ sparametryzowany) i Node.
Klasę Node należy zdefiniować w klasie, w której będzie używana, i poprzedzić m ody
fikatorem private, ponieważ klienty nie będą z niej korzystać. Obiekt typu Node, tak
jak każdego innego typu danych, można tworzyć przez wywołanie konstruktora bez
argumentów — new Node (). Powstaje w ten sposób referencja do obiektu typu Node,
którego obie zmienne egzemplarza są zainicjowane wartością null. Item to miejsce
na dane porządkowane za pomocą listy powiązanej (używamy typów generycznych
Javy, aby można było zastosować dowolny typ referencyjny). Zmienna egzemplarza
typu Node pozwala powiązać omawianą strukturę danych. Aby podkreślić, że klasa
Node służy tylko do strukturyzowania danych, nie definiujemy dla niej żadnych metod,
a w kodzie stosujemy bezpośrednio zmienne egzemplarza. Jeśli first to zmienna typu
Node, zmienne egzemplarza można wskazywać za pomocą kodu f irs t . i tem i fir s t . next.
Klasy tego rodzaju czasem nazywa się rekordami. Nie są one implementacjami abstrak
cyjnych typów danych, ponieważ bezpośrednio wskazujemy ich zmienne egzemplarza.
Jednak we wszystkich omawianych tu implementacjach typ Node i kod klienta tego typu
znajdują się w tej samej klasie, a obiekt typu Node nie jest dostępny dla klientów tej klasy,
dlatego nadal można czerpać korzyści ze stosowania abstrakcji danych.
1.3 s Wielozbiory, kolejki i stosy 155
B udow anie listy pow iązanej Na podstawie rekurencyjnej definicji można przed
stawić listę powiązaną za pomocą zmiennej typu Node. Należy zapewnić, że wartość
zmiennej to albo nuli, albo referencja do obiektu typu Node, którego pole next jest re
ferencją do listy powiązanej. Przykładowo, aby zbudować listę powiązaną obejmującą
elementy to, be i or, m ożna utworzyć obiekt typu Node dla każdego elementu:
Node first = new Node();
Node second = new Node();
Node th ird = new NodeQ;
reprezentuje ciąg to be or. Ciąg elementów m ożna też zapisać jako tablicę. Na przy
kład można użyć kodu:
S tri ng [] s = { " t o " , "be", "o r " };
Node o l d f i r s t = f i r s t ;
o l d fi r s t
f i r s t = new Node() ;
o ld f i r s t
f i r s t . item = "not";
f i r s t . next = o l d f i rs t ;
Node l a s t = new N o d e O ;
W sta w ia n ie i u su w a n ie n a innych
la st.ite m = " n o t " ;
p o zycja ch W skrócie pokazano, że
poniższe operacje na liście powiązanej
m ożna zaimplementować za pomocą
tylko kilku instrukcji, pod warunkiem
że istnieje dostęp do odnośnika first D ołączanie no w ego w ęzła na koniec listy
(do pierwszego elementu) i la s t (do o l d l a s t . next = l a s t ;
ostatniego elementu). Oto te operacje;
" wstawianie na początek,
■ usuwanie z początku,
■ wstawianie na koniec.
W sta w ia n ie n o w e g o w ę zła na k o n ie c listy p o w ią z a n e j
1 58 RO ZD ZIA Ł 1 n Podstawy
są potrzebne.
Przechodzenie Do sprawdzania każdego elementu tablicy służy znany kod, taki jak
poniższa pętla do przetwarzania elementów tablicy a []:
fo r ( in t i = 0; i < N; i++)
{
// Przetwarzanie a [ i ].
}
Istnieje podobny idiom do sprawdzania elementów z listy powiązanej. Należy zai
nicjować zmienną indeksującą pętli, x, referencją do pierwszego obiektu Node na li
ście powiązanej. Następnie trzeba znaleźć element powiązany z x, pobierając wartość
x.item, a potem zmodyfikować x, żeby prowadziła do następnego obiektu Node listy
powiązanej (do zmiennej należy przypisać wartość x.next). Proces ten jest powta
rzany dopóty, dopóki x ma wartość różną od nul 1. Wartość nul 1 oznacza dojście do
końca listy powiązanej. Proces ten to przechodzenie po liście. Można go zwięźle za
pisać za pomocą kodu podobnego do poniższej pętli, przetwarzającej elementy listy
powiązanej, w której do pierwszego elementu prowadzi zmienna first:
fo r (Node x = first; x != n u li; x = x.next)
{
// Przetwarzanie x.item.
}
Idiom ten jest tak naturalny, jak standardowy idiom do iterowania po elementach
tablicy. W implementacjach w tej książce używamy go jako podstawy dla iteratorów,
aby umożliwić w kodzie klienta iterowanie po elementach bez znajomości szczegó
łów implementacji listy powiązanej.
1.3 a Wielozbiory, kolejki i stosy 159
S t d O u t . p r in t ln ( "(elementy na s t o s ie : " + s . s i z e ( ) +
}
Klient te s to w y d la ty p u S tack
160 RO ZD ZIA Ł 1 □ Podstawy
A L G O R Y T M 1.2. S t o s — im p le m e n ta c ja z w y k o rz y s ta n ie m listy p o w ią za n e j
private c la s s Node
{ // Klasa zagnieżdżona z definicją węzłów.
Item item;
Node next;
)
Im plem entacja kolejki Implementacja interfejsu API opracowanego przez nas typu
Queue oparta na liście powiązanej także jest prosta, co pokazano w a l g o r y t m i e 1.3
na następnej stronie. Kolejka jest przechowywana na liście powiązanej w kolejności
od najdawniej do ostatnio dodanego elementu. Do początku kolejki prowadzi zmien
na egzemplarza first, a do końca — zmienna egzemplarza la s t. Dlatego aby dodać
element do kolejki (m etoda e n qu e u e ()), należy umieścić go na końcu listy, używa
jąc kodu opisanego na stronie 157, rozwiniętego tak, aby ustawiał first i la s t na
nowy węzeł, jeśli lista jest pusta. W celu usunięcia elementu (metoda dequeue ()) na
leży skasować go z początku listy, używając tego samego kodu, co dla m etody pop ()
w klasie Stack, wzbogaconego o aktualizację zmiennej 1a st, kiedy lista staje się pusta.
Implementacje m etod si ze () i isEmptyO są takie same jak w klasie Stack. Tu, po
dobnie jak w implementacji klasy Stack, użyto generycznego param etru typu Item
i pominięto kod do obsługi iterowania, omówiony w ramach implementacji klasy Bag
na stronie 167. Dalej pokazano klienta wspomagającego tworzenie aplikacji podob
nego do tego dla klasy Stack. Na następnej stronie znajduje się ślad działania klienta.
W implementacji wykorzystano tę samą strukturę danych, co w klasie Stack (listę
powiązaną), jednak zaimplementowano inne algorytmy do dodawania i usuwania
elementów, co z perspektywy klienta robi różnicę między porządkiem LIFO i FIFO.
Także tu zastosowanie listy powiązanej pozwala zrealizować cele projektowe — roz
wiązanie m ożna wykorzystać dla dowolnego typu danych, ilość potrzebnej pamięci
jest proporcjonalna do liczby elementów w kolekcji, a czas potrzebny na wykonanie
operacji jest zawsze niezależny od rozmiaru kolekcji.
% more to b e .txt
to be o r not t o - b e - - that - - - i s
private c la ss Node
{ // Klasa zagnieżdżona z definicją węzłów.
Item item;
Node next;
}
Dzięki temu w kodzie m ożna używać interfejsu I te r a to r Javy. Drugi krok to dodanie
do deklaracji klasy kodu:
implements Iterable<Item>
listy. M etoda hasNext() sprawdza, czy zmienna current ma wartość n u li, a metoda
next() zapisuje referencję do aktualnego elementu, aktualizuje zmienną current tak,
aby wskazywała na następny węzeł listy, i zwraca zapisaną referencję.
1.3 Wielozbiory, kolejki i stosy 1 67
p rivate c la s s Node
{
Item item;
Node next;
}
public Iterator<Item> i t e r a t o r ()
{ return new L i s t I t e r a t o r ( ) ; }
W tej implementacji klasy Bag przechowywana jest lista powiązana elementów podanych
w wywołaniach metody add(). Kod metod isEmpty() i s iz e () jest taki sam, jak w kla
sie Stack, dlatego go pominięto. Iterator przechodzi po liście, zachowując aktualny węzeł
w zmiennej current. Można umożliwić przechodzenie po klasach Stack i Queue, dodając
wyróżniony kod do a l g o r y t m ó w i . i i 1 .2 , ponieważ w klasach tych użyto tej samej struk
tury danych, przy czym lista przechowywana jest w kolejności LIFO i FIFO.
168 R O Z D Z IA L I o Podstawy
Drzewo z odnośnikiem
1.5 Uni onFi nd Tablica liczb całkowitych
do rodzica
Binarne drzewo
3.2, 3.3 BST Dwa odnośniki na węzeł
wyszukiwań
Tablica, przesunięcie
Łańcuch znaków 5.1 S t rin g
i długość
Sterta binarna 2.4 PQ Tablica obiektów
Tablica z haszowaniem
3.4 SeperateChai ni ngHashST Tablica list powiązanych
(metoda łańcuchowa)
Tablica z haszowaniem
3.4 Li nearProbi ngHashST Dwie tablice obiektów
(badanie liniowe)
stack.push( a ) ;
Ostrzeżenie: takie rzutowanie w kodzie klienta różni się od tego opisanego na stro
nie 146. Możliwe, że spodziewałeś się użycia typu Object zamiast Stack. Przy sto
sowaniu typów generycznych Java sprawdza bezpieczeństwo typów na etapie kom
pilacji, jednak w czasie wykonania programu nie korzysta z uzyskanych informacji,
dlatego dostępna jest struktura Stack<Object>[] (w skrócie Stack[]), którą trzeba
zrzutować na typ Stack<String>[].
1.3 Q Wielozbiory, kolejki i stosy 171
P. Po co przejmować się zmienianiem wielkości tablicy, skoro można użyć list po
wiązanych?
O. Dalej opisano kilka implementacji typów ADT, w których trzeba użyć tablic do
wykonania operacji, jakich nie da się łatwo zrealizować za pom ocą list powiązanych.
Klasa ResizingArrayStack to model kontrolowania wykorzystania pamięci zajmo
wanej przez tablice.
O. Jest to plik klasy wewnętrznej Node. Konwencje nazewnicze Javy wymagają użycia
znaku $ do rozdzielenia nazw klas — zewnętrznej i wewnętrznej.
O. I tak, i nie. Java posiada wbudowaną bibliotekę o nazwie ja va. ut i 1. Stack, jednak
należy jej unikać, jeśli potrzebny jest stos. Posiada ona kilka dodatkowych operacji
(na przykład pobieranie i-tego elementu), które zwykle nie są powiązane ze stosem.
Ponadto umożliwia umieszczenie elementu na dole stosu (zamiast na wierzchu), dla
tego można zaimplementować w ten sposób kolejkę! Choć dodatkowe operacje mogą
wydawać się korzystne, w rzeczywistości są wadą. Stosujemy typy danych nie tylko
jako biblioteki wszystkich możliwych operacji, ale też jako mechanizm do precyzyj
nego określania potrzebnych operacji. Podstawową zaletą takiego podejścia jest to,
17 2 R O Z D Z IA L I □ Podstawy
P. Czy należy umożliwiać klientom wstawianie elementów nul 1 do stosu lub kolejki?
O. To pytanie często pojawia się przy implementowaniu kolekcji w Javie. W imple
mentacjach z tej książki (i w bibliotekach Javy do obsługi stosów oraz kolejek) wsta
wianie wartości nul 1 jest dozwolone.
P. Jak powinien działać iterator klasy Stack, kiedy klient wywoła push () lub pop()
w trakcie iterowania?
P. Dlaczego nie warto tworzyć jednego typu danych Col 1e c ti on, który obejmował
by implementację m etod do dodawania elementów, usuwania ostatnio wstawionego,
usuwania najdawniej wstawionego, usuwania dowolnego, zwracania liczby elemen
tów w kolekcji i wykonywania wszystkich innych potrzebnych operacji? Pozwoliłoby
to zaimplementować wszystkie m etody w jednej klasie używanej w wielu klientach.
I ĆWICZENIA
1.3.4. Napisz klienta Parantheses dla stosu. Klient ma wczytywać strum ień teks
towy ze standardowego wejścia i używać stosu do określenia, czy nawiasy są odpo
wiednio zagnieżdżone. Przykładowo, program powinien wyświetlić tru e dla ciągu
[()]{ } { [()()]()} i fa lse dla [(]).
1.3.5. Co wyświetli poniższy fragment kodu dla Nrównego 50? Podaj wysokopozio-
mowy opis działania kodu dla dodatniej liczby całkowitej N.
1.3.7. Dodaj do klasy Stack metodę peek() zwracającą (bez zdejmowania) element
ostatnio wstawiony do stosu.
1.3.8. Podaj zawartość i wielkość tablicy typu Doubl i ngStackOfStrings po wprowa
dzeniu następujących danych wejściowych:
1 + 2) * 3 - 4 ) * 5 - 6 ) ) )
program ma wyświetlić:
( ( 1 + 2 ) * ( ( 3 - 4 ) * ( 5 - 6 ) )
a. 0 1 2 3 4 5 6 7 8 9
b. 4 6 8 7 5 3 2 9 0 1
c. 2 5 6 7 4 8 9 3 1 0
d. 4 3 2 1 0 5 6 7 8 9
1.3.16. Używając jako modelu metody re ad ln ts () ze strony 138, napisz metodę sta
tyczną readDates () dla typu Date. Metoda ma wczytywać ze standardowego wejścia
daty w formacie określonym w tabeli na stronie 131 i zwracać zawierającą je tablicę.
Ćwiczenia z tej listy mają pomóc zdobyć doświadczenie w korzystaniu z list powią
zanych. Oto sugestia — rysunki wykonaj za pomocą wizualnej reprezentacji opisanej
w tekście.
1.3.1 8 . Załóżmy, że x to węzeł listy powiązanej, który nie znajduje się na jej końcu.
Jaki efekt ma wywołanie poniższego fragmentu kodu:
x.next = x .n ex t.n ex t;
Odpowiedź: kod powoduje usunięcie z listy węzła znajdującego się bezpośrednio po x.
1.3.19. Napisz fragment kodu usuwający ostatni węzeł z listy powiązanej, której
pierwszy węzeł to first.
1.3.20. Napisz metodę del e te (), która przyjmuje argument k typu i nt i usuwa k-ty
element listy powiązanej, jeśli taki istnieje.
1.3.21. Napisz metodę find ( ), która przyjmuje jako argumenty listę powiązaną i łań
cuch znaków key oraz zwraca true, jeśli pole i tem jednego z węzłów listy ma wartość
równą key. W przeciwnym razie m etoda ma zwracać fal se.
1.3.22. Załóżmy, że x to węzeł listy powiązanej. Jak działa poniższy fragment
kodu?
t.n e x t = x.next;
x.next = t ;
Odpowiedź: wstawia węzeł t bezpośrednio za węzłem x.
1.3.23. Dlaczego poniższy fragment kodu nie działa tak samo, jak kod z poprzed
niego ćwiczenia?
x.next = t ;
t.n e x t = x.next;
Odpowiedź: w czasie aktualizowania t.n e x t pole x.next nie prowadzi do węzła znaj
dującego się pierwotnie po x, ale do samego t!
1.3.24. Napisz metodę removeAfter(), która przyjmuje jako argument węzeł listy
powiązanej i usuwa węzeł znajdujący się po podanym (lub nie podejmuje żadnych
działań, jeśli sam argument lub pole next węzła podanego w argumencie to nul 1 ).
1.3.25. Napisz metodę i n sertA fter (), która przyjmuje jako argumenty dwa węzły
listy powiązanej i wstawia drugi po pierwszym na liście tego ostatniego (lub nie wy
konuje żadnych operacji, jeśli choć jeden argument to nul 1 ).
1.3 o Wielozbiory, kolejki i stosy 177
1.3.26. Napisz metodę remove () przyjmującą jako argumenty listę powiązaną i łań
cuch znaków key oraz usuwającą z listy wszystkie węzły, w których pole i tem ma
wartość key.
1.3.27. Napisz metodę max (), która przyjmuje jako argument referencję do pierw
szego węzła listy powiązanej i zwraca wartość maksymalnego klucza z listy. Załóżmy,
że wszystkie klucze to dodatnie liczby całkowite. Jeśli lista jest pusta, m etoda ma
zwracać 0.
1.3.30. Napisz funkcję, która jako argument przyjmuje pierwszy węzeł listy powiąza
nej, odwraca kolejność listy (niszcząc ją samą) i zwraca jako wynik pierwszy węzeł.
I PROBLEMY DO ROZWIĄZANIA
1.3.32. Steque. Steque, czyli kolejka zakończona stosem, to typ danych obsługujący
operacje push, pop i enqueue. Określ interfejs API dla takiego typu ADT. Opracuj
implementację opartą na liście powiązanej.
1.3.33. Deque. Deque, czyli kolejka o dwóch końcach, przypomina stos lub kolejkę,
ale umożliwia dodawanie i usuwanie elementów po obu stronach. Deque przechowu
je kolekcję elementów i obsługuje następujący interfejs API:
Napisz klasę RandomBag z implementacją tego interfejsu API. Zauważ, że interfejs jest
niemal taki sam, jak dla klasy Bag. Różnicą jest słowo random (czyli losowy), oznacza
jące, że iterator powinien zwracać elementy w losowej kolejności (każda z N! permu-
tacji powinna być równie prawdopodobna). Wskazówka: umieść elementy w tablicy
i określ dla nich losową kolejność w konstruktorze iteratora.
180 R O Z D Z IA L I n Podstawy
p u b lic c la s s RandomQueue<Item>
% java Josephus 7 2
1 3 5 0 4 2 6
1.3 a Wiehzbiory, kolejki i stosy
1.3.39. Bufor cykliczny. Bufor cykliczny (inaczej kolejka cykliczna) to struktura da
nych typu FIFO o stałej wielkości N. Jest przydatna do przenoszenia danych między
asynchronicznymi procesami lub do zapisywania plików dziennika. Kiedy bufor jest
pusty, konsum ent czeka do czasu dostarczenia danych. Jeśli bufor jest pełny, produ
cent czeka na możliwość dodania danych. Opracuj interfejs API klasy RingBuffer
oraz implementację opartą na tablicy (z cyklicznym „zawijaniem”).
1.3.42. Kopiowanie stosu. Utwórz nowy konstruktor dla implementacji klasy Stack
opartej na liście powiązanej. Instrukcja:
Stack<Item> t = new Stack<Item >(s);
1.3.44. Bufor edytora tekstu. Opracuj typ danych dla bufora w edytorze tekstu i za
implementuj następujący interfejs API:
p u b lic c la s s B u ffe r
In te rf e js API d la b u fo r a n a t e k s t
Rozwiązanie: próba odczytu z pustego stosu nie ma miejsca, o ile nie istnieje liczba k,
taka że pierwszych k operacji zdejmowania następuje przed pierwszymi k operacjami
dodawania. Jeśli daną permutację można wygenerować, powstaje zawsze w następu
jący sposób: jeżeli następna liczba całkowita w wyjściowej permutacji znajduje się na
wierzchu stosu, należy ją zdjąć; w przeciwnym razie należy umieścić ją na stosie.
1.3.46. Niedozwolone trójki. Udowodnij, że permutację na podstawie stosu można
wygenerować (jak opisano to w poprzednim ćwiczeniu) wtedy i tylko wtedy, jeśli nie
obejmuje ona żadnej niedozwolonej trójki (a, b, c), takiej że a < b < c i c jest pierw
sze, a drugie, natomiast b — trzecie (między c i a oraz a i b mogą znajdować się inne
wartości).
1.3.48. Dwa stosy oparte na strukturze deque. Zaimplementuj dwa stosy za pomocą
jednej struktury deque, tak aby każda operacja wymagała stałej liczby operacji na
strukturze deque (zobacz ć w i c z e n i e 1 .3 .3 3 ).
Rozwiązanie: należy przechowywać licznik dla liczby operacji push () i pop (). W cza
sie tworzenia iteratora wartość tę trzeba zapisać jako zmienną egzemplarza z kla
sy Ite ra to r. Przed każdym wywołaniem hasNext() i next() należy sprawdzić, czy
wartość nie zmieniła się od czasu utworzenia iteratora. Jeśli została zmodyfikowana,
należy zgłosić wyjątek.
ludzie Z a
W R A Z Z NA B Y W A N IEM D O Ś W IA D C Z E N IA W KOR ZY STA N IU Z K O M P U T E R Ó W
czynają używać ich do rozwiązywania trudnych problemów lub przetwarzania du
żych ilości danych. Niezmiennie prowadzi to do pytań w rodzaju:
Jak długo zajmie wykonywanie programu?
Dlaczego programowi zabrakło pamięci?
Z pewnością zadawałeś sobie te pytania, na przykład w trakcie przebudowywania
biblioteki utworów muzycznych lub zdjęć, instalowania aplikacji, pracy nad dużym
dokumentem lub używania dużej ilości danych z eksperymentów. Pytania te są zbyt
ogólne, aby m ożna było na nie precyzyjnie odpowiedzieć. Odpowiedzi zależą od wie
lu czynników, takich jak cechy używanego komputera, przetwarzane dane i program
wykonujący operacje (z implementacją pewnego algorytmu). Wszystkie te czynniki
sprawiają, że trzeba przeanalizować olbrzymią ilość informacji.
Mimo tych trudności droga do uzyskania przydatnych odpowiedzi na podstawo
we pytania jest często niezwykle prosta, co pokazano w tym podrozdziale. Proces
oparty jest na metodzie naukowej — powszechnie przyjętym zestawie technik uży
wanych przez naukowców do zbierania wiedzy o świecie. Stosujemy analizę mate
matyczną do opracowywania zwięzłych modeli kosztów i przeprowadzamy badania
eksperymentalne w celu potwierdzenia tych modeli.
% morę lM in t s . t x t
ny (zawierają one 1000, 2000, 4000 i 8000 liczb całkowitych z pliku
324110 lM ints.txt). Można szybko określić, że w pliku lK ints.txt znajduje się
-442472 70 trójek sumujących się do 0, a w pliku 2Kints.txt — 528 takich trój
626686
-157678
ek. Programowi znacznie więcej czasu zajmuje ustalenie, że w pliku
508681 4Kints.txt jest 4039 trójek sumujących się do 0, a w czasie oczekiwania
123414 na zakończenie sprawdzania pliku 8Kints.txt zadasz sobie pytanie: Jak
-77867
długo potrwa wykonywanie programu? Jak się okaże, uzyskanie odpo
155091
129801 wiedzi na to pytanie dla omawia
% j a v a Threesum l K i n t s . t x t
287381 nego kodu jest łatwe. Dość często
604242
m ożna poczynić całkiem precy
686904 tik tik tik
-247109
zyjne prognozy w czasie działania
77867 programu.
982455 70
-210707 Stoper W iarygodny pom iar do
% j a v a Threesum 2 l C i n t s . t x t
-922943 kładnego czasu działania progra
-738817
mu może być trudny. Na szczęś tik tik tik tik tik tik tik tik
85168 tik tik tik tik tik tik tik tik
855430 cie, zwykle wystarczą szacunki. tik tik tik tik tik tik tik tik
Chcemy móc odróżnić programy
kończące pracę w kilka sekund lub 528
0
k tik tik tik tik tik tik tik
zajmuje kilka dni, miesięcy, a nawet więcej czasu. k tik tik tik tik tik tik tik
Chcemy też wiedzieć, kiedy jeden program jest k tik tik tik tik tik tik tik
k tik tik tik tik tik tik tik
dwa razy szybszy od innego w wykonywaniu da k tik tik tik tik tik tik tik
nego zadania. Nadal trzeba dokonać dokładnych k tik tik tik tik tik tik tik
k tik tik tik tik tik tik tik
pomiarów w celu wygenerowania danych eks k tik tik tik tik tik tik tik
perymentalnych, które m ożna wykorzystać do k tik tik tik tik tik tik tik
k tik tik tik tik tik tik tik
sformułowania i walidacji hipotez dotyczących k tik tik tik tik tik tik tik
zależności między czasem działania a rozmia k tik tik tik tik tik tik tik
k tik tik tik tik tik tik tik
rem problemu. Do pomiarów posłuży typ danych k tik tik tik tik tik tik tik
Stopwatch przedstawiony na następnej stronie. k tik tik tik tik tik tik tik
k tik tik tik tik tik tik tik
Metoda elapsedTime() zwraca czas (w sekun k tik tik tik tik tik tik tik
dach), który upłynął od czasu utworzenia obiek k tik tik tik tik tik tik tik
k tik tik tik tik tik tik tik
tu. Implementacja oparta jest na metodzie syste k tik tik tik tik tik tik tik
mowej currentTimeMi 11 i s () Javy, zwracającej k tik tik tik tik tik tik tik
k tik tik tik tik tik tik tik
aktualny czas w milisekundach. M etoda ta w m o k tik tik tik tik tik tik tik
mencie wywołania konstruktora zapisuje czas, k tik tik tik tik tik tik tik
4039
a w chwili wywołania metody el apsedTime()
jest ponownie używana do obliczenia czasu, jaki Obserwowanie czasu działania programu
upłynął.
1.4 a Analizy algorytmów 1 87
Klient testowy
p u b lic s t a t ic void m a in (S trin g [] args)
{
in t N = In t e g e r .p a r s e ln t ( a r g s [0 ]);
i nt [] a = new i nt [N ];
fo r (in t i = 0; i < N; i++)
a [ i] = StdRandom.uniform(-1000000, 1000000);
Stopwatch tim er = new Stopwatch();
in t cnt = ThreeSum .count(a);
double time = tim er.ela p sed T im e ();
StdOut.p r i n t l n ( " 1 iczba tró je k ; " + cnt + " + tim e);
p u b lic Stopwatch()
{ s t a r t = S y ste m .c u rre n tT im e M illisO ; }
Prosta
Diagram 5 1 ,2 -
o nachyleniu 3 .
logarytm iczny
25,6
1 2 ,8 -
6 ,4 -
3 ,2 “
5
tOl i'6“
0 ,8 -
0,4
0,2
0,1
1 2 4 8
R o zm ia r p ro b le m u - N (w tysiącach) Ig W (w tysiącach)
Do tej pory proces ten odzwierciedla proces stosowany przez naukowców przy
próbie zrozumienia cech świata rzeczywistego. Prosta na diagramie logarytmicznym
pozwala zaproponować hipotezę, zgodnie z którą dane pasują do równania T(N) =
a Nk Taki sposób dopasowania to zależność potęgowa. Zależności potęgowe opisują
bardzo wiele zjawisk naturalnych i sztucznych. Można w uzasadniony sposób p o
stawić hipotezę, że opisują też czas wykonania programu. Na potrzeby analiz algo
rytmów istnieją modele matematyczne, które zapewniają solidne podstawy dla tej
i podobnych hipotez. Przyjrzyjmy się takim modelom.
Tempo wzrostu
Przykładowo, przybliżenie ~ bP/6 opisuje liczbę wy
wołań instrukcji i f w program ie ThreeSum, ponieważ
Opis Funkcja
NV6 - AP/2 + N/3 podzielone przez N 76 zbliża się do 1
Stałe 1 wraz ze wzrostem N. Najczęściej używane są przybliże
nia z tyldą w postaci g(N) ~af{N), gdzie/(N ) = Nb(log
Logarytmiczne log N N)c dla stałych a, b i c. f(N ) jest tu tempem wzrostu
g(N). Przy używaniu logarytmów do określania tem
Liniowe N pa wzrostu zwykle nie podaje się podstawy, ponieważ
szczegół ten można uwzględnić w a. Dotyczy stosun
Liniowo- N lo g N kowo nielicznych funkcji, często spotykanych przy
logarytmiczne analizowaniu tempa wzrostu czasu wykonania progra
m u i pokazanych w tabeli po lewej stronie (wyjątkiem
Kwadratowe N2 jest funkcja wykładnicza, którą omówiono w rozdziale
„ k o n t e k s t ” ) . Bardziej szczegółowy opis tych funkcji
Sześcienne N3 i krótkie wyjaśnienie, dlaczego używa się ich w anali
2n
zach algorytmów, znajduje się po omówieniu progra
Wykładnicze
mu ThreeSum.
Często spotykane
funkcje tempa wzrostu
19 2 R O Z D Z IA Ł ! □ Podstawy
W książce używamy nazwy cecha na określenie hipotezy, którą trzeba poddać walida
cji przez eksperymenty. Wynik końcowy analiz matematycznych jest dokładnie taki
sam, jak efekt analiz eksperymentalnych. Czas wykonania program u T h r e e S u m wyno
si ~ aN3 dla zależnej od maszyny stałej a. Ta spójność dowodzi poprawności zarówno
eksperymentów, jak i m odelu matematycznego, a ponadto pozwala lepiej zrozumieć
program, ponieważ nie trzeba przeprowadzać eksperymentów w celu ustalenia wy
kładnika. Tyle samo pracy wymaga walidacja wartości a w konkretnym systemie,
choć zadanie to zwykle jest wykonywane przez ekspertów w sytuacjach, w których
wydajność ma kluczowe znaczenie.
1.4 a Analizy algorytmów 193
int[] a = In.readlnts(args[0]);
StdOut.println(count(a));
}
Struktura liczby wywołań instrukcji programu
Blok Czas
Liczba wywołań Łączny czas
instrukcji w sekundach
(i,/6) AP
+ {t 12 - 116) N2
Łączna suma 2 1
+ (f,/3 - tJ2 + i3) N
+ K + fox
Tempo wzrostu AP
i
196 RO ZD ZIA Ł 1 0 Podstawy
dziedzina badań, wykraczająca nieco poza zakres tej książki. Jednak, jak okaże się
przy omawianiu wyszukiwania binarnego, sortowania przez scalanie i wielu innych
algorytmów, poznanie pewnych modeli matematycznych jest niezbędne do zrozu
mienia wydajności podstawowych algorytmów. Dlatego często przedstawiamy szcze
góły i (lub) wyniki klasycznych badań. Napotykamy przy tym różne funkcje i przy
bliżenia powszechnie stosowane w analizach matematycznych. W tabelach poniżej
przedstawiono wybrane informacje:
Opis Przybliżenie
Suma częściowa
Hw= 1 + Vi + 1/3 + 'A + ... + 1/N ~ ln N
szeregu harmonicznego
Tempo
Nazwa Typow y kod Opis Przykład
wzrostu
Dodawanie
Stałe a = b + c; Instrukcja
dwóch liczb
Dzielenie Wyszukiwanie
Logarytmiczne log N [zobacz stronę 59]
na pół binarne
double max = a [ 0 ] ;
Znajdowanie
Liniowe N for (int i = 1; i < N; i++) Pętla
if ( a [ i] > max) max = a [ i ] ; maksimum
f o r ( in t i = 0 ; i < N ; i++ )
fo r ( in t j = i+ 1 ; j < N; j++) Podwójna Sprawdzanie
Kwadratowe N1 i f ( a [ i] + a [j] == 0) pętla wszystkich par
cnt++;
Sprawdzanie
Wyszukiwanie
Wykładnicze 2N [zobacz r o zd z ia ł 6.] wszystkich
wyczerpujące
podzbiorów
liwości. Tempo wzrostu kosztów algorytmu może wynosić N 2 log N lub N 3'2 albo
być równe innej podobnej funkcji. Szczegółowe analizy algorytmów mogą wymagać
pełnej gamy rozwijanych przez wieki narzędzi matematycznych.
Wiele omawianych algorytmów ma
S ta n d a r d o w y d ia g r a m
prostą w opisie wydajność, którą można
precyzyjnie określić za pomocą jednego
z przedstawionych temp wzrostu. Dlatego
przeważnie można w modelu kosztów po
dać specyficzne twierdzenia, na przykład:
sortowanie przez scalanie wymaga między
Y z N lg N a N lg Nporównań. Bezpośrednio
wynika z tego hipoteza (cecha): tempo
wzrostu dla czasu wykonania sortowania
przez scalanie jest liniowo-logarytmicz-
ne. Z uwagi na zwięzłość skracamy to do
stwierdzenia, że sortowanie przez scalanie
jest liniowo-logarytmiczne.
Diagramy po lewej stronie pokazują,
Rozmiar p ro b lem u (w tysiącach)
jak ważne jest tem po wzrostu w prakty
ce. Oś x określa rozmiar problemu, a oś y
W y k re s lo g a r y tm ic z n y — czas wykonania. Diagramy pokazują,
że algorytmy kwadratowe i sześcienne są
nieakceptowalne dla dużych problemów.
Okazuje się, że kilka ważnych problemów
ma proste rozwiązania kwadratowe, na
tomiast istnieją też sprytne algorytmy
liniowo-logarytmiczne. Te ostatnie algo
rytmy (w tym sortowanie przez scalanie)
mają bardzo duże praktyczne znaczenie,
ponieważ umożliwiają rozwiązywanie
dużo większych problemów niż przy uży
ciu algorytmów kwadratowych. Dlatego
w książce tej koncentrujemy się na rozwi
janiu logarytmicznych, liniowych i linio-
i------1---- 1--- 1---- 1---- 1---- 1 i 1 r~
1 2 4 8 512 wo-logarytmicznych algorytmów dla pod
Rozmiar p ro b lem u (w tysiącach) stawowych problemów.
T y p o w e t e m p o w z ro stu
1.4 □ Analizy algorytmów 201
wań binarnych zajmuje czas proporcjonalnie do log N, dlatego czas działania całego
algorytmu jest proporcjonalny do N log N. Opracowanie szybszego algorytmu nie
jest tylko akademickim ćwiczeniem. Szybszy algorytm umożliwia rozwiązywanie
dużo większych problemów. Przykładowo, prawdopodobnie możliwe będzie rozwią
zanie na Twoim komputerze w rozsądnym czasie problemu sum par dla miliona liczb
całkowitych (plik lM ints.txt), jednak przy stosowaniu algorytmu kwadratowego za
danie to zajęłoby dużo czasu (zobacz ć w i c z e n i e 1 .4 .4 1 ).
Szybki algorytm dla sum trójek To samo podejście jest skuteczne dla problemu
sum trójek. Także tu zakładamy, że liczby całkowite są niepowtarzalne. Para a [i]
i a [j] to część sumującej się do 0 trójki wtedy i tylko wtedy, jeśli wartość - ( a [ i ] +
a [ j ] ) znajduje się w tablicy (oraz jest różna od a [i ] lub a [ j ] ). Kod poniżej sortu
je tablicę, a następnie wykonuje N ( N -1)/2 wyszukiwań binarnych, z których każde
zajmuje czas proporcjonalny do log N. W sumie daje to czas proporcjonalny do N 2
log N. Zauważmy, że w tym przypadku koszt sortowania jest nieznaczący. Także to
rozwiązanie umożliwia rozwiązanie dużo większych problemów (zobacz ć w i c z e n i e
1 .4 .4 2 ). Diagramy na rysunku w dolnej części następnej strony pokazują rozbież
ność w kosztach pracy czterech algorytmów dla rozważanych rozmiarów problemu.
Różnice te z pewnością stanowią motywację do szukania szybszych algorytmów.
Dolne ograniczenia W tabeli na stronie 203 znajduje się podsumowanie dyskusji
z tego podrozdziału. Natychmiast powstaje ciekawe pytanie: Czy można znaleźć al
gorytmy dla problemów sum par i trójek działające wyraźnie szybciej niż TwoSumFast
i ThreeSumFast? Czy istnieje al
import ja v a .u t i 1 .A r r a y s ; gorytm liniowy dla sum par lub
algorytm liniowo-logarytmiczny
p ub lic c la s s ThreeSumFast
dla sum trójek? Odpowiedzi na te
p u b lic s t a t ic in t c o u n t{in t[] a) pytania brzmią: nie dla sum par
{ // Z lic z a t r ó j k i sumujące s ię do 0. (w modelu, w którym uwzględ
A rra y s.so rt(a );
niane są tylko porównania funk
in t N = a .le n gth ;
in t cnt = 0; cji liniowych lub kwadratowych
f o r ( in t i = 0; i < N; i++) funkcji liczb) i nie wiadomo dla
f o r (in t j = i+1 ; j < N; j++)
sum trójek, choć eksperci sądzą,
i f ( B in a r y S e a r c h . r a n k ( - a [ i] - a [ j ] , a) > j)
cnt++; że najlepszy możliwy algorytm
return cnt; dla sum trójek jest kwadratowy.
Dolne ograniczenie tempa wzro
p u b lic s t a t ic void m a in (S trin g [] args)
stu czasu wykonania dla najgor
szego przypadku dla wszystldch
i n t [] a = ln . r e a d ln t s ( a r g s [ 0 ] ) ; możliwych algorytmów rozwią
S t d O u t. p rin t ln (c o u n t(a ));
zujących dany problem ma bar
dzo duże znaczenie. Zagadnienie
to szczegółowo omówiono w pod-
Rozwiązanie o złożoności N2 Ig N dla problemu sum trójek
1.4 a Analizy algorytm ów 203
wać, że za 18 miesięcy pojawi się kom puter o dwukrotnie większej szybkości i z dwa
razy większą ilością pamięci, a za około 5 lat — maszyna 10-krotnie szybsza z 10-
krotnie większą pamięcią. W tabeli poniżej pokazano, że prawo M oorea nie pozwala
„nadążyć” za wzrostem ilości danych, jeśli algorytm jest kwadratowy lub sześcienny.
Rodzaj algorytmu można szybko określić, przeprowadzając test podwajania i spraw
dzając, czy stosunek podwojenia czasu wykonania przy podwojonej wielkości da
nych wejściowych dochodzi do 2, a nie do 4 lub 8 .
Dla p r o g r a m u d z ia ła ją c e g o k ilk a g o d z in
T e m p o w z ro s tu c z a s u .. , , ... ,
r d la d a n y c h w e jś c io w y c h o w ie lk o śc i N
P ro g n o z o w a n y P r o g n o z o w a n y c z a s d la 1
O p is F u n k c ja C z y n n ik 2 C z y n n ik 10
c z a s d la 10/V n a 10 ra z y s z y b s z y m k o m p u
Liniowo-
N lo g N 2 10 D zień K ilka g o d zin
logarytmiczne
P ro g n o z y n a p o d s ta w ie f u n k c ji t e m p a w z ro s tu
1.4 n Analizy algorytmów 207
Mimo tych wszystkich zastrzeżeń zrozumienie tempa wzrostu czasu wykonania pro
gramu jest cenne dla każdego programisty, a opisane tu m etody dają dużo możliwości
i działają w wielu okolicznościach. Według przemyśleń Knutha m etody te można teo
retycznie stosować w najdrobniejszych detalach, aby uzyskać szczegółowe, precyzyjne
prognozy. Typowe systemy komputerowe są niezwykle złożone, dlatego ścisłe analizy
najlepiej pozostawić ekspertom, jednak te same m etody są skuteczne do określania
przybliżonych szacunków czasu wykonania dowolnego programu. Konstruktor ra
kiet musi móc określić, czy lot testowy zakończy się w oceanie czy w mieście. Badacz
z dziedziny medycyny musi wiedzieć, czy testowany lek zabije pacjentów czy ich wy
leczy. Każdy naukowiec lub inżynier korzystający z program u komputerowego musi
mieć pojęcie, czy potrwa to sekundę czy rok.
1.4 o Analizy algorytmów
R a d z e n ie s o b ie z z a le ż n o ś c ią o d d a n y c h w e jś c io w y c h W wielu proble
mach jednym z najważniejszych zastrzeżeń jest zależność od danych wejściowych,
ponieważ czas wykonania może wtedy znacznie się wahać. Czas wykonania zmody
fikowanej wersji program u ThreeSum wspomnianej na poprzedniej stronie waha się
(w zależności od danych) od stałego do sześciennego, dlatego prognozy wydajności
wymagają dokładniejszych analiz. Pokrótce opisano tu niektóre skuteczne podejścia.
Zastosowano je do niektórych algorytmów w dalszej części książki.
M odele danych wejściowych Jedno z podejść polega na staranniejszym określeniu
w modelu rodzaju danych wejściowych przetwarzanych w rozwiązywanych prob
lemach. Przykładowo, m ożna przyjąć, że liczby w danych wejściowych programu
ThreeSum to losowe wartości typu in t. Podejście to rodzi problemy z dwóch przy
czyn:
D Model może być nierealistyczny.
D Analizy są czasem niezwykle skomplikowane i wymagają umiejętności m ate
matycznych wykraczających poza te dostępne przeciętnemu studentowi lub
programiście.
Pierwszy problem ma większe znaczenie. Często dzieje się tak dlatego, że celem ob
liczeń jest odkrycie cech danych wejściowych. Przykładowo, jak dla program u prze
twarzającego genom można oszacować wydajność dla różnych genomów? Dobry
model opisujący genomy występujące w naturze to właśnie to, czego naukowcy szu
kają, dlatego oszacowanie czasu wykonania program u na danych istniejących w n a
turze sprowadza się do opracowania części tego modelu! Drugi problem prowadzi
do koncentrowania się na wynikach matematycznych tylko dla najważniejszych al
gorytmów. W książce przedstawiono kilka przykładów, w których prosty i wygodny
w użytku m odel danych wejściowych w połączeniu z klasyczną analizą matematycz
ną pomaga przewidzieć wydajność.
Gwarancje wydajności dla najgorszego p rzyp a d ku W niektórych aplikacjach wy
magane jest, aby czas wykonania programu, niezależnie od danych wejściowych, był
krótszy od pewnego limitu. Aby zapewnić tego rodzaju gwarancje wydajności, teore
tycy stosują niezwykle pesymistyczne podejście do wydajności algorytmu i ustalają,
ile wyniesie czas wykonania dla najgorszego przypadku. Takie konserwatywne nasta
wienie może być odpowiednie dla oprogramowania sterującego reaktorem atom o
wym, tem pom atem lub hamulcami samochodu. Należy zagwarantować, że oprogra
mowanie wykona zadanie w określonym czasie, ponieważ jeśli tego nie zrobi, może
dojść do katastrofy. Naukowcy zwykle nie zastanawiają się nad najgorszym przy
padkiem, badając świat. W biologii najgorszym przypadkiem może być wymarcie
rodzaju ludzkiego; w fizyce — koniec wszechświata. Jednak w dziedzinie systemów
komputerowych najgorszy przypadek jest czasem bardzo rzeczywistym problemem,
ponieważ dane mogą być generowane przez innego (potencjalnie złośliwego) użyt
kownika, a nie przez naturę. Na przykład witryny, w których nie stosuje się algoryt
mów z gwarancjami wydajności, są podatne na ataki odmowy usługi (ang. denial of
RO ZD ZIA Ł 1 H Podstawy
N + 4 + 8 + 1 6 + ... + 2 N = 5 N - 4 2 56-
Analizy tego rodzaju mają wiele zastosowań. Tablice o zmiennej wielkości zastosowano
jako struktury danych dla kilku algorytmów omówionych w dalszej części książki.
Pamięć Wykorzystanie pamięci przez program, podobnie jak czas wykonania, wiąże
się bezpośrednio ze światem fizycznym. Duża część układów komputera umożliwia pro
gramom zapisywanie wartości i późniejsze ich pobieranie. Im więcej wartości program
musi przechowywać w danym momencie, tym więcej układów potrzebuje. Zapewne
znasz ograniczenia ilości pamięci na swoim komputerze (nawet lepiej niż limity związa
ne z czasem), ponieważ zapłaciłeś dodatkowe pieniądze za większą ilość pamięci.
Wykorzystanie pamięci przez Javę jest dobrze zdefiniowane dla danego kom pu
tera (każda wartość wymaga dokładnie tej samej ilości pamięci przy każdym u ru
chomieniu programu), jednak Java jest zaimplementowana dla bardzo różnorodnych
urządzeń obliczeniowych, a ilość zajmowanej pamięci jest zależna od implementacji.
W celu zachowania zwięzłości używamy słowa typowe do określenia, że wartości są
zależne od maszyny.
Jednym z najważniejszych mechanizmów Javy jest system aloka-
Typ Bajty cji pamięci. Ma on zwolnić programistów z konieczności zajmowania
boolean 1
się pamięcią. Oczywiście, w odpowiednich sytuacjach warto korzystać
z tego mechanizmu. Jednak trzeba wiedzieć (przynajmniej w przybliże
byte 1 niu), kiedy pamięciowe wymagania program u uniemożliwią rozwiąza
char 2 nie danego problemu.
Analizowanie wykorzystania pamięci jest dużo łatwiejsze od anali
in t 4
zowania czasu wykonania — przede wszystkim dlatego, że nie trzeba
float 4 uwzględniać tak wielu instrukcji program u (ważne są tylko deklaracje),
long
a analizy pozwalają zredukować złożone obiekty do typów prostych,
8
dla których wykorzystanie pamięci jest dobrze zdefiniowane i łatwe do
double 8 zrozumienia (wystarczy określić liczbę zmiennych i pomnożyć ją przez
Typowe w ym agania odpowiednią dla typu liczbę bajtów). Ponieważ w Javie typ danych i nt
pamięciowe dla to zbiór wartości z przedziału od -2 147 483 648 do 2 147 483 647, co
typów prostych
daje w sumie 2 32 różnych wartości, w typowych implementacjach Javy
do reprezentowania wartości typu i nt służą 32 bity. Podobne rozważa
nia dotyczą innych typów prostych. W typowych implementacjach Javy używane są
8 -bitowe bajty, wartości char reprezentowane są za pom ocą 2 bajtów (16 bitów), każ
da wartość doubl e i 1ong zajmuje 8 bajtów (64 bity), a wartość typu bool ean — 1 bajt
(ponieważ komputery zwykle korzystają z pamięci po jednym bajcie). W połączeniu
z wiedzą o ilości dostępnej pamięci na podstawie tych wartości m ożna obliczyć ogra
niczenia. Przykładowo, jeśli w komputerze dostępny jest gigabajt pamięci (miliard
bajtów), nie można w niej jednocześnie pomieścić więcej niż około 32 miliony war
tości typu i nt lub 16 milionów wartości typu doubl e.
Z drugiej strony, analizowanie wykorzystania pamięci jest zależne od rozmaitych róż
nic w sprzęcie i implementacjach Javy, dlatego przedstawione tu specyficzne przykłady
należy traktować jako wskazówki na temat określania zużycia pamięci, a nie ostateczne
informacje dotyczące Twojego komputera. Przykładowo, wiele struktur danych obej
muje reprezentację adresów maszynowych, a ilość pamięci potrzebnej na takie adresy
jest różna w zależności od maszyny. Dla spójności przyjmijmy, że do reprezentowania
1.4 □ Analizy algorytmów 213
adresów służy 8 bajtów, co jest typowe dla Obiekt nakładkowy dla liczb całkowitych 24 bajty
p u b lic c l a s s In t e g e r
powszechnie używanych obecnie architek { Narzut
p r iv a t e i n t x;
tur 64-bitowych. Warto jednak pamiętać, że dla
obiektu
w wielu starszych maszynach używano ar } Wartość
typu i n t
chitektury 32-bitowej, która wymagała tylko Dopełnienie
Tablice Typowe wymogi pamięciowe dla różnych rodzajów tablic Javy przedsta
wiono na diagramach na następnej stronie. Tablice w Javie są implementowane jako
obiekty i zwykle wymagają dodatkowego narzutu na długość. Tablica wartości typu
prostego zazwyczaj wymaga 24 bajtów informacji nagłówkowych (16 bajtów narzutu
dla obiektu, 4 bajtów na długość i 4 bajtów dopełnienia) plus pamięci na zapisanie
wartości. Na przykład tablica N wartości typu i nt zajmuje 24 + 4N bajtów (w za
okrągleniu w górę do wielokrotności liczby 8 ), a tablica N wartości typu doubl e — 24
+ 8N bajtów. Tablica obiektów to tablica referencji do obiektów, dlatego trzeba do
dać pamięć na referencje do pamięci potrzebnej na obiekty. Na przykład tablica N
obiektów typu Date (strona 103) zajmuje 24 bajty (narzut dla tablicy) plus 8N bajtów
(referencje) plus 32 bajty na każdy obiekt i 4 bajty dopełnienia, co w sumie daje 24 +
40Nbajtów. Tablica dwuwymiarowa to tablica tablic (każda tablica jest obiektem). Na
przykład dwuwymiarowa ta b lic a M n a N z wartościami typu doubl e zajmuje 2 4 bajty
(narzut dla tablicy tablic) plus 8M bajtów (referencje do wierszy tablicy) plus M razy
16 bajtów (narzut dla wierszy tablicy) plus M razy N razy 8 bajtów (dla N wartości
typu doubl e w każdym z M wierszy), co w sumie daje 8N M + 32M + 24 ~ 8N M baj
tów. Jeśli elementy tablicy to obiekty, podobne rachunki prowadzą do sumy 8N M +
32M + 24 ~ 8N M bajtów dla tablicy tablic wypełnionej referencjami do obiektów
(plus pamięć na same obiekty).
O biekty typu String Pamięć dla obiektów typu S t r i ng Javy obliczana jest w taki sam
sposób, jak dla innych obiektów, przy czym dla łańcuchów znaków typowe jest utoż
samianie nazw. Standardowa implementacja typu S t r in g obejmuje cztery zmienne
egzemplarza: referencję do tablicy łańcuchów znaków (8 bajtów) i trzy wartości typu
in t (po 4 bajty). Pierwsza wartość typu in t to pozycja w tablicy znaków; druga to
długość łańcucha znaków. W kategoriach nazw zmiennych egzemplarza z rysunku
na następnej stronie łańcuch znaków składa się ze znaków od val ue [offset] do
value [o ffse t + count - 1], Trzecia wartość in t w obiektach typu S t r i ng to skrót,
który pozwala w pewnych warunkach (nie mają one tu znaczenia) uniknąć powta
rzania obliczeń. Dlatego każdy obiekt typu S t r i ng zajmuje łącznie 40 bajtów (16 baj
tów narzutu dla obiektu plus 4 bajty na każdą zmienną egzemplarza typu i nt plus
8 bajtów na referencję do tablicy plus 4 bajty dopełnienia). Jest to pamięć potrzebna
oprócz pamięci na same znalu, znajdujące się w tablicy. Pamięć na znaki liczona jest
osobno, ponieważ tablice elementów typu char często są współużytkowane przez
różne łańcuchy znaków. Ponieważ obiekty typu S trin g są niezmienne, rozwiązanie
to pozwala w implementacji na zaoszczędzenie pamięci, jeśli obiekty tego typu mają
tę samą tablicę val ue [].
Wartości typu String ipo d ła ń cu ch y Obiekt typu S trin g o długości N zwykle zaj
muje 40 bajtów (na obiekt typu S tri ng) plus 24 + 2N bajtów (na tablicę ze znakami),
co w sumie daje 64 + 2 N bajtów. Jednak przy przetwarzaniu łańcuchów znaków typowe
jest korzystanie z podłańcuchów, a reprezentacja łańcuchów znaków w Javie um oż
liwia stosowanie podłańcuchów bez konieczności tworzenia kopii znaków łańcucha.
1.4 o Analizy algorytmów 215
Wartość Wartość
N N
typu i n t - typu i n t -
Dopełnienie Dopełnienie
(A bajty) (4 bajty)
. A/ wartości
; typu i n t N wartości
' (4A/ bajtów) typu d o u b le 24 + 8 N bajtów
y / (8N bajtów)
Łącznie: 24 + 4 N
(dla parzystego N)
Łącznie: 24 + 8 N
16 bajtów
M referencji
(8M bajtów)
doublet] [] ~8NM
p o d s t a w o w e m e c h a n i z m y są przydatne
te
Pytania i odpowiedzi
P. Uruchomiłem program Doubl i ngRati o na moim komputerze, ale wyniki nie były
spójne z tymi z książki. Niektóre stosunki nie były bliskie 8 . Dlaczego?
P. Dlaczego nie?
O. Głównym powodem jest to, że notacja opisuje tylko górne ograniczenie czasu wy
konania. Rzeczywista wydajność może być znacznie wyższa. Czas wykonania algo
rytm u może wynosić zarówno O(isF), jak i ~ a AMog N. Dlatego notacji dużego O nie
można wykorzystać do uzasadnienia technik w rodzaju testów podwajania (zobacz
t w i e r d z e n i e c na stronie 205).
1.4 n Analizy algorytmów 219
O. Tak, ale wolimy omawiać dokładne wyniki w kategoriach liczby wywołań instruk
cji w kontekście modelu kosztów. To podejście zapewnia więcej informacji na temat
wydajności algorytmu, a dla opisywanych algorytmów można uzyskać tego typu wy
niki. Mówimy na przykład, że „program ThreeSum uzyskuje dostęp do tablicy -AP/2
razy” lub „liczba wywołań cnt++ w programie ThreeSum dla najgorszego przypadku
wynosi ~N}/6”. Jest to dłuższe, ale i dużo bogatsze w informacje stwierdzenie niż
„czas wykonania program u ThreeSum wynosi O iN 3)”.
P. Kiedy tempo wzrostu czasu wykonania algorytmu wynosi N log N, test podwa
jania prowadzi do hipotezy, że czas wykonania wynosi ~ a N dla stałej a. Czy nie
stanowi to problemu?
O. Zwykle tak, dlatego w książce stosujemy takie założenie, choć kompilator o za
awansowanej implementacji może próbować uniknąć ponoszenia takich kosztów dla
dużych rzadkich tablic.
ROZDZIAŁ 1 o Podstawy
j ĆWICZENIA
1.4.1. Wykaż, że liczba różnych trójek, które m ożna wybrać z N elementów, wynosi
dokładnie N (N -l){N -2 )/6 . Wskazówka: zastosuj indukcję matematyczną lub twier
dzenie o zliczaniu (ang. counting argument).
1.4.2. Zmodyfikuj program ThreeSum, tak aby działał poprawnie nawet dla tak du
żych wartości typu i nt, że dodanie dwóch z nich może powodować przepełnienie.
1.4.3. Zmodyfikuj program Doubl i ngTest, aby używał biblioteki StdDraw do gene
rowania rysunków w rodzaju wykresów standardowych lub logarytmicznych z teks
tu. W razie potrzeby należy stosować zmianę skali, żeby rysunek zawsze zajmował
dużą część okna.
1.4.4. Utwórz dla program u TwoSum tabelę podobną do tej ze strony 193.
a. N + 1
b. 1 + 1/N
c. (1 + 1/N)(1 + 2IN)
d. 2N 3 - 15N2 + N
e. lg(2N)/lg N
f lg ( N W l) /lg W
g. N 100 / 2 N
1.4.6. Podaj tempo wzrostu czasu wykonania (jako funkcję od N) dla każdego z po
niższych fragmentów kodu:
a) in t sum = 0 ;
fo r (in t n = N; n > 0; n /= 2)
sum++;
b) in t sum = 0 ;
fo r (in t i = 1; i < N; i * = 2 )
sum++;
1.4 a Analizy algorytmów 221
c) in t sum = 0 ;
fo r (in t i = 1; i < N; i *= 2)
1.4.8. Napisz program do określania liczby par równych sobie wartości z pliku wej
ściowego. Jeśli pierwszy zaprojektowany algorytm jest kwadratowy, pomyśl ponow
nie i użyj m etody A rrays. s o rt () do opracowania rozwiązania liniowo-logarytmicz-
nego.
1.4.9. Podaj wzór na prognozowanie czasu wykonania program u dla problemu
0 rozmiarze N, jeśli eksperymenty z podwajaniem wykazały, że czynnik to 2 b, a czas
wykonania dla problemu o wielkości NQwynosi T.
1.4.10. Zmodyfikuj wyszukiwanie binarne tak, aby zawsze zwracało element o naj
mniejszym indeksie pasujący do szukanego elementu (czas wykonania nadal ma być
logarytmiczny).
b. Transaction
d. Point2D
e. In terval ID
f. Interval2D
g. Double
RO ZD ZIA Ł 1 ■ Podstawy
U PROBLEMY DO ROZWIĄZANIA
1.4.15. Szybszy algorytm dla sum trójek. Jako wstęp opracuj implementację
TwoSumFaster z wykorzystaniem liniowego algorytmu zliczającego pary sumujące się
do zera dla posortowanej tablicy (zamiast opartego na wyszukiwaniu binarnym algo
rytm u liniowo-logarytmicznego). Następnie zastosuj podobne podejście do utworze
nia kwadratowego algorytmu dla problemu sum trójek.
1.4.1 6 . Najbliższa para (w jednym wymiarze). Napisz program, który w tablicy a []
z N wartościami typu doubl e wyszukuje najbliższą parę, czyli dwie wartości różniące
się o nie więcej niż dowolna inna para. Czas wykonania programu powinien być dla
najgorszego przypadku liniowo-logarytmiczny.
1.4.17. Najdalsza para (w jednym wymiarze). Napisz program, który w tablicy a[]
z N wartościami typu doubl e wyszukuje najdalszą parę, czyli dwie wartości różniące
się o nie mniej niż dowolna inna para. Czas wykonania program u powinien być dla
najgorszego przypadku liniowy.
1.4.18. M inimum lokalne tablicy. Napisz program, który w tablicy a [] z N różnymi
liczbami całkowitymi znajduje minimum lokalne — indeks i , taki że a [i ] < a [i - 1 ]
i a [i ] < a [i +1]. Program dla najgorszego przypadku powinien przeprowadzać ~2lg
N porównań.
Odpowiedź: sprawdź środkową wartość, a [N/2], i dwie wartości sąsiednie — a [N/2
- 1] i a [N/2 + 1], Jeśli a [N/2] jest m inim um lokalnym, należy zakończyć wyszukiwa
nie. W przeciwnym razie należy szukać w połowie zawierającej mniejszego sąsiada.
1.4.25. Zrzucanie dwóch jajek z budynku. Rozważ poprzednie pytanie, ale tym ra
zem przyjmij, że są tylko dwa jajka, a model kosztów oparty jest na liczbie rzutów.
Opracuj strategię określania F, dla którego liczba rzutów wynosi co najwyżej 2 ~Jn .
Następnie znajdź sposób na zmniejszenie kosztu do ~c . Jest to odpowiednik sy
tuacji, w której przy wyszukiwaniu trafienia (nieuszkodzone jajka) są dużo mniej
kosztowne niż pominięcia (zniszczone jajka).
1.4.27. Kolejka oparta na dwóch stosach. Zaimplementuj kolejkę za pom ocą dwóch
stosów, tak aby każda operacja na kolejce zajmowała stałą (po amortyzacji) liczbę
operacji na stosie. Wskazówka: jeśli umieścisz elementy na stosie, a następnie zdej
miesz je wszystkie, będą miały odwrotną kolejność. Powtórzenie tego procesu spo
woduje przywrócenie pierwotnej kolejności.
1.4.28. Stos oparty na kolejce. Zaimplementuj stos za pomocą jednej kolejki, tak aby
każda operacja na stosie wymagała liniowej liczby operacji na kolejce. Wskazówka:
aby usunąć element, pobierz wszystkie elementy kolejki jeden po drugim i umieść
je na końcu za wyjątkiem jednego, który należy zwrócić i usunąć (przyznajemy, że
rozwiązanie to jest bardzo mało wydajne).
1.4.34. Zimno - ciepło. Celem jest odgadnięcie tajnej liczby całkowitej z przedziału
od 1 do N. Gracz wielokrotnie zgaduje liczby całkowite z tego przedziału. Po każdej
próbie dowiaduje się, czy podana liczba jest równa szukanej. Jeśli tak, gra się kończy;
w przeciwnym razie gracz otrzymuje informację o tym, czy jest bliżej („ciepło”) czy
dalej od szukanej liczby („zimno”) niż w poprzedniej próbie. Zaprojektuj algorytm,
który znajduje tajną liczbę w co najwyżej ~2 lg N próbach. Następnie opracuj algo
rytm, który robi to w co najwyżej ~1 lg N próbach.
1.4 ■ Analizy algorytm ów 225
1 .4.35. Koszty czasowe dla stosów. Wyjaśnij wartości z poniższej tabeli, w której po
kazano typowe koszty czasowe dla różnych implementacji stosu. Wykorzystaj model
kosztów, w którym liczone są zarówno referencje do danych (referencje do danych
umieszczanych na stosie — albo referencje do tablicy, albo do zmiennej egzemplarza
obiektu), jak i tworzone obiekty.
i nt 2N N
Lista powiązana In teger 3N 2N
Tablica o zmiennej i nt -5 N lg N
wielkości In teger -5 N ~N
j] EKSPERYMENTY
fo r ( in t i = 0; i < N; i++)
fo r ( i nt j = 0; j < N; j++)
fo r ( int k = 0; k < N; k++)
i f (i < j && j < k)
i f (a [i] + a [j] + a[k] == 0)
cnt++;
W tym celu opracuj wersję programu Doubl ingTest, która oblicza stosunek czasów
wykonania nowego program u i programu ThreeSum.
1.4.39. Zwiększanie precyzji testów podwajania. Zmodyfikuj program Doubl i ngRati o
tak, aby pobierał z wiersza poleceń drugi argument, określający liczbę wywołań m e
tody timeTri al () dla każdej wartości N. Uruchom program dla 10, 100 i 1000 prób.
Omów dokładność wyników.
1.4.40. Sumy trójek dla losowych wartości. Sformułuj i sprawdź hipotezę opisującą
liczbę sumujących się do 0 trójek wśród N losowych wartości typu i nt. Jeśli znasz się
na analizach matematycznych, opracuj odpowiedni model matematyczny dla tego
problemu, w którym wartości mają rozkład równomierny między - M a M, a M nie
jest małe.
1.4.41. Czasy wykonania. Oszacuj ilość czasu potrzebnego na wykonanie na Twoim
komputerze programów TwoSumFast, TwoSum, ThreeSumFast i ThreeSum w celu rozwią
zania problem u dla pliku zawierającego milion liczb. Wykorzystaj do tego program
Doubl i ngRati o.
1.4 h Analizy algorytmów 227
1.4.42. Rozmiary problemu. Oszacuj największą wartość P, dla której na Twoim kom
puterze m ożna uruchomić programy TwoSumFast, TwoSum, ThreeSumFast i ThreeSum,
aby rozwiązać problemy dla pliku zawierającego 2P tysięcy liczb. Wykorzystaj pro
gram Doubl i ngRatio.
1.4.44. Problem urodzin. Napisz program, który pobiera z wiersza poleceń liczbę
całkowitą N i używa metody StdRandom. uni form() do wygenerowania losowego cią
gu liczb całkowitych z przedziału od 0 do N - 1. Przeprowadź eksperymenty, aby
sprawdzić hipotezę, zgodnie z którą liczba wartości całkowitych wygenerowanych
przed powtórzeniem się jednej z nich wynosi ~.
228
1.5 n Studium przypadku — problem Union-Find 229
LI
Połączona ,
składowa
łe punkty są ze sobą połączone, może okazać się trudne. Dla program u jest to jeszcze
trudniejsze, ponieważ używa tylko nazw punktów i połączeń, natomiast nie ma do
stępu do geometrycznego układu punktów na diagramie. Jak można szybko określić,
czy dane dwa punkty w takiej sieci są połączone?
Pierwsze zadanie, z którym trzeba się zmierzyć przy rozwijaniu algorytmu, polega
na precyzyjnym ujęciu problemu. Można oczekiwać, że im większe są wymagania
wobec algorytmu, tym więcej czasu i pamięci będzie on potrzebował do wykonania
pracy. Nie da się z góry ująć tej zależności w formie liczbowej. Ponadto często spe
cyfikacja problemu zmienia się po stwierdzeniu, że jego rozwiązanie jest trudne lub
kosztowne (lub — w szczęśliwych okolicznościach — po ustaleniu, że algorytm udo
stępnia informacje bardziej przydatne od wymaganych w pierwotnej specyfikacji).
Specyfikacja problemu określania połączeń wymaga tylko tego, aby program ustalał,
1.5 ■ Studium przyp ad ku — problem Union-Find 231
czy dana para p q jest połączona czy nie. Program nie musi podawać zbioru połączeń
dla danej pary. Ten ostatni wymóg zwiększa poziom trudności problemu i prowadzi
do innej rodziny algorytmów, opisanej w p o d r o z d z i a l e 4 . 1 .
Aby ustalić specyfikację problemu, opracowano interfejs API z podstawowymi
potrzebnymi operacjami: inicjowaniem, dodawaniem połączenia między dwoma
punktami, identyfikowaniem składowej obejmującej dany punkt, określaniem, czy
dwa punkty należą do tej samej składowej, i zliczaniem składowych. Interfejs API
wygląda więc tak:
p ub lic c la s s UF
Operacja union() scala dwie składowe, jeśli dwa punkty znajdują się w różnych
składowych. Operacja find () zwraca całkowitoliczbowy identyfikator składowej dla
danego punktu. Operacja connected () określa, czy dwa punkty należą do tej samej
składowej. M etoda count () zwraca liczbę składowych. Zaczynamy od Nskładowych,
a każda operacja uni on () scalająca dwie różne składowe powoduje zmniejszenie ich
liczby o 1 .
Jak się wkrótce okaże, opracowanie rozwiązania algorytmicznego do dynamiczne
go określania połączeń sprowadza się do utworzenia implementacji przedstawionego
interfejsu API. W każdej implementacji trzeba:
° zdefiniować strukturę danych reprezentującą znane połączenia;
■ utworzyć wydajne implementacje operacji union(), find() , connected!) i co
unt!) oparte na tej strukturze danych.
Jak zwykle natura struktury danych m a bezpośredni wpływ na wydajność algoryt
mów, dlatego projektowanie struktury i algorytmu jest powiązane. Interfejs API
określa konwencję, zgodnie z którą zarówno punkty, jak i składowe są identyfikowa
ne za pomocą wartości typu i nt z przedziału od 0 do N-l, dlatego uzasadnione jest
stosowanie indeksowanej punktam i tablicy i d [] jako podstawowej struktury danych
reprezentującej składowe. Identyfikatorem składowej jest zawsze nazwa jednego
z należących do niej z punktów. Dlatego można uznać, że każda składowa jest repre
zentowana przez jeden z jej punktów. Początkowo jest N składowych (każdy punkt
stanowi składową), dlatego należy zainicjować id [i] wartością i dla wszystkich i od
232 RO ZD ZIA Ł 1 ta Podstawy
p u b l i c U F ( i n t N)
{ / / I n i c j o w a n ie t a b l i c y identyfikatorów składowych,
c o u n t = N;
i d = new i n t [ N ] ;
f o r ( i n t i = 0 ; i < N; i + + ) % j ava uf < t in y U F . tx t
id [i] = i ; 4 3
} 3 8
6 5
p ublic i n t count() 9 4
( re tu rn count; } 2 1
5 0
p u b l i c boolean c o n n e c t e d ( i n t p, int q) 72
{ r e t u r n f i n d ( p ) == f i n d ( q ) ; } 1
lic z b a składowych: 2
p u b l i c i n t find ( i n t p)
p u b l i c v o i d u n i o n ( i n t p , i n t q)
/ / Zobacz s t r o n ę 234 ( s z y b k a met o da f i nd) , s t r o n ę 236 ( s z y b k a met oda u n i o n )
/ / i s t r o n ę 240 ( w e r s j a z w a g a m i ) .
A n a lizy techniki z szybką m etodą fin d Operacja find () z pewnością jest szybka,
ponieważ zakończenie jej działania wymaga tylko jednego dostępu do tablicy i d [].
Jednak rozwiązanie to zwykle nie nadaje się dla dużych problemów, ponieważ m eto
da uni on () musi przejść przez całą tablicę i d [] dla każdej pary wejściowej.
i d []
Załóżmy, że technikę z szybką metodą find () zastosowa
p q 0 1 2 3 4 5 6 7 8 9
no do problemu dynamicznego określania połączeń. Jeśli
4 3 0 1 3 4 5 6 7 8 9
istnieje tylko jedna składowa, potrzebnych jest N - 1 wy
0 1 2 3 3 5 6 7 8 9
■> wołań metody uni on () i, co z tego wynika, (N+3)(N-1) ~
3 8 0 1 2 3 5 6 7 8 9
N 2 dostępów do tablicy. Od razu prowadzi to do hipotezy,
0 1 2 8 8 5 6 7 8 9
że dynamiczne określanie połączeń za pomocą techniki
6 5 0 1 2 8 8 5 6 7 8 9
zszybkąmetodąfind () może być procesem, w którym czas
0 1 2 8 8 5 5 7 8 9
T rośnie kwadratowo. Analizy te można uogólnić i stwier
9 4 0 1 8 8 5 5 7 8 9
0 1 2 8 8 5 5 7 8 8
dzić, że technika z szybką metodą find () jest kwadratowa
2 1 0 1 2 8 S 5 5 7 8 8
dla typowych zastosowań, w których ostatecznie liczba
0 1 1 8 8 5 5 7 8 8 składowych jest niewielka. Za pomocą testu podwajania
8 9 0 1 1 8 8 5 5 7 8 8 można łatwo sprawdzić tę hipotezę na własnym kompu
5 0 0 1 1 8 8 5 5 7 8 8 terze (instruktażowy przykład przedstawiono w ć w i c z e
0 1 1 8 8 0 0 7 8 8 n i u 1 .5 .23 ). Współczesne komputery wykonują miliony
7 2 0 1 1 8 8 0 0 7 8 8 lub miliardy instrukcji na sekundę, dlatego koszt jest
0 1 1 8 8 0 0 1 8 8 niezauważalny przy małych N, jednak we współczesnej
6 1 0 1^ 1 8 8 0 0 1 8 8 aplikacji czasem trzeba przetworzyć miliony lub miliar
1 1 8 8 1 1 8 8 dy miejsc, co przedstawiono za pomocą pliku testowego
1 0 1 1 1 o\ 8 11' 1 1 S 8 largeUF.txt. Jeśli nadal nie jesteś przekonany i uważasz, że
6 7 L 1 8 8n y 1 1 8 8 posiadasz wyjątkowo wydajny komputer,
i d [p] / i d[q] mają różną wartość, spróbuj użyć techniki z szybką metodą
dlatego metoda u n io n O zmienia find() do określenia liczby składowych
wartość elementów równych
id [ p ] n o id [q ] (wyróżnione) dla par z pliku largeUF.txt. Nieunikniony
i d [p] i i d [q] są takie same, wniosek jest taki, że nie można rozwiązać
dlatego zmiany nie są potrzebne takiego problemu za pomocą algorytmu
Ślad działania techniki z szybką metodą find z szybką metodą find(), trzeba więc po
szukać lepszych algorytmów.
236 R O ZD ZIA Ł 1 o Podstawy
Reprezentacja lasu drzew Kod szybkiej m etody union () jest krótki, ale dość skom
plikowany. Przedstawienie punktów jako węzłów (kółka z cyframi), a odnośników
jako strzałek między węzłami pozwala utworzyć graficzną reprezentację struktu
ry danych, która pozwala na stosunkowo łatwe zrozumienie działania algorytmu.
Wynikowe struktury to drzewa. W ujęciu technicznym tablica i d[] to reprezentacja
lasu (zbioru) drzew oparta
id[]
na odnośnikach do rodzica. ® © @ © © © © ® ® ®
p q 0 1 2 3 4 5 6 7 8 9
Aby uprościć diagramy, czę
4 3 0 1 2 3 4 5 6 7 8 9 ® ® © @ © © ® ® ®
sto pomijamy zarówno gro 0 1 2 3 3 5 6 7 8 9 ©
ty strzałek w odnośnikach 3 8 0 1 2 3 3 5 6 7 8 9 ® © © © © ® ® ®
(ponieważ wszystkie strzał 0 1 2 8 3 5 6 7 8 9 @
ki są skierowane w górę), 2)
jak i odnośniki z korzenia 65 0 1 2 8 3 5 6 7 8 9
® © © O O ® S
do niego samego. Lasy od 0 1 2 8 3 5 5 7 8 9 © O)
powiadające tablicy i d [] ©
dla pliku tinyUF.txt p o 9 4 0 1 2 8 3 5 5 7 8 9
® ® © ® ® (S
kazano po prawej stronie. 0 1 2 8 3 5 5 7 8 8 ® © OD
Program zaczyna od węzła
odpowiadającego dowol 2 1 0 1 2 8 3 5 5 7 8 8
®
nemu punktowi i podąża 0 1 1 8 3 5 5 7 8 8 (?) (?)
za odnośnikami, ostatecz
nie dochodząc do korzenia 8 9 0 1 1 8 3 5 5 7 8 8
drzewa zawierającego dany 5 0 0 1 1 8 3 5 5 7 8 8
węzeł. To ostatnie stwier 0 1 1 8 3 0 5 7 8 8
że po zainicjowaniu tablicy 0 1 1 8 3 0 5 1 8 8
A naliza techniki z szybką m etodą union() Algorytm z szybką metodą union () wy
daje się szybszy od algorytmu z szybką metodą find ( ) , ponieważ nie musi przechodzić
przez całą tablicę dla każdej pary wejścio
i d[]
® © (D © wej. Jednak o ile jest szybszy? Analizowanie
lo
p q 1 2 3 4 ...
kosztów techniki z szybką metodą u n io n ()
1O
i 0 1 3 4 ... ( p © © (?)
jest dużo trudniejsze niż dla szybkiej metody
1 1 2 3 4 ...
find (), ponieważ koszty w większym stop
0 2 0 1 2 D3 4 . . . © ©
niu zależą od natury danych wejściowych.
1 2 3 4 ...
W najlepszym przypadku metoda find () po
trzebuje jednego dostępu do tablicy w celu
0 3 (4)
znalezienia identyfikatora punktu (tak jak
w szybkiej metodzie find ()). W najgorszym
przypadku potrzeba 2N + 1 dostępów do tab
licy, tak jak dla 0 w przykładzie po lewej stro
0 4 0
nie (są to konserwatywne obliczenia, ponie
waż skompilowany kod zwykle nie wymaga
dostępu do tablicy przy drugim użyciu i d [p]
w pętli while). Nietrudno więc utworzyć
Głębokość = 4 - dane wejściowe dla najlepszego przypadku,
Najgorszy przypadek dla techniki z szybką metodą u ni on O dla których czas wykonania w kliencie do
dynamicznego określania połączeń jest linio
wy. Z drugiej strony, nietrudno też przygotować dane dla najgorszego przypadku, a wte
dy czas wykonania jest kwadratowy (zobacz rysunek po lewej stronie i t w i e r d z e n i e g
dalej). Na szczęście, nie trzeba mierzyć się z problemem analizowania szybkiej metody
uni on ( ) oraz porównywania wydajności technik z szybkimi metodami find ( ) i uni on (),
ponieważ dalej omówiono inną wersję, dużo wydajniejszą od obu opisanych do tej pory.
Na razie można traktować technikę z szybką metodą u n io n () jako usprawnienie tech
niki z szybką metodą find (), ponieważ zlikwidowano tu największą wadę tej ostatniej
(liniowy czas działania metody uni on ()). Różnica ta z pewnością zapewnia poprawę
dla typowych danych, jednak technika z szybką metodą uni on () nadal ma wadę — nie
można zagwarantować, że w każdym przypadku będzie znacząco szybsza od techniki
z szybką metodą find () (dla niektórych danych ta pierwsza jest szybsza).
public c la s s WeightedQuickUnionUF
{
p rivate i n t [ ] id;
// Odnośniki do rodziców (w t a b l ic y indeksowanej
// punktami).
private i n t [] sz; // Wielkości składowych określonych za pomocą korzeni
// (w t a b l ic y indeksowanej punktami),
private in t count; // Liczba składowych.
public WeightedQuickUnionUF(int N)
{
count = N;
id = new i n t [ N ] ;
fo r (in t i = 0 ; i < N; i++) id [i] = i;
sz = new i n t [ N ];
fo r (in t i = 0 ; i < N; i++) sz [i ] = 1;
}
public in t count()
{ return count; }
private in t find (i nt p)
{ // Podążanie za odnośnikami w celu znalezienia korzenia,
while (p ! = i d [ p ] ) p = i d [ p ] ;
return p;
}
Kod ten najłatwiej zrozumieć w kategoriach opisanej w tekście reprezentacji w postaci lasu
drzew. Dodano zmienną egzemplarzasz [](tablica indeksowana punktami), aby metoda
union() mogła powiązać korzeń mniejszego drzewa z korzeniem większego. Ten dodatek
umożliwia rozwiązywanie dużych problemów.
1.5 h Studium przypadku — problem Union-Find 241
p q
® ® @ © © © © ® ® ®
p q
®©®©®®®0
4 3 @ © © ( 4 ) © © ® ® ® o i ® © © © © © ®
® ©
®©© ©©®® 2 3 ® (|) © © © ®
6 5 ® © © © © ® ® 4 5
© ® (?)
9 4 ® © © JS i © ® 6 7
& ® © ©
2 1 ® © jS l © ® 0 2
© © ® ©) ©
8 9
5 0 4 6
7 2
0 4
6 1
1 0
6 7
C. -
242 R O ZD ZIA Ł 1 □ Podstawy
Z wagami
A^
® JK o o o o o o o
Ą
Średnia głębokość -1,52
O ptym alne algorytm y Czy istnieje algorytm, który gwarantuje stały czas na opera
cję? Jest to niezwykle trudne pytanie, nad którym badacze zastanawiają się od wielu
lat. W poszukiwaniu odpowiedzi zbadano wiele odm ian technik z szybką metodą
un ion () i szybką metodą u n io n () z wagami. Opisana dalej przykładowa metoda,
kompresja ścieżek, jest łatwa w implementacji. W idealnym rozwiązaniu każdy węzeł
powinien prowadzić bezpośrednio do korzenia drzewa, jednak należy unikać kosz
tów zmiany dużej liczby odnośników, co było potrzebne w algorytmie z szybką m eto
dą find ( ) . Można zbliżyć się do optimum, ustawiając wszystkie sprawdzane węzły tak,
aby prowadziły bezpośrednio do korzenia. Na pozór wydaje się, że to ekstremalne
rozwiązanie, jednak łatwo je zaimplementować. Nie ma nic specjalnego w strukturze
omawianych drzew. Jeśli można je zmodyfikować w celu zwiększenia wydajności al
gorytmu, należy to zrobić. Aby zaimplementować kompresję ścieżek, wystarczy dodać
do metody find() nową pętlę, która ustawia element id[] dla każdego napotkanego
węzła na odnośnik prowadzący bezpośrednio do korzenia. W efekcie drzewa zostają
prawie całkowicie spłaszczone, co pozwala zbliżyć się do ideału osiągniętego w algo
rytmie z szybką m etodą find ( ) . Technika ta jest prosta i skuteczna, jednak w prak
tycznych zastosowaniach prawdopodobnie nie da się zauważyć poprawy względem
szybkiej m etody uni on () z wagami (zobacz ć w i c z e n i e 1 . 5 .24 ). Teoretyczne wyniki
dotyczące tego zagadnienia są niezwykle skomplikowane i dość istotne. Szybka me
toda uni on () z wagami i kompresję ścieżek jest optymalna, ale nie zapewnia stałego
czasu na operację. Oznacza to nie tylko tyle, że opisana technika nie działa w stałym
czasie na operację (po amortyzacji), ale też to, że nie istnieje algorytm, który gwaran
tuje wykonanie każdej operacji Union-Find w stałym czasie (z amortyzacją) w bar
dzo ogólnym modelu obliczeń opartym na dostępie do komórek (ang. celi probe).
Szybka m etoda u n io n () z wagami i kompresją ścieżek jest bardzo bliska optym alne
mu rozwiązaniu problemu.
244 RO ZD ZIA Ł 1 ■ Podstawy
Wykresy kosztów z am ortyzacją Warto tu, tak jak dla implementacji każdego typu
danych, przeprowadzić eksperymenty w celu sprawdzenia poprawności hipotez doty
czących wydajności dla typowych klientów,
Szybka m e to d a f i n d O
jak opisano to w p o d r o z d z i a l e 1 .4 . Na ry
1300- sunku po lewej stronie pokazano szczegóło
wo wydajność algorytmów wspomagających
tworzenie aplikacji klienta do dynamicz
Jedna szara kropka dla nego określania połączeń dla przykła
każdego połączenia
przetworzonego przez klienta
du z 625 punktami (plik mediumUF.
txt). Tworzenie takich wykresów jest
łatwe (zobacz ć w i c z e n i e 1 .5 .1 6 ). Dla
Operacje metody u n io n ( ) wymagają
przynajmniej 625 dostępów ¿-tego przetwarzanego połączenia należy
zapisać zmienną cost, która określa liczbę
dostępów do tablicy (i d [] lub sz []). Należy
też utworzyć zmienną t ot al , która zawiera
Czerwone 458 sumę dotychczasowych dostępów do tablicy.
kropki oznaczają
skumulowaną średnią Następnie wystarczy narysować szarą kropkę
w punkcie ( i , cost) i czerwoną w punkcie
( i, t o t a l / i ) . Czerwone kropki określają
średni koszt na operację (po amortyzacji).
Wykresy pozwalają dobrze zrozumieć dzia
Operacje metody c o n n e c te d ( ) wymagają łanie algorytmu. W technice z szybką me
dokładnie dwóch dostępów do tablicy
todą find() każda operacja union() wymaga
\ ______ przynajmniej 625 dostępów (plus jeden na
Liczba p o łączeń
“n
900 każdą scalaną składową, aż do kolejnych 625
dostępów), a każda operacja connected () —
Szybka m e to d a u n io n O dwóch dostępów. Początkowo większość po
1 0 0 —1
Operacje f i n d O łączeń wymaga wywołania metody union(),
stają się kosztowne
dlatego skumulowana średnia jest bliska 625.
OJ Później większość połączeń prowadzi do wy
wołań connected (), pozwalających pominąć
Szybka m e to d a u n io n O z w agam i wywołania union(), dlatego średnia skumu
Brak kosztownych operacji
lowana spada, choć wciąż pozostaje stosun
20 n
0 - 1-
\ ______ kowo wysoka. Dane wejściowe, dla których
duża liczba wywołań connected() prowadzi
Koszt wszystkich operacji (dla 625 punktów)
do pominięcia wywołania union(), zapew
niają wyraźnie lepszą wydajność — zobacz na przykład ć w i c z e n i e 1 .5 .2 3 . W technice
z szybką metodą uni on () wszystkie operacje wymagają początkowo tylko kilku dostę
pów do tablicy. Ostatecznie wysokość drzewa zaczyna odgrywać ważną rolę, a koszty
po amortyzacji znacznie rosną. W technice z szybką metodą uni on () z wagami wyso
kość drzewa pozostaje mała, dlatego żadna z operacji nie jest kosztowna, a koszty po
amortyzacji są niskie. Eksperymenty są potwierdzeniem wniosków, zgodnie z którymi
warto zaimplementować szybką metodę un i on () z wagami. Technika ta nie pozostawia
dużo miejsca na poprawę w kontekście praktycznych problemów.
1.5 ■ Studium przypadku— problem Union-Find 245
| Pytania i odpowiedzi
P. Chciałbym dodać do interfejsu API metodę del ete (), która umożliwi klientom
usuwanie połączeń. Macie jakieś wskazówki?
O. Nikt nie zaprojektował algorytmu do usuwania połączeń, który byłby tak prosty
i wydajny, jak rozwiązania przedstawione w tym podrozdziale. Zagadnienie to po
wtarza się w książce. Niektóre z omawianych struktur mają tę cechę, że usuwanie
z nich danych jest dużo trudniejsze niż ich dodawanie.
ĆWICZENIA
1.5.1 . Wyświetl zawartość tablicy i d [] i liczbę dostępów do tablicy dla każdej pary
wejściowej w algorytmie z szybką m etodą find () użytym dla ciągu: 9-0 3-4 5-8 7-2
2-1 5-7 0-3 4-2.
1.5.2. Wykonaj ć w i c z e n i e 1 .5 .1 , ale dla szybkiej m etody u n i o n ( ) (strona 236).
Ponadto narysuj las drzew reprezentowany przez tablicę i d [] po przetworzeniu każ
dej pary wejściowej.
1.5.5. Oszacuj m inim alną ilość czasu (w dniach) potrzebną na rozwiązanie szybką
metodą find() problemu dynamicznego określania połączeń dla 109 punktów i 106
par wejściowych. Przyjmij, że kom puter wykonuje 109 instrukcji na sekundę, a każda
iteracja wewnętrznej pętli for wymaga wykonania 10 instrukcji maszynowych.
1 .5.7. Opracuj klasy Qui ckUni onllF i Qui ckFi ndUF, będące — odpowiednio — imple
mentacjami technik z szybką metodą union() oraz szybką m etodą find().
Odpowiedź: tak, ale wysokość drzewa będzie w nim większa, dlatego gwarancje wy
dajności nie będą obowiązywać.
1.5.1 1. Zaimplementuj technikę z szybkę metodą find() z wagami, w której elementy
mniejszej składowej są zawsze ustawiane w tablicy i d [] na identyfikator większej
składowej. Jak taka zmiana wpłynie na wydajność?
1.5 n Studium przypadku — problem Union-Find 249
i PROBLEMY DO ROZWIĄZANIA
1.5.17. Losowe połęczenia. Opracuj klienta ErdosRenyi dla klasy UF. Klient m a p o
bierać z wiersza poleceń liczbę całkowitą N, generować losowe pary liczb całkowitych
z przedziału od 0 do N-l, wywoływać metodę connected () w celu ustalenia, czy pary
są połączone, a następnie wywoływać metodę union(), jeśli połączenie nie istnieje
(tak jak w kliencie wspomagającym tworzenie aplikacji). Program ma działać w pętli
do czasu połączenia wszystkich punktów i wyświetlać liczbę utworzonych połączeń.
Program ma obejmować metodę statyczną count(), która jako argument przyjmuje
N i zwraca liczbę połączeń, oraz metodę main(), przyjmującą N z wiersza poleceń,
wywołującą count () i wyświetlającą zwróconą wartość.
250 ROZDZIAŁ 1 ■ Podstawy
p riv a t e c l a s s Connection
1
i n t p;
i n t q;
p ub lic C onnectionfint p, i n t q)
( t h i s . p = p; t h i s . q = q; }
)
EKSPERYMENTY
1.5.23. Porównaj techniki z szybkę metodę find() i szybkę metodę union() w modelu
Erdósa-Renyiego. Opracuj klienta do testowania wydajności, który pobiera z wiersza
poleceń wartość T typu i nt i wykonuje T prób opisanego dalej eksperymentu. Użyj
klienta z ć w i c z e n i a 1 .5.17 do wygenerowania losowych połączeń. Zapisz połącze
nia, tak aby można było użyć zarówno szybkiej m etody union(), jak i szybkiej m e
tody find () do sprawdzenia połączeń (tak jak w kliencie wspomagającym tworzenie
aplikacji). Program ma działać w pętli do czasu połączenia wszystkich punktów. Dla
każdego Nwyświetl wartość Ni stosunek dwóch czasów wykonania.
m li Sortowanie
ortowanie to proces porządkowania obiektów w logiczny sposób. Przykładowo,
255
w r a m a c h p i e r w s z e j W YPRA W Y do krainy algorytmów sortowania analizujemy
dwie podstawowe m etody sortowania i odmianę jednej z nich. Oto niektóre powody
do zapoznania się z tymi stosunkowo prostymi algorytmami. Po pierwsze, zapewnia
ją one kontekst, w którym m ożna poznać terminologię i podstawowe mechanizmy.
Po drugie, te proste algorytmy są w niektórych zastosowaniach wydajniejsze od za
awansowanych algorytmów omówionych dalej. Po trzecie, jak się okaże, pozwalają
poprawić wydajność bardziej skomplikowanych rozwiązań.
p u b lic c la s s Example
{
p u b l i c s t a t i c v o i d s o r t ( C o m p a r a b l e [ ] a)
{ / * Zobacz a l g o r y t m y 2 . 1 , 2 . 2 , 2 . 3 , 2 . 4 , 2 . 5 l u b 2 . 7 . * / }
p r i v a t e s t a t i c v o i d e x c h (C o m p a r a b le [] a, i n t i , in t j)
{ C om parable t = a [ i ] ; a [i ] = a [ j ] ; a [ j ] = t; }
p r i v a t e s t a t i c v o i d sh o w (C o m p a ra b le [] a)
{ // W y ś w i e t la t a b l i c ę w jednym w i e r s z u ,
f o r ( i n t i = 0; i < a . l e n g t h ; i + + )
Std O u t.p rin t(a [i] + " ");
S td O u t.p rin tln ();
}
p u b l i c s t a t i c b o o le a n i s S o r t e d ( C o m p a r a b l e [ ] a)
{ // Sp raw d za, c z y e lem enty t a b l i c y mają o d p o w ie d n ią k o l e j n o ś ć ,
fo r ( i n t i = 1; i < a . l e n g t h ; i + + )
i f (1 e s s (a [i ] , a [ i - 1 ] ) ) r e t u r n f a l s e ;
re tu rn true;
}
p u b lic s t a t i c vo id m a in ( S t r in g [ ] a rgs)
{ // W c z y t u je ł a ń c u c h y znaków ze sta n d ard o w e go w e j ś c i a ,
// s o r t u j e j e i w y ś w i e t l a .
Strin g [] a = In .re a d S trin g s();
sort(a);
a sse rt isS o rte d (a );
show (a);
}
}
% more t i n y . t x t
S 0 R T E X A M P L E
W klasie tej przedstawiono konwencje używane
dalej do implementowania technik sortowania tab % j a v a Example < t i n y . t x t
lic. Dla każdego algorytmu sortowania pokazano A E E L M O P R S T X
metodę s o rt() z podobnej klasy, przy czym nazwę
Example zmieniono na nazwę odpowiednią dla al % more w o r d s 3 . t x t
gorytmu. Klient testowy sortuje łańcuchy znaków bed bug dad y e s zoo . . . a l l bad y e t
Spraw dzanie popraw ności Czy implementacja sortowania zawsze umieszcza ele
menty tablicy we właściwej kolejności, niezależnie od ich początkowego uporząd
kowania? Stosujemy konserwatywne podejście i umieszczamy w kliencie testowym
instrukcję a s s e rt i sSorted ( a ) a b y sprawdzić, czy elementy tablicy są po sortowa
niu odpowiednio uporządkowane. Warto umieścić tę instrukcję w każdej implemen
tacji sortowania, choć zwykle testujemy kod i opracowujemy matematyczne dowody
poprawności algorytmów. Warto zauważyć, że test jest wystarczający tylko wtedy,
jeśli do zmiany pozycji elementów tablicy używamy wyłącznie m etody exch (). Przy
stosowaniu kodu zapisującego wartości bezpośrednio w tablicy test nie gwarantuje
poprawności (za prawidłowy uznany zostanie na przykład kod niszczący pierwotną
tablicę wejściową przez ustawienie wszystkich elementów na tę samą wartość).
Czas w ykonania Testujemy też wydajność algorytmów.
Zaczynamy od udowodnienia faktów na temat liczby pod-
Model kosztów dla sorto- stawowych operacji (porównań i przestawień oraz czasem
wania. Przy analizowaniu liczby dostępów tablicy w celu odczytu lub zapisu), któ-
algorytmów sortowania li- re różne algorytmy sortowania wykonują dla rozmaitych
czone są porównania i prze- naturalnych modeli danych wejściowych. Następnie uży-
stawienia. Dla algorytmów, wamy tych faktów do opracowania hipotez dotyczących
które nie przestawiają ele- względnej wydajności algorytmów. Prezentujemy też
mentów, liczone są dostępy narzędzia do eksperymentalnego sprawdzania hipotez.
do tablicy. Używamy spójnego stylu kodowania, aby ułatwić tworze
nie prawidłowych hipotez na tem at wydajności, prawdzi
wych dla typowych implementacji.
D odatkow a pam ięć Ilość dodatkowej pamięci używanej przez algorytm sortowania
jest często równie ważnym czynnikiem jak czas wykonania. Algorytmy sortowania
dzielą się na dwa podstawowe rodzaje — sortujące w miejscu, które nie potrzebują
dodatkowej pamięci (za wyjątkiem małego stosu wywołań funkcji lub stałej liczby
zmiennych egzemplarza), oraz algorytmy wymagające dodatkowej pamięci na drugą
kopię sortowanej tablicy.
Typy danych Kod sortujący działa dla elementów każdego typu obsługującego
interfejs Comparable. Stosowanie się do konwencji Javy jest tu wygodne, ponieważ
wiele typów danych obsługuje ten interfejs. Dotyczy to na przykład nakładkowych
typów numerycznych Javy, takich jak Integer i Doubl e, a także typu S tring i różnych
zaawansowanych typów w rodzaju F ile lub URL. Wystarczy wywołać jedną z m e
tod sortowania, podając jako argument tablicę wartości dowolnego z tych typów.
Przykładowo, w kodzie po prawej stronie użyto
s o r to w a n ia sz y b k ie g o ( z o b a c z p o d r o z d z i a ł
‘ & v
2. 3 ) . m r.n
Double a [] = new Double[N];
do posortowania N losowych wartości typu f o r ( i n t i = 0; i < N; i+ + )
Double. Przy samodzielnym tworzeniu typów a [i]= S t d R a n d o m . u n if o r m O ;
można umożliwić w kodzie klienta sortowanie Quick.s o r t ( a ) ;
fejs Comparabl e. W tym celu wystarczy zaimplementować metodę compareTo (), która
wyznacza uporządkowanie obiektów typu w tak zwanym porządku naturalnym, co
pokazano tu dla typu danych Date (zobacz stronę 103). Zgodnie z konwencjami Javy
wywołanie v . compareTo (w) zwraca licz
bę całkowitą — ujem ną (zwykle - 1 ) dla p u b l i c c l a s s Date implements Comparable<Date>
public c la ss Sélection
{
public s t a t i c void sort(Comparable[] a)
{ / / Sortowanie a [] w porządku rosnącym,
in t N = a .le n g th ; / / Długość ta b lic y ,
fo r (in t i = 0; i < N; i++)
( / / Przestaw ianie a [i ] z najmniejszym elementem z a [ i+ l...N ) .
in t min = i ; / / Indeks minimalnego elementu,
fo r (in t j = i+1; j < N; j++)
i f ( l e s s ( a [ j ] , a[min])) min = j ;
exch(a, i , m in);
}
}
/ / Metody le s s () , exch(), isSortedQ i main() przedstawiono na stro n ie 257.
)
a []
i min 0 1 2 3 4 5 6 7 8 9 10 Czarne elementy są
■ sprawdzane w celu
S O R T E X A M p L E
znalezienia minimum
0 6 S 0 R T E X A M p L E
1 4 A O R T E X S M p L E Czerwone elementy
" foa[min]
2 10 A E R T O X s M p L E
3 9 A E E T 0 X s M p L R
4 7 A E E L 0 X s M p T R
5 7 A E E L M X s 0 p T R
6 8 A E E L M 0 s X p T R
7 10 A E E L M 0 p X S T R
8 8 A E E L M 0 p R S T X Szare elementy
9 9 A E E L M 0 p R s T X znajdują się na
10 10 A E E L M 0 p R s T X ostatecznej pozycji
A E E L M O p R s T X
Sortowanie przez wstawianie działa dobrze dla pewnego rodzaju nielosowych tablic, któ
re często powstają w praktyce (nawet jeśli tablice są bardzo duże). Rozważmy na przykład,
co się stanie po zastosowaniu sortowania przez wstawianie dla już posortowanej tablicy.
Algorytm natychmiast stwierdzi, że każdy element znajduje się we właściwym miejscu
tablicy, a łączny czas wykonania rośnie liniowo (czas wykonania sortowania przez wy
bieranie dla takich tablic jest kwadratowy). To samo dotyczy tablic, w których wszystkie
klucze są równe (stąd warunek niepowtarzalności kluczy w t w i e r d z e n i u b ).
2.1 Podstawowe metody sortowania 263
p ublic c la s s In se rtio n
{
public s t a t ic void sort(Comparable[] a)
{ // Sortowanie a[] w porządku rosnącym,
in t N = a.length;
f o r (in t i = 1; i < N; i++)
{ // Wstawianie a [ i] między a [ i - l ] , a [ i - 2 ] , a [ i -3] itd.
fo r (in t j = i ; j > 0 && l e s s ( a [ j ] , a [ j - l ] ); j — )
exch(a, j, j - l ) ;
}
}
// Metody l e s s ( ) , exch(), isSorted() i main () przedstawiono na stronie 257.
a[]
i j 0 1 2 3 4 5 6 7 8 9 10
S 0 R T E X A M P L E Szare elementy
1 0 0 s R T E X A M P L E pozostają w miejscu
2 1 0 R S T E X A M P L E
3 3 0 R S T E X A M P L E
Czerwony element
4 0 E 0 R S T X A M P L E foa[j]
5 5 E 0 R S T X A M P L E
6 0 A E 0 R S T X M P L E
7 2 A E M 0 R s T X P L E Czarne elementy
należy przenieść
8 4 A E M 0 P R S T X L E
o jedno miejsce w prawo
9 2 A E L M 0 P R S T X E przy wstawianiu
10 2 A E E L M 0 P R S T X
A E E L M 0 P R s T X
posortowanych tablic. Jest też dobrą techniką dla krótkich tablic. Ma to znaczenie
nie tylko z uwagi na to, że takie tablice często występują w praktyce, ale też dlatego,
iż tablice obu rodzajów powstają na etapach pośrednich w zaawansowanych algo
rytm ach sortujących. Dlatego sortowanie przez wstawianie omówiono ponownie
w kontekście takich algorytmów.
2.1 o Podstawowe metody sortowania 265
public c la s s SortCompare
{
public s t a t ic double tim e (Strin g alg, Doublet] a)
{ /* Zobacz te kst. */ }
Ten klient uruchamia dwie techniki sortowania (ich nazwy podano w pierwszych dwóch ar
gumentach wiersza poleceń) dla tablicy zawierającej N (trzeci argument) losowych wartości
typu Double z przedziału od 0.0 do 1.0, ponawia eksperyment T razy (czwarty argument
wiersza poleceń), a następnie wyświetla stosunek łącznych czasów działania.
cech a d celowo jest nieco niejasna (wartość małej stałej jest nieokreślona, a ponadto
nie ma założenia o podobnych kosztach porównań i przestawień), dlatego okazuje
się prawdziwa w wielu sytuacjach. Kiedy to możliwe, kluczowe aspekty wydajności
każdego z analizowanych algorytmów staramy się ująć w stwierdzeniach tego ro
dzaju. Jak opisano to w r o z d z i a l e i ., każda omawiana Cecha wymaga naukowego
przetestowania w danej sytuacji, czasem z wykorzystaniem bardziej dopracowanych
hipotez opartych na powiązanym Twierdzeniu (matematycznej prawdzie).
W kontekście praktycznych zastosowań jest jeszcze jeden kluczowy krok —
przeprowadzenie eksperymentów w celu walidacji hipotez dla używanych danych.
Omawianie tego etapu odkładamy do p o d r o z d z i a ł u 2.5 i ćwiczeń. Jeśli w omawia
nym przykładzie klucze sortujące nie są unikatowe i (lub) losowo uporządkowane,
c e c h a d może nie być prawdziwa. Tablicę m ożna losowo uporządkować za pomocą
public c la s s Shell
{
public s t a t i c void sort(Comparable[] a)
{ // Sortowanie a[] w kolejności rosnącej,
in t N = a.length;
in t h = 1;
while (h < N/3) h = 3*h + 1; // 1, 4, 13, 40, 121, 364, 1093, ...
while (h >= 1)
{ // h-sortowanie ta b lic y ,
fo r (in t i = h; i < N; i++)
{ // Wstawianie a [i ] między a [i - h ] ,a [ i - 2 * h ] , a [ i- 3 *h ] itd.
fo r (in t j = i ; j >= h && l e s s ( a [ j ] , a[j-h ] ) ; j -= h)
exch(a, j, j - h ) ;
}
h = h/3;
}
}
// Metody l e s s Q , exch(), isS o rte d () i main() opisano na stro n ie 257.
D a n e wejś ciowe S H E L L S O R T E X A M P L E
13 -s ortow anie P H E L L S O R T E X A M s L E
4 -sorto w an ie L E E A M H L E P S O L T s X R
1-so rto w an ie A E E E H L L L M O P R S s T X
Jak ustalić, który ciąg odstępów należy zastosować? Zwykle trudno jest odpowiedzieć
na to pytanie. Wydajność algorytmu zależy nie tylko od wartości odstępów, ale też
od arytmetycznych zależności między nimi, na przykład ich wspólnymi dzielnikami
i innymi cechami. Przebadano wiele różnych ciągów odstępów, jednak nie udowod-
niono, że któryś z nich jest najlep
Dane wejściowe S H E L L S 0 R T E X A M p L E
szy. Ciąg odstępów zastosowany
13-sortowanie P H E L L s 0 R T E X A Ms L E
w a l g o r y t m i e 2.3 jest łatwy do
P H E L L s 0 R T E X A Ms L E
obliczenia i w użyciu oraz zapew
P H E L L s 0 R T E X A Ms L E
4-sortowanie L H E L P s 0 R T E X A M s L E nia wydajność niemal tak wysoką,
L H E L P s 0 R T E X A M s L E jak bardziej zaawansowane ciągi
L H E L P s 0 R T E X A M s L E odstępów, dla których udowod
L H E L P s 0 R T E X A M s L E niono wyższą wydajność dla naj
L H E L P s 0 R T E X A M s L E gorszego przypadku. Możliwe, że
L E E L P H 0 R T S X A M s L E ciągi odstępów o znacząco wyż
L E E L P H 0 R T S X A M s L E szej wydajności wciąż czekają na
L E E A P H 0 L T S X R M s L E odkrycie.
L E E A MH 0 L P s X R T s L E Sortowanie Shella jest przydat
L E E A MH0 L P s X R T s L E
ne nawet dla dużych tablic, zwłasz
L E E A MH L L P s 0 R T s X E
cza w porównaniu z sortowaniem
L E E A MH L E P s 0 L T s X R
1 -sortowanie E L E A M H L E P s 0 L T s X R przez wybieranie i wstawianie.
E E L A M H L E P s 0 L T s X R Działa też dobrze dla dowolnie
A E E L M H L E P s 0 L T s X R (niekoniecznie losowo) uporząd
A E E L M H L E P s 0 L T s X R kowanych tablic. Utworzenie tab
A E E H L M L E P s 0 L T s X R licy, dla której sortowanie Shella
A E E H L L M E P s 0 L T s X R działa powoli dla określonego cią
A E E E H L L M P s 0 L T s X R gu odstępów, jest zwykle trudne.
A E E E H L L MP s 0 L T s X R Za pom ocą programu
A E E E H L L M P s 0 L T s X R SortCompare można się prze
A E E E H L L M 0 P s L T s X R konać, że sortowanie Shella jest
A E E E H L. L L M 0 p S T s X R
znacznie szybsze od sortowania
A E E E H L L L M0 p S T s X R
przez wstawianie lub wybieranie,
A E E E H L L L M0 p S S T X R
A E E E H L L L M 0 p S s T X R a przewaga szybkości rośnie wraz
A E E E H L L L M 0 p R s s T X z rozmiarem tablicy. Przed dalszą
Wynik A E E E H L L L M0 p R s s T X lekturą zastosuj na swoim kom
puterze program SortCompare do
Szczegółowy ślad działania sortowania Shella (wstawianie)
porównania sortowania Shella
z sortowaniem przez wstawianie
i wybieranie dla tablic o rozmiarach będących potęgami dwójki (zobacz ć w i c z e n i e
2 .1 . 2 7 ). Przekonasz się, że sortowanie Shella umożliwia rozwiązanie problemów,
z którymi nie radzą sobie prostsze algorytmy. Ten przykład to pierwsza praktycz-
2.1 a Podstawowe metody sortowania 273
Dane wejściowe
akceptowalny czas wykonania nawet dla stosunkowo dużych tablic, wymaga małej
ilości kodu i nie zajmuje dodatkowej pamięci. W kilku następnych podrozdziałach
opisano metody, które są wydajniejsze, ale — za wyjątkiem bardzo dużych N — tylko
dwukrotnie (lub nawet mniej), a ponadto są bardziej skomplikowane. Jeśli potrzebu
jesz m etody sortowania, a sortowanie systemowe jest niedostępne (kod ma działać na
przykład na sprzęcie lub w systemie zagnieżdżonym), możesz swobodnie zastosować
sortowanie Shella, a później ustalić, czy warto zastąpić je bardziej zaawansowanym
rozwiązaniem.
2.1 s Podstawowe metody sortowania 275
P y ta n ia i o d p o w ie d z i
P. Sortowanie wydaje się sztucznym problemem. Czy nie istnieje wiele innych, dużo
ciekawszych zadań wykonywanych za pomocą komputerów?
O. Możliwe, jednak liczne z tych ciekawych operacji są możliwe dzięki szybkim algo
rytm om sortowania. Wiele przykładów znajdziesz w p o d r o z d z i a l e 2.5 i w dalszych
fragmentach książki. Warto teraz zapoznać się z sortowaniem, ponieważ problem
ten jest łatwy do zrozumienia i pozwala docenić pomysłowość twórców szybszych
algorytmów.
P. Kiedy urucham iam program SortCompare, za każdym razem otrzymuję inne wy
niki (różne od tych z książki). Dlaczego tak się dzieje?
O. Zacznijmy od tego, że masz inny kom puter od używanego przez nas; dotyczy
to też systemu operacyjnego, środowiska Javy itd. Wszystkie te różnice mogą pro
wadzić do drobnych różnic w kodzie maszynowym odpowiadającym algorytmom.
Różnice między kolejnymi uruchomieniami mogą wynikać z działania różnych apli
kacji i wielu innych czynników. Przeprowadzenie bardzo dużej liczby prób powinno
zniwelować problem. Warto zauważyć, że małe różnice w wydajności algorytmów są
współcześnie trudne do zauważenia. Jest to główna przyczyna tego, że koncentrujemy
się na dużych różnicach!
276 RO ZD ZIA Ł 2 a Sortowanie
j ĆWICZENIA
2.1.1. Przedstaw (jako ślad działania kodu w stylu zastosowanym dla alg o ryt
mu 2 .i), jak przebiega porządkowanie tablicy E A S Y Q U E S T I 0 Nprzy sorto
waniu przez wybieranie.
2.1.2. Jaka jest maksymalna liczba przestawień elementu w czasie sortowania przez
wybieranie? Jaka jest średnia liczba przestawień elementu?
2.1.3. Podaj przykładową N-elementową tablicę, która prowadzi do maksymalnej
liczby udanych testów a [j] < a [min] (co prowadzi do aktualizacji wartości mi n)
w czasie sortowania przez wybieranie ( a l g o r y t m 2 . 1 ).
2.1.4. Przedstaw (jako ślad działania kodu w stylu zastosowanym dla a lg o ryt
mu 2 .2 ), jak przebiega porządkowanie tablicy E A S Y Q U E S T I 0 Nprzy sorto
waniu przez wstawianie.
2.1.5. Dla każdego z dwóch warunków z wewnętrznej pętli fo r sortowania przez
wstawianie (a l g o r y t m 2 . 2 ) opisz tablicę N elementów, dla której dany warunek jest
zawsze fałszywy po zakończeniu działania pętli.
2.1.6. Która metoda, sortowanie przez wybieranie czy sortowanie przez wstawianie,
działa szybciej dla tablicy, w której wszystkie klucze są takie same?
2.1.7. Która metoda, sortowanie przez wybieranie czy sortowanie przez wstawianie,
działa szybciej dla tablicy, w której elementy mają kolejność odwrotną względem do
celowej?
2.1.8. Załóżmy, że sortowanie przez wstawianie zastosowano dla losowo uporząd
kowanej tablicy, w której elementy przyjmują jedną z trzech wartości. Czy czas wy
konania jest liniowy, kwadratowy, czy pośredni?
2.1.9. Przedstaw(jakośladdziałaniakoduwstyluzastosowanymdlaALGORYTM U 2 .3 ),
jak przebiega porządkow anie tablicy E A S Y S N E L L S 0 R T Q U E S T I 0 N
przy sortow aniu Shella.
2.1.10. Dlaczego nie stosuje się sortowania przez wybieranie przy /;-sortowaniu
w sortowaniu Shella?
2.1.11. Zaimplementuj wersję sortowania Shella, która przechowuje ciąg odstępów
w tablicy, zamiast go obliczać.
2.1.12. Zmodyfikuj sortowanie Shella tak, aby dla każdego odstępu wyświetla
ło liczbę porównań podzieloną przez rozmiar tablicy. Napisz klienta testowego do
sprawdzania hipotezy, wedle której liczba ta jest niewielką stałą. Klient ma sortować
tablice losowych wartości typu Doubl e. Tablice mają mieć rozmiary będące potęgami
10 (zacznij od długości 100 ).
2.1 a Podstawowe metody sortowania
PROBLEMY DO ROZWIĄZANIA
2.1.13. Sortowanie talii kart. Wyjaśnij, jaką metodą uporządkowałbyś talię kart
według kolorów (w kolejności piki, kiery, trefle, kara) i według wartości kart w ra
mach każdego koloru. Uwzględnij następujące warunki — karty są ułożone w rzę
dzie przednią częścią do dołu, a jedyne dozwolone operacje to sprawdzenie wartości
dwóch kart i ich przestawienie (obróconych przednią częścią do dołu).
2.1.14. Sortowanie struktury dequeue. Wyjaśnij, jak posortowałbyś talię kart, jeśli
jedyne dozwolone operacje to sprawdzanie wartości dwóch pierwszych kart, przed
stawianie dwóch pierwszych kart i przenoszenie pierwszej karty na koniec talii.
2.1.15. Kosztowne przestawienia. Pracownik firmy spedycyjnej ma za zadanie zmienić
uporządkowanie dużej liczby skrzyń według czasu ich wysyłki. Koszty porównań są tu
więc bardzo niskie (wystarczy sprawdzić nalepić) w porównaniu z kosztem przestawień
(trzeba przenieść skrzynie). Magazyn jest prawie pełny. Dostępne jest dodatkowe miej
sce na tylko jedną skrzynię. Jaką metodę sortowania powinien zastosować pracownik?
2.1.16. Sprawdzanie poprawności. Napisz metodę check(), która wywołuje metodę
so rt () dla danej tablicy i zwraca t rue, jeśli m etoda so rt () sortuje tablicę oraz zacho
wuje w tablicy te same elementy, co początkowo. W przeciwnym razie check () ma
zwracać fal se. M etoda s o rt() może przestawiać dane nie tylko za pom ocą metody
exch (). Możesz użyć m etody Arrays .s o r t () i przyjąć, że działa poprawnie.
2.1.17. Animacja. Dodaj do Idas In s e rtio n i Sel ection kod, aby rysowały zawartość
tablicy w formie pionowych słupków, tak jak na wizualnych śladach z tego podroz
działu. Kod m a wyświetlać słupki po każdym przebiegu, co prowadzi do powstania
animacji kończącej się obrazem posortowanej tablicy, na którym słupki rozmieszczo
ne są według wysokości. Wskazówka: użyj klienta podobnego do tego z tekstu, gene
rującego losowe wartości typu Doubl e, wstaw w odpowiednich miejscach wywołania
show() w kodzie sortującym i zaimplementuj metodę show(), która czyści zawartość
obrazu i rysuje słupki.
2.1.19. Najgorszy przypadek dla sortowania Shella. Utwórz tablicę o 100 elemen
tach, zawierającą wartości od 1 do 100, dla której sortowanie Shella z odstępami 1 4
13 40 wymaga możliwie dużej liczby porównań.
2.1.20. Najlepszy przypadek dla sortowania Shella. Jaki jest najlepszy przypadek dla
sortowania Shella? Wyjaśnij odpowiedź.
278 RO ZD ZIA Ł 2 Q Sortowanie
Rozwiązanie:
public c la s s Transaction implements Comparable<Transaction>
{
}
2.1.22. Klient testowy do sortowania transakcji. Napisz klasę SortTransactions za
wierającą metodę statyczną mai n (), która wczytuje ciąg transakcji ze standardowego
wejścia, sortuje je i wyświetla wynik w standardowym wyjściu (zobacz ć w i c z e n i e
1-3-17)-
Rozwiązanie:
p ublic c la s s SortTransactions
{
public s t a t ic T ra n sa ctio n [] readTransactions()
( // Zobacz ćwiczenie 1.3.17. }
EKSPERYMENTY
2.1.23. Sortowanie talii. Poproś kilku znajomych, aby posortowali talię kart (zobacz
ć w i c z e n i e 2 . 1 . 1 3 ). Obserwuj ich starannie i zapisz stosowane przez nich metody.
Test podwajania. Napisz klienta, który wykonuje test podwajania dla algo
2 . 1 .3 1 .
rytmów sortowania. Zacznij od N równego 1000 i wyświetl N, prognozowaną liczbę
sekund, rzeczywistą liczbę sekund i stosunek czasu dla podwojonych wartości N. Użyj
tego program u do walidacji stwierdzenia, że sortowanie przez wstawianie i sortowa
nie przez wybieranie działają w czasie kwadratowym dla losowych danych wejścio
wych. Sformułuj i przetestuj hipotezę dla sortowania Shella.
2.1.36. Dane nierównomierne. Napisz klienta generującego dane testowe, które nie
są równomierne. Oto przykłady:
° jedna połowa danych to zera, a druga — jedynki;
■ połowa danych to zera, połowa z reszty to jedynki, połowa pozostałych to dwój
ki i tak dalej;
D jedna połowa danych to zera, a druga — losowe wartości typu i nt.
Sformułuj i przetestuj hipotezy dotyczące wpływu takich danych wejściowych na wy
dajność algorytmów z tego podrozdziału.
2.1.37. Częściowo posortowane. Napisz klienta, który generuje częściowo posorto
wane tablice, takie jak:
0 posortowana w 95% z losowymi wartościami w ostatnich 5%;
0 z wszystkimi elementami znajdującymi się nie dalej niż 10 miejsc od ostatecz
nej lokalizacji;
° posortowana oprócz 5% elementów losowo rozrzuconych po tablicy.
Sformułuj i przetestuj hipotezę dotyczącą wpływu takich danych wejściowych na wy
dajność algorytmów opisanych w tym podrozdziale.
2.1.38. Różne typy elementów. Napisz klienta, który generuje tablice elementów róż
nych typów o losowych wartościach kluczy. Przykładowe typy mogą obejmować:
° klucz typu S tri ng (o przynajmniej 10 znakach) i jedną wartość typu doubl e;
° klucz typu doubl e i 10 wartości typu S tri ng (o przynajmniej 10 znakach);
° klucz typu i nt i jedną wartość typu i nt [ 20].
Sformułuj i przetestuj hipotezę na tem at wpływu takich danych wejściowych na wy
dajność algorytmów z tego podrozdziału.
2.2. S O R T O W A N IE P R Z E Z S C A L A N IE
Dane wejściowe M E R G E S O R T E X A M P L E
Sortowanie lewej połowy E E G M O R R S T E X A M P L E
Sortowanie prawej połowy E E G M O R R S A E E L M P T X
Scalanie wyników A E E E E G L M M O P R R S T X
Przebieg sortowania przez scalanie
282
2.2 Sortowanie przez scalanie 283
Ta metoda najpierw kopiuje dane do pomocniczej tablicy aux [], a następnie scala je z powro
tem w tablicy a []. Przy scalaniu (druga pętla for) występują cztery warunki — wyczerpano
lewą połowę (należy pobrać dane z prawej), wyczerpano prawą połowę (należy pobrać dane
z lewej), aktualny klucz po prawej ma wartość mniejszą niż aktualny klucz po lewej (należy
pobrać dane z prawej), aktualny klucz po prawej ma wartość większą lub równą względem
aktualnego klucza po lewej (należy pobrać dane z lewej).
a[] a u x []
k 0 1 2 3 4 5 6 7 8 9 i j 0 1 2 3 4 5 6 7 8 9
Dane wejściowe E E G M R A C E R T
Kopia E E G M R A C E R T E E G M R A C E R T
0 5
0 A 0 6 E E G M R A C E R T
1 A C 0 7 E E G M R C E R T
2 A C E 1 7 E E G M R E R T
3 A C E E 2 7 E G M R E R T
4 A C E E E 2 8 G M R E R T
5 A C E E E G 3 8 G M R R T
6 A C E E E G M 4 8 M R R T
7 A C E E E G M R 5 8 R R T
8 A C E E E G M R R 5 9 R T
9 A C E E E G M R R T 6 10 T
Scalony wynik A C E E E G M R R T
public c la s s Merge
{
private s t a t ic Comparable[] aux; // Tablica pomocnicza do scalania.
Aby posortować podtablicę a [ 1o . .h i], należy podzielić ją na dwie części — a [lo . .mid]
i a [mid+1 . .h i] — posortować je niezależnie od siebie (przez wywołania rekurencyjne) i sca
lić uzyskane uporządkowane podtablicę w celu otrzymania wyniku.
a[]
lo hi 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
V /
\ / M E R G E S O R T E X A M P L E
m erge (a , 0, 0, 1) E M R G E s O R T E X A M P L E
m e rge (a , 2, 2, 3) E M G R E s O R T E X A M P L E
m e rge (a , 0, 1, 3) E G M R E s 0 R T E X A M P L E
m e rge (a , 4, 4, 5) E G M R E s 0 R T E X A M P L E
mergeCa, 6, 6, 7) E G M R E s 0 R T E X A M P L E
mergeCa, 4, 5, 7) E G M R E 0 R S T E X A | P L E
m erge (a, 0, 3, 7) E E G M O R R S T E X A M P L E
m e rge (a , 8, 8, 9) E E G M O R R S E T X A M P L E
m e rge (a , 10, 10, 11) E E G M O R R s E T A X M P L E
m erge (a, 8, 9, 11) E E G M 0 R R s A E T X M P L E
mergeCa, 12, 12, 13) E E G M 0 R R s A E T X M P L E
mergeCa, 14, 14, 15) E E G M 0 R R s A E T X M P E L
m e rge (a , 12, 13, 15) E E G M 0 R R s A E T X E L M P
m erge fa, 8, 11, 15) E E G M 0 R R s A E E L M P T X
m erge (a, 0, 7, 15) A E E E E G L M M O P R R S T X
Ślad efektów scalania przy zstępującym sortowaniu przez scalanie
286 RO ZD ZIA Ł 2 ■ Sortowanie
Pierwszy wyraz po prawej stronie to liczba porównań przy sortowaniu lewej poło
wy tablicy. Drugi wyraz to liczba porównań przy sortowaniu prawej połowy. Trzeci
wyraz to liczba porównań przy scalaniu. Wynika z tego dolne ograniczenie:
C ( N ) < C ( L W 2 j ) + C([N/2]) + Ln /2]
Ig w
( a [ Q . . l ] ) ( a [ 2 . .3 ] ) ( a [ 4 . . 5 ] ) ( a [ 6 . . 7 ] ) ( a [ 8 . .9] ) (a [ 1 0 . . 1 1 ] ) (a [ 1 2 . . 1 3 ] ) (a [ 1 4 .
Pierwsza połowa
jest posortowana ............................. umilili lllllllllllllll, ll „Lnll Ll . i i l i Ji .Jllll,lii.Iil.lllill.
D ruga połow a
je s t p o so rto w a n a -.» n iiillll mu ...mu
Wizualny ślad sortowania przez scalanie z przełączeniem metody dla małych podtablic
2.2 o Sortowanie przez scalanie 289
public c la s s MergeBU
{
prívate s t a t i c Comparable[] aux; // Tablica pomocnicza do scalania.
Wstępujące sortowanie przez scalanie obejmuje serię przebiegów po całej tablicy, w których
scalane są po dwie podtablice o wielkości sz. Początkowo sz jest równe 1, a każdy prze
bieg powoduje podwojenie tej wartości. Ostatnia podtablica ma rozmiar sz tylko wtedy, jeśli
wielkość tablicy jest wielokrotnością sz (w przeciwnym razie podtablica jest krótsza).
a [i]
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
SZ = 1
M E R G E s 0 R T E X A M P L E
mergefa, 0, 0, 1) E M R G E s 0 R T E X A M P L E
mergefa, 2, 2, 3) E M G R E s 0 R T E X A M P L E
merge(a, 4, 4, 5) E M G R E s 0 R T E X A M P L E
mergefa, 6, 6, 7) E M G R E s 0 R T E X A M P L E
mergefa, 8, 8, 9) E M G R E s 0 R E T X A M P L E
mergefa, 10, 10, 11) E M G R E s 0 R E T A X M P L E
mergefa, 12, 12, 13) E M G R E s 0 R E T A X M P L E
mergefa, 14, 14, 15) E M G R E s 0 R E T A X M P E L
sz = 2
mergefa, 0, 1, 3) E G M R E s 0 R E T A X M P E L
mergefa, 4, 5, 7) E G M R E 0 R s E T A X M P E L
mergefa, 8, 9, 11) E G M R E 0 R S A E T X M P E L
mergefa, 12, 13, 15) E G M R E 0 R s A E T X E L M P
sz =4
mergefa, 0, 3, 7) E E G M O R R s A E T X E L M P
mergefa, 8, 11, 15) E E G M 0 R R s A E E L M P T X
OO
II
w
N
mergefa, 0, 7, 15) A E E E E G L M M 0 P R R S T X
Ślad z wynikami scalania we wstępującym sortowaniu przez scalanie
2.2 0 Sortowanie przez scalanie 291
Takie drzewo nigdy nie jest tworzone bezpośrednio — stanowi tylko narzędzie
matematyczne do opisu porównań używanych przez algorytm.
Pierwszym kluczowym spostrzeżeniem w dowodzie jest to, że drzewo musi
obejmować przynajmniej N\ liści, ponieważ dla N niepowtarzalnych kluczy ist
nieje N! różnych permutacji. Jeśli jest mniej niż N] liści, musi brakować pewnych
permutacji, a algorytm ich nie znajdzie.
Liczba węzłów wewnętrznych na ścieżce z korzenia do liścia to liczba porów
nań wykonywanych przez algorytm dla pewnych danych wejściowych. Interesuje
nas długość najdłuższej ścieżki w drzewie (wysokość drzewa), ponieważ wyznacza
ona liczbę porównań dla najgorszego przypadku. Podstawową cechą kombina-
toryczną drzew binarnych jest to, że drzewo o wysokości h ma nie więcej niż 2h
liści. Drzewo o wysokości h z maksymalną liczbą liści jest w pełni zbalansowane
(kompletne). Na następnej stronie przedstawiono rysunek dla h = 4.
2.2 ■ Sortowanie przez scalanie 293
{ PYTANIA I ODPOWIEDZI
O. W praktyce czas wykonania obu tych algorytmów nie różni się więcej niż o mały
stały czynnik (jeśli w sortowaniu Shella zastosuje się dobrze sprawdzony ciąg odstę
pów, taki jak w a l g o r y t m i e 2 .3 ). Dlatego ich względna wydajność zależy od imple
mentacji.
% java SortCompare Merge Shell 100000
Dla 100000 losowych wartości Double
technika Merge j e s t 1.2 razy szybsza od Shell
P. Jaka jest wydajność sortowania przez scalanie, jeśli wartości w tablicy się powta
rzają?
O. Jeżeli wszystkie elementy mają tę samą wartość, czas wykonania jest liniowy (po
zastosowaniu dodatkowego testu, który pozwala pominąć scalanie, gdy tablica jest
posortowana). Jeśli jednak powtarza się więcej niż jedna wartość, trudno poprawić
wydajność. Załóżmy na przykład, że tablica wejściowa składa się z N elementów o da
nej wartości na pozycjach nieparzystych i N elementów o innej wartości na pozycjach
parzystych. Czas wykonania jest tu liniowo-logarytmiczny (tak jak dla elementów
o różnych wartościach), a nie liniowy.
296 RO ZD ZIA Ł 2 □ Sortowanie
ĆWICZENIA
2.2.1. Przedstaw ślad działania kodu (podobny do śladu z początkowej części pod
rozdziału), aby pokazać, jak klucze A E Q S U Y E I N O S T s ą scalane za pomocą
abstrakcyjnej metody merge() działającej w miejscu.
2.2.4. Czy abstrakcyjne scalanie w miejscu zwraca poprawne dane wyjściowe wte
dy i tylko wtedy, jeśli dwie tablice wejściowe są posortowane? Udowodnij odpowiedź
lub przedstaw kontrprzykład.
2.2.5. Dla N = 39 podaj ciąg rozmiarów podtablic w operacjach scalania w algoryt
mach zstępującego i wstępującego sortowania przez scalanie.
2.2.6. Napisz program obliczający dokładną wartość liczby dostępów do tablicy
w zstępującym i wstępującym sortowaniu przez scalanie. Użyj programu do rysowa
nia wykresów dla wartości N od 1 do 512 i porównaj dokładne wartości z górnym
ograniczeniem — 6N lg N.
2 . 2 . 7 . Pokaż, że liczba porównań w sortowaniu przez scalanie jest monotonicznie
rosnąca (C(IV+1) > C(N) dla wszystkich N > 0).
p r o b l e m y d o r o z w ią z a n ia
2.2.12. Dodatkowa pamięć rosnąca wolniej niż liniowo. Opracuj implementację sca
lania, w której potrzebna dodatkowa pamięć wynosi tylko max(M, N/M). Wykorzystaj
następujący pomysł — podziel tablicę na N / M bloków o wielkości M (dla uproszcze
nia opisu zakładamy, że N to wielokrotność M). Następnie (i), traktując bloki jak
elementy z pierwszym kluczem jako kluczem sortowania, posortuj je za pom ocą sor
towania przez wybieranie i (ii) przejdź przez tablicę, scalając pierwszy blok z drugim,
drugi z trzecim itd.
2.2.13. Dolne ograniczenie dla typowego przypadku. Udowodnij, że oczekiwana licz
ba porównań w dowolnym algorytmie sortowania opartym na porównaniach musi
wynosić przynajmniej ~ N lg N (przy założeniu, że wszystkie możliwe kolejności
danych wejściowych są równie prawdopodobne). Wskazówka: oczekiwana liczba
porównań to przynajmniej długość zewnętrznej ścieżki w drzewie porównań (suma
długości ścieżek z korzenia do wszystkich liści); liczba ta jest m inim alna dla drzewa
zbalansowanego.
2.2.14. Scalanie posortowanych kolejek. Opracuj statyczną metodę, która jako argu
menty przyjmuje dwie kolejki posortowanych elementów i zwraca kolejkę utworzoną
przez scalenie dwóch pierwotnych w jedną posortowaną.
^ e k s p e ry m e n ty
Tworzenie tablicy. Użyj programu SortC om pare, aby ogólnie określić na swo
2 .2 .2 6 .
im komputerze wpływ, jaki na wydajność ma tworzenie tablicy aux[] w metodzie
m erge() zamiast w s o r t ().
Dane w ejściow e Q U I C K S 0 R T E X A M P L E
M ieszanie K A T E L E P U I M Q C X 0 s
Element osiowy
Podział E C A I E K L P U T M Q R X 0 s
Nie większe Nie mniejsze ^
S o rto w an ie lew ej stro n y A C E E I K L P U T M Q R X 0 s
S o rto w an ie p raw ej strony A C E E I K L M 0 P Q R S T U X
Wynik A c E E I K L M 0 P Q R S T u X
300
M
2.3 Sortowanie szybkie 301
p u b l i c c l a s s Quick
{
public s t a t i c void sort(Comparabl e[] a)
{
St d Ra n d om. s h u f f l e ( a ) ; / / Eliminowanie z a le ż n o śc i od d a n y c h w e j ś c i o w y c h .
sort(a, O, a . l e n g t h - 1);
}
lo j hi 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
Początkowe wartości U I C l< S O R T E X A M p L E
Q
Losowe mieszanie K R A T E L E P U I M Q C X O s
0 5 15 E C A I E K L P U T M Q R X O s
0 3 4 E C A E I K L P U T M Q R X O s
0 2 2 A C E E I K L P u T M Q R X 0 s
0 0 1 A C E E I K L P u T M Q R X 0 s
A 1 A C E E I K L P u T M Q R X 0 s
<r 4 4 A c E E I K L P u T M Q R X 0 s
6 6 15 A c E E I K L P u T M Q R X 0 s
Bez podziału 7 9 15 A c E E I K L M 0 P T Q R X u s
podtablic 7 7 8 A c E E I K L M 0 P T Q R X u s
o wielkości 1 ' 8 S A c E E I K L M 0 P T Q R X u s
10 13 15 A c E E I K L M 0 P S Q R T u X
10 12 12 A c E E I K L M 0 P R Q S T u X
10 11 11 A c E E I K L M 0 P Q R S T u X
10 10 A c E E I K L M 0 P Q R s T u X
14 14 15 A c E E I K L M 0 P Q R s T u X
*15 15 A c E E I K L M 0 P 0 R s T u X
Wynik A c E E I K L M 0 P Q R s T u X
30 2 RO ZD ZIA Ł 2 h Sortowanie
Istotą m etody jest proces podziału, który powoduje uporządkowanie tablicy w taki
sposób, aby spełnione były trzy poniższe warunki:
■ Element a [j] znajduje się na ostatecznym miejscu w tablicy (dla pewnego j).
■ Żaden element w przedziale od a [1 o] d o a [ j - l ] nie jest większy niż a [j].
■ Żaden element w przedziale od a [j+ 1 ] d o a [h i] nie jest mniejszy niż a [j].
Można posortować całą tablicę, dzieląc ją, a następnie rekurencyjnie stosując przed
stawioną metodę.
Ponieważ proces podziału zawsze umieszcza jeden element na ostatecznej pozy
cji, nietrudno jest utworzyć formalny dowód przez indukcję na to, że rekurencyjna
metoda poprawnie sortuje dane. Jeśli lewa i prawa podtablica są poprawnie posor
towane, wynikowa tablica, składająca się z lewej podtablicy (uporządkowanej i bez
elementów większych niż osiowy), elementu osiowego i prawej podtablicy (uporząd
kowanej i bez elementów mniejszych niż osiowy), jest posortowana, a l g o r y t m 2.5
to rekurencyjny program będący implementacją opisanego pomysłu. Jest to algorytm
z randomizację, ponieważ przed sortowaniem loso
Przed V
t t
wo miesza zawartość tablicy. Mieszanie stosuje się po
lo hi
to, aby móc przewidzieć cechy z obszaru wydajności
(i wiedzieć, że będą prawdziwe). Zagadnienie to opi
B U<v
W trakcie >V
sano dalej.
Aby uzupełnić implementację, trzeba zaimple
Po >V
mentować metodę dzielącą. Służy do tego następu
ł ł
lo hi jąca ogólna strategia — najpierw należy arbitralnie
Podział w sortowaniu szybkim wybrać a [1 o] jako element osiowy, który znajdzie się
na ostatecznej pozycji. Następnie należy sprawdzać
elementy od lewej strony tablicy do m om entu znalezienia elementu większego od
osiowego (lub m u równego), a następnie przeszukiwać elementy od prawej strony
tablicy do czasu wykrycia wartości mniejszej od osiowej (lub jej równej). Dwa ele
menty, na których się zatrzymano, znajdują się w niewłaściwych miejscach, dlatego
należy je przestawić. Kontynuacja tego procesu gwarantuje, że żaden element tablicy
na lewo od lewego indeksu (i) nie jest większy od elementu osiowego, a żaden ele
m ent na prawo od prawego indeksu (j) nie jest mniejszy od osiowego. Kiedy w arto
ści indeksów się przetną, wystarczy zakończyć proces podziału przez przestawienie
elementu osiowego a [1 o] z pierwszym od prawej elementem lewej podtablicy (a [ j ] )
i zwrócić indeks j .
Z implementowaniem sortowania szybkiego związanych jest kilka zaawansowa
nych kwestii. Uwzględniono je w kodzie i warto o nich wspomnieć, ponieważ każda
może prowadzić do powstania nieprawidłowego kodu i mieć duży wpływ na wydaj
ność. Dalej omówiono niektóre takie kwestie. W dalszej części podrozdziału opisano
trzy ważne usprawnienia algorytmiczne wyższego poziomu.
2.3 Sortowanie szybkie 303
Kod dzieli tablicę według elementu v z pozycji a [1 o ]. Pętla główna kończy pracę, kiedy uży
wane do przeglądania tablicy indeksy i oraz j się przetną. W pętli indeks i jest zwiększany
dopóty, dopóki a [i ] ma wartość mniejszą niż v, natomiast indeks j jest zmniejszany dopóty,
dopóki a [j] ma wartość większą niż v. Wtedy ma miejsce przestawianie, co pozwala zacho
wać niezmiennik, zgodnie z którym żaden element na lewo od i nie jest większy niż v i żaden
element na prawo od j nie jest mniejszy niż v. Po przecięciu się indeksów można dokończyć
podział, przestawiając a [1 o] z a [j] (przez co wartość osiowa zostaje zapisana w a [ j ] ).
V a[]
i 1 2 3 4 5 6 7 8 9 10 1 1 12 13 14 15
j \ \°
Początkow e w artości 0 16 l< R A T E L E P U X M Q C X 0 s
P rzeg ląd an ie od lewej,
p rz e g lą d an ie od praw ej
1 12 l< _R - A - I __£___ L E P U jr_ c X 0 s
Przestaw ianie 1 12 K C TT T - r L E P U i ~TT~q— r X 0 s
Przeg ląd an ie od lewej,
3 9 K C A T E i M Q R X 0 s
p rz e g lą d an ie od praw ej
Jeśli klucze mogą być równe, co jest typowe w praktycznych zastosowaniach, pre
cyzyjne analizy są dużo bardziej skomplikowane, jednak nietrudno wykazać, że
średnia liczba porównań jest nie większa niż CN nawet przy powtarzających się
kluczach (na stronie 308 opisano sposób na usprawnienie sortowania szybkiego
w takiej sytuacji).
Mimo wielu zalet podstawowe sortowanie szybkie ma jedną potencjalną wadę
— może być niezwykle niewydajne, jeśli podziały są niezrównoważone. Pierwszy po
dział może być oparty na najmniejszym elemencie, drugi — na kolejnym najm niej
szym i tak dalej, dlatego program w każdym wywołaniu usuwa tylko jeden element,
co prowadzi do zbyt dużej liczby podziałów długich podtablic. Losowe mieszanie
tablicy przed sortowaniem szybkim służy właśnie uniknięciu takiej sytuacji. Operacja
ta sprawia, że niekorzystne podziały są tak mało prawdopodobne, że nie trzeba się
nimi przejmować.
2.3 a Sortowanie szybkie 307
różnił się o stałą od 1,39 N l g N przy sortowaniu N elementów. To samo dotyczy sor
towania przez scalanie, jednak sortowanie szybkie jest zwykle szybsze (mimo liczby
porównań większej o 39% procent), ponieważ obejmuje znacznie mniej przestawień
danych. Ta matematyczna gwarancja jest probabilistyczna, jednak z pewnością m oż
na na niej polegać.
Jeśli kod sortujący ma być stosowany wielokrotnie lub służy do sortowania dużych
tablic (a zwłaszcza jeżeli ma pełnić funkcję sortowania bibliotecznego, stosowanego
do tablic o nieznanych cechach), warto zastanowić się nad usprawnieniami opisany
mi w kilku następnych akapitach. Jak wspomniano, trzeba przeprowadzić ekspery
menty, aby określić skuteczność usprawnień i ustalić param etry optymalne dla im
plementacji. Zwykle możliwe jest uzyskanie poprawy od 20 do 30%.
Przełączanie na sortowanie p rzez wstawianie Wydajność sortowania szybkiego,
podobnie jak większości algorytmów rekurencyjnych, można łatwo zwiększyć na
podstawie dwóch następujących obserwacji:
■ Dla małych podtablic sortowanie szybkie jest wolniejsze niż sortowanie przez
wstawianie.
■ M etoda s o rt() w sortowaniu szybkim jest rekurencyjna, dlatego może wywo
ływać samą siebie dla małych podtablic.
Dlatego dla małych podtablic warto zastąpić sortowanie szybkie sortowaniem przez
wstawianie. Prosta modyfikacja a l g o r y t m u 2.5 pozwala zastosować to usprawnie
nie. W metodzie s o rt() należy zastąpić instrukcję:
O bie p o d ta b lic e są
częściow o p o so rto w a n e
Wynik
ce (nawet dla dużej liczby równych kluczy) sortowanie szybkie jest asymptotycznie
szybsze niż sortowanie przez scalanie i inne metody. Później J. Bentley i R. Sedgewick
udowodnili tę obserwację, co opisano dalej.
Udowodniono jednak, że sortowanie przez scalanie jest optymalne. Jak udało się
przekroczyć to dolne ograniczenie? Oto odpowiedź: t w i e r d z e n i e i z p o d r o z d z i a ł u
2.2 dotyczy wydajności dla najgorszego przypadku dla wszystkich możliwych da-
2.3 Sortowanie szybkie 311
public c la s s Quick3way
{
private s t a t ic void sort(Comparable[] a, in t lo, in t hi)
{ // Publiczna metoda s o r t ( ) wywołująca tę metodę znajduje s ię na
// stro n ie 301.
i f (hi <= lo) return;
in t l t = lo, i = 1o + l , gt = hi;
Comparable v = a [1 o] ;
while (i <= gt)
{
in t cmp = a [ i ] .compareTo(v);
if (cmp < 0) exch(a, lt++, i++);
else i f (cmp > 0) exch(a, i, g t--);
else i++;
} // Teraz a [1 o . .11-1] < v = a [11 . . gt] < a [g t+ 1 . . h i].
s o rt (a , lo, l t - 1);
s o rt (a , gt + 1, h i ) ;
}
}
Ten kod dzieli tablicę, aby umieścić klucze równe elementowi osiowemu na docelowych po
zycjach, przez co nie trzeba uwzględniać tych kluczy w podtablicach w wywołaniach reku-
rencyjnych. Dla tablic o dużej liczbie powtarzających się kluczy jest to znacznie wydajniejsze
niż w standardowej implementacji sortowania szybkiego (zobacz opis w tekście).
V a[]
lt i gt \ o 1 2 3 4 5 6 7 8 9 10 11
0 0 11 R B W W R W B R R w B R
0 1 11 R B W w R W B R R w B R
1 2 11 B R W —w---- R__M___ B R R _JW — B— R
1 2 10 B R R — W-- R W B R R W B ■W
1 3 10 B R R W- -W___ B_ — w- B W
1 3 9 B R -,R, B — R " TflT~ B 1 T~ R T T " W W
2 4 9 B B R 'R R W B R R w w w
2 5 9 B B R R R W ■— - W w w
2 5 8 B B R R R w r:" w w w
2 5 7 B E R R R R .B R w w w w
2 6 7 B B R R B R w w w w
3 7 7 B B B —R R R'- R R w w w w
3 8 7 B B B R R R R R w w w w
3 8 7 B B B R R R R R w w w w
Ślad podziału na trzy części (zawartość tablicy po każdej Iteracji pętli)
312 RO ZD ZIA Ł 2 ■ Sortowanie
nil Ij.ll.ll.llljlillillJiliiiilIllilhjlililliiiJlIU.Ilh.lliliiiIlllljllLnljiijjliii
iiiiBSiiiiliiii>inhiiiiiiiiiiiiogiDQIQBI9IBHI0101010000020000010002020002
... ¡DOI
■ .
i mu
p ń iA /rtP p lp m p n t n if l/ i n ę ir n A / p m il ^
Illllllllllllllllllllllllllllllllllllllllllllllllllllll
n nn n n □ o n a n nnn n n
905348235353485348532353235348483048010102234802
■g g a s s i n i a i l i l S _ _ _ _ _ _ _ _ _ _ _ _ _ _ ____ _________________________ . . . . . ___
.......................urn.....Illllllllllllllllllllllllllllllllllllllllllllllllllllll
i i i lllllllllllllllllllllllllllllllllllllllllllllllllllllll
iiimiiimiiiiHimlllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllll
Wizualny ślad sortowania szybkiego z podziałem na trzy części
nych wejściowych, natom iast teraz w ażna jest w ydajność dla najgorszego przypadku
z uw zględnieniem pew nych inform acji o w artościach kluczy. Sortow anie przez sca
lanie nie gw arantuje optym alnej w ydajności dla dowolnego układu pow tarzających
się kluczy w danych wejściowych. Technika ta jest liniow o-logarytm iczna dla losowo
uporządkow anej tablicy zawierającej stałą liczbę niepow tarzalnych w artości kluczy,
natom iast sortow anie szybkie z podziałem na 3 części jest dla takiej tablicy liniowe.
Patrząc na w izualny ślad przedstaw iony powyżej, m ożna zauważyć, że N razy liczba
w artości kluczy to konserw atyw ne ograniczenie czasu w ykonania.
W analizach precyzujących te kwestie uw zględniono rozkład w artości kluczy. Dla
N kluczy o k różnych w artościach dla każdego i od 1 do A: zdefiniow ano^ jako liczbę
w ystąpień i-tej w artości klucza, a p. jako f . I N , czyli praw dopodobieństw o, że i-ta
w artość klucza zostanie znaleziona po w ybraniu losowego elem entu tablicy. Entropia
Shannona dla kluczy (klasyczna m iara ilości inform acji) wynosi:
H = - (P, lg P , + Pi lg P 2 + - + Pk lg P *)
D la dowolnej tablicy sortow anych elem entów m ożna określić entropię, licząc w y
stąpienia poszczególnych w artości kluczy. Co ciekawe, na podstaw ie entropii m ożna
też określić zarów no dolne, jak i górne ograniczenie liczby porów nań potrzebnych
w sortow aniu szybkim z podziałem na trzy części.
PYTANIA I ODPOWIEDZI
P. Czy istnieje sposób na taki podział tablicy na dwie połowy, aby nie robić tego
w przypadkowym miejscu wyznaczanym przez element osiowy?
O. Eksperci głowią się nad tym pytaniem od lat. Problem sprowadza się do znalezie
nia mediany wśród wartości kluczy z tablicy i przeprowadzenia podziału na podstawie
tej wartości. Problem znajdowania mediany opisano na stronie 358. Operację można
przeprowadzić w czasie liniowym, jednak koszt wykonania jej za pomocą znanych al
gorytmów (opartych na podziale z sortowania szybkiego!) znacznie przekracza 39%
oszczędności, jakie można uzyskać przez podział tablicy na równe części.
P. Mam wrażenie, że losowe mieszanie tablicy zajmuje istotną część czasu potrzeb
nego na sortowanie. Czy naprawdę warto to robić?
ĆWICZENIA
2.3.2. Pokaż, za pomocą śladu podobnego do śladu użytego dla sortowania szybkiego
w tym podrozdziale, jak sortowanie szybkie sortuje tablicę E A S Y Q U E S T I O N .
W tym ćwiczeniu pom iń początkowe mieszanie.
2.3.3. Jaka jest maksymalna liczba przestawień największego elementu tablicy o dłu
gości N w czasie wykonywania m etody Qui ck. so rt () ?
2.3.5. Podaj fragment kodu do sortowania tablicy, o której wiadomo, że klucze jej
elementów mają tylko dwie różne wartości.
2.3.8. Ile mniej więcej porównań wykonuje m etoda Quick, s o rt () przy sortowaniu
tablicy o N elementach, z których każdy ma tę samą wartość?
2.3.9. Wyjaśnij, co dzieje się po uruchom ieniu metody Quick.s o rt() dla tablicy
z tylko dwoma różnymi kluczami. Następnie wytłumacz, co dzieje się po uruchom ie
niu metody dla tablicy o trzech różnych kluczach.
2.3.12. Pokaż, za pomocą śladu podobnego do śladu użytego dla kodu w tekście, jak
sortowanie optymalne ze względu na entropię podzieli początkowo tablicę B A B A B
ABACADABRA.
2.3.13. Jaka jest głębokość rekurencji w sortowaniu szybkim dla najlepszego, najgor
szego i typowego przypadku? Odpowiada ona rozmiarowi stosu potrzebnego przez
system do śledzenia rekurencyjnych wywołań. W ć w i c z e n i u 2 .3.20 znajdziesz spo
sób na zagwarantowanie, że głębokość rekurencji rośnie logarytmicznie dla najgor
szego przypadku.
[ p r o b l e m y d o r o z w ią z a n ia
2.3.15. Nakrętki i śruby (autor — G.J.E. Rawlins). Masz wymieszaną stertę N nakrę
tek i N śrub. Musisz szybko znaleźć pasujące do siebie pary nakrętek i śrub. Każda
nakrętka pasuje do dokładnie jednej śruby, a każda śruba pasuje do dokładnie jednej
nakrętki. Sprawdzając nakrętkę i śrubę, możesz stwierdzić, która część jest większa,
nie można jednak bezpośrednio porównać dwóch nakrętek lub śrub. Przedstaw wy
dajną metodę rozwiązania problemu.
2.3.16. Najlepszy przypadek. Napisz program, który generuje tablicę dla najlepszego
przypadku (wolną od powtórzeń) dla m etody s o rt() z a l g o r y t m u 2 .5 . Ma to być
tablica N elementów o różnych kluczach i cechująca się tym, że każdy podział daje
podtablice różniące się rozmiarem o najwyżej jeden element (ich wielkości mają być
takie same, jak dla tablicy o N równych kluczach). W tym ćwiczeniu pom iń począt
kowe mieszanie.
Dalsze ćwiczenia dotyczą odmian sortowania szybkiego. Każda wersja wymaga imple
mentacji, przy czym oczywiście warto użyć też programu SortCompare do eksperymen
tów w celu oceny skuteczności każdej proponowanej modyfikacji.
2.3.17. Wersja z wartownikami. Zmodyfikuj kod a l g o r y t m u 2 .5 , aby usunąć oba
testy granic w wewnętrznych pętlach whi 1e. Test lewego końca podtablicy jest zbęd
ny, ponieważ element osiowy jest wartownikiem (v nigdy nie jest mniejsza niż a [1 o ]).
Aby umożliwić usunięcie drugiego testu, bezpośrednio po mieszaniu umieść element
mający największy klucz w tablicy na pozycji a [le n g th -l]. Element ten nigdy nie
zmieni pozycji (chyba że zostanie przestawiony z elementem o identycznym kluczu)
i posłuży za wartownika we wszystldch podtablicach obejmujących koniec tablicy.
Uwaga: przy sortowaniu wewnętrznych podtablic lewy element podtablicy znajdują
cej się po prawej służy za wartownika na prawym krańcu danej podtablicy.
[ eksperym enty
320
2.4 a Kolejki priorytetowe 321
p u b l i c c l a s s TopM
{
public s t a t i c void m ain(String[] args)
{ // W y ś w i e t l a n i e M n a j w i ę k s z y c h w a r t o ś c i ze s t r u m i e n i a we j ś c i o we g o ,
in t M = In te g e r. p a r s e l n t ( a r g s [0]);
M i n P Q < T r a n s a c t i o n > pq = new M i n P Q < T r a n s a c t i o n > ( M + l ) ;
while (Stdln.hasNextLineQ)
{ // Two r z e n i e el ementu na p od s t a w i e n a s t ę p n e g o w i e r s z a
// i dodawani e danych do k o l e j k i priorytetowej.
pq.insert(new T ra n s a c t i o n ( S t d In . r e a d L i n e ( ) ) ) ;
if (pq.sizeQ > M)
pq.delMin(); // Usuwani e minimum, j e ś l i w k o l e j c e j e s t M+l
// elementów.
} // W k o l e j c e z n a j d u j e s i ę M n a j w i ę k s z y c h elementów.
Klient klasy Mi nPQ przyjmuje liczbę całkowitą M(podaną w wierszu poleceń) i stru
mień wejściowy, w którym każdy wiersz zawiera transakcję, a następnie wyświet
la Mwierszy o największych wartościach. Wykorzystano przy tym opracowaną
przez nas klasę T r a n s a c t i o n (zobacz stronę 91, ć w ic z e n ie 1 .2.19 i ć w ic z e n ie
2 .1 .2 1 ) do zbudowania kolejki priorytetowej z wartościami jako kluczami. Kiedy
rozmiar kolejki priorytetowej przekracza M, pro
% more t in y B a tc h .tx t gram usuwa minim alną wartość po wstawieniu
Turing 6/17/1990 644.08 nowej. Po przetworzeniu wszystkich transakcji
vonNeumann 3/26/2002 4121.85
Di jk s t r a 8/22/2007 2678.40
M największych wartości pobieranych jest z ko
vonNeumann 1/11/1999 4409.74 lejki priorytetowej w kolejności rosnącej. Kod
Di jk s t r a 11/18/1995 837.42 umieszcza elementy na stosie, a następnie prze
Hoare 5/10/1993 3229.27
chodzi po nim, aby odwrócić kolejność wartości
vonNeumann 2/12/1994 4732.35
Hoare 8/18/1992 4381.21 i wyświetlić je w porządku malejącym.
Turi ng 1/11/2002 66.10
Thompson 2/27/2000 4747.08
Turing 2/11/1991 2156.86 % java TopM 5 < tin y B a tc h .tx t
Hoare 8/12/2003 1025.70 Thompson 2/27/2000 4747.08
vonNeumann 10/13/1993 2520.97 vonNeumann 2/12/1994 4732.35
Di jk s t r a 9/10/2000 708.95 vonNeumann 1/11/1999 4409.74
Turi ng 10/12/1993 3532.36 Hoare 8/18/1992 4381.21
Hoare 2/10/2005 4050.20 vonNeumann 3/26/2002 4121.85
324 R O ZD ZIA Ł 2 ■ Sortowanie
Wstaw
Wstaw
Wstaw
Usuń maks.
Wstaw
Wstaw
Wstaw
Usuń maks.
Wstaw
Wstaw
Wstaw
Usuń maks.
Tym samym klucz w każdym węźle drzewa binarnego uporządkowanego w kopiec j est
mniejszy lub równy względem klucza w węźle rodzica (jeśli ten istnieje). Przechodząc
w górę od dowolnego węzła, natrafiamy na niemalejący ciąg kluczy. Poruszając się
w dół, otrzymujemy nierosnący ciąg kluczy. Oto ważne spostrzeżenie.
Definicja. Kopiec binarny to kolekcja kluczy zapisana jako zupełne drzewo bi
narne uporządkowane w kopiec, reprezentowana według poziomów w tablicy
(z wolnym pierwszym elementem).
przejść w dół drzewa i degradować nową osobę oraz awansować innych ludzi do
momentu dojścia do poziomu kompetencji danej osoby, kiedy nie będzie miała wyżej
wykwalifikowanych podwładnych. Ten scenariusz rzadko ma miejsce w rzeczywi
stym świecie, jednak może pomóc w lepszym zrozumieniu podstawowych operacji
na stertach.
Operacje s in k () i swim() są podstawą wydajnej implementacji interfejsu API ko
lejek priorytetowych. Poniżej przedstawiono rysunki obrazujące interfejs, a na na
stępnej stronie znajduje się jego implementacja ( a l g o r y t m 2 .6 ).
Wstaw. Należy dodać nowy klucz na koniec tablicy, zwiększyć rozmiar kopca, a na
stępnie spowodować wypłynięcie klucza w celu przywrócenia struktury kopca.
Usuń m aksym alny. Należy usunąć największy klucz z korzenia, umieścić na jego
miejscu element z końca kopca, zmniejszyć wielkość kopca i spowodować zatopie
nie przestawionego elementu w celu przywrócenia struktury.
a l g o r y t m 2.6 to rozwiązanie podstawowego problemu postawionego na początku
m aksym aln y
Usuw any klucz
Przestawienie tego
klucza z korzeniem
Wypływanie ^
Zatapianie
Operacje na kopcu
330 RO ZD ZIA Ł 2 Sortowanie
public in t s iz e ()
{ return N; }
O tym typie danych można myśleć jak o implementacji tablicy, jednak z szybkim do
stępem do najmniejszego elementu. W rzeczywistości możliwości są jeszcze większe —
typ zapewnia szybki dostęp do minimalnej wartości określonego podzbioru elementów
(tych wstawionych). Oznacza to, że zmienna pq typu IndexMi nPQ reprezentuje podzbiór
tablicy p q[0. . N -l]. Wywołanie p q.insert(k, item) dodaje do tego podzbioru kiusta-
wiapq[k] = item. Wywołanie pq.change(k, item) ustawia pq [k] = item. Oba wywo
łania zachowują strukturę potrzebną do obsługi innych operacji — przede wszystkim
delMin() (usuwa i zwraca indeks minimalnego klucza) i change() (zmienia element
powiązany z indeksem, który już znajduje się w strukturze danych — tak jak wywo
łanie pq [i] = i tern). Operacje te są ważne w wielu zastosowaniach, a można ich uży
wać z uwagi na możliwość wskazywania kluczy (za pomocą indeksu). W ć w i c z e n i u
2 .4.33 opisano, jak rozwinąć a l g o r y t m 2 .6 , aby zaimplementować niezwykle wydajną
indeksowaną kolejkę priorytetową za pomocą bardzo małej
ilości kodu. Intuicyjnie widać, że kiedy element na kopcu się Operacja
Tempo wzrostu
zmienia, można przywrócić strukturę kopca przez zatapianie liczby porównań
Klient I ndexMi n PQscala posortowane strumienie wejściowe (podane jako argumenty w wier
szu poleceń) w jeden posortowany strumień wyjściowy kierowany do standardowego wyjścia
(zobacz opis w tekście). Indeks w każdym strumieniu jest powiązany z kluczem (następnym
łańcuchem znaków w strumieniu). Po zainicjowaniu klient wchodzi w pętlę, która wyświetla
najmniejszy łańcuch znaków z kolejki i usuwa powiązany element, a następnie dodaje nowy
element na następny łańcuch znaków z danego strumienia. Z uwagi na zwięzłość dane wyj
ściowe pokazano poniżej w jednym wierszu. W rzeczywistych danych wyjściowych istnieje
jeden łańcuch znaków na wiersz.
% more m l.tx t
A B C F G I I Z
% more m2.txt
B D H P Q Q
% more m3.txt % java Multiway m l.tx t m2.txt m3.txt
A B E F J N A A B B B C D E F F G H I I J N P Q Q Z
2.4 ra Kolejki priorytetowe 335
in t N = a.length;
f o r ( i n t k = N/2; k >= 1; k - )
s i n k ( a , k, N);
while (N > 1)
r
i
exch(a, 1, N— );
s i n k ( a , 1, N);
}
}
a [i]
N k 0 1 2 3 4 5 6 7 8 9 10 11
Początkowe wartości s 0 R T E X A M P L E
11 5 s 0 R T L X A M P E E
11 4 s 0 R T L X A M P E E
11 3 s 0 X T L R A M P E E
11 2 s T X P L R A M 0 E E
11 1 X T S P L R A M 0 E E
Uporządkowane w kopiec X T S P L R A M 0 E E
10 1 T P S 0 L R A M E E X
9 1 S P R 0 L E A M E T X
8 1 R P E 0 L E A M S T X
7 1 P 0 E M L E A R S T X
6 1 0 M E A L E P R s T X
5 1 M L E A E 0 P R s T X
4 1 L E E A M 0 P R s T X
3 1 E A E L M 0 P R s T X
2 1 E A E L M 0 P R s T X
1 1 A E E L M 0 P R s T X
Posortowane wyniki A E E L M 0 P R s T X
e x c h (l, 6)
s i n k ( l , 5)
M O P
R S T X
L M O P
R S T X
L M O p
R S T X
e x c h (l, 8) e x c h ( l , 2) g)
s i n k ( l , 7) s i n k ( l , 1)
L M O P
R S T X
e xch (l, 7)
s in k (l, 11 ) s i n k ( l , 6)
2 E 3 E
4L 5 M 60 7P
1 9 10 11
R S T X
Efekt (posortowane)
Efekt (kopiec)
Sortowanie przez kopcowanie - tworzenie kopca (po lewej) i sortowanie (po prawej)
338 R O ZD ZIA Ł 2 a Sortowanie
Działanie algorytmu — jak zwykle — można lepiej zrozumieć, analizując ślad wi
zualny. Początkowo może się wydawać, że proces wykonuje operacje odwrotne od
sortowania, ponieważ przenosi duże elementy na początek tablicy w ramach tworze
nia kopca. Jednak później metoda bardziej przypomina lustrzane odbicie sortowania
przez wybieranie (choć wymaga znacznie mniej porównań).
Wiele osób badało sposoby na usprawnienie implementacji kolejek prioryteto
wych opartych na kopcach i sortowania przez kopcowanie (podobnie jak wszystkich
innych omawianych metod). Dalej pokrótce opisano jedną z modyfikacji.
Zatapianie do poziom u dna i późniejsze wypływanie Większość elementów ponow
nie wstawianych do kopca w czasie sortowania dociera do dna. Floyd w 1964 roku
zauważył, że można zaoszczędzić czas przez pominięcie sprawdzania, czy element
znalazł się na docelowej pozycji. W tym celu wystarczy awansować większe z dwójki
dzieci do m om entu dotarcia węzła do dna, a następnie przenieść węzeł w górę ster
ty na właściwe miejsce. Rozwiązanie to zmniejsza liczbę porównań asymptotycznie
o czynnik równy 2 , co pozwala zbliżyć się do liczby potrzebnej w sortowaniu przez
scalanie (dla losowo uporządkowanych tablic). M etoda wymaga dodatkowych ope
racji porządkujących i jest przydatna w praktyce tylko wtedy, kiedy koszt porównań
jest stosunkowo wysoki (na przykład przy sortowaniu elementów o długich kluczach,
takich jak łańcuchy znaków).
żonością sortowania (zobacz stronę 291), ponieważ jako jedyna z opisanych metod
jest optymalna (w ramach stałego czynnika) w zakresie wykorzystania czasu i pam ię
ci. Dla najgorszego przypadku gwarantowana jest liczba ~2N lg N porównań i stała
ilość dodatkowej pamięci. Przy bardzo małej ilości pamięci (na przykład w syste
mach osadzonych lub w tanich urządzeniach przenośnych) technika ta jest popular
na, ponieważ m ożna ją zaimplementować za pom ocą kilkudziesięciu wierszy (nawet
w kodzie maszynowym) przy zachowaniu optymalnej wydajności. Jednak w typo
wych sytuacjach we współczesnych systemach rzadko się ją stosuje, ponieważ nie
współdziała z buforowaniem. Elementy tablicy rzadko są porównywane z bliskimi
elementami, dlatego liczba dostępów do innych buforów jest znacznie wyższa niż
w sortowaniu szybkim, sortowaniu przez scalanie, a nawet sortowaniu Shella, gdzie
większość porównań dotyczy bliskich elementów.
Jednak używanie kopców do implementowania kolejek priorytetowych jest coraz
powszechniejsze we współczesnych zastosowaniach, ponieważ umożliwia łatwe za
gwarantowanie logarytmicznego czasu wykonania w sytuacjach, kiedy duża liczba
operacji wstaw i usuń maksymalny jest wymieszana ze sobą. Kilka przykładów wyko
rzystania tej techniki opisano w dalszej części książki.
i
340 R O ZD ZIA Ł 2 □ Sortowanie
| PYTANIA I ODPOWIEDZI
P. Nadal nie jestem pewien, jaki jest cel stosowania kolejek priorytetowych. Dlaczego
nie wystarczy posortować danych, a następnie używać elementów umieszczonych
rosnąco w posortowanej tablicy?
O. Czasem przy przetwarzaniu danych, na przykład w programach TopM i Mul t i way,
łączna ilość danych jest zdecydowanie za duża, aby móc je posortować (a nawet za
pisać w pamięci). Jeśli szukasz 10 największych elementów wśród miliarda, czy na
prawdę chcesz sortować tablicę o miliardzie elementów? Za pom ocą kolejek priory
tetowych m ożna to zrobić przy użyciu 10-elementowej kolejki tego rodzaju. W in
nych sytuacjach może się zdarzyć, że w danym momencie nie wszystkie dane istnieją.
Trzeba na przykład pobrać dane z kolejki priorytetowej, przetworzyć je, a następnie
dodać do kolejki nowe elementy.
O. Użycie interfejsu wymagałoby, aby klient rzutował wartość zwracaną przez m eto
dę del Max () na rzeczywisty typ, na przykład S tri ng. Zwykle należy unikać rzutowa
nia w kodzie klienta.
P. Dlaczego w reprezentacji kopca nie używa się elementu a [0] ?
O. To podejście pozwala nieco uprościć obliczenia. N ietrudno jest zaimplementować
m etody dla kopca oparte na tablicy zaczynającej się od 0. Wtedy dziećmi elementu
a [0] są a [1] i a [2], dzieci elementu a [1] to a [3] i a [4], dzieci elementu a [2] to a [5]
i a [ 6] itd. Jednak większość programistów woli prostsze, stosowane także przez nas
obliczenia. Ponadto w niektórych zastosowaniach kopców przydatne jest ustawienie
a [ 0] jako wartownika (w rodzicu elementu a [ 1 ]).
P. Budowanie kopca w sortowaniu przez kopcowanie za pom ocą wstawiania ele
mentów jeden po drugim wydaje się prostsze niż skomplikowana metoda przecho
dzenia od dołu do góry, opisana na stronie 335. Po co stosować tę ostatnią?
£ ĆWICZENIA
2.4.1 . Załóżmy, że kolejka priorytetow a jest początkowo pusta i otrzym ano ciąg
P R I 0 * R * * I * T * Y * * * Q U E * * * U * E (litera oznacza wstaw, a gwiazdka
— usuń maksymalny). Podaj ciąg liter zwróconych przez operacje usuń maksymalny.
2.4.4. Czy tablica posortowana w porządku malejącym jest kopcem z łatwym do
stępem do maksimum?
2.4.5. Przedstaw kopiec uzyskany po wstawieniu kluczy E A S Y Q U E S T I O N
(w tej kolejności) do początkowo pustego kopca z łatwym dostępem do maksimum.
2.4.6. Na podstawie konwencji z ć w i c z e n i a 2 .4.1 podaj ciąg kopców wygenerowa
nych po wykonaniu operacji P R I O * R * * I * T * Y * * * Q U E * * * U * E
na początkowo pustym kopcu z łatwym dostępem do maksimum.
2.4.11 . Przyjmijmy, że aplikacja wykonuje bardzo dużą liczbę operacji wstaw, nato
miast tylko nieliczne operacje usuń maksymalny. Która implementacja kolejki prio
rytetowej będzie Twoim zdaniem najwydajniejsza: kopiec, tablica nieuporządkowana
czy tablica uporządkowana?
342 RO ZD ZIA Ł 2 a Sortowanie
ĆWICZENIA (ciągdalszy)
2.4.14. Jaka jest minimalna liczba elementów, które trzeba przestawić w operacji
usuń maksymalny w kopcu o wielkości N i bez powtarzających się kluczy? Przedstaw
15-elementowy kopiec, dla którego można uzyskać to minimum. Wykonaj to samo
ćwiczenie dla dwóch i trzech kolejnych operacji usuń maksymalny.
2 .4.15. Zaprojektuj działający w czasie liniowym algorytm kontrolny do sprawdza
nia, czy tablica pq [] jest kopcem z łatwym dostępem do minimum.
£ PROBLEMY DO ROZWIĄZANIA
2.4.26. Kopiec bez przestawień. Ponieważ w operacjach sink() i swim() używana jest
prosta metoda exch (), elementy są wczytywane i zapisywane dwa razy częściej niż to ko
nieczne. Przedstaw wydajniejsze, podobne do sortowania przez wstawianie implemen
tacje, pozwalające uniknąć tego niewydajnego podejścia (zobacz ć w i c z e n i e 2 .1 .25 ).
2.4.28. Filtr pobieranych danych. Napisz klienta TopM, który wczytuje ze standardo
wego wejścia punkty (x , y, z), pobiera wartość M z wiersza poleceń i wyświetla M
punktów najbliższych (według odległości euklidesowej) początkowi układu. Oszacuj
czas działania klienta dla N = 108 i M = 104.
344 RO ZD ZIA Ł 2 a Sortowanie
nie z którą qp [i ] = -1, jeśli i nie znajduje się w kolejce. Dodaj metodę contains()
sprawdzającą tę wartość. Musisz zmodyfikować m etody pomocnicze exch () i 1ess (),
jednak nie trzeba zmieniać m etod si nk() lub swim().
2.4 o Kolejki priorytetowe 345
Częściowe rozwiązanie:
public c la s s IndexMinPQ<Key extends Comparable<Key»
{
private in t N; // Liczba elementów w kolejce priorytetowej,
private i n t [] pq; // Kopiec binarny indeksowany od jedynki,
private in t [ ] qp; // Odwrotność: q p[pq[i]] = pq[qp[i]] = i.
private Key[] keys; // Elementy z priorytetami,
public IndexMinPQ(int maxN)
{
keys = (Key[]) new Comparable [maxN + 1];
pq = new int[maxN + 1];
qp = new int[maxN + 1];
fo r (in t i = 0; i <= maxN; i++) q p[i] = -1;
}
public in t delMin()
{
in t indexOfMin = pq[1];
exch(l, N --);
s in k (l);
keys[pq[N+l]] = n u l l ;
qp[pq[N+l]] = -1;
return indexOfMin;
}
}
346 R O ZD ZIA Ł 2 ■ Sortow anie
[ e k spe r y m en t y
2.4.38. Spraw dzanie popraw ności. Napisz program kliencki do sprawdzania po
prawności, używający m etod z interfejsu dla kolejki priorytetowej z a l g o r y t m u 2.6
dla trudnych lub „patologicznych” przypadków, które mogą pojawić się w praktycz
nych zastosowaniach. Proste przykłady to już uporządkowane klucze, klucze zapisa
ne w odwrotnej kolejności, same identyczne klucze i ciągi kluczy o dwóch różnych
wartościach.
2.4.39. K oszt tw orzen ia kopca. Określ empirycznie procent czasu, jaki w sortowaniu
przez kopcowanie zajmuje etap tworzenia kopca dla N = 103, 105 i 109.
2.4.40. M etoda Floyda. Zaimplementuj wersję sortowania przez kopcowanie opartą
na opisanym w tekście pomyśle Floyda (zatapianie do dna i późniejsze wypływanie).
Ustal liczbę porównań wykonywanych przez ten program i liczbę porównań w stan
dardowej implementacji dla losowo uporządkowanych różnych kluczy przy N - 10 3,
106 i 10 9.
wań. Naszym celem w tym podrozdziale jest krótki przegląd niektórych zastosowań,
przedstawienie kluczowej roli opisanych wcześniej wydajnych m etod i omówienie
kroków potrzebnych do wykorzystania kodu do sortowania i obsługi kolejek prio
rytetowych.
Sortowanie jest tak przydatne głównie dlatego, że dużo łatwiej jest wyszukiwać
elementy w tablicach posortowanych niż w nieposortowanych. Już od ponad stu lat
ludzie wiedzą, że łatwo jest znaleźć num er telefonu w książce telefonicznej, w której
wpisy są posortowane według nazwisk. Obecnie cyfrowe odtwarzacze muzyki po
rządkują pliki według nazwisk wykonawców lub tytułów utworów; wyszukiwarki
wyświetlają wyniki według ich adekwatności w porządku malejącym; arkusze kal
kulacyjne wyświetlają kolumny posortowane według konkretnego pola; narzędzia
do przetwarzania macierzy sortują liczby rzeczywiste będące wartościami własnymi
macierzy w porządku malejącym itd. Kiedy tablica jest posortowana, łatwiejsze jest
wykonywanie także innych zadań — od wyszukiwania nazw w posortowanym in
deksie w końcowej części książki przez usuwanie powtórzeń na długich listach wy
syłkowych, osób uprawnionych do głosowania lub witryn po wykonywanie obliczeń
statystycznych, takich jak usuwanie skrajnych wartości, znajdowanie mediany lub
wyznaczanie percentyli.
Sortowanie bywa też kluczowym podproblemem w wielu obszarach, które na po
zór nie mają nic wspólnego z sortowaniem. Kompresja danych, grafika kom putero
wa, biologia obliczeniowa, zarządzanie łańcuchem dostaw, optymalizacja kombinato-
ryczna, wybory społeczne i głosowanie to tylko kilka z wielu przykładów. Algorytmy
rozważane w tym rozdziale odgrywają kluczową rolę w rozwijaniu wydajnych algo
rytmów przedstawionych w każdym z dalszych rozdziałów książki.
Najważniejsze jest sortowanie systemowe, dlatego rozpoczynamy od omówienia
wielu praktycznych zagadnień, pojawiających się przy projektowaniu sortowania do
użytku w różnorodnych klientach. Choć niektóre z omawianych zagadnień są specy
ficzne dla Javy, każda kwestia odzwierciedla trudności, które trzeba rozwiązać w do
wolnym systemie.
Głównym celem jest tu zademonstrowanie, że choć użyto stosunkowo prostych
mechanizmów, rozważane implementacje mają wiele zastosowań. Lista sprawdzo
nych zastosowań szybkich algorytmów sortowania jest długa, dlatego omawiamy
tylko mały wycinek: wybrane zastosowania naukowe, algorytmiczne i komercyjne.
O wiele więcej przykładów znajduje się w ćwiczeniach i w witrynie. Ponadto często
odwołujemy się do wcześniejszych fragmentów tego rozdziału w celu skutecznego
rozwiązania problemów omawianych w dalszych częściach tej książkil
2.5 B Zastosowania 349
p u b lic c la s s Transaction
{
p riv a te final S t r in g who;
p riv a te final Date when;
p riv a te final double amount;
Sortowanie
Nie Tak N2
przez wybieranie
Zależy od
Sortowanie
Tak Tak Od N do N 2 kolejności
przez wstawianie
elementów
N log N?
Sortowanie Shella Nie Tak
N6/5?
Gwarancje
Sortowanie szybkie Nie Tak NlogN lg N
probabilistyczne
Probabilistyczne;
Sortowanie szybkie
OdNdoN zależy też od
z podziałem Nie Tak Ig N
na trzy części
log N rozkładu kluczy
na wejściu
Sortowanie
Tak Nie N\ogN N
przez scalanie
Sortowanie
Nie Tak N\ogN 1
przez kopcowanie
a [k], a elementy od a [k+1 ] do końca tablicy są większe (lub równe) względem a [k].
Aby zrozumieć, dlaczego algorytm działa w czasie liniowo-logarytmicznym, załóżmy,
że dane za każdym razem dzielone są dokładnie na połowę. Wtedy liczba porównań
wynosi N + N /2 + N /4 + N/8 + ..., a proces kończy się po znalezieniu k-tego najm niej
szego elementu. Suma wyrazów wynosi mniej niż 2 N. Ponadto, tak jak w sortowa
niu szybkim, trzeba posłużyć się matematyką, aby znaleźć rzeczywiste ograniczenie,
które jest nieco wyższe. Także podobnie jak w sortowaniu szybkim, analizy dotyczą
podziału według losowego elementu, dlatego gwarancje są probabilistyczne.
| PYTANIA I ODPOWIEDZI
jj ĆWICZENIA
)
Opisz sposób na rozwiązanie problemu.
2.5.4. Zaimplementuj metodę S trin g [] dedup(String[] a), która zwraca obiekty
z tablicy a [] w posortowanej kolejności i bez powtórzeń.
2.5.5. Wyjaśnij, dlaczego sortowanie przez wybieranie jest niestabilne.
366 RO ZD ZIA Ł 2 ■ Sortow anie
2.5.10. Utwórz typ danych Vers i on reprezentujący num er Dane wejściowe (wartość
wersji oprogramowania, na przykład 115.1.1, 115.10.1, transakcji dla indeksu DJI
z poszczególnych dni)
115.10.2. Zaimplementuj interfejs Comparable tak, aby
l-0 c t -2 8 3500000
wersja 115.1.1 była mniejsza niż 115.10.1 itd.
2 -0 c t-2 8 3850000
2.5.11. Jeden ze sposobów na opisanie wyników algoryt 3 -0 c t-2 8 4060000
4 -0 c t-2 8 4330000
m u sortowania polega na określeniu permutacji p[] dla 5 -0 ct-2 8 4360000
liczb od 0 do a .le n g th - 1 , takiej że p [i] określa końcową
lokalizację klucza znajdującego się początkowo w a [i]. 30-Dec-99 554680000
31-Dec-99 374049984
Podaj permutacje, które opisują wyniki sortowania przez 3-Jan-00 931800000
wstawianie, sortowania przez wybieranie, sortowania 4-Jan-00 1009000000
Shella, sortowania przez scalanie, sortowania szybkiego 5-Jan-00 1085500032
i sortowania przez kopcowanie dla tablicy zawierającej
siedem równych kluczy. Dane wyjściowe
19-Aug-40 130000
26-Aug-40 160000
2 4 - J u l-40 200000
10-Aug-42 210000
23-Jun-42 210000
PROBLEMY DO ROZWIĄZANIA
2.5.14. Sortowanie według odwróconych nazw domeny. Napisz typ danych Domain
reprezentujący nazwy domeny. Typ ma obejmować odpowiednią metodę compa-
reTo(), w której porządkiem naturalnym jest kolejność odwróconych nazw dom e
ny. Przykładowo, odwróconą nazwą domeny cs.princeton.edu jest edu.princeton.es.
Technika ta jest przydatna do analizowania dzienników sieciowych. Wskazówka:
użyj metody s.sp l i t ( " \ \ . ") do rozbicia łańcucha znaków s na fragmenty ograni
czone kropkami. Napisz klienta, który wczytuje nazwy domeny ze standardowego
wejścia i wyświetla odwrócone nazwy w posortowanej kolejności.
2.5.15. Kampania oparta na spamie. Jako punktu wyjścia do nielegalnej kam pa
nii opartej na spamie użyj listy adresów e-mail z różnych dom en (domena to część
adresu e-mail po symbolu @). Aby lepiej sfałszować adresy zwrotne, wysyłaj e-ma-
ile z kont innych użytkowników z tej samej domeny. Przykładowo, możesz wysłać
fałszywy e-mail od użytkownika wayne@princeton.edu do rs@princeton.edu. W jaki
sposób przetworzysz listę e-maili, aby wydajnie wykonać zadanie?
2.5.16. Uczciwe wybory. Aby nie zmniejszać szans kandydatów, których nazwiska
zaczynają się na końcowe litery alfabetu, w Kalifornii nazwiska pojawiające się na
kartach do głosowania w wyborach gubernatora w 2003 roku posortowano w nastę
pującej kolejności:
R W Q O J M V A H B S G Z X N T C I E K U P D Y F L
Utwórz typ danych, w którym jest to porządek naturalny. Napisz klienta Cal i forni a
z jedną m etodą statyczną main(), która sortuje łańcuchy znaków według tego p o
rządku. Przyjmij, że każdy łańcuch znaków składa się wyłącznie z wielkich liter.
2.5.17. Sprawdzanie stabilności. Rozwiń metodę check() z ć w ic z e n ia aby
2 .1 .1 6 ,
wywoływała metodę s o rt () dla danej tablicy i zwracała true, jeśli s o rt () sortuje
tablicę w stabilny sposób. W przeciwnym razie należy zwrócić fal se. Nie zakładaj, że
metoda sort () przestawia dane wyłącznie za pom ocą m etody exch ().
368 RO ZD ZIA Ł 2 o Sortowanie
P R O B L E M Y D O R O Z W I Ą Z A N I A (ciąg dalszy)
2.5.25. Punkty w przestrzeni. Napisz trzy statyczne kom paratory dla typu danych
Poi nt2D ze strony 89. Jeden m a porównywać punkty według współrzędnej x, drugi —
według współrzędnej y, a trzeci — według odległości od początku układu. Ponadto
napisz dwa niestatyczne kom paratory dla tego typu. Jeden ma porównywać punkty
według odległości od podanego punktu, a drugi — według kąta biegunowego wzglę
dem podanego punktu.
2.5 o Zastosowania 369
2.5.28. Sortowanie plików według nazw. Napisz program F ileS o rter, który jako
argument przyjmuje z wiersza poleceń nazwę katalogu i wyświetla wszystkie pliki
z tego katalogu posortowane według nazw. Wskazówka: użyj typu danych Fi 1e.
2.5.29. Sortowanie plików według rozmiaru i daty ostatniej modyfikacji. Napisz kom
paratory dla typu Fi 1e, aby umożliwić sortowanie w kolejności rosnącej i malejącej
według rozmiarów plików, w kolejności rosnącej i malejącej według nazw plików
oraz w kolejności rosnącej i malejącej według dat ostatniej modyfikacji. Użyj kom
paratorów w programie LS, który przyjmuje argument z wiersza poleceń i wyświetla
pliki z danego katalogu według określonej kolejności (na przykład opcja " -t" ozna
cza sortowanie według znaczników czasu). Dodaj obsługę wielu opcji, aby umożli
wić porządkowanie plików równych pod pewnym względem. Zapewnij stabilność
sortowania.
|i EKSPERYMENTY
373
Główną funkcją tablic symboli jest łączenie wartości z kluczem. Klient może wstawiać
pary klucz-wartość do tablicy symboli i oczekiwać, że później będzie mógł znaleźć
wartość powiązaną z danym kluczem wśród wszystkich umieszczonych w tabeli par.
W rozdziale opisano kilka sposobów na ustrukturyzowanie takich danych, aby wy
dajne były nie tylko operacje wstaw i wyszukaj, ale też pewne inne przydatne funk
cje. W celu zaimplementowania tablicy symboli trzeba zdefiniować strukturę danych,
a następnie opracować algorytmy do wstawiania, wyszukiwania i wykonywania in
nych operacji związanych z tworzeniem struktury danych oraz manipulowaniem nią.
Wyszukiwanie jest tak ważne w tak wielu zastosowaniach informatycznych, że
tablice symboli są dostępne jako wysokopoziomowe abstrakcje w wielu środowiskach
programistycznych, w tym w Javie (implementacje tablicy symboli w Javie omówiono
w p o d r o z d z i a l e 3 . 5 ). W tabeli poniżej przedstawiono przykładowe klucze i war
tości, które mogą występować w typowych zastosowaniach. Dalej omówiono kilka
wzorcowych klientów, a w p o d r o z d z i a l e 3.5 pokazano, jak wydajnie stosować tab
lice symboli w klientach. Tablic symboli używamy też do rozwijania innych algoryt
mów w książce.
Wyszukiwanie Wyszukiwanie
Słowo kluczowe Lista stron
w sieci W W W adekwatnych stron WWW
Wyszukiwanie typu
Kompilator Nazwa zmiennej Typ i wartość
i wartości
Zapewnia on, że żaden klucz w tablicy nie jest powiązany z wartością nul 1. Z uwagi
na zwięzłość nie zamieszczamy tego kodu w książce (nie wywołujemy też metody
put () z wartością nul 1 w kodzie klienta).
M e to d y skrócone Aby kod klienta był przejrzysty, w interfejsie API uwzględniono m e
tody contains () i i sEmpty(). Ich domyślne implementacje przedstawiono w tym miej
scu. Z uwagi na zwięzłość dalej
. . Metoda Implementacja domyślna
me powtarzamy tego kodu — ------------------------------------------------------------------
zakładamy, że jest dostępny we void del ete (Key key) put(key, n u ll) ;
wszystkich implementacjach boolean con ta in s(k e y) return get(key) != n u li;
interfejsu API tablicy symboli
boolean isEm pty() return s i z e () *== 0;
i swobodnie korzystamy z tych
m eto d W kodzie klienta. Implementacje domyślne
3.1 a Tablice symboli 377
Zapytania zakresowe Ile kluczy znajduje się w danym przedziale (między dwoma
podanymi kluczami)? Które klucze znajdują się w danym przedziale? Dwuargumento-
we metody si ze () i keys (), które odpowiadają na te pytania, są przydatne w wielu
zastosowaniach — zwłaszcza w dużych bazach danych. Możliwość obsługi takich za
pytań to jedna z głównych przyczyn popularności tablic symboli.
W yjątkowe przypadki Jeśli metoda ma zwracać klucz, a żaden klucz tablicy nie od
powiada opisowi, przyjmujemy, że należy zgłosić wyjątek (inne możliwe podejście,
także sensowne, to zwracanie wartości nul 1). Na przykład, m etody min(), max(), de-
1 eteMi n (), del eteMax (), floor () i cei 1 i ng () zgłaszają wyjątki, jeśli tablica jest pusta.
Podobnie działa wywołanie sel ect (k), jeśli k jest mniejsze niż 0 lub nie mniejsze niż
s iz e ().
M etody skrócone Jak pokazano już na przykładzie metod isEmpty() i c o n ta in s()
z podstawowego interfejsu API, w interfejsie znajdują się pewne nadmiarowe m eto
dy, co pozwala zwiększyć przejrzystość kodu klienta. Z uwagi na zwięzłość zakłada
my, że poniższe domyślne wersje znajdują się w każdej implementacji interfejsu API
uporządkowanej tablicy symboli (chyba że napisano inaczej).
M odel kosztów Niezależnie od tego, czy używamy m etody equalsQ (dla tablic
symboli, w których klucze nie implementują interfejsu Comparable) czy compare-
To() (dla uporządkowanych tablic symboli z kluczami implementującymi interfejs
Comparabl e), stosujemy określenie porównanie do operacji porównywania elemen
tów tablicy symboli z kluczem wyszukiwania. W większości implementacji tablicy
symboli operacja ta znajduje się w pętli
wewnętrznej. W nielicznych sytuacjach,
kiedy jest inaczej, liczone są też dostępy Model kosztów przy wyszukiwaniu. W cza
do tablicy. sie badania implementacji tablicy symboli
liczymy porównania (testy równości lub p o
równania kluczy). W (rzadkich) sytuacjach,
i m p l e m e n t a c j e t a b l i c s y m b o l i zw ykle
kiedy porównania nie znajdują się w pętli
różnią się u ż y w a n y m i stru k tu ra m i danych
wewnętrznej, liczymy dostępy do tablicy.
i im plem entacjam i m etod get ( ) i put ( ).
Nie zawsze przedstawiamy implementa
cje wszystkich pozostałych m etod opisanych w tekście, ponieważ opracowanie wielu
z nich to dobre ćwiczenie, pozwalające sprawdzić poziom zrozumienia używanych
struktur danych. Do rozróżniania implementacji służy opisowy przedrostek nazwy
ST, określający implementację zapisaną w klasie o danej nazwie. W klientach używa
my nazwy ST do wywoływania wzorcowej implementacji, chyba że chcemy wskazać
konkretną inną implementację. Stopniowo zaczniesz lepiej rozumieć przeznaczenie
metod z interfejsu API w kontekście licznych klientów i implementacji tablic sym
boli, które przedstawiamy i omawiamy w tym rozdziale oraz w dalszej części książki.
W pytaniach i odpowiedziach oraz w ćwiczeniach opisujemy też inne możliwości
w zakresie różnych wyborów projektowych omówionych w tym miejscu.
382 RO ZD ZIA Ł 3 * W yszukiwanie
% more t in y T a le . tx t
i t was the best o f times i t was the worst o f times
i t was the age o f wisdom i t was the age o f f o o lis h n e s s
i t was the epoch of b e lie f i t was the epoch of in c r e d u lit y
i t was the season o f lig h t i t was the season of darkness
i t was the s p rin g o f hope i t was the w in ter o f d e sp a ir
Tekst ten zawiera w sumie 60 wystąpień 20 różnych słów. Cztery słowa występują po
10 razy (jest to najwyższa liczba). Na podstawie tych danych wejściowych program
FrequencyCounter wyświetla jedno ze słów i t, was, the lub of (wybrane mogą zostać róż
ne słowa; zależy to od cech implementacji tablicy symboli) i liczbę jego wystąpień — 10.
Łatwo dostrzec, że przy badaniu wydajności dla większych danych wejściowych
ważne będą dwie kwestie. Po pierwsze, każde słowo w danych wejściowych jest uży
wane jako klucz wyszukiwania jednokrotnie, dlatego istotna jest łączna liczba słów
w tekście. Po drugie, każde różne słowo z danych wejściowych jest umieszczane
w tablicy symboli (a łączna liczba różnych słów w danych wejściowych wyznacza
rozmiar tablicy po wstawieniu wszystkich kluczy), dlatego, oczywiście, znaczenie ma
łączna liczba słów w strumieniu wejściowym. Aby oszacować czas wykonania progra-
t in y T a le . t x t t a le .,tx t le ip z ig lM .t x t
Ten klient klasy ST zlicza wystąpienia łańcuchów znaków ze standardowego wejścia, a na
stępnie wyświetla łańcuch o największej liczbie wystąpień. Argument podawany w wierszu
poleceń określa dolne ograniczenie długości sprawdzanych kluczy.
R 3 R 3 A 2 E 1 S 0
C 4 C 4 R 3 A 2 E 1 S 0
Zakreślone pozycje
H 5 H 5 C 4 R 3 A 2 E 1 to zmieniane wartości
E 6 H 5 c 4 R 3 A 2 E
X 7 X 7 H 5 C 4 R 3 A 2 S 0
Szare węzły
A 8 X 7 H 5 C 4 R 3 A S | 0 - pozostajq nietknięte
M 9 M 9 X 7 H 5 C 4 R 3 E 6 S 0
P 10 P 10 M 9 X 7 H 5 C 4 A 8 E 6 S 0
L 11 L 11 P 10 M 9 X 7 H 5 R 3 A 8 E 6 S 0
E 12 L 11 P 10 M 9 X 7 H 5 R 3 A 8 E S 0
Ślad działania implementacji klasy ST (opartej na liście powiązanej) w standardowym kliencie używającym indeksu
3.1 Tablice symboli 387
private c la s s Node
{ // Węzeł l i s t y powiązanej.
Key key;
V alue v a l ;
Node next;
public Node(Key key, Value val, Node next)
{
t h is .k e y = key;
this.val = v a l;
th is .n e x t = next;
}
}
To prawda, że czas wyszukiwania kluczy znajdujących się w tablicy nie musi rosnąć linio
wo. Przydatną miarą jest łączny koszt wyszukiwania wszystkich kluczy z tablicy podzie
lony przez N. Wartość ta to dokładnie oczekiwana liczba porównań potrzebnych przy
wyszukiwaniu w warunkach, kiedy wyszukiwanie dowolnego klucza z tabeli jest równie
prawdopodobne. Znalezienie takiego elementu nazywamy trafieniem przy wyszukiwa
niu losowym. Choć wzorce wyszukiwania w klientach zwykle nie są losowe, model ten
często dobrze je opisuje. Łatwo wykazać, że średnia liczba porównań do trafienia przy
wyszukiwaniu losowym wynosi ~N/2. Metoda g e t () w a l g o r y t m i e 3.1 wykonuje jed
no porównanie w celu znalezienia pierwszego klucza, dwa porównania do znalezienia
drugiego klucza i tak dalej. Średni koszt wynosi (1 + 2 + ... + N )/N - (N + 1)12 ~ N I2.
Analizy wyraźnie pokazują, że oparta na liście powiązanej implementacja z wyszu
kiwaniem sekwencyjnym jest zbyt wolna, aby używać jej do rozwiązywania dużych
problemów, takich jak przykładowe dane wejściowe, za pom ocą klientów w rodzaju
programu FrequencyCounter. Łączna liczba porównań jest proporcjonalna do ilo
czynu liczby wyszukiwań i liczby wstawień. Iloczyn ten wynosi 109 dla tekstu książki
Tale o f Two Cities i 1014 dla zbioru Leipzig Corpora.
3.1 o Tablice symboli 389
keysj] va ls[]
Klucz Wartość 0 1 2 3 4 5 6 7 8 9 N 0 1 2 3 4 5 6 7 8 9
S 0 S 1 0
E 1 E S 2 1 0 Czarne elementy
Czerwone elementy
A 2 A E s 3 2 1 0 przesunięto wprawo
^ zostały wstawione X
R 3 A E R S 4 1 1 3 0
Szare 4 Zakreślone
C 4 A C E R s 5 1 3 0
elementy y elementy
H 5 A c E H R S iig ¿11
iKlic ymionih/
licimy 6 4 1 5 3 JL
zmieniły
E 6 A c E H R s pozycji 6 y 4 © ~T 0 wartość
X 7 A c E H R s X 7 2 4 6 5 3 0 7
A 8 A c E H R s X 7 4 6 5 3 0 y
®
M 9 A c E H M R S X 8 8 4 6 5 9 3 0 7
P 10 A c E H M P R S X 9 8 4 6 5 9 10 3 0 7
L 11 A c E H L M P R S X 10 8 4 6 5 11 9 10 3 0 7
E 12 A c E H L M P R S X 10 8 4 5 11 9 10 3 0 7
©
A c E H L M P R S X 8 4 12 5 11 9 10 3 0 7
Ślad działania standardowego klienta używającego indeksu;
implementacja tablicy symboli oparta jest tu na tablicy uporządkowanej
3.1 Tablice symboli 391
public in t s iz e ()
{ return N; }
W tej implementacji tablicy symboli klucze i wartości znajdują się w równoległych tabli
cach. Implementacja metody put () przenosi większe klucze o jedną pozycję w prawo przed
wydłużeniem tablicy, tak jak oparta na tablicy implementacja stosu z p o d r o z d z i a ł u 1 .3 .
W tym miejscu pominięto kod do zmiany długości tablicy.
392 RO ZD ZIA Ł 3 ■ W yszukiwanie
{
in t lo = 0, hi = N-l;
while (lo <= hi)
{
in t mid = lo + (hi - lo) / 2 ;
in t cmp = key.compareTo(keys[mid]);
}
return lo;
Tu do ustalenia liczby kluczy mniejszych niż key użyto klasycznej metody opisanej w tekście.
Należy porównać klucz key ze środkowym kluczem. Jeśli są równe, trzeba zwrócić indeks
środkowego klucza. Jeżeli key jest mniejszy, należy sprawdzić lewą połowę podtablicy, a jeśli
jest większy, przeszukać prawą połowę.
____________keys[]____________
Udane wyszukiwanie P 0 1 2 3 4 5 6 7 8 9
lo h i mid
Czarne litery
0 9 4 A c E H L M P
R S x to elementy
5 9 7 A C E H L M P R S X a [lo . .h i]
5 6 5 A C E H L M P R C zerw ona litera
6 6 6 A C E H L M P r S X to element a [mid]
deleteMax() 1
Dla książki Tale of Two Cities, w której różnych kluczy jest 104,
Koszty w klasie Bi narySearchST koszt zbudowania tablicy to prawie 108dostępów do tablicy. Dla
pliku z projektu Leipzig, gdzie różnych kluczy jest 106, koszt
zbudowania tablicy wynosi ponad 1011 dostępów do tablicy. Choć na współczesnych komputerach
możliwe jest wykonanie takiej liczby operacji, koszty są niezwykle (i niepotrzebnie) wysokie.
Wróćmy do kosztów operacji put () w programie FrequencyCounter dla słów od długości 8
i więcej znaków. Widać tu zmniejszenie średniego kosztu z 2246 porównań (plus dostępy do
tablicy) na operację dla wersji Sequential SearchST do 484 dla wersji Bi narySearchST. Tak jak
wcześniej, w praktyce koszt jest nawet niższy, niż wskazują na to analizy, a poprawę ponownie
można przypisać cechom aplikacji (zobacz ć w i c z e n i e 3 . 1 .36 ). Poprawa robi duże wrażenie,
jednak — jak się okaże — można uzyskać znacznie lepsze wyniki.
5737-,
Wyszukiwanie
sekwencje
(nieuporządkowana
lista powiązana)
Wyszukiwanie
binarne (tablica lg N 2N lg N N T ak
uporządkowana)
Jak m ożna osiągnąć wspomniany cel? Wydaje się, że aby umożliwić wydajne wsta
wianie, trzeba użyć struktury powiązanej. Jednak lista jednokrotnie powiązana unie
możliwia stosowanie wyszukiwania binarnego, ponieważ m etoda ta wymaga, aby
można było szybko pobrać środkowy element dowolnej podtablicy, używając indeksu
(a jedyny sposób na dotarcie do środka listy jednokrotnie powiązanej to podążanie
za odnośnikami). Połączenie wydajności wyszukiwania binarnego z elastycznością
struktur powiązanych wymaga zastosowania bardziej skomplikowanych struktur da
nych. Mogą to być zarówno binarne drzewa wyszukiwań (temat dwóch następnych
podrozdziałów), jak i tablice z haszowaniem (omówione w p o d r o z d z i a l e 3 .4 ).
W tym rozdziale omawiamy sześć implementacji tablicy symboli, dlatego krótki
przegląd wstępny jest uzasadniony. Tabela poniżej obejmuje listę struktur danych
wraz z ich głównymi zaletami i wadami w omawianym kontekście. Struktury wymie
niono w kolejności ich omawiania.
Cechy algorytmów i implementacji opisano bardziej szczegółowo w miejscach ich
omawiania, jednak krótka charakterystyka przedstawiona w tabeli pomoże przyjrzeć
się im w szerszym kontekście w trakcie ich poznawania. Ostateczny wniosek jest taki,
że istnieje lulka szybkich implementacji tablic symboli, które mogą dawać (i dają)
doskonałe efekty w niezliczonych zastosowaniach.
; PYTANIA I ODPOWIEDZI
P. Dlaczego nie użyć dla tablicy symboli typu Item implementującego interfejs
Comparabl e (w taki sam sposób, jak dla kolejek priorytetowych w p o d ro z d z ia le 2 .4 ),
zamiast stosować odrębne klucze i wartości?
O. Nie wszystkie typy danych mają lducze, które można łatwo porównać, ale nawet
dla nich tablica symboli może być przydatna. Posłużmy się skrajnym przykładem
— jako kluczy m ożna użyć obrazów lub piosenek. Nie istnieje naturalny sposób na
stwierdzenie, który z tych elementów jest większy, jednak — pewnym nakładem pra
cy — z pewnością m ożna sprawdzić, czy są sobie równe.
O. Równość odgrywa specjalną rolę w tablicach symboli, dlatego potrzebna jest też
metoda do jej sprawdzania. Aby uniknąć tworzenia wielu m etod o w zasadzie tej sa
mej funkcji, wykorzystaliśmy wbudowane metody Javy — equal s () i compareTo().
O. Może talc, a może nie. Dwa pociągi nie mogą przyjechać na stację o tej samej
godzinie tym samym torem (choć mogą pojawić się o tym samym czasie na róż
nych torach). Są dwa sposoby na poradzenie sobie z taką sytuacją — można użyć
innych informacji do zapewnienia jednoznaczności lub zastosować obiekt Queue
dla wartości o tym samym kluczu. Zastosowania tych technik opisano szczegółowo
W P O D R O Z D Z IA L E 3 .5 .
P. W stępne sortowanie tablicy, opisane na stronie 397, wydaje się być dobrym p o
mysłem. Dlaczego technikę tę omówiono tylko w ćwiczeniu ( ć w i c z e n i e 3 . 1 . 1 2 )?
ĆWICZENIA
3.1.1. Napisz klienta, który tworzy tablicę symboli przez odwzorowanie ocen w p o
staci liter na liczby, tak jak w poniższej tabeli, a następnie wczytuje ze standardowego
wejścia listę ocen w formie liter oraz oblicza i wyświetla średnią z liczb odpowiada
jących ocenom.
A+ A A- B+ B B- C+ C C- D F
4,33 4,00 3,67 3,33 3,00 2,67 2,33 2,00 1,67 1,00 0,00
3.1.4. Opracuj typy ADT Time (czas) i Event (zdarzenie), umożliwiające przetwa
rzanie danych w taki sposób, jak w przykładzie przedstawionym na stronie 379.
3.1.5. Zaimplementuj operacje siz e (), d elete () i keys() dla typu Sequential
SearchST.
3.1.7. Jaka jest średnia liczba różnych kluczy, które program FrequencyCounter
znajdzie wśród N losowych nieujemnych liczb całkowitych mniejszych niż 1000 dla
N = 10 , 10 2, 10 3, 10 4, 105 i 10 6?
3.1.8. Jakie jest najczęściej występujące w książce Tale o f Two Cities słowo o przy
najmniej 10 literach?
; PROBLEMY DO ROZWIĄZANIA
prowadzi do dwóch lub trzech wyszukiwań tego samego klucza. Aby umożliwić pisa
nie przejrzystego kodu klienta bez rezygnacji z wydajności, można użyć programowej
pamięci podręcznej, co polega na zapisaniu lokalizacji ostatniego używanego klucza
w zmiennej egzemplarza. Zmodyfikuj klasy Sequential SearchST i Bi narySearchST
tak, aby wykorzystać ten pomysł.
404 RO ZD ZIA Ł 3 n W yszukiw anie
EKSPERYMENTY
3.1.36. Sprawdzanie wydajności II. Ustal empirycznie stosunek ilości czasu, jald kla
sa Bi narySearchST spędza w metodzie put (), do czasu wykonywania operacji get (),
ldedy program FrequencyCounter określa liczbę wystąpień liczb w milionie losowych
M-bitowych wartości typu i nt dla M = 10, 20 i 30. Wykonaj to ćwiczenie dla pliku
tale.txt i porównaj wyniki.
406 RO ZD ZIA Ł 3 a W yszukiw anie
Definicja. Binarne drzewo wyszukiwań (ang. binary search tree — BST) to spe
cyficzne drzewo binarne — każdy węzeł ma w nim klucz zgodny z interfejsem
Comparabl e (i powiązaną z nim wartość), a drzewo spełnia warunek, zgodnie z któ
rym klucz w każdym węźle jest większy niż klucze we wszystkich węzłach lewego
poddrzewa i mniejszy niż klucze we wszystkich węzłach prawego poddrzewa.
private c la s s Node
public in t s iz e ()
{ return s iz e ( r o o t ) ; }
private in t size(Node x)
{
i f (x == n u ll) return 0 ;
else return x.N;
}
Te implementacje metod g e t() i put () dla interfejsu API tablicy symboli są charaktery
stycznymi rekurencyjnymi metodami dla drzew BST i służą jako wzorzec dla kilku innych
implementacji omawianych dalej w rozdziale. Każdą metodę można zrozumieć zarówno na
podstawie działającego kodu, jak i za pomocą dowodu przez indukcję na podstawie hipotezy
indukcyjnej przedstawionej na początku.
412 RO ZD ZIA Ł 3 ■ W yszukiw anie
R jest mniejsze
niż S, dlatego należy T jest większe
szukać po lewej niż S, dlatego należy
Czarne węzły m o gą p a so w ać
do klucza w yszukiw ania szukać p o prawej
s o A 8
&
(A J8 IR )
Zmodyfikowana /
y CaaA (Hp
/A
E 1 wartość
M 9
A 2
R 3
P 10
C 4
H 5
Zmodyfikowana sp,
wartość
E 6
(aj l£ )
O ć) © x
AA AA
i E
X 7
(Aj iR )
(c ) ( h)
r\ aa /A aA
Ślad zm ian w drzewie BST dla standardow ego klienta używ ającego indeksu
3.2 o Drzewa wyszukiwań binarnych 415
t a le . t x t le ip z ig lM . t x t
Wszystkie słowa 135 635 10 679 18,6 17,5 21 191 455 534 580 23,4 22,1
Ponad 8 liter 14 350 5737 17,6 13,9 4 239 597 299 593 22,7 21,4
Ponad 10 liter 4582 2260 15,4 13,1 1 610 829 165 555 20,5 19,3
Średnia liczba porównań na operację put() w programie FreguencyCounter korzystającym z klasy BST
418 R O ZD ZIA Ł 3 □ W yszukiw anie
ALGORYTM 3.3 (ciąg dalszy). Minimum, maksimum, podłoga i sufit dla drzew BST
return min(root).key;
i f ( x . l e f t == n u ll) return x;
return m i n ( x . l e f t ) ;
Każda metoda klienta wywołuje odpowiednią metodę prywatną, która przyjmuje jako ar
gument dodatkowy odnośnik (do obiektu Node) i zwraca nul 1 lub obiekt Node zawierający
pożądany obiekt Key. Metoda działa w rekurencyjny sposób opisany w tekście. Metody max ()
i cei 1 i ng () są takie same jak mi n () oraz floor (), przy czym strony prawa i lewa (oraz ope
ratory <i >) są zamienione.
420 R O ZD ZIA Ł 3 n W yszukiw anie
W metodzie del eteMi n () należy poruszać się w lewo do momentu znalezienia obiektu
Node, którego lewy odnośnik jest pusty. Wtedy trzeba zastąpić odnośnik do węzła jego
prawym odnośnikiem (wystarczy zwrócić prawy odnośnik w metodzie rekurencyjnej).
Usunięty węzeł, do którego nie prowadzą żadne odnośniki, jest dostępny dla mechani
zmu przywracania pamięci. Standardowe, rekurencyjne rozwiązanie po usunięciu wę
zła ustawia odpowiedni odnośnik w rodzicu i aktualizuje liczbę węzłów we wszystkich
węzłach na ścieżce do korzenia. Metoda del eteMax() działa symetrycznie.
W tych metodach zaimplementowano zachłanne usuwanie Hibbarda dla drzew BST, co opi
sano w tekście na poprzedniej stronie. Kod metody delete() jest zwięzły, ale skompliko
wany. Prawdopodobnie najlepszy sposób na jego zrozumienie to przeczytać opis po lewej
stronie, spróbować samodzielnie napisać kod na podstawie tekstu, a następnie porównać swój
kod z kodem z książki. Przedstawiona tu metoda jest zwykle skuteczna, jednak jej wydajność
w dużych aplikacjach może być problematyczna (zobacz ć w i c z e n i e 3 .2 .42 ). Metoda del ete-
Max() wygląda tak samo, jak deleteMinO, jednak zamieniono w niej stronę prawą z lewą.
424 RO ZD ZIA Ł 3 ■ W yszukiwanie
Zapytania zakresowe Aby zaimplementować metodę keys (), zwracającą klucze z da
nego przedziału, należy zacząć od podstawowej rekurencyjnej metody poruszania się
po drzewach BST, nazywanej przechodzeniem w porządku inorder. Rozważmy wyświet
lanie po kolei wszystkich kluczy drzewa BST. W tym celu należy wyświetlić wszystkie
klucze z lewego poddrzewa (z definicji drzewa BST wynika, że są mniejsze niż klucz
korzenia), następnie klucz korzenia, a potem wszystkie klucze
p riv a te void print(N ode x) z prawego poddrzewa (według definicji drzewa BST są większe
( niż klucz korzenia). Tak działa kod pokazany po lewej. Jak zwy
i f (x == n u ll) return;
kle, opis służy za dowód przez indukcję, że kod wyświetla klu
p rin t ( x .le f t );
S t d O u t. p rin t ln (x . k e y ); cze po kolei. Aby zaimplementować dwuargumentową metodę
p rin t (x. r ig h t ) ; keys(), która zwraca klientowi wszystkie klucze z określonego
) przedziału, należy zmodyfikować ten kod. Trzeba dodać każ
dy klucz z przedziału do obiektu Queue i pominąć rekurencyjne
Wyświetlanie po kolei kluczy
drzewa BST wywołania dla poddrzew, które z pewnością nie zawierają klu
czy z danego przedziału. Tak jak w klasie BinarySearchST, tak
i tu zapisywanie kluczy w obiekcie Queue jest ukryte przed klientem. Chodzi o to, że
w klientach powinno być możliwe przetwarzanie wszystkich kluczy z danego przedzia
łu za pomocą konstrukcji foreach Javy, tak aby nie trzeba było znać struktury danych
użytej do implementacji interfejsu Iterable<Key>.
Analiza Jak wydajne są operacje oparte na kolejności na drzewach BST? Aby odpowie
dzieć na to pytanie, zastanówmy się nad wysokością drzewa (maksymalną głębokością
dowolnego węzła w drzewie). Wysokość drzewa określa koszt dla najgorszego przypad
ku dla wszystkich operacji na drzewie BST (wyjątkiem jest wyszukiwanie zakresowe,
które powoduje dodatkowe koszty proporcjonalne do liczby zwracanych kluczy).
Aby dodać do kolejki wszystkie klucze z drzewa o korzeniu w danym węźle, które należą do
przedziału, należy rekurencyjnie dodać wszystkie klucze z lewego poddrzewa (jeśli któreś
z nich znajdują się w przedziale), następnie dodać węzeł korzenia (jeżeli należy do przedzia
łu), a potem rekurencyjnie dodać wszystkie klucze z prawego poddrzewa (jeśli którekolwiek
z nich znajdują się w przedziale).
Wyszukiwanie sekwencyjne
(nieuporządkowana N N NI 2 N Nie
lista powiązana)
Wyszukiwanie binarne
lg N N lg N NI 2 Tak
(tablica uporządkowana)
Binarne drzewa
N N 1,39 lg N 1,39 lg N Tak
wyszukiwań (BST)
PYTANIA I O D PO W IED ZI
P. Zetknąłem się już z drzewami BST, ale bez stosowania rekurencji. Jakie są wady
i zalety użycia tej techniki?
P. Utrzymywanie pola z liczbą węzłów w obiektach Node wymaga dużo kodu. Czy
pole to jest niezbędne? Dlaczego na potrzeby m etody klienckiej s i ze () nie przecho
wujemy jednej zmiennej egzemplarza zawierającej liczbę węzłów w drzewie?
I ĆW ICZEN IA
a. 10, 9, 8 , 7, 6 , 5
b. 4, 10, 8 , 7, 5, 3
c. 1, 10, 2, 9, 3, 8 , 4, 7, 6 , 5
d. 2, 7, 3, 8 , 4, 5
e. 1, 2, 10, 4, 8 , 5
3.2.5. Załóżmy, że z góry oszacowano, jak często potrzebny jest dostęp do poszcze
gólnych kluczy wyszukiwania w drzewie BST, i można wstawić je w dowolnej kolej
ności. Czy klucze należy wstawić w rosnącej lub malejącej kolejności według prawdo
podobieństwa dostępu, czy w innym porządku? Wyjaśnij odpowiedź.
3.2.6. Dodaj do klasy BST metodę hei ght (), która oblicza wysokość drzewa. Opracuj
dwie implementacje — metodę rekurencyjną (ilość czasu i pamięci jest tu proporcjonalna
liniowo do wysokości drzewa) i metodę w rodzaju si ze(), która dodaje pole do każdego
węzła drzewa (ilość pamięci rośnie tu liniowo, a czas na obsługę zapytania jest stały).
3.2.7. Dodaj do klasy BST metodę avgCompares(), która określa średnią liczbę po
równań dla trafienia w danym drzewie BST (ta liczba to długość ścieżki wewnętrznej
drzewa podzielona przez jego rozmiar plus 1). Opracuj dwie implementacje — m e
todę rekurencyjną (ilość czasu i pamięci jest tu proporcjonalna liniowo do wysoko
ści drzewa) i metodę w rodzaju s i ze (), która dodaje pole do każdego węzła drzewa
(ilość pamięci rośnie tu liniowo, a czas na obsługę zapytania jest stały).
w drzewie wszystkie puste odnośniki znajdują się na tym samym poziomie; jeżeli ta
liczba jest inna, puste odnośniki występują na dwóch poziomach.
3.2.9. Narysuj wszystkie różne kształty drzew BST, które mogą powstać po wstawie
niu Nkluczy do początkowo pustego drzewa. Przyjmij N= 2, 3, 4, 5 i 6.
3.2.11. Ile jest kształtów drzew binarnych o N węzłach i wysokości JV? Na ile róż
nych sposobów można wstawić N różnych kluczy do początkowo pustego drzewa
BST, aby uzyskać drzewo o wysokości N? Zobacz ć w i c z e n i e 3 .2 .2 .
3.2.12. Opracuj implementację klasy BST pozbawioną m etod rank() i s e le c t() oraz
pola z liczbą węzłów w obiektach Node.
3.2.13. Przedstaw nierekurencyjne implementacje metod get () i put () dla klasy BST.
afl
430 RO ZD ZIA Ł 3 s W yszukiwanie
a. floor("Q")
b. se lect(5 )
c. ceiling("Q ")
d. r a n k ("J ")
e. s i z e ( " D " , "T ")
f keys("D\ "T")
3.2.16. Zdefiniujmy długość ścieżki zewnętrznej drzewa jako sumę liczb węzłów
na ścieżkach z korzenia do wszystkich odnośników pustych. Udowodnij, że różnica
między długością ścieżki wewnętrznej i zewnętrznej dla dowolnego drzewa binarne
go o N węzłach wynosi 2N (zobacz t w i e r d z e n i e c ).
3.2.17. Narysuj serię drzew BST, które powstają w czasie usuwania kluczy z drzewa
zćw ic z e n ia 3 . 2.1 zgodnie z kolejnością ich wstawiania.
3.2.1 8 . Narysuj serię drzew BST, które powstają w czasie usuwania kluczy z drzewa
zćw ic z e n ia 3 .2.1 w porządku alfabetycznym.
3.2.19. Narysuj serię drzew BST, które powstają w czasie usuwania kluczy z drzewa
z ć w i c z e n i a 3 .2 .1 przez usuwanie za każdym razem klucza z korzenia.
3.2.21. Dodaj do klasy BST metodę randomKey(), która zwraca losowy klucz z tablicy
symboli w czasie proporcjonalnym do wysokości drzewa (dla najgorszego przypadku).
3.2.22. Udowodnij, że jeśli węzeł w drzewie BST ma dwoje dzieci, to następnik nie
ma lewego dziecka, a poprzednik nie ma prawego dziecka.
PROBLEMY DO ROZWIĄZANIA
3.2.29. Sprawdzanie drzewa binarnego. Napisz rekurencyjną metodę i sBi naryTree (),
która przyjmuje jako argument obiekt typu Node. Metoda ma zwracać true, jeśli pole
z liczbą węzłów (N) poddrzewa jest spójne w strukturze danych, której korzeniem jest
dany węzeł. W przeciwnym razie metoda ma zwracać fal se. Uwaga: ten test gwarantu
je też, że w strukturze danych nie ma cykli, dlatego jest ona drzewem binarnym!
3.2.32. Sprawdzanie, czy struktura to drzewo. Napisz metodę i s BST (). Ma ona przyj
mować jako argument obiekt typu Node i zwracać true, jeśli węzeł podany jako ar
gument jest korzeniem drzewa BST. W przeciwnym razie metoda ma zwracać fal se.
Wskazówka: zadanie to jest trudniejsze, niż może się wydawać, ponieważ kolejność
wywoływania m etod z trzech poprzednich ćwiczeń jest istotna.
Rozwiązanie:
private boolean is B S T ()
{
i f ( ! is B in a ry T re e ( ro o t )) return fa lse ;
i f (!isO rdered(root, min(), max())) return fa ls e ;
i f (!hasNoDuplicates(root)) return fa lse ;
return true;
}
3.2.33. Sprawdzanie metod s e le c t() i rank(). Napisz metodę, która sprawdza
dla wszystkich i od 0 do s iz e ( ) - l, czy i jest równe ra n k (s e le c t( i) ). Ponadto m e
toda dla wszystkich kluczy drzewa BST ma sprawdzać, czy klucz key jest równy
select(ran k (k ey )).
3.2.34. Wątki. Cel to dodanie obsługi rozbudowanego interfejsu API ThreadedST,
tak aby można wykonać w stałym czasie dodatkowe operacje:
Key next (Key key) Zwraca klucz następujący p o k e y (nu}], jeśli key to m aksimum)
Key prev(Key key) Zwraca klucz poprzedzający key (n ull, jeśli key to m inim um )
3.2.36. Iterator. Czy m ożna napisać nierekurencyjną wersję metody keys(), która
wymaga pamięci w ilości proporcjonalnej do wysokości drzewa (niezależnie od licz
by kluczy w przedziale)?
3.2 □ Drzewa wyszukiwań binarnych 433
3.2.37. Przechodzenie według poziomów. Napisz metodę pri ntLevel (), która przyj
muje jako argument obiekt typu Node i wyświetla według poziomów (według odle
głości od korzenia, przy czym węzły z danego poziomu wyświetlane są od lewej do
prawej) klucze z poddrzewa o korzeniu w danym węźle. Wskazówka: użyj obiektu
typu Queue.
3.2.38. Rysowanie drzewa. Dodaj do klasy BST metodę draw(), która rysuje drzewa
BST podobne do tych przedstawionych w tekście. Wskazówka: użyj zmiennych eg
zemplarza do przechowywania współrzędnych węzłów i m etody rekurencyjnej do
ustawiania wartości tych zmiennych.
434 RO ZD ZIA Ł 3 Q W yszukiw anie
| EKSPERYMENTY
Średnia długość ścieżki do losowego węzła w drzewach BST zbudowanych z losowych kluczy
3.3. Z B A L A N S O W A N E D R Z E W A W Y S Z U K IW A Ń
Definicja. Drzewo wyszukiwań 2-3 to drzewo, które jest albo puste, albo jest:
■ węzłem podwójnym — o jednym kluczu (i powiązanej wartości) oraz dwóch
odnośnikach; lewy prowadzi do drzewa wyszukiwań 2-3 z mniejszymi klucza
mi, a prawy — do drzewa wyszukiwań 2-3 z większymi kluczami;
■ węzłem potrójnym — o dwóch kluczach (i powiązanych wartościach) oraz
trzech odnośnikach; lewy prowadzi do drzewa wyszukiwań 2-3 z mniejszy
mi kluczami, środkowy do drzewa wyszukiwań 2-3 z kluczami o wartościach
pomiędzy wartościami kluczy z węzła, a prawy — do drzewa wyszukiwań 2-3
z większymi kluczami.
Jak zwykle odnośnik do pustego drzewa nazywamy odnośnikiem pustym.
436
3.3 Q Zbalansowane drzewa wyszukiwań 437
( E 3 i R) ( E 2,
Ca 0D (P) Cs x )
r\ r \
t
Znaleziono H, dlatego należy B ma wartość pomiędzy A / c, dlatego należy szukać pośrodku.
zwrócić wartość (trafienie) Odnośnik jest pusty, więc B nie znajduje się w drzewie (chybienie)
dwójnych i potrójnych oraz drzewach 2-3. Na razie załóżmy, że można wygodnie m ani
pulować takimi drzewami, i zobaczmy, jak zastosować je jako drzewa wyszukiwań.
W yszukiwanie Algorytm wyszukiwania kluczy w drzewach 2-3 to bezpośrednie
uogólnienie algorytmu wyszukiwania w drzewach BST. Aby ustalić, czy klucz znajduje
się w drzewie, należy najpierw porównać go z kluczami w korzeniu. Jeśli jest równy jed
nemu z nich, lducz znaleziono. W przeciwnym razie należy podążyć za odnośnikiem
z korzenia do poddrzewa odpowiadającego przedziałowi wartości klucza, w którym
może znajdować się klucz wyszukiwania. Jeśli ten odnośnik jest pusty, wyszukiwanie
jest nieudane. W przeciwnym razie należy rekurencyjnie przeszukać dane poddrzewo.
Wyszukiwanie d
kończy się w tym , Podział węzła poczwórnego na dwa węzły podwójne.
węźle potrójnym \ Środkowy klucz należy przenieść do rodzica
Wstawianie do węzła potrójnego,
którego rodzicem jest węzeł potrójny
Dodawanie nowego klucza D do węzła potrójnego,
przez co powstaje tymczasowy węzeł poczwórny
E 3 ich środkowe klucze do rodziców do
f L) m om entu natrafienia na węzeł podwój
ri ny (zastępujemy go węzłem potrójnym,
Dodawanie środkowego klucza c do węzła potrójnego, którego nie trzeba dalej dzielić) lub na
przez co powstaje tymczasowy węzeł poczwórny
węzeł potrójny będący korzeniem.
\
P odział korzenia Jeśli węzły potrójne
(
wa J (
wd ) (h) T l ) znajdują się na całej ścieżce od punk
\ / tu wstawiania do korzenia, ostatecznie
Podział węzła poczwórnego na dwa węzły podwójne.
Środkowy klucz należy przenieść do rodzica powstaje węzeł poczwórny w korzeniu.
Podział węzła poczwórnego Wtedy można postąpić tak samo, jak
na trzy węzły podwójne, przy wstawianiu do węzła składającego
co powoduje zwiększenie
wysokości drzewa o 1
się z jednego węzła potrójnego. Należy
podzielić tymczasowy węzeł poczwór
ny na trzy węzły podwójne, zwiększając
Podział korzenia
440 R O ZD ZIA Ł 3 n W yszukiw anie
^ “A $
b d e
L ew a - Xa c e X
- c t h >
P raw a
P r a w a z a b d
/
~ k
P o d z ia ł ty m c z a s o w e g o w ę z ła p o c z w ó r n e g o n a d rz e w o 2-3 (p o d s u m o w a n ie )
Dowód. Wysokość drzewa 2-3 o N węzłach wynosi pomiędzy Llog 3 A/J = L(lg
N )/( lg 3)J (jeśli drzewo składa się z samych węzłów potrójnych) a Lig N_J (jeżeli
drzewo obejmuje same węzły podwójne). Zobacz ć w i c z e n i e 3 .3 .4 .
442 R O ZD ZIA Ł 3 a W yszukiwanie
Wstawianie S Wstawianie A
C A ) (S)
Gl j D
{ A C ) ( H ^M ) d l x )
>~t A Ca} ( e) CO / A 1
CO
d v . ^
i a ) ( e ) ( l) p Cs )
n n
a c)(h O ( p) Cs x ) (A ) (£ } (L ) ( p) ( s x '
nrK W \ / \ / /~ \ / A >-\ /O >—r~<
Standardowy klient używający indeksu Te same klucze wstawione w kolejności rosnącej
\mmm / n A A A M M
Typowe drzewo 2-3 zbudowane na podstawie losowych kluczy
444 RO ZD ZIA Ł 3 o W yszukiwanie
Może być prawy lub lewy zawsze rozwiązują te problemy przez odpowiednie ro
oraz czerwony lub czarny tacje. Rotacja zmienia położenie czerwonych odnośni
ków. Najpierw załóżmy, że istnieje czerwony odnośnik
po prawej stronie i trzeba go zrotować, aby znalazł się
, Mniejszy
po lewej (zobacz rysunek po lewej stronie). Ta operacja
z) // Między
\ \ // Większy
\ \ to rotacja w lewo. Przetwarzanie umieszczono w m e
1 /S J V niżs ) todzie, która przyjmuje jako argument odnośnik do
Node r o t a t e l _ e f t ( N o d e h) czerwono-czarnego drzewa BST i — przy założeniu,
{ że odnośnik prowadzi do obiektu h typu Node, którego
Node x = h . r i g h t ;
h. r i g h t = x . ) e f t ; prawy odnośnik jest czerwony — wprowadza niezbęd
x .1e f t = h ;
ne zmiany, po czym zwraca odnośnik do węzła będące
x .c o !o r = h .co lo r;
h . c o l o r = RED; go korzeniem czerwono-czarnego drzewa BST dla tego
x .N = h .N ;
h.N = 1 + s i z e ( h . l e f t )
samego zbioru kluczy, w którym lewy odnośnik jest
+ size (h . r i g h t ) ; czerwony. Jeśli sprawdzisz każdy wiersz kodu wzglę
r e t u r n x;
} x dem rysunków przed i po, zobaczysz, że operację łatwo
jest zrozumieć. Kod umieszcza w korzeniu większy za
miast mniejszego z dwóch kluczy. Implementacja rota
cji w prawo, która przekształca lewy czerwony odnoś
/ Większy\
nik w prawy, to ten sam kod z zamienionymi stronami
/ Mniejszy', / M ię d z y \ ■ ■
[ niż e ) f a is j (zobacz rysunek po lewej, w dolnej części strony).
Rotacja w lewo (prawego odnośnika węzła h) Ponowne ustawianie odnośnika w rodzicu po rotacji
Każda rotacja, niezależnie od strony, prowadzi do zwróce
,h
nia odnośnika. Zawsze używamy odnośnika zwróconego
przez metodę rotateR ight() lub rotatel_eft() do usta
wienia odpowiedniego odnośnika w rodzicu (lub w ko
rzeniu drzewa). Zwracany jest prawy lub lewy odnośnik,
jednak zawsze można użyć go do ustawienia odnośnika
w rodzicu. Odnośnik może być czerwony lub czarny.
Node r o t a t e R i g h t ( N o d e h) Metody rotateL eft() i rotateR ight() zachowują kolor
{ przez ustawienie zmiennej x . col or na h. col or. Może to
Node x = h . l e f t ;
h . l e f t = x. r i g h t ; spowodować powstanie w drzewie dwóch kolejnych czer
x . r i g h t = h;
x .c o lo r = h .co lo r;
wonych odnośników, jednak w algorytmach stosujemy
h . c o l o r = RED; rotację, aby rozwiązać ten problem. Przykładowo, kod:
x .N = h.N;
h.N = 1 + s i z e ( h . le f t ) h = ro ta te L e ft(h );
+ sizeCh. r ig h t ) ;
r e t u r n x;
rotuj e wlewo prawy czerwony odnośnik węzła h i ustawia
Xx i:
fi) h w taki sposób, aby prowadził do korzenia uzyskanego
poddrzewa (które zawiera wszystkie węzły poddrzewa,
do którego h prowadził przed rotacją, ale ma inny ko
/ Mniejszy \ /
V niż E ) /
rzeń). Łatwość pisania kodu tego rodzaju to główny po
wód stosowania rekurencyjnych implementacji metod
dla drzew BST. Dzięki temu można łatwo zastosować
Rotacja w prawo (lewego odnośnika węzła h)
rotację jako uzupełnienie normalnego wstawiania.
3.3 a Zbalansow ane drzewa wyszukiwań 447
W staw ianie do drzew a o trzech kluczach (do węzła potrójnego) Tę sytuację m oż
na sprowadzić do trzech przypadków — nowy klucz jest mniejszy niż oba klucze
z drzewa, zawiera się między nim i lub jest większy niż każdy z nich. W każdym przy
padku powstaje węzeł o dwóch czerwonych odnośnikach. Zadanie polega na rozwią
zaniu tego problemu.
■ Najprostszy z trzech przypadków ma miejsce wtedy, kiedy nowy klucz jest więk
szy niż dwa klucze w drzewie i dlatego dołączamy go do prawego odnośnika
węzła potrójnego. Powstaje wtedy drzewo zbalansowane z czerwonymi odnoś
nikami do węzłów zawierających mniejszy i większy klucz. Po zamianie kolo
rów tych dwóch odnośników z czerwonego na czarny powstaje drzewo zba
lansowane o wysokości 2, mające trzy węzły. Dokładnie to jest potrzebne do
zachowania zależności 1 do 1 względem drzewa 2-3. Dwa pozostałe przypadki
są ostatecznie sprowadzane do tego.
■ Jeśli nowy klucz jest mniejszy niż oba klucze drzewa i zostaje dołączony do le
wego odnośnika, powstają dwa kolejne czerwone odnośniki (każdy prowadzi
w lewo). Można sprowadzić to do poprzedniego przypadku (gdzie środkowy
klucz jest korzeniem połączonym z innymi kluczami dwoma czerwonymi od
nośnikami), wykonując rotację górnego odnośnika w prawo.
° Jeżeli nowy klucz znajduje się pomiędzy dwoma kluczami drzewa, powstają dwa
kolejne czerwone odnośniki. Górny jest skierowany w lewo, a dolny — wprawo.
Można to sprowadzić do poprzedniego przypadku (dwa kolejne lewe czerwone
odnośniki), obracając dolny odnośnik w lewo.
Podsumujmy — pożądany efekt uzyskujemy, wykonując zero, jedną lub dwie rotacje,
po czym następuje zmiana koloru dwóch dzieci korzenia. Tak jak przy poznawaniu
drzew 2-3, tak i tu upewnij się, że rozumiesz transformacje. Są one kluczem do działa
nia drzew czer-
Większy Mniejszy Pomiędzy .
Wyszukiwarie wono-czarnych.
kończy się Wyszukiwanie Zmiana koloru-
w tym pustym kończy się w tym
odnośniku Wyszukiwanie pustym odnośniku
Do zmiany ko
kończy się w tym
loru dwóch czer
pustym odnośniku
Dołączony wonych dzieci
Dołączony
nowy węzeł
nowy węzeł
węzła służy
z czerwonym Dołączony
odnośnikiem
z czerwonym p rz e d s ta w io n a
nowy węzeł odnośnikiem
z czerwonym po lewej m eto
odnośnikiem da flipColors().
Rotacja Rotacja Oprócz zamia
w prawo lewo
Kolor ny koloru dzieci
zmieniony Rotacja
na czarny
z czerwonego na
wprawo
czarny mody
Kolor
(b ) ^ zmieniony fikujemy kolor
Kolor
ę y f f y y na czarny rodzica z czarne-
zmieniony
na czarny go na czerwony.
Niezwykle waż-
Wstawianie do jednego węzła potrójnego (trzy przypadki)
3.3 c Zbatansowane drzewa wyszukiwań 449
Wstawianie H
Dodawanie nowego
węzła w tym miejscu
PODSUMUJMY — M O ŻN A ZACHOWAĆ
zależność 1 do 1 między drzewami
2-3 a czerwono-czarnymi drzewami
BST w czasie wstawiania węzłów, od
powiednio stosując trzy proste opera
cje — rotację w lewo, rotację w prawo
i zmianę koloru. Węzeł można wsta
wić za pomocą wymienionych dalej
operacji, które należy wykonać jedna
po drugiej na każdym węźle przy po
ruszaniu się w górę drzewa od punktu
wstawiania:
a Jeśli prawe dziecko jest czerwo
ne, a lewe — czarne, należy wy
konać rotację w lewo.
° Jeżeli lewe dziecko i jego lewe dziecko są czerwone, należy wykonać rotację
w prawo.
n Jeśli każde z dzieci jest czerwone, należy zmienić kolor.
Z pewnością warto sprawdzić, czy ten ciąg operacji pokrywa każdy z opisanych przy
padków. Zauważmy, że pierwsza operacja obsługuje zarówno rotację potrzebną do
przechylenia węzła potrójnego w lewo, jeśli rodzic jest węzłem podwójnym, jak i do
przechylenia dolnego odnośnika w lewo, jeżeli nowy czerwony odnośnik jest środko
wym odnośnikiem węzła potrójnego.
3.3 Zbatansowane drzewa wyszukiwań 451
Kod rekurencyjnej metody put() dla czerwono-czarnych drzew BST jest prawie identyczny
z kodem metody put () dla podstawowych drzew BST. Wyjątkiem są trzy instrukcje i f po wy
wołaniach rekurencyjnych, które pozwalają zachować niemal pełne zbalansowanie w drzewie
przez zapewnienie zależności 1 do 1 względem drzew 2-3 przy poruszaniu się w górę ścieżki
wyszukiwania. Pierwsza instrukcja rotuje w lewo przechylony w prawo węzeł potrójny (lub
przechylony w prawo czerwony odnośnik na dole tymczasowego węzła poczwórnego). Druga
rotuje w prawo górny odnośnik w tymczasowym węźle poczwórnym o dwóch czerwonych
odnośnikach przechylonych w lewo. Trzecia zmienia kolory w celu przeniesienia czerwonego
odnośnika w górę drzewa (zobacz opis w tekście).
452 RO ZD ZIA Ł 3 □ W yszukiwanie
Ś la d y t w o rz e n ia c z e rw o n o -c z a rn y c h d rz e w B ST
3.3 n Zbalansow ane drzewa wyszukiwań 453
przy podziale węzłów poczwórnych w drzewach 2-3. Jeśli korzeń to węzeł poczwórny,
należy podzielić go na trzy węzły podwójne i zwiększyć tym samym wysokość drzewa
o 1. Przy przechodzeniu w dół drzewa po napotkaniu węzła poczwórnego z rodzicem
w postaci węzła podwójnego należy podzielić węzeł poczwórny na dwa węzły podwój
ne i przenieść środkowy klucz do rodzica, przekształcając go na węzeł potrójny. Jeśli
rodzicem węzła poczwórnego jest węzeł potrójny, należy podzielić węzeł poczwórny
na dwa węzły podwójne i przenieść środkowy klucz do rodzica, przekształcając go na
węzeł poczwórny. Z uwagi na niezmiennik nie trzeba się obawiać, że napotkamy węzeł
poczwórny, którego rodzicem też jest taki węzeł. Na dole, także z uwagi na niezmien
nik, znajduje się węzeł podwójny lub potrójny, dlatego do
W korzeniu stępne jest miejsce na nowy klucz. Aby zaimplementować
ten algorytm za pomocą czerwono-czarnych drzew BST,
wykonujemy następujące kroki:
Przy przechodzeniu w dół Przedstawiamy węzły poczwórne jako zbalansowane pod-
drzewo trzech węzłów podwójnych, w którym lewe i pra
A
a we dziecko jest powiązane z rodzicem czerwonym odnoś
nikiem.
A
A Dzielimy węzły poczwórne na drodze w dół drzewa przez
zmianę kolorów.
Równoważymy węzły poczwórne na drodze w górę drze
P A wa przez rotacje (tak jak przy wstawianiu).
Co ciekawe, zstępujące drzewa 2-3-4 m ożna zaim ple
m entować przez przeniesienie jednego wiersza kodu
w m etodzie put () z a l g o r y t m u 3 .4 . Należy przenieść
Na dole
A wywołanie colorFl i p () (i powiązany test) przed wywo
łanie rekurencyjne (między sprawdzanie w artości nuli
a porów nanie). W sytuacjach, kiedy wiele procesów
ma dostęp do tego samego drzewa, algorytm ten ma
a pewne zalety względem drzewa 2-3, ponieważ zawsze
a tu p działa w odległości odnośnika lub dwóch od bieżącego
węzła. A lgorytm y usuw ania opisane dalej są oparte na
Transformacje przy wstawianiu danych
w zstępujących drzewach 2-3-4 znanych schemacie i działają zarówno dla takich drzew,
jak i dla drzew 2-3.
Usuwanie m inim um W ramach drugiej rozgrzewki przed usuwaniem rozważmy
usuwanie m inimum z drzew 2-3. Podstawowy pomysł oparty jest na obserwacji, że
na dole drzewa można łatwo usunąć klucz z węzła potrójnego, ale już nie z węzła po
dwójnego. Usunięcie klucza z węzła podwójnego powoduje, że powstaje węzeł bez klu
czy. Naturalnym rozwiązaniem jest zastąpienie takiego węzła pustym odnośnikiem,
jednak operacja ta narusza warunek pełnego zbalansowania. Dlatego stosujemy na
stępujące podejście — aby zagwarantować, że dojdziemy do węzła podwójnego, przy
przechodzeniu w dół drzewa wykonujemy odpowiednie transformacje w celu zacho
wania niezmiennika, zgodnie z którym bieżący węzeł nie jest podwójny (może być wę
3.3 □ Zbalansow ane drzewa wyszukiwań 455
Typowe czerwono-czarne drzewo BST zbudowane z losowych kluczy (pominięto puste odnośniki)
3.3 a Zbalansow ane drzewa wyszukiwań 457
t a le . t x t le ip z ig lM . t x t
Wszystkie 135 635 10 679 13,6 13,5 21 191 455 534 580 19,4 19,1
słowa
Przynajmniej 14 350 5737 12,6 12,1 4 239 597 299 593 18,7 18,4
8 liter
Przynajmniej 4582 2260 11,4 11,5 1 610 829 165 555 17,5 17,3
10 liter
Średnia liczba porównań na operację put () w programie FrequencyCounter
używającym klasy RedBlackBST
Metoda g et() w czerwono-czarnych drzewach BST nie sprawdza koloru węzła, dla
tego mechanizm równoważenia nie powoduje dodatkowych kosztów. Wyszukiwanie
jest szybsze niż w podstawowych drzewach BST, ponieważ drzewo jest zbalansowane.
Każdy klucz jest wstawiany raz, ale może być używany w wielu, wielu operacjach wy
szukiwania, dlatego efekt końcowy jest taki, że czas wyszukiwania jest bliski optymal
nemu (ponieważ drzewa są prawie zbalansowane i w czasie wyszukiwania nie trzeba
wykonywać żadnych operacji w tym celu) i dzieje się to stosunkowo małym kosztem
(inaczej niż w wyszukiwaniu binarnym wstawianie odbywa się w czasie logarytmicz
nym). Pętla wewnętrzna przy wyszukiwaniu obejmuje operację porównywania, po
której następuje aktualizacja odnośnika. Pętla ta jest dość krótka, podobnie jak pętla
wewnętrzna wyszukiwania binarnego (porównanie i operacje arytmetyczne na indek
sach). Jest to pierwsza implementacja, która gwarantuje logarytmiczny czas wyszuki
wania i wstawiania oraz ma krótką pętlę wewnętrzną. Dlatego stosowanie tego rozwią
zania jest uzasadnione w wielu sytuacjach, w tym w implementacjach bibliotek.
Interfejs A P I dla uporządkow anej tablicy sym boli Jedną z najbardziej atrakcyj
nych cech czerwono-czarnych drzew BST jest to, że skomplikowany kod znajduje się
tylko w metodzie put () iw metodach związanych z usuwaniem. Można bez żadnych
zmian zastosować kod szukania m inim um i maksimum, wybierania, określania p o
zycji, podłogi oraz sufitu, a także zapytań zakresowych używany dla standardowych
drzew BST, ponieważ nie wymaga podawania koloru węzłów, a l g o r y t m 3.4 wraz
z tymi metodam i (i m etodam i usuwania) stanowi kompletną implementację inter
fejsu API dla uporządkowanej tablicy symboli. Ponadto we wszystkich metodach ko
rzystne jest prawie pełne zbalansowanie drzewa, ponieważ każda z tych m etod działa
najwyżej w czasie proporcjonalnym do wysokości drzewa. Dlatego t w i e r d z e n i e g
w połączeniu z t w i e r d z e n i e m e wystarczają do zagwarantowania logarytmicznego
czasu działania wszystkich wymienionych metod.
3.3 n Zbalansow ane drzewa wyszukiwań 459
Wyszukiwanie
sekwencyjne
N N N/2 N Nie
(nieuporządkowane
listy powiązane)
Wyszukiwanie binarne
(uporządkowane lg N N lg N N/2 Tak
tablice)
Drzewa wyszukiwań
N N 1,39 IgN 1,39 lgN Tak
binarnych
P o d s u m o w a n ie k o s z t ó w im p le m e n t a c ji t a b lic y s y m b o li ( z a k tu a liz o w a n e )
460 RO ZD ZIA Ł 3 a W yszukiwanie
| PYTANIA I ODPOWIEDZI
P. Przy podziale węzła poczwórnego czasem kolor prawego węzła ustawiany jest na
RED w metodzie ro tate R ig h t(), a następnie od razu na BLACK w metodzie flipCo-
1ors (). Czy nie jest to zbędne?
ĆWICZENIA
Narysuj diagramy, taicie jak w górnej części strony 440, dla pię
3 .3 .7 .
ciu innych przypadków przedstawionych na dole owej strony.
3 .2 . 1 0 .
j1 PROBLEMY DO ROZWIĄZANIA
3.3.24. Najgorszy przypadek dla czerwono-czarnych drzew BST. Pokaż, jak utworzyć
czerwono-czarne drzewo BST, aby zademonstrować, że w najgorszym przypadku
prawie wszystkie ścieżki z korzenia do pustego odnośnika w takim drzewie składają
cym się z N węzłów mają długość 2 lg N.
3.3.25. Zstępujące drzewa 2-3-4. Opracuj implementację interfejsu API tablicy sym
boli opartą na zbalansowanych drzewach 2-3-4. Użyj reprezentacji w postaci drzew
czerwono-czarnych i opisanej w tekście m etody wstawiania, polegającej na podziale
węzłów poczwórnych przez zmianę kolorów przy przechodzeniu w dół ścieżki wy
szukiwania i równoważeniu drzewa na drodze w górę.
3.3.31. Rysowanie drzew. Dodaj do klasy RedBlackBST metodę draw (), rysującą czerwo
no-czarne drzewa BST w rodzaju tych pokazanych w tekście (zobacz ć w ic z e n ie 3 .2 .38 ).
3.3.32. Drzewa AVL. Drzewo AVL to drzewo BST, w którym wysokość każdego węzła
i jego brata różni się najwyżej o 1 (najstarsze algorytmy dotyczące drzew zbalansowa-
nych są oparte na stosowaniu rotacji do zachowania zbalansowania wysokości drzew
AVL). Wykaż, że kolorowanie na czerwono odnośników prowadzących z węzłów o pa
rzystej wysokości do węzłów o nieparzystej wysokości w drzewie AVL daje (w pełni
zbalansowane) drzewo 2-3-4, w którym czerwone odnośniki nie zawsze są skierowane
w lewo. Dodatkowe zadanie: opracuj implementację interfejsu API tablicy symboli op
artą na opisanej strukturze danych. Jedną z możliwości jest przechowywanie wysokości
drzewa w każdym węźle i używanie rotacji po rekurencyjnych wywołaniach w celu
dostosowania wysokości. Inny sposób to użycie drzew czerwono-czarnych i metod
w rodzaju moveRedLef() imoveRedRight() z ć w i c z e ń 3 .3.39 i 3 .3 .40 .
3.3.33. Sprawdzanie. Dodaj do klasy RedBl ackBST metodę i s23() sprawdzającą, czy
żaden węzeł nie jest powiązany z dwoma czerwonymi odnośnikam i i czy nie istnieją
czerwone odnośniki skierowane w prawo, oraz metodę i sBalanced(), która spraw
dza, czy wszystkie ścieżki od korzenia do pustego odnośnika obejmują tę samą licz
bę czarnych odnośników. Połącz te m etody z kodem m etody i sBST() z ć w i c z e n i a
3 .2 .3 2 , aby utworzyć m etodę isRedBlackBST() służącą do sprawdzania, czy drzewo
jest czerwono-czarnym drzewem BST.
3.3.34. Wszystkie drzewa 2-3. Napisz kod generujący wszystkie strukturalnie różne
drzewa 2-3-4 o wysokości 2, 3 i 4. Jest ich, odpowiednio, 2, 3 i 127. Wskazówka: wy
korzystaj tablicę symboli.
3.3.35. Drzewa 2-3. Napisz program TwoThreeST.java. Zastosuj w nim dwa rodzaje
węzłów do bezpośredniego zaimplementowania drzew wyszukiwań 2-3.
3.3.36. Drzewa 2-3-4-5-6-7-8. Opisz algorytmy wyszukiwania i wstawiania w drze
wach wyszukiwań 2-3-4-5-6-7-8.
3.3.37. Bez efektu pamięci. Wykaż, że czerwono-czarne drzewa BST nie są pozba
wione efektu pamięci. Przykładowo, jeśli wstawisz klucz mniejszy niż wszystkie klucze
drzewa, a następnie natychmiast usuniesz m inim um , może powstać inne drzewo.
3.3 Q Zbalansow ane drzewa wyszukiwań 465
Rozwiązanie:
private Node moveRedLeft(Node h)
{ // Przy założeniu, że h je s t czerwony, a h . le f t i h .l e f t . l e f t
// są czarne, zmień kolor h . le f t lub jednego z jego dzieci
/ / n a czerwony.
f lip C o lo r s(h );
i f (is R e d (h .r i g h t . l e f t ) )
{
h .rig h t = ro t a te R ig h t(h .r i g h t ) ;
h = r o t a t e L e f t ( h );
}
return h;
}
Rozwiązanie:
private Node moveRedRight(Node h)
{ // Przy założeniu, że h je s t czerwony, a h .rig h t i h .r i g h t . l e f t
// są czarne,
// należy ustawić h .rig h t lub jedno z jego dzieci na czerwony.
flipColors(h)
i f ( ! is R e d (h . l e f t . l e f t ) )
h = ro t a t e R ig h t ( h );
return h;
}
Q EKSPERYMENTY
3.3.42. Zliczanie czerwonych węzłów. Napisz program, który określa procent czer
wonych węzłów w danym czerwono-czarnym drzewie BST. Przetestuj program przez
uruchomienie przynajmniej 100 powtórzeń eksperymentu polegającego na wstawie
niu Włosowych kluczy do początkowo pustego drzewa (przyjmij N = 104, 105 i 10s).
Sformułuj hipotezy.
3.3.43. Wykresy kosztów. Zmodyfikuj klasę RedBlackBST tak, aby można było two
rzyć wykresy podobne do przedstawionych w podrozdziale, pokazujących koszt każ
dej operacji put () w czasie obliczeń (zobacz ć w i c z e n i e 3 . 1 .38 ).
3.3.44. Średni czas wyszukiwania. Przeprowadź badania empiryczne, aby obliczyć
średnią i odchylenie standardowe średniej długości ścieżki do losowego węzła (czyli
długości ścieżki wewnętrznej podzielonej przez rozmiar drzewa) w czerwono-czar
nym drzewie BST zbudowanym przez wstawienie N losowych kluczy do początkowo
pustego drzewa (dla W od 1 do 10 000) .Wykonaj przynajmniej 1000 powtórzeń dla
każdej wielkości drzewa. Przedstaw wyniki jako wykres Tuftea, taki jak na dole tej
strony. Nałóż je na krzywą odpowiadającą funkcji Ig N - 0,5.
Średnia długość ścieżki do losowego węzła w czerwono-czarnych drzewach BST zbudowanych z losowych kluczy
3 .4 .T A B L IC E Z H A S Z O W A N IE M
U
Jeśli kluczami są małe liczby całkowite, można użyć tablicy do zaimplementowania
nieuporządkowanej tablicy symboli. Klucze są wtedy indeksem tablicy, dlatego m oż
na zapisać wartość powiązaną z kluczem i na pozycji i tablicy, co zapewnia bezpo
średni dostęp do wartości. W tym podrozdziale omawiamy haszowanie — rozwinię
cie wspomnianej prostej m etody umożliwiające obsługę bardziej skomplikowanych
rodzajów kluczy. Pary klucz-wartość w tablicach wskazywane są na podstawie opera
cji arytmetycznych przekształcających klucze w indeksy tablicy.
Algorytmy wyszukiwania oparte na haszowaniu składają się z dwóch odrębnych czę
ści. Pierwsza oblicza funkcję haszującą, która przekształca klucz wyszukiwania na skrót
wyznaczający indeks tablicy. W idealnych warunkach różne
Klucz Skrót Wartość
pqi klucze odpowiadają różnym indeksom. Zwykle ideał ten jest
2 xyz
nieosiągalny, dlatego może się zdarzyć, że dwa klucze (lub
pqr
większa ich liczba) będą odpowiadać temu samemu indek
ijk
uvw sowi tablicy. Dlatego drugą częścią wyszukiwania opartego
na haszowaniu jest proces rozwiązywania kolizji, który po
zwala radzić sobie z taką sytuacją. Po opisaniu sposobu obli
Kolizja czania funkcji haszujących omawiamy dwa różne podejścia
i jk
do rozwiązywania kolizji — metodę łańcuchową (ang. sepa-
rate chaining) i próbkowanie liniowe (ang. linear probing).
Przy haszowaniu występuje klasyczny problem równo
ważenia czasu i pamięci. Gdyby nie było ograniczeń pa
mięciowych, przy każdym wyszukiwaniu wystarczyłby je
M-l den dostęp do pamięci — przez zastosowanie klucza jako
indeksu do (potencjalnie bardzo dużej) tablicy. Często jest
Haszowanie - istota problemu
to jednak niemożliwe, ponieważ jeśli liczba możliwych
wartości kluczy jest wielka, ilość potrzebnej pamięci jest
niedopuszczalnie duża. Z kolei gdyby nie istniały ograniczenia czasowe, wystarczy
łaby m inim alna ilość pamięci i wyszukiwanie sekwencyjne w nieuporządkowanej
tablicy. Haszowanie pozwala ograniczyć do rozsądnej ilości potrzebny czas i pamięć
oraz uzyskać równowagę między opisanymi skrajnymi sytuacjami. Okazuje się, że
w algorytmach haszowania m ożna zyskać czas kosztem pamięci (i na odwrót), dosto
sowując parametry. Nie wymaga to modyfikowania kodu. Aby ułatwić dobór w arto
ści parametrów, m ożna wykorzystać znane wyniki z teorii prawdopodobieństwa.
Teoria prawdopodobieństwa jest osiągnięciem z dziedziny analizy matematycznej,
którego omawianie wykracza poza zakres tej książki, jednak opisywane algorytmy
haszowania, w których wykorzystano wiedzę opartą na tej teorii, są dość proste i p o
wszechnie stosowane. Za pomocą haszowania m ożna zaimplementować w tablicach
symboli wyszukiwanie i wstawianie, które w typowych zastosowaniach wymagają
stałego (po amortyzacji) czasu na operację. Dlatego jest to m etoda w wielu sytuacjach
stosowana z wyboru do implementowania podstawowych tablic symboli.
470
3.4 □ Tablice z haszowaniem 471
pierwszą, może się okazać, że nie wszystkie bity klucza są uwzględniane, co prowadzi
do tego, że niemożliwy staje się równomierny podział wartości. Jeśli kluczami są na
przykład liczby o podstawie 10, a M to lOk, wtedy używanych będzie tylko k najmniej
znaczących cyfr. W ramach prostego przykładu sytuacji, w której wybór liczby różnej
niż pierwsza może prowadzić do problemów, przyjmijmy, że klucze to num ery kie
runkowe, a M = 100. Z przyczyn historycznych środkowa cyfra w większości kodów
w Stanach Zjednoczonych to 0 lub 1 , dlatego w podanym rozwiązaniu faworyzowane
są wartości poniżej 20, natomiast zastosowanie liczby pierwszej 97 pozwala lepiej
rozdzielić dane (jeszcze lepsza byłaby liczba pierwsza bardziej oddalona od 100 ).
Także adresy IP są liczbami binarnymi, które z przyczyn historycznych (podobnie
jak num ery kierunkowe) nie są losowe, dlatego rozmiar tablicy powinien być liczbą
pierwszą (a przede wszystkim nie być potęgą dwójld), jeśli chcemy zastosować haszo-
wanie m odularne do podziału adresów.
Liczby zm iennoprzecinkow e Jeśli kluczami są liczby rzeczywiste z przedziału od 0 do
1, można pomnożyć je przez M i zaokrąglić do najbliższej liczby całkowitej, aby uzyskać
indeks z przedziału od 0 do M - 1. Choć podejście to jest intuicyjne, ma wadę, ponieważ
większą wagę przypisuje się tu najbardziej znaczącym bitom kluczy. Najmniej znaczące
bity nie mają znaczenia. Jednym z rozwiązań jest użycie haszowania modularnego na
binarnej reprezentacji klucza (to podejście zastosowano w Javie).
Łańcuchy znaków Haszowanie m odularne działa też dla długich kluczy, talach jak
łańcuchy znaków. Można traktować je jak duże liczby całkowite. Przykładowy kod
pokazany po lewej oblicza funkcję haszowania m odularnego dla zmiennej s typu
String. Przypominamy, że m etoda charAt() zwraca wartość typu char Javy, czyli
16-bitową nieujemną liczbę całkowitą. Jeśli R
in t hash = 0;
jest większe niż wartość jakiegokolwiek znaku,
f o r (in t i = 0; i < s . 1 en gth(); i++)
hash = (R * hash + s .c h a r A t (i)) % M; obliczenia odbywają się tak, jakby wartość typu
S tring potraktowano jako N-cyfrową liczbę
Haszowanie klucza w postaci łańcucha znaków całkowitą o podstawie R. Metoda oblicza resztę
z dzielenia tej liczby przez M. Klasyczny algorytm
(metoda Homera) wykonuje to zadanie za pom ocą N operacji mnożenia, dzielenia
i dzielenia modulo. Jeśli wartość R jest odpowiednio mała, przez co nie następuje
przepełnienie, wynikiem jest — zgodnie z potrzebami — liczba całkowita pomiędzy
0 a M-l. Zastosowanie małej pierwszej liczby całkowitej, na przykład 31, gwarantuje,
że wszystkie bity każdego znaku są uwzględniane. W Javie w domyślnej im plementa
cji dla typu S tri ng wykorzystano podobną metodę.
Klucze złożone Jeśli typ lducza obejmuje kilka pól całkowitoliczbowych, zwykle
m ożna je połączyć w sposób opisany dla wartości typu String. Załóżmy, że klucz
wyszukiwania ma typ Date, obejmujący trzy pola całkowitoliczbowe: day (dwie cyfry
określające dzień), month (dwie cyfry określające miesiąc) i year (cztery cyfry okre
ślające rok). Należy obliczyć wartość:
Jeśli Rjest odpowiednio małe, tak aby nie nastąpiło przepełnienie, uzyskana wartość
to liczba całkowita pomiędzy 0 a M-l (zgodnie z potrzebami). Tu m ożna uniknąć
wewnętrznej operacji % Mprzez wybranie dla Rumiarkowanie dużej liczby pierwszej,
na przykład 31. Metodę tę, podobnie jak dla łańcuchów znaków, m ożna uogólnić, tak
aby obsługiwała dowolną liczbę pól.
Konwencje stosowane w Javie Java pomaga rozwiązać podstawowy problem (po
legający na tym, że każdy typ danych wymaga funkcji haszującej) przez to, że każdy
typ danych dziedziczy funkcję hashCode(), która zwraca 32-bitową liczbę całkowitą.
Implementacja metody hashCode() w typie danych musi być spójna względem meto
dy equals. Oznacza to, że jeśli wyrażenie a.equals(b) ma wartość true, wywołanie
a.hashCode() musi zwracać tę samą wartość, co b.hashCode(). Natomiast jeżeli warto
ści funkcji hashCode() są różne, wiadomo, że obiekty nie są sobie równe. Jeśli wartości
funkcji hashCode () są identyczne, obiekty mogą, ale nie muszą być równe. Trzeba użyć
metody equal s (), aby to ustalić. To podejście trzeba zastosować w każdym kliencie,
aby móc używać metody hashCode() dla tablic symboli. Warto zauważyć, że wynika
z tego, iż trzeba przesłonić obie metody, hashCode() i equal s(), jeśli haszowanie ma
działać dla typu zdefiniowanego przez użytkownika. Domyślna implementacja zwraca
adres maszynowy obiektu reprezentującego klucz. Rzadko jest to odpowiednia war
tość. Dla wielu często używanych typów (w tym S tri ng, Integer, Doubl e, Fi 1e i URL)
Java udostępnia implementacje metody hashCode () przesłaniające domyślną metodę.
Przekształcanie wartości fu n k c ji hashCode() na indeks tablicy Ponieważ celem
jest uzyskanie indeksu tablicy, a nie 32-bitowej liczby całkowitej, w implementacjach
łączymy wartość funkcji hashCode () z haszowaniem m odularnym, aby otrzymać
liczbę całkowitą pomiędzy 0 a M-l. Odbywa się to tak:
private in t hash(Key x)
{ return (x.hashCodeQ & 0 x 7 f f f f f f f ) % M; }
Ten kod maskuje bit znaku (aby przekształcić 32-bitową liczbę w 31-bitową nieujemną
liczbę całkowitą), a następnie oblicza resztę z dzielenia przez M, tak jak w haszowa-
niu modularnym. Programiści przy stosowaniu podobnego kodu często używają liczb
pierwszych jako rozmiaru tablicy (M). Jest to próba uwzględnienia wszystkich bitów
skrótu. Uwaga: aby uniknąć niejednoznacz- ^ s E A R c H X M P L
ności, w przykładach dotyczących haszowania skrót(M=5) 2 0 0 4 4 4 2 4 3 3
pomijamy wszystkie obliczenia tego rodzaju, Skrót(M=i6) 6 10 4 14 5 4 15 l 14 6
a w zamian używamy wartości skrótów poda- Wartośd skrótów k|uczy stoSowane w przykładach
nych w tabeli po prawej stronie.
M etoda hashC ode() definiowana p rzez użytkow nika W kodzie klienta można
oczekiwać, że m etoda has hCode () rozdziela wszystkie klucze równomiernie między
możliwe 32-bitowe wartości. Oznacza to, że dla dowolnego obiektu x m ożna napisać
x. hashCode () i — w zasadzie — z równym prawdopodobieństwem oczekiwać jednej
z 232 możliwych 32-bitowych wartości. W Javie implementacje m etody hashCode ()
dla typów S t r i ng, Integer, Doubl e, Fi 1e i URL mają działać w ten sposób. Dla własne-
474 RO ZD ZIA Ł 3 o W yszukiwanie
p u b lic c la s s Transaction
go typu danych trzeba samodzielnie spróbować
f uzyskać ten efekt. Przykład dla typu Date przed
stawiony na stronie 472 to jedno z możliwych
p riv a te final S t r in g who;
rozwiązań — tworzenie liczb całkowitych ze
p riv a te final Date when;
p riv a te final double amount; zmiennych egzemplarza i stosowanie haszowa
nia modularnego. W Javie konwencja, zgodnie
p u b lic in t hashCode()
z którą wszystkie typy danych dziedziczą metodę
{
in t hash = 17; hashCode (), pozwala zastosować jeszcze prostsze
hash = 31 * hash + who.hashCode() ; podejście. Można użyć m etody hashCode() na
hash = 31 * hash + when.hashCode() ; zmiennych egzemplarza, aby przekształcić każdą
hash = 31 * hash
+ ((Double) amount).hashCode()
z nich w 32-bitową wartość typu i nt, a następnie
return hash; wykonać operacje arytmetyczne, co pokazano po
1 lewej dla typu Transaction. Warto zauważyć, że
zmienne egzemplarza typu prostego trzeba zrzu
tować na typ nakładkowy, aby móc użyć m etody
hashCode(). Także tu konkretna wartość używa
Implementowanie metody hashCodeQ w typie
zdefiniowanym przez użytkownika na w m nożeniu (w przykładzie jest to 31) nie ma
większego znaczenia.
Programowa pam ięć podręczna Jeśli obliczanie skrótów jest kosztowne, czasem warto
zapisać w pamięci podręcznej skrót każdego klucza. Polega to na przechowywaniu w obiek
tach typu klucza zmiennej egzemplarza hash obejmującej wartość funkcji hashCode () dla
każdego obiektu klucza (zobacz ć w i c z e n i e 3 .4 .25 ). Przy pierwszym wywołaniu metody
hashCode() trzeba obliczyć skrót (i wartość zmiennej hash), natomiast w późniejszych
wywołaniach wystarczy zwrócić obliczoną wartość. W Javie zastosowano tę technikę do
zmniejszenia kosztów obliczania funkcji hashCode () dla obiektów typu S tri ng.
110 = 10679/97
2348485323484853532323484848485348532323535323532348532323
0 W a r t o ś ć k lu c z a
Liczba wystąpień wartości skrótów dla słów z książki ToleofTwo Cities (10 679 kluczy, M = 97)
warto przetestować każdą używaną funkcję haszującą. Co zajmuje więcej czasu: obli
czenie funkcji haszującej czy porównanie dwóch kluczy? Czy funkcja haszująca dzieli
typowy zbiór kluczy równomiernie między wartości od 0 do M - 1? Przeprowadzenie
prostych eksperymentów, które dają odpowiedzi na te pytania, może zabezpieczyć
twórców przyszłych klientów przed nieprzyjemnymi niespodziankami. Na powyż
szym histogramie pokazano, że opracowana przez nas implementacja metody hash ()
oparta na metodzie hashCode () typu danych S tring Javy prowadzi do sensownego
rozkładu słów z pliku z książką Tale ofTwo Cities.
Omówienie to oparte jest na podstawowym założeniu, przyjmowanym przy stoso
waniu haszowania. Przyjmujemy wyidealizowany model, którego nie spodziewamy
się zrealizować, ale który mimo to wyznacza sposób myślenia przy implementowaniu,
algorytmów haszowania. Oto to założenie.
public SeparateChainingHashST(int M)
{ // Tworzy M l i s t powiązanych,
thi s.M = M;
st = (SequentialSearchST<Key, V a lue>[]) new SequentialSearchST[M];
fo r (in t i = 0; i < M; i++)
st [i] = new SequentialSearchST();
}
W tej implementacji podstawowej tablicy symboli przechowywana jest tablica list powiąza
nych, a do wyboru listy dla każdego klucza służy funkcja haszująca. Dla uproszczenia wyko
rzystano metody z klasy Sequenti al SearchST. Przy tworzeniu tablicy s t [] potrzebne jest rzu
towanie, ponieważ Java nie zezwala na tworzenie tablic dla typów generycznych. Konstruktor
domyślny tworzy 997 list, tak więc dla dużych tablic kod działa około 1000 razy szybciej niż
klasa Sequenti al SearchST. To szybkie rozwiązanie jest łatwym sposobem na osiągnięcie wy
sokiej wydajności, jeśli znana jest przybliżona liczba par klucz-wartość dodawanych za pomocą
metody put () przez klienta. Lepszym rozwiązaniem jest zmienianie wielkości tablicy, co nieza
leżnie od liczby par klucz-wartość pozwala zagwarantować, że listy będą krótkie (zobacz stronę
486 i ć w i c z e n i e 3 .4 . 18 ).
478 R O ZD ZIA Ł 3 □ W yszukiwanie
ot
Rozkład dwumianowy (N = 104, M = 103, a = 10)
( ? ) ( * ) * ( » - #
a ke-n o
1 1
io
1
20
1
30
]ę 1 Rozkład Poissona (N = 104, M = 103, a = 10)
D ł u g o ś c i list (10 6 7 9 k lu cz y, M = 9 9 7)
Długości list w wywołaniu FrequencyC ounter 8 < t a l e . t x t z wykorzystaniem klasy separateC hainingH ashST
ilość pamięci jest ograniczona, nadal można zwiększyć wydajność M razy, ustawiając
tak duże M, na jakie m ożna sobie pozwolić. Na rysunku poniżej pokazano na przy
kładzie programu FrequencyCounter spadek średniego kosztu z tysięcy porównań na
operację dla klasy Sequenti al SearchST do małej stałej dla klasy SeperateChai ni ngST.
Jest to zgodne z oczekiwaniami. Inna możliwość to zmienianie długości tablicy w celu
zachowania krótkich list (zobacz ć w i c z e n i e 3 .4 .1 8 ).
Usuwanie Aby usunąć parę klucz-wartość, wystarczy określić skrót w celu znalezienia
obiektu Sequential SearchST zawierającego klucz, a następnie wywołać metodę dele
te () na tej tablicy (zobacz ć w i c z e n i e 3 .1 .5 ). Lepiej powtórnie wykorzystać kod w ten
sposób, niż ponownie implementować podstawowe operacje na liście powiązanej.
Operacje na kluczach uporządkow anych Głównym celem haszowania jest równo
m ierne rozłożenie kluczy, dlatego jakakolwiek kolejność zostaje w trakcie haszowa
nia utracona. Jeśli trzeba szybko znaleźć klucz minim alny lub maksymalny, znaleźć
klucze z danego przedziału lub zaimplementować inne operacje z interfejsu API dla
uporządkowanej tablicy symboli (strona 378), haszowanie nie jest odpowiednim roz
wiązaniem, ponieważ operacje te będą działać liniowo.
1 S ! _ s E : j I
E 10 zerwone
elementy \
0 i 1 Szare i i
i { \ elbmepty ri\e
A 4 7 sq powę kA s 1 I Z -
_ 1 f 2 0 l- K ^ sąsprawdźane
i i
R 14 A s II 1 R I
z z l ___i 2 0 . 1L_ ___ 1 3 1
C 5 4 fiL/irnó z r C ZT 1E 1 1 Pk]
elementy sq 2 5 0 1 1 1 ___I___ L 3 J __
H 4 5 sorowclzane z r
i
c s ZT| I 1 E I 1 I~ r 1
1 1 5 0 5 i j 1 ’ ___ 1 13 1
E 10 fi
u n r A c s H. E 1 r 1
i i 2 5 0 5 © 1 13 ]
7 __ r i_ A c s H E : 1 1R |X
15
i 1 5 0 5 6 1 1 3 |7
4 8 A C s H1 E ! i i ~R ~ n r
H ~ r ~
i i ® 5 0 51 6 "‘ 1 13 1 7
1 9 M i A C s H1 E r | r i x Próbkowanie
9 1 1 S 5 0 5~i r 6 | 1 1 3| 7 przechodzi
14 10 [ P M1 | A C s H1 i E | i i R Lx do elementu 0
10 9 1___L _ 8 5 0 H JI 6 1 13 1 7
p mT ! ~A 1 c s H 1L f E~|
6 11 i 1 R1x
10 9 1 1 8 5 0 5 11 161 1 1 3 lT
10 12
p “ Ml [Z ł A j T I ~s~| ~H~| L E 1 |R |X keys []
10 9 i 1 8 5 0 5 ;ii 1 1 3 I 7 v a ls [ ]
public LinearProbingHashST()
{
keys = ( Key []) new Object[M];
va ls = (ValueJJ) new Object[M];
}
i nt i ;
fo r (i = hash(key); keys[i] != n u li; i = (i + 1) % M)
i f (k e y s[ i ] .equals(key)) { v a l s [ i ] = val; return; }
keys [i] = key;
val s [ i ] = v a l ;
N++;
}
k e y s [8 0 6 4 ..8 1 9 2 ]
Tablica wzorców (2048 kluczy; tablice zapisane jako wiersze o 128 pozycjach)
3.4 o Tablice z haszowaniem 485
A n a liz a p r ó b k o w a n ia lin io w e g o M im o s to s u n k o w o p ro s te j f o r m y w y n ik ó w ,
d o k ła d n e a n a liz o w a n ie p ró b k o w a n ia lin io w e g o je s t b a r d z o tr u d n y m z a d a n ie m .
W y p ro w a d z e n ie w 1962 ro k u p rz e z K n u th a o p is a n y c h d a lej w z o ró w b y ło p r z e ło m e m
w d z ie d z in ie a n a liz y a lg o ry tm ó w .
~ - - ( l ~ — !— ) o r a z ~ r i + 7 i Z")
2 1 -a 2 (1- a )
o d p o w ie d n io d la u d a n e g o w y sz u k iw a n ia i n ie u d a n e g o w y sz u k iw a n ia (lu b w sta w ia
n ia). K ie d y a w y n o s i m n ie j w ięcej 1/2, ś r e d n ia lic z b a te s tó w p rz y u d a n y m w y sz u
k iw a n iu w y n o s i o k o ło 3 /2 , a d la n ie u d a n e g o w y sz u k iw a n ia — o k o ło 5 /2 . S z a c u n k i
sta ją się m n ie j p re c y z y jn e , k ie d y a zb liż a się d o 1 , je d n a k w te d y n ie są p o trz e b n e ,
p o n ie w a ż p ró b k o w a n ie lin io w e s to su je m y ty lk o d la a m n ie js z y c h n iż 1/ 2 .
Omówienie. Ś re d n ią u s ta la m y p rz e z o b lic z e n ie k o s z tó w n ie u d a n e g o w y s z u
k iw a n ia ro z p o c z ę te g o n a k a ż d e j p o z y c ji ta b lic y i p o d z ie le n ie s u m y p rz e z M .
K ażd e n ie u d a n e w y s z u k iw a n ie w y m a g a p r z y n a jm n ie j je d n e g o te s tu , d la te g o
lic z y m y lic z b ę p r ó b p o p ie r w s z y m teście. R o z w a ż m y d w a n a s tę p u ją c e s k ra jn e
p r z y p a d k i w ta b lic y z p r ó b k o w a n ie m lin io w y m , k tó r a je s t w p o ło w ie p e łn a
( M = 2N ). W n a jle p s z y m p r z y p a d k u p o z y c je ta b lic y o in d e k s a c h p a rz y s ty c h są
p u s te , a o n ie p a r z y s ty c h — zaję te. W n a jg o r s z y m — je d n a p o ło w a ta b lic y je s t
p u s ta , a d r u g a — z a ję ta . Ś re d n ia d łu g o ś ć g r u p w o b u s y tu a c ja c h w y n o s i N /{ 2 N )
= 1 / 2 , je d n a k ś r e d n ia lic z b a te s tó w p r z y n ie u d a n y m w y s z u k iw a n iu je s t ró w n a 1
(k a ż d e w y s z u k iw a n ie w y m a g a p rz y n a jm n ie j je d n e j p ró b y ) p lu s ( 0 + 1 + 0 + 1 +
...)/(2 N ) = 1/2 d la n a jle p s z e g o p r z y p a d k u o ra z 1 p lu s ( N + ( N - 1) + ...)/(2 N ) ~
N / 4 d la n a jg o rs z e g o p r z y p a d k u . To w n io s k o w a n ie m o ż n a u o g ó ln ić , a b y p o k a
zać, że ś r e d n ia lic z b a p r ó b p r z y n ie u d a n y m w y s z u k iw a n iu je s t p r o p o r c jo n a ln a
d o k w a d ra tó w d łu g o ś c i g ru p . Jeśli g r u p a m a d łu g o ś ć t, w y ra ż e n ie ( t + ( i - 1 ) + ...
+ 2 + 1 ) / A i = f ( f + l ) /( 2 M ) u w z g lę d n ia w k ła d tej g ru p y w su m ę . S u m a d łu g o ś c i
g ru p w y n o s i N , d la te g o p o d o d a n iu k o s z tó w d la k a ż d e j p o z y c ji w ta b lic y o k a z u je
się, że łą c z n y ś r e d n i k o s z t d la n ie u d a n e g o w y s z u k iw a n ia to s u m a 1 + JV/(2 Ai)
i s u m y k w a d ra tó w d łu g o ś c i g ru p p o d z ie lo n a p rz e z 2M . T a k w ię c n a p o d s ta w ie
ta b lic y m o ż n a sz y b k o o b lic z y ć ś r e d n i k o s z t n ie u d a n e g o w y s z u k iw a n ia (z o b a c z
ć w ic z e n ie 3 . 4 . 2 1 ). O g ó ln ie g r u p y p o w s ta ją w s k o m p lik o w a n y m d y n a m ic z n y m
p ro c e s ie ( o p a r ty m n a a lg o r y tm ie p ró b k o w a n ia lin io w e g o ), k tó r y t r u d n o o p is a ć
a n a lity c z n ie . Z a g a d n ie n ie to w y k ra c z a p o z a z a k re s k sią ż k i.
486 RO ZD ZIA Ł 3 a W yszukiw anie
z g o d n i e z t w i e r d z e n i e m M (p r z y s ta n d a r d o w y m z a ł o ż e n i u j ) m o ż n a o c z e k iw a ć ,
że w y s z u k iw a n ie w p ra w ie p e łn e j ta b e li b ę d z ie w y m a g a ć b a r d z o d u ż e j lic z b y p ró b
(w ra z ze z b liż a n ie m się a d o 1 w a rto ś c i w z o ró w o p is u ją c y c h lic z b ę p ró b sta ją się b a r
d z o d u ż e ). J e d n a k lic z b a p ró b w y n o s i m ię d z y 1,5 a 2,5, je ś li m o ż n a z a g w a ra n to w a ć ,
że w s p ó łc z y n n ik z a p e łn ie n ia a w y n o s i p o n iż e j 1/2. R o z w a ż m y te r a z w y k o rz y s ta n ie
z m ia n y w ie lk o ś c i ta b lic y w ty m celu .
A n a l i z y z u w z g lę d n i e n ie m a m o r t y z a c j i Z p e rs p e k ty w y te o re ty c z n e j p r z y z m ie n ia
n iu w ie lk o ś c i ta b lic y tr z e b a z a d o w o lić się o g ra n ic z e n ie m z u w z g lę d n ie n ie m a m o r ty
zacji, p o n ie w a ż w ia d o m o , że w s ta w ia n ie p o w o d u ją c e p o d w o je n ie r o z m ia r u ta b lic y
w y m a g a d u ż e j lic z b y p ró b .
Dowód. Z a r ó w n o w m e to d z ie ła ń c u c h o w e j, j a k i p r z y p r ó b k o w a n iu lin io w y m
w y n ik a to z p ro s te g o p r z e f o r m u ło w a n ia u w z g lę d n ia ją c y c h a m o r ty z a c ję a n a liz
d łu g o ś c i ta b lic y (p o ra z p ie r w s z y p r z e d s ta w io n o je w r o z d z i a l e i . ) w p o łą c z e
n iu Z T W IE R D Z E N IA M I K i M.
K o s z t y w y w o ła n ia j a v a F r e q u e n c y C o u n t e r 8 < t a l e . t x t z w y k o rz y s t a n ie m k la sy L i n e a r C h a i n i n g H a s h S T (z p o d w a ja n ie m )
488 R O ZD ZIA Ł 3 Q W yszukiw anie
W y k re s y ś r e d n ic h s k u m u lo w a n y c h d la p rz y k ła d o w e g o p r o g r a m u F re q u e n c y C o u n te r
( p r z e d s ta w io n e n a d o le p o p rz e d n ie j s tro n y ) d o b r z e ilu s tr u ją d y n a m ik ę z m ie n ia n ia
w ie lk o ś c i ta b lic y w h a s z o w a n iu . P rz y k a ż d y m p o d w o je n iu w ie lk o ś c i ta b lic y ś r e d n ia
s k u m u lo w a n a ro ś n ie m n ie j w ię ce j o 1 , p o n ie w a ż d la k a ż d e g o k lu c z a ta b lic y tr z e b a
p o n o w n ie o b lic z y ć s k ró t. N a s tę p n ie ś r e d n ia sp a d a , p o n ie w a ż lic z b a k lu c z y o d p o w ia
d a ją c y c h k a ż d e j p o z y c ji ta b lic y z m n ie js z a się m n ie j w ię ce j o p o ło w ę , p r z y c z y m t e m
p o s p a d k u z m n ie js z a się w ra z z p o n o w n y m z a p e łn ia n ie m się tablicy.
Pamięć W s p o m n ie liś m y , że z ro z u m ie n ie w y k o rz y s ta n ia p a m ię c i to w a ż n y c z y n n ik
p rz y d o s tr a ja n iu a lg o r y tm ó w h a s z o w a n ia p o d k ą te m o p ty m a ln e j w y d a jn o ś c i. C h o ć
d o s tra ja n ie je s t z a d a n ie m d la e k sp e rtó w , w a rto ś c io w y m ć w ic z e n ie m je s t z g ru b n e
o k re ś le n ie ilo śc i p o tr z e b n e j p a m ię c i p rz e z o sz a c o w a n ie lic z b y u ż y w a n y c h r e f e r e n
cji. Jeśli p o m in ą ć p a m ię ć n a k lu c z e i w a rto ś c i, w p rz e d s ta w io n e j tu im p le m e n ta c ji
k la s y SeperateChai ni ngHashST p o tr z e b n a je s t p a m ię ć n a M re fe re n c ji d o o b ie k tó w
SequentialSearchST i M o b ie k tó w te g o ty p u . K a ż d y o b ie k t SequentialSearchST
o b e jm u je s ta n d a rd o w y c h 16 b a jtó w n a n a r z u t d la o b ie k tó w p lu s je d n ą 8 -b a jto w ą r e
fe re n c ję (first), a w s u m ie je s t N o b ie k tó w Node, z k tó r y c h k a ż d y o b e jm u je 2 4 b a j
ty n a n a r z u t d la o b ie k tó w i tr z y re fe re n c je (k ey, value i n e x t) . M o ż n a p o r ó w n a ć to
z d o d a tk o w ą re fe re n c ją n a w ę z e ł w d rz e w a c h w y s z u k iw a ń b in a r n y c h . P rz y p r ó b k o
w a n iu lin io w y m ze z m ie n ia n ie m w ie lk o ś c i ta b lic y (w c e lu u tr z y m a n ia w s p ó łc z y n
n ik a z a p e łn ie n ia m ię d z y je d n ą ó s m ą a je d n ą d r u g ą ) p o tr z e b n y c h je s t o d 4 N d o 1 6 N
re fe re n c ji. D la te g o s to s o w a n ie h a s z o w a n ia ze w z g lę d u n a p o z io m z u ż y c ia p a m ię c i
je s t z w y k le n ie u z a s a d n io n e . O b lic z e n ia w y g lą d a ją n ie c o in a c z e j d la ty p ó w p ro s ty c h
(z o b a c z ć w i c z e n i e 3 .4 . 2 4 ).
Metoda łańcuchowa -4 8 N + 6 4 M
Próbkowanie liniowe M ię d zy - 3 2 N a - 1 2 8 N
Drzewa BST -5 6 N
o d z a r a n i a i n f o r m a t y k i n a u k o w c y b a d a li (i w c ią ż b a d a ją ) h a s z o w a n ie . O d k r y to
p rz y ty m w ie le s p o s o b ó w n a u s p r a w n ie n ie p o d s ta w o w y c h , o m ó w io n y c h w c z e śn ie j
a lg o ry tm ó w . D o s tę p n a je s t b o g a ta lite r a tu r a n a te n te m a t. W ię k s z o ś ć u s p r a w n ie ń p o
w o d u je o b n iż e n ie k rz y w e j n a w y k re sie p a m ię c i i c z a su . M o ż n a u z y sk a ć te n s a m czas
w y s z u k iw a n ia , w y k o rz y s tu ją c m n ie j p a m ię c i, lu b p rz y s p ie s z y ć w y s z u k iw a n ie p rz y
ty m s a m y m z u ż y c iu p a m ię c i. I n n e u s p r a w n ie n ia d o ty c z ą le p s z y c h g w a ra n c ji o c z e k i
w a n y c h k o s z tó w w y s z u k iw a n ia d la n a jg o rs z e g o p r z y p a d k u . Jeszcze in n e z w ią z a n e są
z le p s z y m i fu n k c ja m i h a s z u ją c y m i. N ie k tó r e m e to d y p o r u s z o n o w ć w ic z e n ia c h .
S z czeg ó ło w e w y n ik i p o r ó w n a n ia m e to d y ła ń c u c h o w e j i p ró b k o w a n ia lin io w e g o
za le ż ą o d m n ó s tw a s z c z e g ó łó w im p le m e n ta c y jn y c h o ra z w y m o g ó w p a m ię c io w y c h
i c z a so w y c h o b o w ią z u ją c y c h w k lie n c ie . Z w y k le n ie u z a s a d n io n e je s t w y b ie ra n ie m e
to d y ła ń c u c h o w e j z a m ia s t p r ó b k o w a n ia lin io w e g o n a p o d s ta w ie w y d a jn o ś c i (z o b a c z
ć w i c z e n i e 3 . 5 . 3 1 ). W p ra k ty c e p o d s ta w o w a ró ż n ic a w w y d a jn o ś c i m ię d z y ty m i
te c h n ik a m i w y n ik a z teg o , że w m e to d z ie ła ń c u c h o w e j d la k a ż d e j p a r y k lu c z - w a r to ś ć
u ż y w a n y je s t m a ły b lo k p a m ię c i, n a to m ia s t w p r ó b k o w a n iu lin io w y m z a jm o w a n e są
d w ie d u ż e ta b lic e d la całej tab licy . Jeśli ta b lic e są d u ż e , o b a r o z w ią z a n ia sta w ia ją in n e
w y m o g i sy s te m o w i z a rz ą d z a n ia p a m ię c ią . W e w s p ó łc z e s n y c h s y s te m a c h ro z w ią z y
w a n ie m te g o ro d z a ju d y le m a tó w p o w in n i z a jm o w a ć się e k s p e rc i w w y ją tk o w y c h sy
tu a c ja c h , w k tó r y c h w y d a jn o ś ć o d g ry w a k r y ty c z n ą ro lę.
P rz y o p ty m is ty c z n y c h z a ło ż e n ia c h m o ż n a o c z e k iw a ć , że h a s z o w a n ie z a p e w n i w y
k o n a n ie w s ta ły m c z a sie o p e ra c ji w y s z u k iw a n ia o ra z w s ta w ia n ia w ta b lic y s y m b o li
— i to n ie z a le ż n ie o d w ie lk o ś c i tab lic y . Jest to te o r e ty c z n ie o p ty m a ln a w y d a jn o ś ć d la
d o w o ln e j im p le m e n ta c ji ta b lic y s y m b o li. J e d n a k h a s z o w a n ie n ie je s t ro z w ią z a n ie m
u n iw e rs a ln y m . W y n ik a to z k ilk u p rz y c z y n . O to o n e:
° Potrzebna jest dobra funkcja haszująca dla każdego typu kluczy.
■ G w arancje w ydajności zależą o d jakości funkcji haszującej.
■ F u n k c je h a s z u ją c e m o g ą b y ć s k o m p lik o w a n e i k o s z to w n e d o o b lic z e n ia .
■ N iełatwo jest zapew nić obsługę operacji na uporządkow anych tablicach sym
boli.
Poruszyliśmy tylko podstawowe kwestie. Porów nanie haszowania z innym i om ów iony
m i m etodam i tw orzenia tablic symboli odkładam y do początku p o d r o z d z i a ł u 3 . 5 .
490 RO ZD ZIA Ł 3 o W yszukiw anie
I
P.
PYTANIA I ODPOWIEDZI
P. Z a p o m n ia łe m , d la c z e g o n ie im p le m e n tu je m y m e to d y h a s h ( x ) p rz e z z w ró c e n ie
w a rto ś c i x .h a s h C o d e () % M?
O . P o tr z e b n y je s t w y n ik z p r z e d z ia łu o d 0 d o M -l, je d n a k w Javie fu n k c ja % m o ż e
z w ra c a ć w a rto ś ć u je m n ą .
3.4 n Tablice z haszowaniem 491
P. D la c z e g o w ię c n ie z a im p le m e n to w a ć m e to d y h a s h ( x ) p rz e z z w ró c e n ie w a rto ś c i
M a t h . a b s ( x . h a s h C o d e ( ) ) % M?
O . D o b r a p ró b a . N ie ste ty , m e to d a M a th .a b s ( ) z w ra c a w y n ik u je m n y d la n a jw ię k
szej m o ż liw e j lic z b y u je m n e j. W w ie lu ty p o w y c h o b lic z e n ia c h to p rz e p e łn ie n ie n ie
s ta n o w i rz e c z y w is te g o p ro b le m u , je d n a k p r z y h a s z o w a n iu m o ż e s p o w o d o w a ć , że
p r o g r a m p o k ilk u m ilia r d a c h w s ta w ie ń p r a w d o p o d o b n ie u le g n ie a w a rii. Jest to n ie
p rz y je m n a p e rs p e k ty w a . P rz y k ła d o w o , in s tr u k c ja s .h a s h C o d e Q w ja v i e d a je w a rto ś ć
- 2 31 d la w a rto ś c i " p o ly g e n e l u b ri c a n ts " ty p u S t r i ng. W y m y ś la n ie in n y c h ła ń c u c h ó w
z n ak ó w , k tó r y c h s k r ó t m a tę w a rto ś ć (lu b je s t r ó w n y 0 ), to c ie k a w a ła m ig łó w k a a lg o
ry tm ic z n a .
O . O g ó ln ie u s ta w ia m y p a r a m e tr y w ta k i s p o s ó b , a b y lic z b a k lu czy , k tó r y c h s k r ó t m a
d a n ą w a rto ś ć , b y ła m a ła . D la m a ły c h ta b lic z w y k le lep iej u ż y w a ć p o d s ta w o w y c h t a b
lic s y m b o li. W p e w n y c h s y tu a c ja c h za p o m o c ą h y b ry d o w y c h m e t o d m o ż n a u z y sk a ć
p e w n ą p o p ra w ę w y d a jn o ś ć , je d n a k te g o ro d z a ju d o s tra ja n ie n a jle p ie j p o z o s ta w ić e k s
p e r to m .
P. S zy b sze je s t w y s z u k iw a n ie za p o m o c ą h a s z o w a n ia c z y p r z y u ż y c iu c z e rw o n o -
c z a rn y c h d rz e w B ST?
P. D la c z e g o p r z y p r ó b k o w a n iu lin io w y m n ie p o z w a la m y n a z a p e łn ie n ie ta b lic y n a
p rz y k ła d w tr z e c h c z w a rty c h ?
| ĆWICZENIA
3.4.1 . W s ta w k lu c z e E A S Y Q U T I 0 N (w tej k o le jn o ś c i) d o p o c z ą tk o w o p u s te j
ta b lic y o M = 5 lista c h . Z a sto s u j m e to d ę ła ń c u c h o w ą . U żyj f u n k c ji h a sz u ją c e j 11 k %
Md o p r z e k s z ta łc e n ia k - tej lite r y a lfa b e tu n a in d e k s tab licy .
3.4.2. O p ra c u j in n ą im p le m e n ta c ję k la s y S e p e ra te C h a i ni ngHashST, w k tó re j b e z p o
ś r e d n io s to s o w a n y je s t k o d lis t p o w ią z a n y c h z k la s y S e q u e n ti a l S earchS T .
3.4.5. C z y p o n iż s z a im p le m e n ta c ja m e to d y h a sh C o d e () je s t d o p u s z c z a ln a ?
p u b l i c i n t h a sh C o d e ()
{ re tu rn 17; }
Jeśli ta k , o p is z e fe k t je j z a s to s o w a n ia . Jeżeli n ie , w y ja śn ij d la c z eg o .
3.4.7. Z a s ta n ó w się n a d p e w n ą im p le m e n ta c ją h a s z o w a n ia m o d u la r n e g o d la k lu c z y
c a łk o w ito lic z b o w y c h , (a * k) % M, g d z ie a to d o w o ln a s ta ła lic z b a c a łk o w ita . C z y ta
z m ia n a p o w o d u je n a ty le d o b re w y m ie s z a n ie b itó w , że m o ż n a u ż y ć lic z b y M, k tó r a n ie
je s t p ie rw s z a ?
3.4.10. W s ta w k lu c z e E A S Y Q U T I 0 N (w tej k o le jn o ś c i) d o p o c z ą tk o w o p u
stej ta b lic y o ro z m ia r z e M = 16, u ż y w a ją c p ró b k o w a n ia lin io w e g o . Z a s to s u j fu n k c ję
h a s z u j ą c ą l l k % M, a b y p rz e k s z ta łc ić k - tą lite rę a lf a b e tu n a in d e k s tab lic y . P o n o w n ie
w y k o n a j ć w ic z e n ie d la M = 10.
3.4 o Tablice z haszowaniem 493
[
3 .4 .1 1 . P rz e d s ta w z a w a rto ś ć ta b lic y z h a s z o w a n ie m u tw o rz o n e j p rz e z p ró b k o w a n ie
lin io w e p r z y w s ta w ia n iu k lu c z y E A S Y Q U T I 0 N (w tej k o le jn o ś c i) d o p o c z ą t
k o w o p u s te j ta b lic y o w y jśc io w y m ro z m ia r z e M = 4. T a b lic a je s t p o w ię k s z a n a p rz e z
p o d w a ja n ie , k ie d y sta je się w p o ło w ie p e łn a . U żyj fu n k c ji h a s z u ją c e j 11 k % M d o
p r z e k s z ta łc e n ia k -tej lite r y a lf a b e tu n a in d e k s tab licy .
3 .4 .1 2 . Z a łó ż m y , że k lu c z e o d A d o G (p o d a j ic h w a r to ś c i s k ró tó w ) są w s ta w ia n e
w p e w n e j k o le jn o ś c i d o p o c z ą tk o w o p u s te j ta b lic y o w ie lk o ś c i 7 z a p o m o c ą p r ó b k o
w a n ia lin io w e g o ( tu n ie z m ie n ia m y w ie lk o ś c i ta b lic y ). K tó ra z p o n iż s z y c h ta b lic n ie
m o ż e p o w s ta ć w te n s p o s ó b ?
a. E F G A C B D
b. C E B G F D A
c. B D F A C E G
d. C G B A D E F
e. F G B D A C E
f. G E C A D B F
P o d a j m in im a ln ą i m a k s y m a ln ą lic z b ę p ró b , k tó r e m o g ą b y ć p o tr z e b n e d o z b u d o w a
n ia ta b lic y o w ie lk o ś c i 7 za p o m o c ą ty c h k lu czy . P rz e d s ta w te ż k o le jn o ś ć w s ta w ia n ia
u z a s a d n ia ją c ą o d p o w ie d ź .
3 .4 .1 3 . K tó ry z p o n iż s z y c h s c e n a riu s z y p r o w a d z i d o o c z e k iw a n e g o lin io w eg o c z a su
w y k o n a n ia d la lo s o w e g o u d a n e g o w y s z u k iw a n ia za p o m o c ą p r ó b k o w a n ia lin io w e g o
w ta b lic y z h a s z o w a n ie m ?
a. S ieroty w s z y s tk ic h k lu c z y o d p o w ia d a ją te m u s a m e m u in d e k s o w i.
b. S k ró ty w s z y s tk ic h k lu c z y o d p o w ia d a ją r ó ż n y m in d e k s o m .
c. S k ró ty w s z y s tk ic h k lu c z y o d p o w ia d a ją in d e k s o w i o n u m e r z e p a rz y s ty m .
d. S k ró ty w s z y s tk ic h k lu c z y o d p o w ia d a ją ró ż n y m in d e k s o m o n u m e r a c h
p a rz y s ty c h .
3 .4 .1 4 . O d p o w ie d z n a p y ta n ie z p o p r z e d n ie g o ć w ic z e n ia d la n ie u d a n e g o w y s z u k i
w a n ia p r z y z a ło ż e n iu , że s k r ó ty k lu c z y w y s z u k iw a n ia z r ó w n y m p r a w d o p o d o b ie ń
s tw e m o d p o w ia d a ją k a ż d e j p o z y c ji tab licy .
3 .4 .1 5 . Ilu p o r ó w n a ń w y m a g a w n a jg o rs z y m p r z y p a d k u w s ta w ie n ie N k lu c z y d o
p o c z ą tk o w o p u s te j ta b lic y p rz y s to s o w a n iu p ró b k o w a n ia lin io w e g o z p o w ię k s z a n ie m
ta b lic y ?
3 .4 .1 7 . P rz e d s ta w e fe k t w y k o rz y s ta n ia m e to d y d el e t e () ze s tr o n y 4 8 3 d o u s u n ię c ia
C z ta b lic y u tw o rz o n e j p rz e z z a s to s o w a n ie k la s y Li n e a rP r o b i ngHashST w s t a n d a r d o
w y m k lie n c ie u ż y w a ją c y m in d e k s u ( p o k a z a n y m n a s tro n ie 4 8 1 ).
3 .4 .1 8 . D o d a j d o k la s y S e p a ra te C h a i ni ngHashST k o n s tr u k to r , k tó r y u m o ż liw ia
k lie n to m o k re ś le n ie ś re d n ie j lic z b y p r ó b d o p u s z c z a ln e j p r z y w y s z u k iw a n iu . Z a sto s u j
z m ie n ia n ie w ie lk o ś c i tab lic y , ta k a b y ś r e d n ia d łu g o ś ć lis t b y ła m n ie js z a o d o k re ś lo n e j
w a rto ś c i. U żyj te c h n ik i o p is a n e j n a s tr o n ie 4 9 0 w c e lu z a g w a ra n to w a n ia , że w s p ó ł
c z y n n ik w m e to d z ie hash () je s t lic z b ą p ie rw s z ą .
3 .4 .1 9 . Z a im p le m e n tu j m e to d ę keys () d la k la s S e p a ra te C h a i ni ngHashST
i Li n e a rP r o b i ngHashST.
3 .4 .2 0 . D o d a j d o k la s y Li n e a rP r o b i ngHashST m e to d ę , k tó r a o b lic z a ś r e d n i k o s z t
u d a n e g o w y s z u k iw a n ia w ta b lic y p r z y z a ło ż e n iu , że s z u k a n ie k a ż d e g o k lu c z a ta b lic y
je s t ró w n ie p r a w d o p o d o b n e .
3 .4 .2 1 . D o d a j d o k la s y Li n e a rP r o b i ngHashST m e to d ę , k tó r a o b lic z a ś r e d n i k o s z t
n ie u d a n e g o w y s z u k iw a n ia w ta b lic y p r z y z a ło ż e n iu , że s to s o w a n a je s t lo s o w a fu n k c ja
h a s z u ją c a . U w a g a : n ie m u s is z o b lic z a ć ż a d n e j f u n k c ji h a s z u ją c e j, a b y w y k o n a ć ć w i
c z en ie.
3 .4 .2 2 . Z a im p le m e n tu j m e to d ę h a sh C o d e () d la ró ż n y c h ty p ó w : PointŻ D , I n t e r v a l ,
I n t e r v a l 2D i D ate.
3 .4 .2 3 . R o z w a ż m y h a s z o w a n ie m o d u l a r n e d la k lu c z y w p o s ta c i ła ń c u c h ó w zn a k ó w .
P rz y jm ijm y R = 256 i M = 255. W y k a ż , że są to z łe w a rto ś c i, p o n ie w a ż k a ż d a p e r m u -
ta c ja lite r d a n e g o ła ń c u c h a z n a k ó w d a te n s a m sierót.
3 .4 .2 4 . P rz e a n a liz u j w y k o rz y s ta n ie p a m ię c i w m e to d z ie ła ń c u c h o w e j, p r ó b k o w a n iu
lin io w y m i d rz e w a c h B ST d la k lu c z y ty p u doubl e. P rz e d s ta w w y n ik i w ta b e li p o d o b
nej d o tej ze s tr o n y 488.
3.4 a Tablice z haszowaniem 495
PROBLEMY DO ROZWIĄZANIA
3.4.27. D w u k r o tn e p ró b y . Z m o d y fik u j k la s ę S e p a ra te C h a in in g H a sh S T p rz e z u ż y c ie
d ru g ie j fu n k c ji h a s z u ją c e j i w y b ie ra n ie k ró ts z e j z d w ó c h list. P rz e d s ta w śla d d z ia ła n ia
p ro c e s u w s ta w ia n ia k lu c z y E A S Y Q U T I 0 N (w tej k o le jn o ś c i) d o p o c z ą tk o w o
p u ste j ta b lic y o w ie lk o ś c i M = 3. Z a s to s u j fu n k c ję 11 k % M (d la k -te j lite ry ) ja k o
p ie rw s z ą fu n k c ję h a s z u ją c ą i fu n k c ję 17 k % M (d la k - tej lite ry ) ja k o d r u g ą fu n k c ję
h a sz u ją c ą . P o d a j ś r e d n ią lic z b ę p ró b d la lo s o w e g o u d a n e g o i n ie u d a n e g o w y s z u k iw a
n ia w tej tab licy .
3.4.29. U su w a n ie. Z a im p le m e n tu j z a c h ła n n ą m e to d ę d e l e t e ( ) d la te c h n ik o p is a
n y c h w d w ó c h p o p r z e d n ic h ć w ic z e n ia c h .
3.4.30. S ta ty s ty k a ch i k w a d ra t. D o d a j d o k la s y S e p a ra te C h a in in g S T m e to d ę d o o b
lic z a n ia s ta ty s ty k i y j d la ta b lic y z h a s z o w a n ie m . D la N k lu c z y i ta b lic y o w ie lk o ś c i A i
s ta ty s ty k a z d e fin io w a n a je s t ró w n a n ie m :
X2 = (M /N ) ( ( / - N / M Y + ( f1 - N / M Y + ... (fM_ , - N I M Y )
3 .4 .3 1 . H a szo w a n ie d y n a m ic z n e (a n g . cu cko o h a sh in g ). O p ra c u j im p le m e n ta c ję
ta b lic y s y m b o li o b e jm u ją c ą d w ie ta b lic e z h a s z o w a n ie m i d w ie fu n k c je h a sz u ją c e .
K a ż d y k lu c z z n a jd u je się ty lk o w je d n e j z ta b lic . P rz y w s ta w ia n iu n o w e g o k lu c z a n a
le ż y u m ie ś c ić g o w je d n e j z ta b lic . Jeśli p o z y c ja w tej ta b lic y je s t z a ję ta , n a le ż y z a s tą p ić
d a w n y k lu c z n o w y m i p rz e n ie ś ć d a w n y k lu c z d o d ru g ie j ta b lic y (ta k ż e z n ie j n a le ż y
p rz e n ie ś ć k lu c z , k tó r y z n a jd u je się n a p o tr z e b n e j p o z y c ji). Jeśli w p ro c e s ie w y stą p i
cy k l, n a le ż y ro z p o c z ą ć o d n o w a . N a le ż y d b a ć o to , a b y ta b lic e b y ły z a p e łn io n e m n ie j
n iż w p o ło w ie . T a m e t o d a d la n a jg o rs z e g o p r z y p a d k u w y m a g a sta łe j lic z b y te s tó w
r ó w n o ś c i p r z y w y s z u k iw a n iu (c o o c z y w iste ) i sta łe g o c z a s u (p o a m o rty z a c ji) p rz y
w s ta w ia n iu .
3 .4 .3 2 . A ta k p r z e z h a sz o w a n ie . Z n a jd ź 2N ła ń c u c h ó w z n a k ó w , k a ż d y o d łu g o ś c i 2N,
d a ją c y c h tę s a m ą w a rto ś ć f u n k c ji hashC ode () p r z y z a ło ż e n iu , że je j im p le m e n ta c ja d la
ty p u S t r i ng w y g lą d a ta k :
public in t hashCode()
{
in t hash =0;
fo r (in t i = 0 ; i <le n gth (); i ++)
hash = (hash * 31) + c h a rA t(i);
return hash;
}
D u ż a p o d p o w ie d z : Aa i BB m a ją tę s a m ą w a rto ś ć .
3 .4 .3 3 . Z ła f u n k c j a h a szu ją c a . R o z w a ż p o n iż s z ą im p le m e n ta c ję f u n k c ji hashCode()
d la ty p u S t r i ng, u ż y w a n ą w e w c z e śn ie jsz y c h w e rs ja c h Javy:
public in t hashCode()
{
in t hash =0;
in t skip =Math.max(l, 1ength{ ) / 8 ) ;
fo r (in t i = 0 ; i < 1ength(); i += skip)
hash = (hash * 37) + c h a rA t(i);
return hash;
}
[ EKSPERYMENTY
3 .4 .3 4 . K o s z ty h a sz o w a n ia . O k re ś l e m p iry c z n ie s to s u n e k c z a s u p o tr z e b n e g o d o w y
k o n a n ia m e to d y hash () d o c z a su p o tr z e b n e g o d la m e to d y co m p areT o () d la c z ę sto
u ż y w a n y c h ty p ó w k lu czy , d la k tó r y c h m o ż n a u z y s k a ć s e n s o w n e w y n ik i.
3 .4 .3 6 . Z a k re s d łu g o śc i list. N a p isz p ro g r a m , k tó r y za p o m o c ą m e to d y ła ń c u c h o w e j
w sta w ia W ło s o w y c h k lu c z y ty p u i n t d o ta b lic y o w ie lk o ś c i N / 1 0 0 , a n a s tę p n ie o k r e
śla d łu g o ś ć n a jk ró ts z e j i n a jd łu ż s z e j listy. P rz y jm ij N = 103, 104, 10 5 i 106.
3 .4 .3 7 . H y b ry d a . P rz e p r o w a d ź b a d a n ia e k s p e r y m e n ta ln e , a b y u s ta lić e fe k t z a s to
s o w a n ia k la s y RedBlackBST z a m ia s t SequentialSearchST d o ro z w ią z y w a n ia k o liz ji
w k la s ie SeparateChai ni ngHashST. Z a le tą r o z w ią z a n ia je s t g w a r a n to w a n a w y d a jn o ś ć
lo g a r y tm ic z n a (n a w e t d la z ły c h fu n k c ji h a s z u ją c y c h ). W a d ą je s t k o n ie c z n o ś ć u tr z y
m y w a n ia d w ó c h ró ż n y c h im p le m e n ta c ji ta b lic y s y m b o li. Ja k ie są p ra k ty c z n e e fe k ty
w p ro w a d z e n ia te g o ro z w ią z a n ia ?
3 .4 .3 8 . R o z k ła d p r z y m e to d z ie ła ń c u c h o w ej. N a p is z p ro g r a m , k tó r y za p o m o c ą m e
to d y ła ń c u c h o w e j w s ta w ia 1 0 5 lo s o w y c h n ie u je m n y c h lic z b c a łk o w ity c h m n ie js z y c h
n iż 1 0 6 d o ta b lic y o ro z m ia r z e 1 0 5 i ry s u je w y k re s łą c z n e j lic z b y p r ó b p o tr z e b n y c h
p rz y k a ż d e j z 10 3 k o le jn y c h o p e ra c ji w s ta w ia n ia . W y ja śn ij, w ja k i m s to p n iu w y n ik i
s ta n o w ią d o w ó d n a t w i e r d z e n i e ic .
3 .4 .3 9 . R o z k ła d p r z y p r ó b k o w a n iu lin io w y m . N a p isz p r o g r a m , k tó r y za p o m o c ą
p r ó b k o w a n ia lin io w e g o w s ta w ia N /2 lo s o w y c h k lu c z y ty p u i n t d o ta b lic y o r o z m ia
rz e N , a n a s tę p n ie n a p o d s ta w ie d łu g o ś c i g ru p o b lic z a ś r e d n i k o s z t n ie u d a n e g o w y
s z u k iw a n ia w w y n ik o w e j tab licy . P rz y jm ij N = 103, 104, 10 5 i 106. W y ja śn ij, w ja k im
s to p n iu w y n ik i s ta n o w ią d o w ó d n a t w ie r d z e n ie m .
3 .4 .4 1 . D w u k r o tn e p ró b y . P rz e p r o w a d ź b a d a n ia e k s p e r y m e n ta ln e , a b y o c e n ić s k u
te c z n o ś ć d w u k r o tn y c h p r ó b (z o b a c z ć w ic z e n ie 3 .4 . 2 7 ).
3 .4 .4 2 . P o d w ó jn e h a sz o w a n ie . P rz e p r o w a d ź b a d a n ia e k s p e r y m e n ta ln e , a b y o c e n ić
s k u te c z n o ś ć p o d w ó jn e g o h a s z o w a n ia (z o b a c z ć w ic z e n ie 3 .4 .2 8 ).
3 .4 .4 3 . P ro b le m p a r k o w a n ia (a u to r — D . K n u th ). P rz e p r o w a d ź b a d a n ia e k s p e r y
m e n ta ln e , a b y p o tw ie r d z ić h ip o te z ę , z g o d n ie z k tó r ą lic z b a p o r ó w n a ń p o tr z e b n y c h
d o w s ta w ie n ia za p o m o c ą p ró b k o w a n ia lin io w e g o M lo s o w y c h ld u c z y d o ta b lic y
o w ie lk o ś c i M w y n o s i ~ cA P 12, g d z ie c = ~Jn! 2 .
3.5. Z A S T O S O W A N IA
498
3.5 ■ Zastosowania 499
Koszt Koszt
dla najgorszego przypadku dla typowego przypadku interfejs
Algorytm Pamięć
(po N wstawieniach) (po IMlosowych wstawieniach) klucza
(struktura danych) (w bajtach)
Wyszukiwanie Wstawianie Trafienie Wstawianie
Wyszukiwanie lg N N Ig h l N I2 compareTo() 16 N
binarne (tablica
uporządkowana)
Przedstaw ione przez nas im plem entacje tablicy sym boli są przydatne w wielu za
stosow aniach, przy czym opisane algorytm y m ożna łatwo zaadaptow ać p o d kątem
kilku innych możliwości. Rozw iązania te są pow szechnie stosow ane i w arto się im
przyjrzeć.
T ypy p ro ste Załóżmy, że tablica sym boli obejm uje klucze całkowitoliczbowe i p o
wiązane liczby zm iennoprzecinkow e. W standardow ym podejściu klucze i w artości
są zapisane za pom ocą typów nakładkow ych Integer i Double, dlatego potrzebne
są dwie dodatkow e referencje do pam ięci w celu uzyskania dostępu do każdej pary
klucz-w artość. Referencje te nie stanow ią problem u w aplikacji, która tysiące razy
w yszukuje tysiące kluczy, je d n a k m ogą prow adzić do nadm iernych kosztów, jeśli
trzeba m iliardy razy przeszukiw ać m iliony kluczy. Zastosow anie typu prostego za
m iast typu Key pozw ala zaoszczędzić jed ną referencję na każdą parę klucz-w artość.
Jeżeli pow iązana w artość też jest typu p ro
S tan d ard o w a im plem entacja
stego, m ożna pom inąć kolejną referencję.
Sytuację tę przedstaw iono po prawej dla Dane znajdują się
w obiektach Key i Val ue
m etody łańcuchow ej. Te sam e kwestie trz e / \
ba uw zględnić dla innych im plem entacji.
W zastosow aniach, gdzie wydajność o d
gryw a krytyczną rolę, w arto (i nietru d n o)
□
opracow ać wersje im plem entacji działające
w ten sposób (zobacz ć w i c z e n i e 3 .5 .4 ).
Im plem entacja o p a rta na ty p a c h prostych
P o w ta rza ją ce się k lu cze M ożliwość p o
Dane są przechowywane
w tarzania się kluczy w ym aga czasem spe w węzłach listy powiązanej
cjalnego zastanow ienia przy im plem ento
w aniu tablicy symboli. W w ielu zastosow a A
niach w arto pow iązać wiele w artości z tym
sam ym kluczem . Przykładowo, w systemie
przetw arzania transakcji liczne transakcje
m ogą mieć ten sam klucz klienta. Przyjęta Wykorzystanie pamięci w metodzie łańcuchowej
przez nas konw encja niedopuszczania do
pow tarzania się kluczy sprow adza się do pozostaw ienia zarządzania pow ielanym i
kluczam i klientowi. Przykładow ego klienta tego rodzaju opisujem y w dalszej części
podrozdziału. W wielu przedstaw ionych tu im plem entacjach m ożna zastanowić się
n a d pozostaw ieniem p ar klucz-w artość z pow tarzającym i się kluczam i w podstaw o
wej strukturze danych w spom agającej w yszukiwanie i zwracać dowolną w artość o d a
nym kluczu. M ożna też dodać m etody zwracające wszystkie w artości o danym klu
czu. Pokazane im plem entacje drzew BST i haszow ania nietru d n o zaadaptow ać w taki
sposób, aby przechow yw ać pow tarzające się klucze w strukturze danych. Uzyskanie
tego efektu dla czerw ono-czarnych drzew BST jest tylko trochę trudniejsze (zobacz
ć w i c z e n i a 3 . 5.9 i 3 . 5 . 1 0 ). Takie im plem entacje są często opisywane w literaturze
(w tym we wcześniejszych w ydaniach tej książki).
3.5 o Zastosowania 501
Interfejs API dla zbiorów N iektóre ldienty tablic sym boli nie wymagają p o
bierania wartości. Potrzebna jest jedynie m ożliw ość w staw iania kluczy do tablicy
i spraw dzania, czy klucz się w niej znajduje. Ponieważ pow tarzające się klucze są nie
dopuszczalne, operacje odpow iadają pokazanem u poniżej interfejsowi API. W ażny
jest tu tylko zbiór kluczy tablicy, a nie pow iązane z nim i wartości.
public c la s s SET<Key>
SET() Tworzy zbiór pusty
void add(Key key) Dodaje klucz key do zbioru
void delete(Key key) Usuwa klucz key ze zbioru
boolean co nta ins(K ey key) Czy klucz key znajduje się w zbiorze?
boolean isEmpty() Czy zbiór jest pusty?
int size() Zwraca liczbę kluczy w zbiorze
String to Strin g () Łańcuchowa reprezentacja zbioru
D ow olną im plem entację tablicy sym boli m ożna przekształcić na im plem entację typu
SET, pom ijając w artości lub używając prostej klasy nakładkowej (zobacz ć w i c z e n i a
od 3 . 5 .1 do 3 . 5 .3 ).
502 RO ZD ZIA Ł 3 a W yszukiw anie
B ia łe i c z a r n e li s t y I n n y k la s y c z n y filtr k o rz y s ta z k lu c z y z o d rę b n e g o p lik u d o d e
c y d o w a n ia , k tó r e k lu c z e ze s tr u m ie n ia w e jśc io w e g o n a le ż y p r z e p u ś c ić d o s tr u m i e n ia
w y jścio w eg o . T e n o g ó ln y p ro c e s m a w ie le n a tu r a ln y c h z a s to s o w a ń . N a jp ro s ts z y m
p rz y k ła d e m są b ia łe listy. K a ż d y k lu c z , k tó r y z n a jd u je się w p lik u z b ia łą listą , je s t
u z n a w a n y za „ d o b r y ”. K lie n t m o ż e p rz e k a z y w a ć d o s ta n d a rd o w e g o w y jśc ia k a ż d y
k lu c z , k tó r y n ie z n a jd u je się n a b ia łe j liśc ie , i p o m ija ć w sz y stk ie k lu c z e z n a jd u ją
ce się n a ta k ie j liśc ie ( ta k ja k w p rz y k ła d z ie o m ó w io n y m w p ie r w s z y m p r o g r a m ie
w r o z d z i a l e i.) . I n n y k lie n t m o ż e p rz e k a z y w a ć d o s ta n d a rd o w e g o w y jśc ia k a ż d y
k lu c z n ie z n a jd u ją c y się n a b ia łe j liśc ie i p o m ija ć w sz y stk ie k lu c z e , k tó r e z n a jd u ją się
n a ta k ie j liśc ie ( ta k d z ia ła k lie n t W h i t e F i l t e r
k la s y HashSET). W p r o g r a m ie p o c z to w y m p ub lic c l a s s W h i t e F i lt e r
m o ż n a w y k o rz y s ta ć filtro w a n ie p rz e z o k re ś le {
pub lic s t a t i c void m a in ( S tr in g [] args)
n ie a d re s ó w z n a jo m y c h i u z n a n ie w ia d o m o ś c i 1
o d in n y c h o s ó b z a s p a m . P rz e d s ta w io n y p r o HashSET<String> set;
set = new H a s h S E T < S tr in g> ();
g ra m tw o rz y o b ie k t HashSET n a p o d s ta w ie k lu
In in = new I n ( a r g s [0 ]) ;
czy z p o d a n e j listy, a n a s tę p n ie w c z y tu je k lu c z e while ( ! i n . isEm pty())
ze s ta n d a rd o w e g o w e jśc ia. Jeśli n a s tę p n y k lu c z set.add(in.readStringO );
z n a jd u je się w z b io rz e , n a le ż y g o w y św ie tlić . while ( I S t d l n . i s E m p t y O )
{
Jeżeli k lu c z n ie z n a jd u je się w z b io rz e , je s t ig
S t r i n g word = S t d l n . r e a d S t r i n g O ;
n o ro w a n y . C za rn a lista p e łn i o d w r o tn ą fu n k c ję i f (set.c ontain s(w ord))
i o b e jm u je „z łe ” k lu c z e . T ak że tu is tn ie ją d w ie StdOut. pri n t ln ( w o r d ) ;
n a tu r a ln e m e to d y filtro w a n ia . W p r z y k ła d o
w y m p r o g r a m ie p o c z to w y m m o ż n a o k re ś lić
a d re s y z n a n y c h s p a m e ró w i n a k a z a ć p r z e p u s z
c z a n ie w s z y s tk ic h w ia d o m o ś c i p o c h o d z ą c y c h Filtrowanie na podstawie białej listy
o d in n y c h n a d a w c ó w . M o ż n a z a im p le m e n to
w a ć k lie n ta BI ack F i 1 t e r k la s y HashSET, w k tó
% more l i s t . t x t
r y m z a n e g o w a n y je s t te s t filtru ją c y z p r o g r a was i t the of
m u W h i t e F i l t e r . W ty p o w y c h p ra k ty c z n y c h
% java W h i t e F i lt e r l i s t . t x t < t i n y T a le . t x t
z a s to s o w a n ia c h , n a p r z y k ła d u o p e r a to r ó w
it was the of i t was the of
k a r t k re d y to w y c h u ż y w a ją c y c h c z a rn y c h list it was the of i t was the of
d o o d filtro w y w a n ia n u m e r ó w s k ra d z io n y c h it was the of i t was the of
it was the of i t was the of
k a r t k r e d y to w y c h lu b w r u te r z e in te r n e to w y m
it was the of i t was the of
z b ia łą listą , d z ia ła ją c y m ja k z a p o ra , z w y k le
u ż y w a n e są b a r d z o d łu g ie lis ty i n ie o g r a n i % java B l a c k F i l t e r l i s t . t x t < t i n y T a l e . t x t
c z o n e s tr u m ie n ie w e jśc io w e o ra z o b o w ią z u ją best times worst times
age wisdom age f o o li s h n e s s
śc isłe w y m o g i c o d o c z a s u re a k c ji. O m ó w io n e
epoch b e l i e f epoch i n c r e d u l i t y
ro d z a je im p le m e n ta c ji ta b lic y s y m b o li u m o ż li season l i g h t season darkness
w ia ją ła tw ą o b s łu g ę ty c h w a ru n k ó w . s p rin g hope winte r despair
504 R O ZD ZIA Ł 3 o W yszukiw anie
g o śc ią p r o g r a m ó w k o s z t o p e ra c ji n a ta b lic y s y m b o li s ta w a ł się w ą s k im g a rd łe m
w c zasie ro z w ija n ia p ro g r a m u , co d o p ro w a d z iło d o p o w s ta n ia s t r u k tu r d a n y c h
i a lg o r y tm ó w p o d o b n y c h d o ty c h o m ó w io n y c h w ro z d z ia le .
° Systemy plików. R e g u la rn ie k o rz y s ta m y z ta b lic s y m b o li d o p o r z ą d k o w a n ia d a
n y c h w s y s te m a c h k o m p u te ro w y c h . P r a w d o p o d o b n ie n a jb a rd z ie j z n a n y m p r z y
k ła d e m je s t system plików, k tó r y łą c z y n a z w ę p lik u (k lu c z ) z m ie js c e m p r z e c h o
w y w a n ia je g o z a w a rto ś c i (w a rto ś c ią ).
Obszar Klucz Wartość
W o d tw a r z a c z u m u z y c z n y m p o d o b n y
s y s te m w ią ż e ty tu ły u tw o ró w (k lu c z e ) Książka N azw isk o N um er
z lo k a liz a c ja m i n a g r a ń (w a rto ś c ia m i). telefoniczna tele fo n u
D Internetowy system DNS. S y stem n a z w
Słownik Słow o D efin icja
d o m e n y (an g . domain name system —
D N S ) s ta n o w i p o d s ta w ę p rz y p o r z ą d Konto N u m e r k o n ta S tan k o n ta
k o w a n iu in fo rm a c ji w in te rn e c ie i łąc z y
Badania genomu Ko d o n A m in o k w a s
a d re s y U R L (k lu cze; n a p rz y k ła d www.
p r i n c e to n .e d u lu b w w w .w ik ip e d ia .p l) Dane D a n e i czas W y n ik i
z ro z u m ia łe d la lu d z i z a d re s a m i IP (w a r Kompilator N azw a L okalizacja
to ś c ia m i; n a p rz y k ła d 208.216.181.15 z m ie n n ej w p a m ię c i
lu b 207.142.131.206) z ro z u m ia ły m i d la
System wymiany T ytuł K o m p u te r
r u te ró w w sieci k o m p u te ro w e j. S y stem
plików u tw o ru
te n to „ k sią ż k a te le fo n ic z n a ” n o w e j g e
n e ra c ji. L u d z ie m o g ą u ż y w a ć ła tw y c h Internet W itry n a A d res IP
d o z a p a m ię ta n ia nazw , a k o m p u te ry
Typowe zastosowania słowników
— w w y d a jn y sp o s ó b p rz e tw a rz a ć liczby.
L iczb a w y sz u k iw a ń w ta b lic y sy m b o li
w y k o n y w a n a w ty m c e lu w ru te ra c h in te rn e to w y c h n a c a ły m św iecie je s t n ie z w y
k le d u ż a , d la te g o w y d a jn o ść je st, o czy w iście, is to tn a . D o in te r n e tu k a ż d e g o r o k u
p o d łą c z a n e są m ilio n y n o w y c h k o m p u te ró w i in n y c h u rz ą d z e ń , d la te g o ta b lic e
sy m b o li w r u te r a c h in te rn e to w y c h m u s z ą b y ć d y n a m ic z n e .
M im o r ó ż n o r o d n o ś c i lis ta ta to ty lk o re p r e z e n ta ty w n a p ró b k a , k tó r a m a d a ć p r z e d
s m a k w ie lo r a k ic h z a s to s o w a ń a b s tra k c y jn y c h ta b lic s y m b o li. K ie d y k o lw ie k o k re ś la sz
co ś z a p o m o c ą n azw y , k o rz y s ta s z z ta b lic y s y m b o li. S y ste m p lik ó w w k o m p u te r z e lu b
sieć W W W m o g ą ro b ić to z a C ie b ie , je d n a k g d z ie ś u ż y w a n a je s t ta k a ta b lic a .
W r a m a c h k o n k r e tn e g o p r z y k ła d u ro z w a ż m y k lie n ta ta b lic y sy m b o li, k tó re g o
m o ż n a u ż y ć d o w y s z u k iw a n ia in f o rm a c ji p rz e c h o w y w a n y c h w ta b e li w p lik u lu b n a
s tro n ie in te rn e to w e j w fo r m a c ie wartości rozdzielonych przecinkami (a n g . comma-
separated-value — ,csv). T e n p r o s ty f o r m a t p o z w a la z re a liz o w a ć z a d a n ie ( p r z y z n a
jem y , że m a ło a m b itn e ) p rz e c h o w y w a n ia d a n y c h ta b e la ry c z n y c h w fo r m ie c zy te ln e j
d la k a ż d e g o (i p r a w d o p o d o b n ie m o ż liw e j d o o d c z y ta n ia w p rz y s z ło ś c i) b e z k o n ie c z
n o ś c i s to s o w a n ia sp e c ja ln e j a p lik a c ji. D a n e m a ją p o s ta ć te k s to w ą , p o je d n y m w ie r
s z u n a lin ię , a w a rto ś c i są r o z d z ie lo n e p rz e c in k a m i. W w itr y n ie p o ś w ię c o n e j k sią ż c e
m o ż n a z n a le ź ć w ie le p lik ó w ,csv p o w ią z a n y c h z r ó ż n y m i o p is a n y m i z a s to s o w a n ia m i.
P rz y k ła d o w e p lik i to : amino.csy (o d w z o ro w a n ia k o d o n ó w n a a m in o k w a s y ), DJlA.csv
506 RO ZD ZIA Ł 3 ■ W yszukiw anie
% more amino.csv (c e n a o tw a rc ia , w o lu m e n i c e n a z a m k n ię c ia d la
TTT,Phe,F,Phenyl alanine in d e k s u D JIA z k a ż d e g o d n ia n o to w a ń ) , ip.csv
TTC,Phe,F,Phenyl alanine
TTA,Leu,L,Leucine
(w y b ó r w p is ó w z b a z y d a n y c h D N S ) i u p c.csv
TTG,Leu,L,Leucine (k o d k re s k o w e U P C p o w s z e c h n ie sto s o w a n e
TCT,Ser,S,Serine
d o id e n ty fik o w a n ia p r o d u k tó w ) . A rk u s z e k a l
TCC,Ser,S,Serine
k u la c y jn e i in n e p r o g r a m y d o p rz e tw a r z a n ia
GAA,Gly,G,Glutamic Acid
d a n y c h p o tr a fią w c z y ty w a ć o ra z z a p is y w a ć p li
GAG,Gly.G,Glutamic Acid
GGT,Gly,G,Glycine k i ,csv. W p rz y k ła d a c h p o k a z a n o , że m o ż n a te ż
GGC,Gly,G,Glyci ne n a p is a ć p r o g r a m Javy d o p r z e tw a r z a n ia ta k ic h
GGA,Gly,G,Glyci ne
GGG,Gly,G,Glycine d a n y c h w d o w o ln y sp o s ó b .
P r o g r a m LookupCSV ( n a n a s tę p n e j s tro n ie )
% more DJIA.csv
tw o rz y z b ió r p a r k lu c z - w a r to ś ć n a p o d s ta w ie
20-0ct-87,1738.74,608099968,1841.01 p lik u C S V p o d a n e g o w w ie rs z u p o le c e ń , a n a
19-0ct-87,2164.16,604300032,1738.74
s tę p n ie w y św ie tla w a rto ś c i o d p o w ia d a ją c e k lu
16-0ct-87,2355.09,338500000,2246.73
15-0ct-87,2412.70,263200000,2355.09 c z o m w c z y ta n y m ze s ta n d a rd o w e g o w ejścia.
A r g u m e n ta m i p o d a w a n y m i w w ie rs z u p o le c e ń
30-0ct-29,230.98,10730000,258.47
29-0ct-29,252.38,16410000,230.07 są n a z w a p lik u i d w ie lic z b y c a łk o w ite (je d n a
28-0ct-29,295.18,9210000,260.64 o k re ś la p o le u ż y w a n e ja k o k lu c z , a d r u g a —
25-0ct-29,299.47,5920000,301.22
p o le p e łn ią c e fu n k c ję w a rto ś c i).
P rz y k ła d te n m a ilu s tro w a ć p rz y d a tn o ś ć
% more ip.csv
i e la s ty c z n o ś ć a b s tra k c y jn e j ta b lic y s y m b o li.
www. e b a y . com, 6 6 .1 3 5 .1 9 2 . 8 7 Jak a w itr y n a m a a d re s IP128.112.136.35?www.
www.p r in c e t o n . e d u , 1 2 8 . 1 1 2 . 1 2 8 . 1 5
c s . pri nceton. edu. K tó ry a m in o k w a s o d p o w ia
w w w .cs. p r i n c e t o n . e d u , 1 2 8 . 1 1 2 .1 3 6 . 3 5
w w w .ha rva rd . e d u ,1 2 8 . 1 0 3 . 6 0 . 2 4 d a k o d o n o w i TCA? Seri ne. Jak i b y ł k u rs DJLA 29
www.y a le . e d u , 1 3 0 . 1 3 2 . 5 1 . 8
p a ź d z ie r n ik a 1929 ro k u ? 252.38. K tó ry p r o d u k t
www.cn n .c o m ,6 4 .2 3 6 .1 6 .2 0
w w w .google.com ,2 1 6 .2 3 9 .4 1 . 9 9 m a k o d U P C 0002100001085? Kraft Parmesan.
www. n y t im e s . com ,1 9 9 .2 3 9 .1 3 6 .2 0 0 Z a p o m o c ą p r o g r a m u LookupCSV i w ła śc iw y c h
w w w .a p p le .c o m ,1 7 .1 1 2 .1 5 2 .3 2
www.si a s h d o t . o r g , 6 6 . 3 5 . 2 5 0 . 1 5 1 p lik ó w ,csv m o ż n a ła tw o z n a le ź ć o d p o w ie d z i
www.e s p n .c o m ,1 9 9 .1 8 1 .1 3 5 .2 0 1 n a p y ta n ia te g o ro d z a ju .
w w w .w eather.com ,6 3 . 111. 6 6 .1 1
w w w .yahoo.com ,2 1 6 .1 0 9 .1 1 8 .6 5
W y d a jn o ś ć n ie j e st p r o b le m e m p r z y o b s łu d z e
z a p y ta ń in te ra k ty w n y c h (p o n ie w a ż k o m p u te r
p o tr a fi s p ra w d z ić m ilio n y w p is ó w w cz a sie p o
% more UPC.csv
tr z e b n y m n a w p is a n ie z a p y ta n ia ), d la te g o p rz y
0002058102040.,"1 1/4"” STANDARD STORM DOOR"
k o rz y s ta n iu z p r o g r a m u LookupCSV sz y b k ie i m
0002058102057.,"1 1/4"" STANDARD STORM DOOR"
0002058102125.,"DELUXE STORM DOOR UNIT" p le m e n ta c je k la s y ST są n ie o d c z u w a ln e . J e d n a k
0002082012728,"100/ per box","12 gauge shells" k ie d y p ro g r a m p rz e s z u k u je d a n e (a je s t ic h b a r
0002083110812,"Classical CO","'Bits and Pieces"’
002083142882,CD,"Garth Brooks - Ropin1 The Wind" d z o d u ż o ), w y d a jn o ś ć je s t w a ż n a . P rz y k ła d o w o ,
0002094000003,LB,"PATE PARISIEN" r u t e r in te r n e to w y m u s i c z a s e m p rz e s z u k iw a ć
0002098000009,LB,"PATE TRUFFLE COGNAC-M&H 8Z RW"
0002100001086,"16 02 “,"Kraft Parmesan"
m ilio n y a d re s ó w IP n a s e k u n d ę . W k sią ż c e
0002100002090,"15 pieces","Wrigley's Gum" p o k a z a n o ju ż p o tr z e b ę z a p e w n ie n ia w y so k ie j
0002100002434,"One pint","Trader Joe's milk"
w y d a jn o ś c i w p r o g r a m ie FrequencyCounter.
W ty m p o d r o z d z ia le p r z e d s ta w io n o k ilk a i n
Typowe pliki CSV n y c h p rz y k ła d ó w .
3.5 Zastosowania 507
Wyszukiwanie w słowniku
p u b li c c l a s s LookupCSV
p u b li c s t a t i c v o id m a i n ( S t r i n g [ ] a r g s )
{
In in = new l n ( a r g s [ 0 ] ) ;
i n t k e y F ie ld = I n t e g e r . p a r s e l n t ( a r g s [ l ] ) ;
i n t v a lF ie ld = I n t e g e r .p a r s e ln t ( a r g s [ 2 ] ) ;
w h ile ( i n .h a s N e x tL in e ( ) )
{
S tr in g li n e = in .r e a d L in e Q ;
S t r i n g [ ] to k e n s = 1 i n e . s p l i t ( " , " ) ;
S t r i n g key = t o k e n s [ k e y F i e l d ] ;
S tr in g val = to k e n s [ v a lF i e ld ] ;
s t.p u t(k e y , v a l) ;
}
w h ile ( I S t d l n .i s E m p t y O )
{
S t r i n g q u e ry = S t d l n . r e a d S t r i n g O ;
i f ( s t.c o n ta in s (q u e ry ))
S td O u t.p r in tln ( s t.g e t( q u e r y ) ) ;
}
W ć w ic z e n ia c h o p is a n o p o d o b n e , a le b a rd z ie j z a a w a n s o w a n e k lie n ty te s to w e d la p li
k ó w ,csv. P rz y k ła d o w o , m o ż n a u tw o rz y ć d y n a m ic z n y s ło w n ik , z e z w a la ją c n a m o d y f i
k a c ję (za p o m o c ą p o le c e ń ze s ta n d a rd o w e g o w e jśc ia ) w a rto ś c i p o w ią z a n e j z k lu c z e m .
M o ż n a te ż u m o ż liw ić w y s z u k iw a n ie z a k re s o w e lu b b u d o w a n ie w ie lu s ło w n ik ó w n a
p o d s ta w ie je d n e g o p lik u .
m u je a m in o k w a s i listę o d p o w ia d a ją c y c h m u t H / /
k o d o n ó w . In d e k s to ta b lic a sy m b o li, w k tó re j Klucz Wartości
w a rto ś c i. O to k ilk a in n y c h p rz y k ła d ó w :
* T ra n sa kcje h a n d lo w e . J e d n y m ze s p o s o b ó w ś le d z e n ia tr a n s a k c ji z d a n e g o d n ia
w firm ie p rz e c h o w u ją c e j k o n ta k lie n tó w je s t u tr z y m y w a n ie in d e k s u ty c h t r a n s
ak cji. K lu c z e m je s t n u m e r k o n ta , a w a rto ś c ią — lis ta w y s tą p ie ń n u m e r u n a li
ście tra n s a k c ji.
“ W y s z u k iw a n ie w sieci W W W . K ie d y w p isu je sz sło w o k lu c z o w e i o tr z y m u je s z
listę o b e jm u ją c y c h je w itr y n , k o rz y s ta s z z in d e k s u u tw o rz o n e g o p rz e z w y s z u
k iw a rk ę . Z k a ż d y m k lu c z e m (z a p y ta n ie m ) p o w ią z a n a je s t je d n a w a rto ś ć (z b ió r
s tr o n ) , c h o ć w p ra k ty c e je s t to b a rd z ie j s k o m p lik o w a n e , p o n ie w a ż c z ę sto p o d a je
się w ie le klu czy .
■ F ilm y i w y k o n a w c y . P lik m o vies. t x t z w itr y n y (jeg o f r a g m e n t z n a jd u je się n a d o le
n a s tę p n e j s tro n y ) p o c h o d z i z b a z y IM D B (an g . In te r n e t M o v ie D a ta b a se ). K a ż d y
w ie rs z to ty t u ł film u (k lu c z ), p o k tó r y m n a s tę p u je lis ta w y k o n a w c ó w (w a rto ś c i)
ro z d z ie lo n y c h u k o ś n ik a m i.
3.5 a Zastosowania 509
In d e k s m o ż n a ła tw o z b u d o w a ć , u m ie s z c z a ją c w a r to ś c i w ią z a n e z k a ż d y m k lu c z e m
w p o je d y n c z e j s t r u k tu r z e d a n y c h ( n a p r z y k ła d Queue), a n a s tę p n ie łą c z ą c k lu c z
z w a r to ś c ią w p o s ta c i s t r u k t u r y d a n y c h . R o z w in ię c ie p r o g r a m u LookupCSV w te n
s p o s ó b je s t ła tw e , je d n a k p o z o s ta w ia m y to ja k o ć w ic z e n ie (z o b a c z ć w i c z e n i e
3 . 5 . 1 2 ) i w z a m ia n o m a w ia m y p r o g r a m Lookuplndex ze s t r o n y 5 1 1 , w k tó r y m t a b
lic ę s y m b o li w y k o r z y s ta n o d o z b u d o w a n ia in d e k s u n a p o d s ta w ie p lik ó w w r o d z a ju
a m in o I .t x t i m o v ie s .tx t ( s e p a r a
to r e m n ie m u s i b y c t u in a c z e j Dziedzina Klucz Wartość
n iż w p lik a c h ,csv — p rz e c in e k ;
Badania'nad genomem A m in o k w a s L ista k o d o n ó w
znak m ożna o k re ś lić w w ie r
sz u p o le c e ń ) . Po z b u d o w a n iu Handel N u m e r k o n ta L ista tra n sa k c ji
in d e k s u p ro g ra m Lookuplndex
Wyszukiwanie K lucz L ista
p rz y jm u je z a p y ta n ia o k lu c z
w sieci W W W w y sz u k iw a n ia s tro n W W W
i w y ś w ie tla w a r to ś c i p o w ią z a
n e z k a ż d y m k lu c z e m . C o c ie Baza IMDB F ilm L ista w y k o n aw có w
k a w sz e , p ro g ram Lookuplndex Typowe zastosowania indeksów
tw o rz y te ż in d e k s o d w r o tn y ,
w k tó r y m w a r to ś c i i k lu c z e p e łn i ą o d w r o tn e f u n k c je . W p r z y k ła d z ie d o ty c z ą c y m
a m in o k w a s ó w p r o g r a m d a je w ię c te s a m e m o ż liw o ś c i, c o p r o g r a m Lookup (p o z w a
la z n a le ź ć a m in o k w a s p o w ią z a n y z d a n y m k o d o n e m ) . N a p o d s ta w ie lis ty film ó w
i w y k o n a w c ó w m o ż liw e je s t d o d a tk o w o z n a le z ie n ie film ó w p o w ią z a n y c h z d a n y m
a k to r e m . P o ś r e d n io d a n e z a w ie ra ją te in f o r m a c je , j e d n a k t r u d n o je s t je u z y s k a ć
b e z z a s to s o w a n ia ta b lic y s y m b o li. S ta r a n n ie p r z e a n a liz u j te n p r z y k ła d , p o n ie w a ż
p o m a g a d o b r z e z r o z u m ie ć n a tu r ę ta b lic s y m b o li.
m o v ie s.t x t
Separator,,/"
T i n Men ( 1 9 8 7 ) / D e B o y , D a v id / B iu m e n fe id , A l a n / . . . /
T i r e z s u r l e p i a n i s t e (1960 )/H e ym a n n , C la u d e / . . . r
T i t a n i c ( 1 9 9 7 ) / M a z in , S t a n / . . . D i c a p r i o , L e o n a r d o / .. .
T i t u s ( 1 9 9 9 ) / w e is s k o p f , H erm ann/R hys, M a tt h e w / ...
To Be o r N ot t o Be ( 1 9 4 2 ) / v e r e b e s , E rn o ( I ) / . ..
To Be o r N ot t o Be ( 1 9 8 3 ) / . . . / B r o o k s , Mel ( I ) / . . .
To C a tc h a T h i e f ( 1 9 5 5 ) / P a r i s , M a n u e l/ . . .
To D ie F o r ( 1 9 9 5 ) / S m it h , K u r t w o o d / . . ./K idm an, N i c o l e / . . .
Klucz Wartości
Mały fragment dużego pliku z indeksem (ponad 250 000 wierszy)
RO ZD ZIA Ł 3 W yszukiw anie
In d e lc s o d w r o t n y In d e k s o d w r o tn y z w y k le u ż y w a n y je s t w sy tu a c ji, w k tó re j w a rto ś c i
słu ż ą d o lo k a liz o w a n ia k lu czy . D o s tę p n y c h je s t d u ż o d a n y c h i c h c e m y u s ta lić , g d z ie
z n a jd u ją się p o tr z e b n e k lu c z e . T a k d z ia ła k o le jn y p ro to ty p o w y k lie n t, w k tó r y m w y
m ie s z a n e są w y w o ła n ia g e t () i p u t ( ) . T a k ż e tu k a ż d y k lu c z w ią z a n y je s t ze s t r u k tu r ą
SET z lo k a liz a c ja m i, w k tó r y c h z n a jd u je się d a n y k lu c z . N a tu r a i s p o s ó b w y k o rz y s ta
n ia lo k a liz a c ji z a le ż y o d p r o g r a m u . W k sią ż c e lo k a liz a c ją m o ż e b y ć n u m e r s tro n y ;
w p r o g r a m ie — n u m e r w ie rs z a ; w b a d a n ia c h n a d g e n o m e m — p o z y c ja w se k w e n c ji
g e n e ty c z n e j itd .
n B a z a IM D B . W o m ó w io n y m p rz y k ła d z ie d a n e w e jśc io w e to in d e k s łą c z ą c y
k a ż d y film z lis tą w y k o n a w c ó w . In d e k s o d w r o tn y w ią ż e k a ż d e g o a k to r a z listą
film ó w .
° In d e k s k sią żk i. K a ż d y p o d r ę c z n ik m a in d e k s , w k tó r y m m o ż n a z n a le ź ć p o ję
cie i n u m e r stro n y , g d z ie o n o w y stę p u je . C h o ć u tw o rz e n ie d o b re g o in d e k s u
w y m a g a o d a u to r a w y e lim in o w a n ia p o to c z n y c h i n ie is to tn y c h słów , sy s te m
p rz y g o to w y w a n ia in d e k s u z p e w n o ś c ią k o rz y s ta z ta b lic y s y m b o li i w s p o m a g a
c a ły p ro c e s . C ie k a w y m
s p e c ja ln y m p rz y p ad Dziedzina Klucz Wartość
k ie m je s t sk o ro w id z.
Baza IMDB A k to r Z b ió r film ó w
Jego p rz y g o to w a n ie
p o le g a n a p o w ią z a n iu Książka P ojęcie Z b ió r stro n
k a ż d e g o sło w a z te k s Kompilator Id e n ty fik a to r Z b ió r m iejsc uży cia
tu ze z b io r e m pozy
Przeszukiwanie S zu k an e Z b ió r p lik ó w
cji, n a k tó r y c h sło w o
plików p o jęcie
to w y s tę p u je (z o b a c z
ć w i c z e n i e 3 . 5 . 2 0 ). Badania P o d se k w en c ja Z b ió r lo k alizacji
D K o m p ila to r. W d u ż y c h nad genomem
p ro g r a m a c h , w k tó Typowe indeksy odwrotne
r y c h u ż y w a n a je s t d u ż a
lic z b a sy m b o li, w a r to w ie d z ie ć , g d z ie w y k o rz y s ta n o k a ż d ą n a z w ę . H is to ry c z n ie
d r u k o w a n e ta b lic e s y m b o li b y ły je d n y m z n a jw a ż n ie js z y c h n a r z ę d z i s to s o w a
n y c h p rz e z p r o g r a m is tó w d o ś le d z e n ia m ie js c u ż y c ia s y m b o li w p ro g r a m a c h .
W e w s p ó łc z e s n y c h s y s te m a c h ta b lic e s y m b o li są p o d s ta w ą n a r z ę d z i p r o g r a m i
s ty c z n y c h u ż y w a n y c h p rz e z p r o g r a m is tó w d o z a rz ą d z a n ia n a z w a m i.
n P r z e s z u k iw a n ie p lik ó w . W s p ó łc z e s n e s y s te m y o p e ra c y jn e u m o ż liw ia ją w p is a n ie
p o ję c ia i z n a le z ie n ie n a z w z a w ie ra ją c y c h je p lik ó w . K lu c z e m je s t p o ję c ie , a w a r
to ś c ią — z b ió r o b e jm u ją c y c h je p lik ó w .
■ B a d a n ia n a d g e n o m e m . W ty p o w y m (c h o ć m o ż e n a d m ie r n ie u p ro s z c z o n y m )
s c e n a r iu s z u z b a d a ń n a d g e n o m e m n a u k o w ie c c h c e u s ta lić p o z y c je d a n e j s e k
w e n c ji g e n e ty c z n e j w is tn ie ją c y m g e n o m ie lu b z b io rz e g e n o m ó w . Is tn ie n ie lu b
b lisk o ść p e w n y c h se k w e n c ji m o ż e m ie ć z n a c z e n ie n a u k o w e . P u n k te m w y jśc ia
d o ta l a c h b a d a ń je s t in d e k s p r z y p o m in a ją c y s k o ro w id z , a le z m o d y f ik o w a
n y z u w a g i n a to , że g e n o m y n ie są p o d z ie lo n e n a sło w a (z o b a c z ć w i c z e n i e
3-5-15)-
3.5 Zastosowania 511
public c la s s Lookuplndex
{
public s t a t i c void tnain(String[] args)
{
In in = new I n ( a r g s [ 0 ] ) ; // Baza danych dla indeksu.
S t r in g sp = a r g s [1]; // Separator.
while (in.hasNextLine())
{
S t r i n g [ ] a = in .re a d Lin e Q . s p l i t ( s p ) ;
S trin g key = a [ 0 ] ;
f o r (in t i = 1; i < a.length; i++)
{
S trin g val = a [ i ];
i f (is t. c o n ta in s (k e y ) ) st.put(key, new Queue<String>());
i f ( it s . c o n t a in s ( v a l ) ) t s . p u t ( v a l, new Queue<String>());
st.get(key).enqueue(val);
t s . g e t ( v a l ) .enqueue(key);
}
}
P r o g r a m Fi 1 e In d e x (n a n a s tę p n e j s tro n ie ) p rz y jm u je z w ie rs z a p o le c e ń n a z w y p lik ó w
i w y k o rz y s tu je ta b lic ę s y m b o li d o z b u d o w a n ia in d e k s u o d w r o tn e g o łą c z ą c e g o k a ż d e
sło w o z d o w o ln e g o p lik u ze s t r u k tu r ą SET z n a z w a m i p lik ó w , w k tó r y c h d a n e s ło
w o się z n a jd u je . N a s tę p n ie p r o g r a m p rz y jm u je ze s ta n d a rd o w e g o w y jśc ia z a p y ta n ia
o sło w a k lu c z o w e i w y św ie tla p o w ią z a n e lis ty p lik ó w . P o d o b n ie d z ia ła ją p o p u la r n e
n a rz ę d z ia d o p rz e s z u k iw a n ia sie c i W W W lu b w y s z u k iw a n ia in f o rm a c ji n a k o m p u t e
rz e — n a le ż y w p is a ć sło w o k lu c z o w e , a b y u z y sk a ć listę m ie js c , w k tó r y c h w y stę p u je .
T w ó rc y ta k ic h n a r z ę d z i z w y k le w z b o g a c a ją p ro c e s , z w ra c a ją c b a c z n ą u w a g ę n a:
■ fo r m ę z a p y ta n ia ;
■ z b ió r in d e k s o w a n y c h p lik ó w lu b s tro n ;
n k o le jn o ś ć w y m ie n ia n ia p lik ó w w o d p o w ie d z i.
Z p e w n o ś c ią c z ę sto w p ro w a d z a s z w w y s z u k iw a rc e (k tó ra in d e k s u je d u ż ą c zę ść s tr o n
z sie c i W W W ) z a p y ta n ia o b e jm u ją c e w ie le słó w k lu c z o w y c h . W y s z u k iw a rk a z w ra c a
o d p o w ie d z i w e d łu g ic h a d e k w a tn o ś c i lu b z n a c z e n ia (d la C ie b ie a lb o re k la m o d a w -
c ó w ). W ć w ic z e n ia c h o p is a n y c h w k o ń c o w e j c z ę śc i p o d r o z d z i a łu p r z e d s ta w io n o n ie
k tó r e d o d a tk o w e te c h n ik i. D a le j o m a w ia m y ró ż n e k w e stie a lg o r y tm ic z n e z w ią z a n e
z p rz e s z u k iw a n ie m sie c i W W W , je d n a k ta b lic a s y m b o li je s t is to tą te g o p ro c e s u .
Z a c h ę c a m y , o c z y w iśc ie , d o p o b r a n i a p r o g r a m u Filelndex (a ta k ż e Lookuplndex)
z w itr y n y p o ś w ię c o n e j k sią ż c e i w y k o rz y s ta n ia g o d o z in d e k s o w a n ia k ilk u p lik ó w
te k s to w y c h n a T w o im k o m p u te r z e lu b in te re s u ją c y c h C ię w itr y n . P o z w o li to je s z c z e
b a rd z ie j d o c e n ić p r z y d a tn o ś ć ta b lic s y m b o li. Z o b a c z y s z te ż , że m o ż n a sz y b k o b u
d o w a ć d u ż e in d e k s y d la w ie lk ic h p lik ó w , p o n ie w a ż k a ż d a o p e ra c ja d o d a n ia i k a ż d e
ż ą d a n ie p o b r a n ia je s t w y k o n y w a n e b ły s k a w ic z n ie . Z a p e w n ie n ie n a ty c h m ia s to w e j r e
a k c ji d la d u ż y c h , d y n a m ic z n y c h ta b lic je s t je d n y m z k la s y c z n y c h o s ią g n ię ć w b a d a
n ia c h n a d a lg o ry tm a m i.
3.5 Zastosowania 513
Indeksowanie plików
import j a v a . i o . F i l e ;
public c la s s Filelndex
{
public s t a t i c void m ain(String[] args)
public c la s s SparseVector
{
private HashST<Integer, Double> st;
public SparseVector()
( st = new HashST<Integer, Double>(); }
public in t s iz e ()
( return s t . s i z e Q ; }
public double g e t ( in t i)
{
i f ( I s t . c o n t a i n s ( i ) ) return 0.0;
else return s t . g e t (i );
}
0 1 2 3 4
0 1 2 3 4
0.0 0.0 .3 6 .3 6 .1 8
0 1 2 3 4
0 1 2 3 4
0 1 2 3 4
a [4][2]
D la m a ły c h m a c ie rz y lu b m a c ie rz y , k tó r e n ie są rz a d k ie , k o s z ty p rz e c h o w y w a n ia
ta b lic y m o g ą b y ć is to tn e . W a rto je d n a k p o ś w ię c ić ch w ilę n a z ro z u m ie n ie sk u tk ó w
s to s o w a n ia ta b lic s y m b o li d la d u ż y c h m a c ie rz y r z a d k ic h . W y o b ra ź so b ie d u ż y p r o b
le m (ta k i ja k te n , p r z e d k tó r y m s ta n ę li B rin i P a g e ), w k tó r y m N w y n o s i 10 lu b 100
m ilia rd ó w , a ś r e d n ia lic z b a n ie z e ro w y c h e le m e n tó w n a w ie rs z je s t m n ie js z a n iż 1 0 .
W ta k ic h a p lik a c ja c h u ży c ie ta b lic sy m b o li p r z y s p ie s z a m n o ż e n ie m a c ie r z y p r z e z w e k
to r m ilia rd razy, a n a w e t b a rd zie j. P ro s ta n a tu r a a p lik a c ji n ie p o w in n a p rz e s ła n ia ć jej
z n a c z e n ia . P ro g r a m iś c i, k tó r z y n ie w y k o rz y s tu ją m o ż liw o ś c i z a o s z c z ę d z e n ia c z a su
i p a m ię c i w te n s p o s ó b , p o w a ż n ie o g ra n ic z a ją m o ż liw o ś ć ro z w ią z a n ia p ra k ty c z n y c h
p ro b le m ó w ; n a to m ia s t p r o g r a m iś c i, k tó r z y d e c y d u ją się n a m i lia r d k r o tn e p rz y s p ie
s z e n ie p r o g r a m u , je ś li je s t to w y k o n a ln e , p r a w d o p o d o b n ie z d o ła ją p o r a d z ić so b ie
z p r o b le m a m i n ie m o ż liw y m i d o ro z w ią z a n ia w in n y sp o s ó b .
B u d o w a n ie m a c ie rz y n a p o tr z e b y G o o g le a to z a d a n ie d la a p lik a c ji d o p r z e tw a
r z a n ia g ra fó w (i k lie n ta ta b lic y s y m b o li!), d z ia ła
jące j n a d u ż e j m a c ie rz y rz a d k ie j. Jeśli m a c ie rz je s t
SparseVector[] a;
d o s tę p n a , o b lic z a n ie w a rto ś c i P a g e R a n k p o le g a
a = new Spa rseV ector[N ];
n a m n o ż e n iu m a c ie rz y p rz e z w e k to r, z a s tę p o w a double[] x = new double [N];
n iu ź ró d ło w e g o w e k to ra w y n ik o w y m i p o w ta r z a doubl e [] b = new doubl e [N ];
n iu te g o p ro c e s u d o c z a s u u z y s k a n ia s p ó jn o ś c i
// Inicjo wanie a[] i x [ ] .
( g w a ra n tu ją to p o d s ta w o w e tw ie r d z e n ia z te o r ii
p ra w d o p o d o b ie ń s tw a ). D la te g o z a s to s o w a n ie k la f o r ( i n t i = 0; i < N; i++)
b[i] = a [i] ,dot(x);
sy w ro d z a ju S p a rs e V e c to r m o ż e p ro w a d z ić d o
o s z c z ę d n o ś c i w z a k re s ie c z a s u i p a m ię c i n a p o z io Mnożenie macierzy rzadkiej
m ie 1 0 , 1 0 0 , a n a w e t w ię ce j m ilia r d ó w razy. przez wektor
3.5 □ Zastosowania 517
P o d o b n e o s z c z ę d n o ś c i m o ż n a u z y sk a ć w w ie lu o b lic z e n ia c h n a u k o w y c h , d la te g o
rz a d k ie w e k to ry i m a c ie rz e są p o w s z e c h n ie s to s o w a n e i z w y k le w łą c z a n e w w y s p e
c ja liz o w a n e sy s te m y d o o b lic z e ń n a u k o w y c h . P rz y k o r z y s ta n iu z d u ż y c h w e k to ró w
i m a c ie rz y w a rto p r z e p r o w a d z ić p ro s te te s ty w y d a jn o ś c i, a b y n ie p o m in ą ć o k a z ji d o
u z y s k a n ia p rz e d s ta w io n y c h z y sk ó w w w y d a jn o ś c i. P o n a d to w ię k s z o ś ć ję z y k ó w p r o
g ra m o w a n ia u d o s tę p n ia w b u d o w a n e m e c h a n iz m y p r z e tw a r z a n ia ta b lic ty p ó w p r o
sty ch , d la te g o z a s to s o w a n ie ta b lic d la w e k to ró w , k tó r e n ie są rz a d k ie (ta k ja k w p r z y
k ła d z ie ), p o z w a la d o d a tk o w o p rz y s p ie sz y ć p ra c ę . W o m a w ia n y c h z a s to s o w a n ia c h
z p e w n o ś c ią w a r to d o b rz e z ro z u m ie ć k o s z ty i p o d ją ć o d p o w ie d n ie d e c y z je w z a k re s ie
im p le m e n ta c ji.
| PY T A N IA I O D P O W IE D Z I
O . N ie. O b ie k t SET m o ż e b y ć p u s ty (n ie z a w ie ra ć o b ie k tó w ), je d n a k n ie m o ż e m ie ć
w a rto ś c i nul 1. Z m ie n n a ty p u SET ( ta k ja k k a ż d e g o ty p u d a n y c h w Javie) m o ż e m ie ć
w a rto ś ć nuli, o z n a c z a to je d n a k , że n ie w s k a z u je o b ie k tu SET. E fe k te m u ż y c ia i n
s tr u k c ji new d o u tw o rz e n ia o b ie k tu SET je s t z aw sz e o b ie k t r ó ż n y o d nul 1.
P. M a m d a n e w a rk u s z u k a lk u la c y jn y m . C z y m o g ę u tw o rz y ć p r o g r a m p o d o b n y d o
LookupCSV d o ic h p rz e s z u k iw a n ia ?
O . P r o g r a m d o z a rz ą d z a n ia a rk u s z a m i k a lk u la c y jn y m i p r a w d o p o d o b n ie m a o p c ję
e k s p o r to w a n ia d a n y c h d o p lik u .csv, d la te g o m o ż e s z b e z p o ś r e d n io w y k o rz y s ta ć p r o
g r a m LookupCSV.
P. D o czeg o m o g ę p o tr z e b o w a ć p r o g r a m u F il e ln d e x ? C z y s y s te m o p e ra c y jn y n ie
ro z w ią z u je te g o s a m e g o p ro b le m u ?
O . Jeśli k o rz y s ta s z z s y s te m u o p e ra c y jn e g o , k tó r y z a s p o k a ja T w o je p o trz e b y , u ż y w aj
go n a d a l. P o d o b n ie j a k w ie le in n y c h p ro g ra m ó w , t a k i Fi 1 e ln d e x m a p o k a z y w a ć p o d
sta w o w e m e c h a n iz m y ró ż n y c h a p lik a c ji o ra z su g e ro w a ć m o ż liw o śc i.
P. D la c z e g o d o k la s y S p a rs e V e c to r n ie d o d a n o m e to d y d o t ( ) , k tó r a p rz y jm u je ja k o
a r g u m e n t o b ie k t ty p u S p a rs e V e c to r i z w ra c a o b ie k t te g o sa m e g o ty p u ?
O . To ta k ż e je s t d o b r y p o m y s ł, a je d n o c z e ś n ie c ie k a w e ć w ic z e n ie p ro g r a m is ty c z n e ,
w y m a g a ją c e n ie c o b a rd z ie j s k o m p lik o w a n e g o k o d u n iż z a p re z e n to w a n e ro z w ią z a n ie
(z o b a c z ć w i c z e n i e 3 . 5 . 1 6 ). N a p o tr z e b y o g ó ln e g o p r z e tw a r z a n ia m a c ie rz y w a r to d o
d a ć ta k ż e ty p S p a rs e M a tri x.
3.5 □ Zastosowania 519
|| ć w ic z e n ia
3 .5 .2 . O p ra c u j im p le m e n ta c ję S e q u e n ti a l SearchSE T d la ty p u SET. Z a c z n ij o d k o d u
k la sy S e q u e n ti a l S earch S T i u s u ń c a ły k o d z w ią z a n y z w a rto ś c ia m i.
3 .5 .5 . O p ra c u j k la s y ST i n t i ST doubl e d o p rz e c h o w y w a n ia u p o rz ą d k o w a n y c h ta b lic
sy m b o li, k tó r y c h k lu c z e są ty p u p ro s te g o i n t i d o u b le . P rz e k s z ta łć ty p y g e n e ry c z n e
n a ty p y p r o s te w k o d z ie k la s y RedBl ackBST. P rz e te s tu j ro z w ią z a n ie , u ż y w a ją c ja k o
k lie n ta w e rsji k la s y S p a rs e V e c to r.
3 .5 .7 . O p ra c u j M asy S E T in t i SE Tdouble d o p rz e c h o w y w a n ia z b io r ó w ld u c z y ty p u
p ro s te g o i n t i doubl e. U s u ń k o d z w ią z a n y z w a r to ś c ia m i z ro z w ią z a n ia ć w i c z e n i a
3-5-5-
3 .5 .1 4 . O p ra c u j i p rz e te s tu j m e to d ę s ta ty c z n ą i n v e r t ( ) , k tó r a ja k o a r g u m e n t p r z y j
m u je o b ie k t S T < S tri n g , B a g < S tri n g » i ja k o z w ra c a n ą w a rto ś ć g e n e ru je o d w r o tn o ś ć
d a n e j ta b lic y s y m b o li (ta b lic ę s y m b o li te g o s a m e g o ty p u ).
3 .5 .1 5 . N a p isz p ro g r a m , k tó r y p rz y jm u je ła ń c u c h z n a k ó w ze s ta n d a rd o w e g o w e jśc ia
i lic z b ę c a łk o w itą k ja k o a r g u m e n t w ie rs z a p o le c e ń , a n a s tę p n ie u m ie s z c z a w s t a n d a r
d o w y m w y jśc iu p o s o r to w a n ą listę k -g r a m ó w z n a le z io n y c h w ła ń c u c h u z n a k ó w , p rz y
cz y m p o k a ż d y m /c-g ra m ie n a le ż y p o d a ć je g o in d e k s w ła ń c u c h u .
3 .5 .1 6 . D o d a j d o k la s y S p a rs e V e c to r m e to d ę su m (), k tó r a p rz y jm u je ja k o a r g u m e n t
o b ie k t S p a rs e V e c to r i z w ra c a o b ie k t te g o ty p u , k tó r y je s t o b lic z o n ą w y ra z p o w y r a
zie s u m ą d a n e g o w e k to ra i w e k to ra p o d a n e g o ja k o a rg u m e n t. Uwaga: p o tr z e b n a je s t
m e to d a d el e t e ( ) (i sz c z e g ó ln a u w a g a n a p re c y z ję ) p r z y o b s łu d z e sy tu a c ji, w k tó re j
w a rto ś ć sta je się z e re m .
3.5 a Zastosowania 521
PROBLEMY DO ROZWIĄZANIA
3 .5 .1 7 . Z b io r y m a te m a ty c z n e . C e le m je s t o p ra c o w a n ie im p le m e n ta c ji in te rfe js u A P I
k la s y MathSET p rz e z n a c z o n e j d o p r z e tw a r z a n ia (z m ie n n y c h ) z b io r ó w m a te m a ty c z
n y ch .
public c l a s s MathSET<Key>
boolean conta ins(K ey key) Czy klucz key znajduje się w zbiorze?
M e to d a ta m a z w ra c a ć w szy s tk ie w a r to ś c i p o w ią z a n e z d a n y m k lu c z e m . U ż y w a ją c
ja k o p u n k tu w y jśc ia k o d u k la s S e p a r a te C h a in i ngST i B in a ry S e a rc h S T , o p ra c u j i m
p le m e n ta c je Id as S e p a ra te C h a in in g M u ltiS T i B in a ry S e a rc h M u ltiS T d la o m a w ia n y c h
in terfe jsó w .
3 .5 .2 0 . S k o ro w id z . N a p is z k lie n ta C o n c o rd an ce k la s y ST, k tó r y u m ie s z c z a w s t a n d a r
d o w y m w y jśc iu sk o ro w id z ła ń c u c h ó w z n a k ó w ze s ta n d a rd o w e g o s t r u m ie n ia w e jśc ia
(z o b a c z s tr o n ę 51 0 ).
3 .5 .2 1 . S k o r o w id z o d w ro tn y . N a p is z p r o g r a m In v e r te d C o n c o rd a n c e , k tó r y p r z y j
m u je sk o ro w id z ze s ta n d a rd o w e g o w e jśc ia i w y św ie tla p ie r w o tn y ła ń c u c h z n a k ó w
w s ta n d a r d o w y m s t r u m i e n iu w y jścia . Uwaga: o b lic z e n ia z w ią z a n e są ze s ły n n ą h is to
rią d o ty c z ą c ą z w o jó w z n a d M o r z a M a rtw e g o . Z e s p ó ł, k tó r y o d k r y ł rę k o p isy , p o s t a
n o w ił u ta jn ić ic h tr e ś ć i u d o s tę p n ił ty lk o sk o ro w id z . P o p e w n y m c z a sie in n i b a d a c z e
o d k ry li, j a k o d w ró c ić sk o ro w id z , i o s ta te c z n ie u p u b lic z n io n o c a ły te k s t.
3 .5 .2 4 . P r z e s z u k iw a n ie ro z łą c zn y c h p r z e d z ia łó w . Z a łó ż m y , że is tn ie je lis ta r o z łą c z
n y c h p r z e d z ia łó w e le m e n tó w . N a p is z fu n k c ję , k tó r a p rz y jm u je ja k o a r g u m e n t e le
m e n t i o k re ś la , w k tó r y m p r z e d z ia le się o n z n a jd u je (jeśli w o g ó le n a le ż y d o k tó re g o ś
z n ic h ) . P rz y k ła d o w o , je ś li e le m e n ty to lic z b y c a łk o w ite , a p rz e d z ia ły to 1643-2033,
5 5 3 2-7643, 8 9 9 9 -1 0 3 3 2 i 566 6 6 5 3 -5 6 6 9 3 2 1 , lic z b a 9122 z n a jd u je się w tr z e c im p r z e
d z ia le , a 8122 n ie n a le ż y d o ż a d n e g o z n ic h .
3 .5 .2 5 . P la n z a ję ć d la w y k ła d o w c ó w . W s e k r e ta ria c ie z n a n e g o p ó łn o c n o - w s c h o d
n ie g o u n iw e rs y te tu n ie d a w n o o p ra c o w a n o p la n , w e d le k tó r e g o w y k ła d o w c a m ia ł
w ty m s a m y m c z a sie p ro w a d z ić d w a ró ż n e w y k ła d y . O p is z m e to d ę w y k ry w a n ia t a
k ic h k o n flik tó w , a b y p o m ó c w u n ik n ię c iu p rz y s z ły c h p o m y łe k . D la u p ro s z c z e n ia z a
łó żm y , że w sz y stk ie z a ję c ia tr w a ją p o 50 m i n u t i z a c z y n a ją się o 9:00, 10:00, 11:00,
13:00, 14:00 lu b 15:00.
3 . 5 .2 6 P a m ię ć p o d r ę c z n a L R U . U tw ó rz s t r u k tu r ę d a n y c h u m o ż liw ia ją c ą d o s tę p
d o e le m e n tó w i ic h u s u w a n ie . O p e r a c ja d o s tę p u p o w o d u je w s ta w ie n ie e le m e n tu d o
s t r u k tu r y d a n y c h , je ś li e le m e n t je s z c z e się w n ie j n ie z n a jd u je . O p e r a c ja u s u w a n ia
k a s u je i z w ra c a n a jd łu ż e j n ie u ż y w a n y e le m e n t. W s k a zó w k a : p rz e c h o w u j e le m e n ty
3.5 a Zastosowania 523
w k o le jn o ś c i d o s tę p u d o n ic h n a liśc ie p o d w ó jn ie p o w ią z a n e j. U trz y m u j te ż w s k a ź n i
k i d o p ie rw s z e g o i o s ta tn ie g o w ę z ła . W y k o rz y s ta j ta b lic ę sy m b o li, w k tó re j k lu c z e to
e le m e n ty , a w a r to ś c i to m ie js c a n a liśc ie p o w ią z a n e j. P rz y d o s tę p ie d o e le m e n tu u s u ń
go z lis ty p o w ią z a n e j i w s ta w n a p o c z ą te k . P rz y u s u w a n iu e le m e n tu u s u ń g o z k o ń c a
i z ta b lic y s y m b o li.
void add( i n t i , Item item) Dodaje i tem jako i -ty element listy
3.5.28. U n iQ u eu e. U tw ó rz ty p d a n y c h , k tó r y je s t k o le jk ą , p r z y c z y m e le m e n t m o ż n a
w sta w ić d o n ie j ty lk o raz . U żyj ta b lic y s y m b o li d o ś le d z e n ia w s z y s tk ic h w s ta w io n y c h
w p rz e s z ło ś c i e le m e n tó w , t a k a b y m o ż n a b y ło ig n o r o w a ć ż ą d a n ia p o n o w n e g o ic h d o
d a n ia .
524 R O ZD ZIA Ł 3 ■ W yszukiwanie
; eksperym en ty
3 .5 .3 0 . P o w tó rze n ia (p o n o w n ie ). W y k o n a j je s z c z e ra z ć w i c z e n i e 2 . 5 . 3 1 , u ż y w a ją c
filtra Dedup p r z e d s ta w io n e g o n a s tr o n ie 502. P o ró w n a j c z a sy w y k o n a n ia o b u r o z w ią
zań . N a s tę p n ie z a s to s u j filtr Dedup d o p r z e p r o w a d z e n ia e k s p e r y m e n tó w d la N = 107,
10 8 i 109. P o w tó rz e k s p e r y m e n ty d la lo s o w y c h w a r to ś c i ty p u 1 ong i o m ó w w y n ik i.
3 .5 .3 4 . W e k to r y rz a d k ie . P rz e p r o w a d ź e k s p e r y m e n ty w c e lu p o r ó w n a n ia w y d a jn o
ści m n o ż e n ia m a c ie rz y p rz e z w e k to r za p o m o c ą k la s y S p a r s e V e c to r i s ta n d a rd o w e j
im p le m e n ta c ji o p a rte j n a ta b lic a c h .
3 .5 .3 5 . T y p y p ro ste . O c e ń p r z y d a tn o ś ć z a s to s o w a n ia ty p ó w p r o s ty c h z a m ia s t w a r to
ści ty p u I n t e g e r i D ouble w k la s a c h Li n e a r P r o b i ngHashST o r a z RedBl ackBST. Ile p a
m ię c i i c z a s u m o ż n a z a o sz c z ę d z ić d la d u ż e j lic z b y w y s z u k iw a ń w d u ż y c h ta b lic a c h ?
ROZDZIAŁ 4
DTL
n o r o d n y c h a p lik a c ja c h o b lic z e n io w y c h . R elacje w y z n a c z a n e p rz e z te p o łą c z e -
n ia b e z p o ś r e d n io z w ią z a n e są z n a tu r a ln y m i p y ta n ia m i: C z y istn ie je sp o s ó b n a
do jście z je d n e g o e le m e n tu d o in n e g o za p o m o c ą p o łą c z e ń ? Ile in n y c h e le m e n tó w je s t
p o łą c z o n y c h z d a n y m ? Jak i je s t n a jk ró ts z y c ią g p o łą c z e ń m ię d z y d a n y m e le m e n te m
a in n y m ?
D o m o d e lo w a n ia ta k ic h sy tu a c ji s łu ż ą a b s tra k c y jn e o b ie k ty m a te m a ty c z n e n a z y
w a n e g ra fa m i. W ty m ro z d z ia le sz c z e g ó ło w o o m a w ia m y p o d s ta w o w e c e c h y g rafó w ,
co d a je p o d s ta w y d o b a d a n ia ró ż n o r o d n y c h a lg o r y tm ó w p rz y d a tn y c h d o o d p o w ia d a
n ia n a p y ta n ia p o d o b n e d o p o s ta w io n y c h w c z e śn ie j. A lg o ry tm y te są p u n k te m w y j
ścia d o z m ie r z e n ia się z r ó ż n o r o d n y m i p ro b le m a m i. B ez d o b r y c h te c h n ik a lg o r y t
m ic z n y c h ro z w ią z a ń ty c h p ro b le m ó w n ie m o ż n a so b ie n a w e t w y o b ra z ić .
T e o ria g rafó w , je d n a z g łó w n y c h g a łę z i m a te m a ty k i, je s t in te n s y w n ie b a d a n a o d
s e te k lat. O d k r y to w ie le is to tn y c h i p rz y d a tn y c h c e c h a lg o ry tm ó w , o p ra c o w a n o lic z n e
w a ż n e a lg o ry tm y , a p o n a d to n a d a l b a d a n y c h je s t w ie le is to tn y c h p ro b le m ó w . W ty m
ro z d z ia le p rz e d s ta w ia m y r ó ż n o r o d n e p o d s ta w o w e a lg o r y tm y d la g rafó w , w a ż n e
w ro z m a ity c h z a s to s o w a n ia c h .
P o d o b n ie ja k w ie le in n y c h o m a w ia n y c h o b sz a ró w , ta k i a lg o r y tm ic z n e b a d a n ia
g ra fó w są s to s u n k o w o m ł o d ą d z ie d z in ą . C h o ć n ie k tó re p o d s ta w o w e a lg o r y tm y są
z n a n e o d stu le c i, w ię k s z o ś ć c ie k a w y c h a lg o r y tm ó w o d k r y to w c ią g u k ilk u o s ta tn ic h
d z ie s ię c io le c i d z ię k i p o ja w ie n iu się te c h n ik a lg o ry tm ic z n y c h , k tó r e b a d a m y . N a w e t
n a jp r o s ts z e a lg o r y tm y d la g ra fó w p ro w a d z ą d o p rz y d a tn y c h p r o g r a m ó w k o m p u t e
ro w y c h , a s k o m p lik o w a n e ro z w ią z a n ia , k tó r y m się p rz y jrz y m y , n a le ż ą d o je d n y c h
z n a jb a rd z ie j e le g a n c k ic h i c ie k a w y c h ze w s z y s tk ic h z n a n y c h a lg o ry tm ó w .
W c e lu p o k a z a n ia r ó ż n o r o d n o ś c i z a s to s o w a ń p r z e tw a r z a n ia g ra fó w p rz e g lą d a lg o
r y tm ó w z tej b o g a te j d z ie d z in y z a c z y n a m y o d k ilk u p rz y k ła d ó w .
527
528 R O ZD ZIA Ł 4 a Grafy
M apy O s o b a p la n u ją c a w y c ie c z k ę m o ż e p o tr z e b o w a ć o d p o w ie d z i n a p y ta n ia w r o
d z aju : „Jak a je s t n a jk r ó ts z a tr a s a z W ro c ła w ia d o G d a ń s k a ? ”. D o ś w ia d c z o n y p o d r ó ż
n ik , k tó r y z e tk n ą ł się z u tr u d n ie n i a m i n a n a jk ró ts z e j d ro d z e , m o ż e z a d a ć p y ta n ie :
„Ja k m o ż n a n a js z y b c ie j d o s ta ć się z W ro c ła w ia d o G d a ń s k a ? ”. A b y o d p o w ie d z ie ć n a
te p y ta n ia , tr z e b a p rz e tw o r z y ć in f o rm a c je n a te m a t p o łą c z e ń (d ró g ) m ię d z y e le m e n
ta m i (s k rz y ż o w a n ia m i).
O bwody O b w ó d e le k try c z n y o b e jm u je p o łą c z o n e ze s o b ą u r z ą d z e n ia w ro d z a ju
tr a n z y s to ró w , o p o r n ik ó w i k o n d e n s a to ró w . S to s u je m y k o m p u t e r y d o k o n tr o lo w a n ia
m a s z y n w y tw a rz a ją c y c h o b w o d y i d o s p ra w d z a n ia , cz y o b w o d y s p e łn ia ją sw o je z a d a
n ia . P o tr z e b n e są o d p o w ie d z i n a p ro s te p y ta n ia w ro d z a ju : „C zy w y s tę p u je s p ię c ie ? ”,
a ta k ż e n a p y ta n ia s k o m p lik o w a n e , n a p rz y k ła d : „C zy m o ż n a u m ie ś c ić te n o b w ó d n a
c h ip ie b e z k rz y ż o w a n ia k a b li? ”. O d p o w ie d ź n a p ie r w s z e p y ta n ie z a le ż y ty lk o o d c e c h
p o łą c z e ń (k a b li), n a to m ia s t d o u d z ie le n ia o d p o w ie d z i n a d r u g ie p y ta n ie p o tr z e b n e są
sz c z e g ó ło w e in f o rm a c je o k a b la c h , p o łą c z o n y c h p rz e z n ie u r z ą d z e n ia c h i fiz y cz n y c h
o g ra n ic z e n ia c h c h ip a .
H arm onogram y P ro c e s p r o d u k c ji w y m a g a w y k o n a n ia w ie lu z a d a ń . O b o w ią z u ją
p r z y ty m o g ra n ic z e n ia , o k re ś la ją c e , że p e w n y c h z a d a ń n ie m o ż n a ro z p o c z ą ć p r z e d
z a k o ń c z e n ie m in n y c h . Jak m o ż n a u s z e re g o w a ć z a d a n ia ta k , a b y u w z g lę d n ić o g r a n i
c z e n ia , a p r z y ty m z a k o ń c z y ć c a ły p ro c e s w j a k n a jk r ó ts z y m czasie?
Handel S p rz e d a w c y i in s ty tu c je fin a n s o w e ś le d z ą z le c e n ia k u p n a i s p r z e d a ż y n a r y n
k u . P o łą c z e n ie re p r e z e n tu je tu tr a n s f e r g o tó w k i i to w a ró w m ię d z y in s ty tu c ją a k lie n
te m . W ie d z a o n a tu r z e s t r u k tu r y p o łą c z e n ia m o ż e w z b o g a c ić s p o s ó b r o z u m ie n ia
ry n k u .
O p ro g ra m o w a n ie K o m p ila to r tw o rz y grafy , a b y re p r e z e n to w a ć z w ią z k i m ię d z y
m o d u ła m i w d u ż y c h s y s te m a c h o p r o g r a m o w a n ia . E le m e n ta m i są r ó ż n e k la s y lu b
m o d u ły w c h o d z ą c e w s k ła d sy s te m u . P o łą c z e n ia d o ty c z ą a lb o m o ż liw o ś c i w y w o ła n ia
p rz e z m e to d ę z je d n e j k la s y in n e j m e to d y (a n a liz a sta ty c z n a ), a lb o s a m y c h w y w o ła ń
w tr a k c ie d z ia ła n ia s y s te m u (a n a liz a d y n a m ic z n a ). T rz e b a p rz e a n a liz o w a ć g ra f, ab y
u sta lić , ja k w n a jw y d a jn ie js z y s p o s ó b p rz y d z ie lić z a so b y p ro g r a m o w i.
S ie c i s p o łe c z n o ś c io w e P rz y k o r z y s ta n iu z sie c i s p o łe c z n o ś c io w e j tw o rz y s z b e z p o
ś r e d n ie p o łą c z e n ia ze z n a jo m y m i. E le m e n to m o d p o w ia d a ją o so b y , a p o łą c z e n ia p r o
w a d z ą d o z n a jo m y c h lu b fan ó w . O k re ś la n ie c e c h ta k ic h siec i je s t je d n y m z o b sz a ró w ,
g d zie w s p ó łc z e ś n ie w y k o rz y s tu je się p rz e tw a r z a n ie grafó w . D z ie d z in a ta je s t w a ż n a
n ie ty lk o d la f irm z a rz ą d z a ją c y c h s ie c ia m i s p o łe c z n o ś c io w y m i, a le te ż w p o lity c e ,
d y p lo m a c ji, ro z ry w c e , e d u k a c ji, m a r k e tin g u i w ie lu in n y c h o b s z a ra c h .
p r z y k ł a d y t e i l u s t r u j ą , z a k r e s z a s t o s o w a ń , w k tó r y c h g ra f y są o d p o w ie d n ią
a b s tra k c ją , a ta k ż e z a k re s p ro b le m ó w o b lic z e n io w y c h w y s tę p u ją c y c h w c z a sie k o r z y
s ta n ia z grafó w . P r z e b a d a n o ty s ią c e ta k ic h p ro b le m ó w . W ie le z n ic h m o ż n a ro z w ią z a ć
w k o n te k ś c ie je d n e g o z k ilk u p o d s ta -
w o w y c h m o d e li grafó w . N a jw a ż n ie jsz e Zastosowanie Element Połączenie
m o d e le p r z e b a d a m y w ty m ro z d z ia le .
Mapa S k rzy żo w an ie D ro g a
W p ra k ty c z n y c h z a s to s o w a n ia c h ilo ść
d a n y c h c z ę sto je s t b a r d z o d u ż a , d la te Zawartość sieci W W W S tro n a O d n o ś n ik
go o d w y d a jn y c h a lg o r y tm ó w zależy,
Obwód U rz ą d z e n ie K abel
czy p r o b le m d a się ro z w ią z a ć .
W ra m a c h p rz e g lą d u p r z e d s ta Harmonogram zadań Z a d a n ie O g ra n ic z e n ie
w ia m y c z te ry n a jw a ż n ie js z e ro d z a je Handel T ra n sak cja
K lien t
m o d e li g ra fó w : g r a fy n ie sk iero w a n e
Dopasowywanie S tu d e n t P o d a n ie
(z p r o s ty m i p o łą c z e n ia m i), g ra fy sk ie
ro w a n e (w k tó r y c h k ie r u n e k k a ż d e Sieci komputerowe Punkt P o łącze n ie
go p o łą c z e n ia m a z n a c z e n ie ), g ra fy
Oprogramowanie M e to d a W y w o łan ie
w a żo n e (g d z ie k a ż d e p o łą c z e n ie m a
o k re ś lo n ą w ag ę) i w a żo n e g r a fy sk ie Sieci społecznościowe O so b a Z n a jo m o ść
ro w a n e (g d z ie k a ż d e p o łą c z e n ie m a
Typowe zastosowania grafów
i k ie r u n e k , i w ag ę).
4.1. G R A F Y N IE S K IE R O W A N E
N a z w y w ie rz c h o łk ó w n ie m a ją z n a c z e n ia , p o tr z e b n y je s t
je d n a k s p o s ó b n a ic h w s k a z y w a n ie . Z g o d n ie z k o n w e n
c ją d la w ie rz c h o łk ó w g ra f u o V w ie rz c h o łk a c h u ż y w a m y
n a z w o d 0 d o V - 1. G łó w n y m p o w o d e m z a s to s o w a n ia
te g o s y s te m u je s t ła tw o ś ć p is a n ia k o d u , k tó r y w w y d a jn y
s p o s ó b u z y sk u je d o s tę p d o in f o r m a c ji o d p o w ia d a ją c y c h
k a ż d e m u w ie rz c h o łk o w i (w y sta rc z y p o d a ć in d e k s y ta b l i
cy ). N ie tr u d n o z a sto so w a ć ta b lic ę sy m b o li d o u tw o rz e n ia
o d w z o ro w a n ia 1 d o 1 i p o w ią z a n ia V d o w o ln y c h n a z w
w ie rz c h o łk ó w z V lic z b a m i c a łk o w ity m i z p r z e d z ia łu
^ ^ 2) ° d 0 d o V - 1 (z o b a c z s tr o n ę 5 6 0 ), d la te g o w y g o d a , ja k ą
( | ) - ( o © d a je z a s to s o w a n ie in d e k s ó w ja k o n a z w w ie rz c h o łk ó w ,
n ie z m n ie js z a o g ó ln o ś c i ro z w ią z a n ia (i ty lk o n ie z n a c z n ie
■ysunki przedstawiające ten sam graf o b n iż a w y d a jn o ś ć ). Z a p is v-w o z n a c z a k ra w ę d ź łą c z ą c ą
v z w. Z a p is w-v to in n y s p o s ó b n a w s k a z a n ie tej sa m e j
N a r y s u n k u g ra f u k ó łk a o z n a c z a ją w ie rz c h o łld , a łą c z ą c e je lin ie — k ra w ę d z ie .
R y s u n e k p o z w a la in tu ic y jn ie z ro z u m ie ć s t r u k tu r ę g ra fu . J e d n a k in tu ic ja b y w a tu
m y lą c a , p o n ie w a ż g r a f je s t d e fin io w a n y n ie z a le ż n ie o d ry s u n k u . N a p rz y k ła d d w a
r y s u n k i p o lew ej re p r e z e n tu ją te n s a m g ra f, p o n ie w a ż s t r u k tu r a t a je s t n ic z y m w ięc e j
j a k (n ie u p o rz ą d k o w a n y m ) z b io r e m w ie rz c h o łk ó w i (n ie u p o rz ą d k o w a n ą ) k o le k c ją
k ra w ę d z i (p a r w ie rz c h o łk ó w ).
Pętla Krawędzie
A n o m a l i e D e fin ic ja d o p u s z c z a w y s tą p ie n ie d w ó c h p r o s ty c h a n o własna równolegle
m a lii. O to o n e: ł
■ pętla w łasna, czyli k ra w ę d ź łącząca w ie rz c h o łek z n im sam y m ;
■ k ra w ę d zie ró w n o leg łe, cz y li d w ie k ra w ę d z ie łą c z ą c e tę s a m ą Anomalie
p a rę w ie rz c h o łk ó w .
M a te m a ty c y c z a se m n a z y w a ją g ra fy o ró w n o le g ły c h k ra w ę d z ia c h m u ltig ra fa m i, a g ra fy
b e z k ra w ę d z i te g o ro d z a ju — g ra fa m i p ro s ty m i. W p rz e d s ta w ia n y c h p rz e z n a s im p le
m e n ta c ja c h o g ó ln ie p ę tle w ła sn e i k ra w ę d z ie ró w n o le g łe są d o p u s z c z a ln e (p o n ie w a ż
w y stę p u ją w p ra k ty c e ), je d n a k n ie u w z g lę d n ia m y ic h w p rz y k ła d a c h . D la te g o k a ż d ą
k ra w ę d ź m o ż n a w sk a z a ć za p o m o c ą n a z w d w ó c h łą c z o n y c h p rz e z n ią w ie rz c h o łk ó w .
530
4.1 n Grafy nieskierowane 531
S ło w n ic z e k Z g ra f a m i z w ią z a n y c h je s t w ie le n a zw . W ię k s z o ś ć p o ję ć m a p ro s te
d e fin icje. P rz e d s ta w ia m y je w je d n y m m ie js c u — tu ta j.
Jeśli is tn ie je k r a w ę d ź łą c z ą c a d w a w ie rz c h o łk i, m ó w im y , że są o n e są sia d u ją c e ,
a k ra w ę d ź je s t in c y d e n tn a d la o b u w ie rz c h o łk ó w . S to p ie ń w ie rz c h o łk a to lic z b a k r a
w ę d z i in c y d e n tn y c h . P o d g r a f to p o d z b ió r k ra w ę d z i g ra f u (i p o w ią z a n y c h w ie r z c h o ł
k ó w ), k tó r y s a m tw o r z y g raf. W ie le z a d a ń o b lic z e n io w y c h w y m a g a z id e n ty fik o w a n ia
p o d g ra f ó w ró ż n e g o ro d z a ju . S z c z e g ó ln ie c ie k a
w e są k ra w ę d z ie p o z w a la ją c e p rz e jś ć p rz e z ć ia ę Wierzchołek
w ie rz c h o łk ó w g ra fu .
D e f in i c ja . Ś c ie ż k a w g ra fie to c ią g w ie r z c h o ł
k ó w p o łą c z o n y c h k ra w ę d z ia m i. N a ścieżce
p ro stej ż a d e n w ie rz c h o łe k się n ie p o w ta rz a .
C y k l to śc ie ż k a , w k tó re j je d e n w ie rz c h o łe k
je s t z a ró w n o p o c z ą tk o w y m , ja k i k o ń c o w y m .
C ykl p r o s ty to ta k i, w k tó r y m k ra w ę d z ie a n i
w ie rz c h o łk i się n ie p o w ta rz a ją (w y ją tk ie m je s t
k o n ie c z n e p o w tó r z e n ie p ie rw s z e g o i o s ta tn ie
go w ie rz c h o łk a ). D łu g o ść śc ie ż k i lu b c y k lu to
lic z b a k ra w ę d z i.
D e f in i c ja . G r a f je s t sp ó jn y, je ś li is tn ie je śc ie ż k a z k a ż d e g o w ie rz c h o łk a d o k a ż
d e g o in n e g o w ie rz c h o łk a g ra fu . G r a f n ie sp ó jn y s k ła d a się ze sp ó jn y c h sk ła d o w y c h ,
k tó re są m a k s y m a ln y m i s p ó jn y m i p o d g ra f a m i.
In tu ic y jn ie stw ie rd z a m y , że g d y b y w ie rz c h o łk i b y ły fiz y c z n y m i o b ie k ta m i, ta k im i ja k
w ęzły lu b k o ra lik i, a k ra w ę d z ie — fiz y c z n y m i p o łą c z e n ia m i, n a p rz y k ła d s z n u r k a m i
lu b d r u c ik a m i, g r a f s p ó jn y p o z o s ta łb y w je d n e j c z ęśc i, g d y b y p o d n ie ś ć g o za p o m o c ą
d o w o ln e g o w ie rz c h o łk a , a g r a f n ie s p ó jn y s k ła d a łb y się z d w ó c h lu b w ię c ej e le m e n tó w
te g o ro d z a ju . P rz e tw a r z a n ie g ra fó w o g ó ln ie w y m a g a p rz e tw a r z a n ia o s o b n o s p ó jn y c h
sk ła d o w y c h .
532 R O ZD ZIA Ł 4 □ Grafy
G r a f a c y k lic z n y to ta k i, w k tó r y m n ie w y s tę p u ją 19 wierzchołków
Graf
cykle. K ilk a s p o ś r ó d o m a w ia n y c h a lg o r y tm ó w w y 18 krawędzi |
acykliczny
s z u k u je w g ra f a c h a c y k lic z n e p o d g r a f y o o k re ś lo
n y c h c e c h a c h . D o o p is u ta k ic h s t r u k tu r p o tr z e b n e są
d o d a tk o w e p o ję c ia .
D e f in i c ja . D r z e w o to a c y k lic z n y g r a f sp ó jn y .
R o z łą c z n y z b ió r d rz e w n a z y w a n y je s t la sem .
Spójny
D r z e w o ro zp in a ją c e d la g ra f u s p ó jn e g o to p o d -
g r a f s k ła d a ją c y się z w s z y s tk ic h w ie rz c h o łk ó w
Drzewo
g ra f u i b ę d ą c y je d n y m d rz e w e m . L a s ro z p in a ją c y
d la g ra f u to g r u p a d r z e w r o z p in a ją c y c h d la s p ó j
n y c h sk ła d o w y c h g ra fu .
T a d e fin ic ja d rz e w a je s t d o ś ć o g ó ln a . P o o d p o w ie d
n im d o p ra c o w a n iu o b e jm u je d rz e w a u ż y w a n e d o
m o d e lo w a n ia d z ia ła n ia p r o g r a m u ( h ie ra rc h ii w y w o
ła ń fu n k c ji) i s t r u k tu r d a n y c h (d r z e w a BST, d rz e w a
2 -3 itd .). M a te m a ty c z n e c e c h y d rz e w są d o b rz e p r z e
b a d a n e i in tu ic y jn e , d la te g o p rz y ta c z a m y je b e z d o
w o d ó w . P rz y k ła d o w o , g r a f G o V w ie rz c h o łk a c h je s t
d rz e w e m w te d y i ty lk o w ted y , je ś li s p e łn ia d o w o ln y
z p o n iż s z y c h p ię c iu w a ru n k ó w :
■ G m a V - 1 k ra w ę d z i i n ie m a cy k li. Las rozpinający
■ G m a V - 1 k ra w ę d z i i je s t sp ó jn y .
■ G je s t sp ó jn y , a le u s u n ię c ie d o w o ln e j k ra w ę d z i sp ra w ia , że sta je się n ie sp ó jn y .
■ G je s t acy k liczn y , je d n a k d o d a n ie d o w o ln e j k ra w ę d z i p o w o d u je p o w s ta n ie cy k lu .
■ K a ż d ą p a r ę w ie rz c h o łk ó w w G łą c z y d o k ła d n ie je d n a śc ie ż k a p ro s ta .
N ie k tó r e z o m a w ia n y c h a lg o r y tm ó w s łu ż ą d o w y s z u k iw a n ia d rz e w i la s ó w r o z p i n a
ją c y c h . O p is a n e p o w y ż e j c e c h y o d g ry w a ją w a ż n ą ro lę w a n a liz o w a n iu i im p le m e n to
w a n iu ty c h a lg o ry tm ó w .
G ęstość g ra f u o k re ś la , ile m o ż
Rzadki (£ = 200) Gęsty (£= 1000)
liw y c h k r a w ę d z i is tn ie je w grafie.
W g ra fie r z a d k im w y s tę p u je s t o s u n
k o w o n ie w ie le m o ż liw y c h k ra w ę d z i.
W g ra fie g ę s ty m b ra k u je s t o s u n k o
w o n ie w ie lu m o ż liw y c h k ra w ę d z i.
O g ó ln ie g r a f je s t u z n a w a n y z a rz a d k i,
je ś li lic z b a r ó ż n y c h k ra w ę d z i je s t n ie
w ię k sz a o p e w n ą n ie d u ż ą w ie lo k r o t
n o ś ć o d V. W p rz e c iw n y m ra z ie g r a f
Dwa grafy (IZ = 50)
je s t gęsty. T a p r o s ta re g u ła c z a se m
4.1 o Grafy nieskierowane
n ie p o z w a la p o d ją ć d e c y z ji ( n a p rz y k ła d k ie d y lic z b a k ra w ę d z i w y n o s i - c l / 3'2), j e d
n a k w p ra k ty c e p o d z ia ł n a g ra fy rz a d k ie i g ę ste je s t z w y k le b a r d z o w y ra ź n y . P ra w ie
w sz y stk ie o m a w ia n e z a s to s o w a n ia o p a r te są n a g ra fa c h rz a d k ic h .
G r a f d w u d z ie ln y to g raf, w k tó r y m w ie rz c h o łk i m o ż n a p o d z ie
lić n a d w a z b io ry , taicie że k a ż d a k ra w ę d ź łą c z y w ie rz c h o łe k z j e d
n e g o z b io r u z w ie rz c h o łk ie m z d ru g ie g o z b io ru . P rz y k ła d o w y g r a f
d w u d z ie ln y p o k a z a n o n a r y s u n k u p o p ra w e j. W ie r z c h o łk i z je d n e g o
z b io r u są k o lo r u c z e rw o n e g o , a z d ru g ie g o — c z a rn e g o . G ra fy d w u
d z ie ln e w p ra k ty c e w y s tę p u ją w w ie lu sy tu a c ja c h . J e d n ą z n ic h o m a
w ia m y s z c z e g ó ło w o w k o ń c o w e j c z ę śc i p o d r o z d z ia łu . Graf dwudzielny
p o t y m w s t ę p i e m o ż e m y p rz e jś ć d o a lg o r y tm ó w p r z e tw a r z a n ia g rafó w . Z a c z y n a m y
d o o m ó w ie n ia in te rfe js u A P I i im p le m e n ta c ji ty p u d a n y c h d la g rafó w . N a s tę p n ie o p i
s u je m y k la s y c z n e a lg o r y tm y d o p r z e s z u k iw a n ia g ra fó w i id e n ty fik o w a n ia s p ó jn y c h
s k ła d o w y c h . W k o ń c o w e j c z ę śc i p o d r o z d z ia łu o m a w ia m y p ra k ty c z n e z a s to s o w a n ia ,
w k tó ry c h n a z w a m i w ie rz c h o łk ó w n ie są lic zb y ca łk o w ite , a g ra fy o b e jm u ją d u ż ą lic z b ę
w ie rz c h o łk ó w i k ra w ę d z i.
534 RO ZD ZIA Ł 4 n Grafy
p ub lic c l a s s Graph
Zadanie Implementacja
p ub lic s t a t i c in t degree(Graph G, i n t v)
f
Obliczanie stopnia in t degree = 0;
wierzchołka v f o r ( i n t w : G.a d j (v )) degree++;
return degree;
}
pub lic s t a t i c i n t maxDegree(Graph G)
{
in t max = 0;
Obliczanie f o r (i n t v = 0; v < G.V(); v++)
maksymalnego stopnia i f (degree(G, v) > max)
max = degree(G, v ) ;
return max;
p ublic s t a t i c i n t numberOfSelfLoops(Graph G)
(
in t count = 0;
f o r (i n t v = 0; v < G.V(); v++)
Zliczanie
f o r (i n t w : G.adj (v ))
pętli własnej i f (v == w) count++;
return count/2; // Każdą krawędź policzono
// dwukrotnie.
}
p ub lic S t r i n g t o S t r i n g O
(
S t r i n g s = "W ierzchołki: " + V + ", krawędzie:" + E + " \ n " ;
Zwracanie f o r (i n t v = 0; v < V; v++)
łańcucha znaków {
reprezentującego s += v + ": ";
listy sąsiedztwa grafu f o r ( i n t w : t h i s .adj ( v ) )
(metoda egzemplarza s += w + " 11;
s += " \ n " ;
w klasie Graph)
}
return s;
}
Typowy kod do przetwarzania grafów
536 RO ZD ZIA Ł 4 □ Grafy
>
Reprezentacje tej
s to s o w a n ia tej s tru k tu r y . samej krawędzi
Tablica k r a w ę d z i z e le m e n ta
m i ty p u Edge, k tó r e o b e jm u ją
d w ie z m ie n n e e g z e m p la rz a
ty p u i n t . T a b e z p o ś r e d n ia r e
p r e z e n ta c ja je s t p ro s ta , je d n a k
n ie s p e łn ia d ru g ie g o w a r u n
9 — 12
ku. Im p le m e n ta c ja m e to d y
ad j () w y m a g a tu s p r a w d z e n ia s . 11 9
w s z y s tk ic h k ra w ę d z i g ra fu .
Reprezentacja oparta na listach sąsiedztwa
Tablica list są s ie d ztw a , in d e k (dla grafu nieskierowanego)
so w a n a w ie rz c h o łk a m i i p rz e -
c h o w u ją c a lis ty w ie rz c h o łk ó w s ą s ia d u ją c y c h z d a n y m . T a s t r u k tu r a d a n y c h
w ty p o w y c h z a s to s o w a n ia c h s p e łn ia o b a w a r u n k i i to ją s to s u je m y w ro z d z ia le .
O p r ó c z c e ló w z o b s z a r u w y d a jn o ś c i są te ż in n e , w a ż n e w n ie k tó r y c h z a s to s o w a n ia c h
k w e stie , k tó r e m o ż n a w y k ry ć p o d o k ła d n y m p rz y jrz e n iu się s tr u k tu r o m . P rz y k ła d o w o ,
d o p u s z c z e n ie k ra w ę d z i ró w n o le g ły c h u n ie m o ż liw ia z a s to s o w a n ie m a c ie rz y s ą s ie d z
tw a , p o n ie w a ż z a p o m o c ą tej s t r u k tu r y n ie m o ż n a p r z e d s ta w ić ta k ic h k ra w ę d z i.
4.1 a Grafy nieskierowane 537
L is ty s ą s ie d z t w a S ta n d a r d o w ą re p r e z e n ta c ją g ra fó w rz a d k ic h je s t s tr u k tu r a d a n y c h
o n a z w ie listy są s ie d ztw a . W tej s tr u k tu r z e z w ie rz c h o łk ie m s k o ja rz o n e są w sz y stk ie
są s ia d u ją c e z n im w ie rz c h o łk i, z a p is a n e n a liśc ie p o w ią z a n e j. P rz e c h o w y w a n a je s t t a b
lica list, d la te g o n a p o d s ta w ie w ie rz c h o łk a m o ż n a n a ty c h m ia s t u z y s k a ć d o s tę p d o o d
p o w ie d n ie j listy. D o im p le m e n to w a n ia list u ż y w a m y ty p u A D T Bag z p o d r o z d z i a ł u
1.3 w w e rsji o p a rte j n a liśc ie p o w ią z a n e j. P o z w a la to d o d a w a ć n o w e k ra w ę d z ie w s ta
ły m c zasie i ite ro w a ć p o s ą s ia d u ją c y c h w ie rz c h o łk a c h w c z a sie s ta ły m n a k a ż d y ta k i
w ie rz c h o łe k . I m p le m e n ta c ja ty p u G raph, p r z e d s ta w io n a n a s tr o n ie 5 3 8 , o p a r ta je s t n a
ty m p o d e jś c iu . N a r y s u n k u n a p o p rz e d n ie j s tr o n ie p r z e d s ta w io n o s t r u k tu r ę d a n y c h
u tw o rz o n ą za p o m o c ą te g o k o d u n a p o d s ta w ie p lik u tin y G .tx t. A b y d o d a ć k ra w ę d ź
łą c z ą c ą v i w, n a le ż y d o d a ć w d o listy s ą s ie d z tw a d la v o ra z v d o lis ty s ą s ie d z tw a d la w.
T ak w ię c k a ż d a k ra w ę d ź w y s tę p u je w tej s tr u k tu r z e d w u k r o tn ie . O m a w ia n a im p le
m e n ta c ja ty p u G raph m a n a s tę p u ją c e c e c h y z o b s z a r u w y d a jn o śc i:
° P a m ię ć z a jm o w a n a je s t p r o p o r c jo n a ln ie d o V + E.
■ D o d a n ie k ra w ę d z i z a jm u je s ta ły czas.
D C z a s ite ro w a n ia p o w ie rz c h o łk a c h s ą s ia d u ją c y c h z v je s t p r o p o r c jo n a ln y d o
s to p n ia v (p o tr z e b n y je s t s ta ły czas n a p rz e tw a r z a n y s ą s ia d u ją c y w ie rz c h o łe k ).
C ech y te są o p ty m a ln e d la p rz e d sta w io n e g o z b io ru o p eracji, k tó r y je s t o d p o w ie d n i d la
o m aw ian y c h zasto so w a ń m e to d p rz e tw a rz a n ia grafów . K raw ęd zie ró w n o le g łe i p ę tie
w łasn e są tu d o z w o lo n e (k o d n ie sp ra w d z a ich w y stą p ie n ia ). Uwaga: w a ż n e je s t to, że k o
lejn o ść d o d a w a n ia k ra w ę d z i d o g ra fu je st w y z n a c z n ik ie m k o lejn o ści p o ja w ia n ia się w ie rz
c h o łk ó w w tab licy list sąsie d z tw a tw o rz o n y c h za p o m o c ą ty p u Graph. T en sa m g ra f m o ż n a
p rzed staw ić za p o m o c ą w ielu ró ż n y ch tab lic list sąsiedztw a. P rz y sto so w a n iu k o n s tru k to
ra w czytującego k ra w ę d z ie ze s tru m ie n ia w ejścio w eg o o z n a c z a to , że fo rm a t d a n y c h w e j
ściow ych i k o le jn o ść k raw ęd z i w p lik u je s t w y z n a c z n ik ie m k o le jn o śc i w ie rz c h o łk ó w n a
listach sąsied ztw a b u d o w a n y c h p rz y u ż y c iu
ldasy Graph. P o n iew aż a lg o ry tm y o p a rte są
n a m e to d z ie a d j () i p rz e tw a rz a ją w szy stk ie
sąsied n ie w ie rz c h o łk i b e z u w z g lę d n ia n ia ich
k o lejn o ści n a listach, k w estia ta n ie w p ły w a
n a p o p ra w n o ś ć k o d u , je d n a k w a rto o niej
p a m ię ta ć w czasie d ia g n o z o w a n ia lu b a n a li t i n y G .t x t
% j ava Graph t in y G . t x t
zo w an ia śla d u d z ia ła n ia p ro g ra m u . W celu
13 v e r t ic e s , 13 edges
u ła tw ie n ia ty c h z a d a ń zak ład am y , że k lasa 0: 6 2 1 5
5
Graph m a k lie n ta testo w eg o , k tó r y w czy tu je 3
1: 0 X
2: 0
1 4 Pierwszy sąsiedni
g ra f ze s tru m ie n ia w ejścio w eg o p o d a n e g o 12
3: 5
4: 5 6 3 wierzchołek z danych
ja k o a rg u m e n t w iersza p o le c e ń , a n a s tę p 4 wejściowych jest
5: 3 4 0
4 ostatnim na liście
n ie w y św ied a g ra f (uży w ając im p le m e n ta c ji 2
6: 0 4
7:
m e to d y t o S t r i n g ( ) ze stro n y 535), ab y p o 11 12
9 10
kazać k o le jn o ść w y stę p o w a n ia w ie rz c h o ł 9 : 11 10 12 Drugie wystąpienie
0 6
10: 9 każdej krawędzi
k ó w n a listach sąsied ztw a. W tej k o le jn o ści 7 8
9 11
1 1 : 9 12 wyróżniono kolorem
a lg o ry tm y p rz e tw a rz a ją w ie rz c h o łk i (zo b acz 5 3
12: 11 9 czerwonym
ć w ic z e n ie 4 . 1 .7 ). Dane wyjściowe dla danych
wejściowych w postaci listy krawędzi
538 R O ZD ZIA Ł 4 Grafy
public c la s s Graph
{
private final in t V; 11 L i czba wierzchołków,
private in t E; // Li czba krawędzi,
private Bag<Integer>[] adj; // L i s t y sąsiedztwa.
public Graph(int V)
{
t h is .V = V; t h i s . E = 0;
adj = (B ag<Integer>[]) new Bag[V]; // Tworzenie t a b l i c y l i s t ,
fo r (in t v = 0; v < V; v++) // I ni cj o w a n i e w s z y st k i c h l i s t
adj [v] = new Ba g<In teg er> (); // (początkowo pust y ch) .
}
z p e w n o ś c i ą w a r t o z a s ta n o w ić się n a d in n y m i o p e ra c ja m i, k tó r e m o g ą b y ć p r z y
d a tn e w a p lik a c ja c h . Są to n a p r z y k ła d m e to d y d o :
° d o d a w a n ia w ie rz c h o łk a ,
° u s u w a n ia w ie rz c h o łk a .
J e d n y m ze s p o s o b ó w n a o b s łu g ę ta l a c h o p e ra c ji je s t ro z w in ię c ie in te rfe js u A P I p rz e z
z a s to s o w a n ie ta b lic y s y m b o li (ST) z a m ia s t ta b lic y in d e k s o w a n e j w ie rz c h o łk a m i (p o
tej z m ia n ie ja k o n a z w w ie rz c h o łk ó w n ie tr z e b a u ż y w a ć in d e k s ó w c a łk o w ito lic z b o -
w y c h ). M o ż n a te ż z a s ta n o w ić się n a d m e to d a m i d o :
° u s u w a n ia k ra w ę d z i,
0 s p ra w d z a n ia , c zy g r a f o b e jm u je k ra w ę d ź v-w.
A b y z a im p le m e n to w a ć te m e to d y (i u n ie m o ż liw ić is tn ie n ie k ra w ę d z i ró w n o le g ły c h ),
m o ż n a z a s to s o w a ć d la lis t s ą s ie d z tw a ty p SET z a m ia s t ty p u Bag. T ę m o ż liw o ś ć n a z y
w a m y re p r e z e n ta c ją w p o s ta c i z b io r u są s ie d ztw a . Jest k ilk a p o w o d ó w , d la k tó r y c h
w k sią ż c e n ie u ż y w a m y te g o ro z w ią z a n ia .
° O m a w ia n e t u k lie n ty n ie m u s z ą d o d a w a ć w ie rz c h o łk ó w , u s u w a ć w ie rz c h o łk ó w
i k ra w ę d z i a n i sp ra w d z a ć , c z y k ra w ę d ź is tn ie je .
D Jeśli k lie n ty w y m a g a ją ta k ic h o p e ra c ji, z w y k le w y w o łu ją je r z a d k o lu b d la k r ó t
k ic h lis t s ą s ie d z tw a , d la te g o ła tw y m r o z w ią z a n ie m je s t z a s to s o w a n ie im p le m e n
ta c ji p rz e z a ta k siło w y i ite ro w a n ie p o lis ta c h s ą s ie d z tw a .
a R e p re z e n ta c je o p a r te n a ty p a c h SET i ST n ie c o k o m p lik u ją k o d a lg o r y tm ó w o ra z
o d w ra c a ją o d n ic h u w ag ę .
° W p e w n y c h s y tu a c ja c h m o ż e n a s tą p ić s p a d e k w y d a jn o ś c i n a p o z io m ie lo g V.
N ie tr u d n o d o s to s o w a ć p rz e d s ta w io n e tu a lg o r y tm y d o in n y c h p ro je k tó w (n a p r z y
k ła d z a b ro n ić tw o rz e n ia k ra w ę d z i ró w n o le g ły c h lu b p ę tli w ła s n y c h ) b e z z n a c z n e g o
s p a d k u w y d a jn o ś c i. W ta b e li p o n iż e j z n a jd u je się p rz e g lą d c e c h z o b s z a r u w y d a jn o
ści d la ró ż n y c h ro z w ią z a ń . W ty p o w y c h z a s to s o w a n ia c h p r z e tw a r z a n e są d u ż e g ra fy
rz a d k ie , d la te g o s to s u je m y r e p r e z e n ta c je w p o s ta c i lis t są s ie d z tw a .
M acierz sąsiedztwa V1 1 1 V
W z o r c e p r o j e k to w e z z a k r e s u p r z e t w a r z a n i a g r a f ó w P o n ie w a ż o m a w ia m y w iele
a lg o r y tm ó w p r z e tw a r z a n ia g rafó w , p ie r w s z y m c e le m p r o je k to w y m je s t o d d z ie le n ie
im p le m e n ta c ji o d r e p r e z e n ta c ji grafó w . W ty m c e lu d la k a ż d e g o z a d a n ia ro z w ija m y
s p e c y fic z n ą d la n ie g o k la sę . K lie n ty w c e lu w y k o n a n ia z a d a n ia m o g ą tw o rz y ć o b ie k ty
tej klasy. K o n s tr u k to r p rz e p r o w a d z a w s tę p n e p rz e tw a r z a n ie p r z y tw o r z e n iu s t r u k tu r
d a n y c h , a b y m ó c w y d a jn ie re a g o w a ć n a z a p y ta n ia o d k lie n ta . T y p o w y k lie n t tw o rz y
g raf, p rz e k a z u je g o d o k la s y z im p le m e n ta c ją a lg o r y tm u (ja k o a r g u m e n t k o n s t r u k
to r a ), a n a s tę p n ie w y w o łu je m e to d y k lie n c k ie w c e lu u s ta le n ia ró ż n y c h c e c h g ra fu .
W r a m a c h ro z g r z e w k i z a s ta n ó w m y się n a d p o n iż s z y m in te rfe js e m A P I.
p ub lic c l a s s Search
U ż y w a m y n a z w y w ie rz c h o łe k źr ó d ło w y , a b y o d ró ż n ić w ie rz c h o łe k p rz e k a z a n y ja k o
a r g u m e n t d o k o n s t r u k to r a o d in n y c h w ie rz c h o łk ó w g ra fu . W ty m in te rfe js ie A P I z a
d a n ie m k o n s t r u k to r a je s t z n a le z ie n ie w g ra fie w ie rz c h o łk ó w p o łą c z o n y c h ze ź r ó d ł o
w y m . N a s tę p n ie k o d k lie n ta w y w o łu je m e to d y e g z e m p la rz a marked() i c o u n t ( ) , a b y
p o z n a ć c e c h y g ra fu . N a z w a mar ked () (czy li „ o z n a c z o n y ”) n a w ią z u je d o p o d e jś c ia s to
s o w a n e g o w p o d s ta w o w y c h a lg o r y tm a c h o m a w ia n y c h w ro z d z ia le — m e t o d a p r z e
c h o d z i śc ie ż k ą z w ie rz c h o łk a ź ró d ło w e g o d o in n y c h w ie rz c h o łk ó w g ra f u i o z n a c z a
k a ż d y n a p o tk a n y . P rz y k ła d o w y k lie n t T e s t Search p r z e d s ta w io n y n a n a s tę p n e j s tro n ie
p o b ie r a z w ie rs z a p o le c e ń n a z w ę s t r u m ie n ia w e jśc io w e g o i n u m e r ź ró d ło w e g o w ie r z
c h o łk a , w c z y tu je g r a f ze s t r u m ie n ia w e jśc io w e g o (z a p o m o c ą d ru g ie g o k o n s t r u k to r a
k la s y Graph), tw o rz y o b ie k t Search d la d a n e g o g ra f u i w ie rz c h o łk a ź ró d ło w e g o o ra z
u ż y w a m e to d y m ar ked () d o w y ś w ie tle n ia w ie rz c h o łk ó w p o łą c z o n y c h ze ź ró d ło w y m .
P r o g r a m w y w o łu je te ż m e to d ę c o u n t( ) i w y św ie tla in f o rm a c ję , c z y g r a f je s t s p ó jn y
(g r a f je s t s p ó jn y w te d y i ty lk o w ted y , je ś li p r z y w y s z u k iw a n iu o z n a c z o n o w sz y stk ie
w ie rz c h o łk i).
4.1 B Grafy nieskierowane 541
p o k a z a l i ś m y j u ż je d e n ze s p o s o b ó w n a z a im p le m e n to w a n ie in te rfe js u A P I k la s y
S e a rc h . U m o ż liw ia ją to a lg o r y tm y z w ią z a n e z p r o b le m e m U n io n - F in d ( r o z d z i a ł i.).
K o n s tr u k to r m o ż e u tw o rz y ć o b ie k t ty p u UF, w y k o n a ć o p e ra c ję uni on () n a k a ż d e j k r a
w ę d z i g ra f u i o b s łu ż y ć o p e ra c ję m ar ked ( v) p rz e z w y w o ła n ie m e to d y c o n n e c te d ( s , v ) .
Z a im p le m e n to w a n ie m e to d y c o u n t () w y m a g a z a s to s o w a n ia w a ż o n e j w e rsji k la s y UF
i ro z w in ię c ia je j in te rfe js u A P I o m e to d ę c o u n t () z w ra c a ją c ą w a r to ś ć wt [find (v ) ] ( z o
b a c z ć w i c z e n i e 4 . 1 . 8 ). Im p le m e n ta c ja ta je s t p r o s ta i w y d a jn a , je d n a k ro z w ią z a n ie
o p is a n e d alej je s t je s z c z e ła tw ie js z e i sz y b sz e. O p a rliś m y je n a p r z e s z u k iw a n iu w g łę b .
Jest to je d n a z g łó w n y c h te c h n ik re k u r e n c y jn y c h , p o le g a ją c a n a p r z e c h o d z e n iu p o
k ra w ę d z ia c h g ra f u w c e lu z n a le z ie n ia w ie rz c h o łk ó w p o łą c z o n y c h z w ie rz c h o łk ie m
ź ró d ło w y m . P rz e s z u k iw a n ie w g łą b je s t p o d s ta w ą k ilk u a lg o r y tm ó w p rz e tw a r z a n ia
grafów , k tó r e o m a w ia m y w ro z d z ia le .
public c l a s s TestSearch
(
p ublic s t a t i c void m a in ( Str in g [] args )
{
Graph G = new Graph(new I n ( a r g s [ 0 ] ) ) ;
in t s = I n t e g e r . p a r s e l n t ( a r g s [ l ] );
Search search = new Search(G, s );
i f (s earc h.count() != G .V ( )) t in y G .t x t
Std O u t.p rint("N IE");
StdOu t.pri n t l n ( "spój ny" ) ; 13
} 0 5
) 4 3
0 1
Przykładowy klient do przetwarzania grafów (rozgrzewka) 9 12
64
5 4
% java TestSearch t in y G .t x t 0 0 2
11 12
0 1 2 3 4 5 6
9 10
NIEspójny
0 6
7 8
% java TestSearch t in y G .t x t 9 9 11
9 10 11 12 5 3
NIEspójny
542 RO ZD ZIA Ł 4 a Grafy
P rz e s z u k iw a n ie w g łą b C e c h y g ra f u c z ę sto o k re ś la się p rz e z s y s te m a ty c z n e
s p r a w d z a n ie k a ż d e g o w ie rz c h o łk a i w s z y s tk ic h je g o k ra w ę d z i. P e w n e p r o s te c e c h y
g ra fu , n a p rz y k ła d s to p ie ń w s z y s tk ic h w ie rz c h o łk ó w , m o ż n a ła tw o u s ta lić n a p o d s t a
w ie s a m y c h k ra w ę d z i (s p ra w d z a n y c h w d o w o ln e j k o le jn o ś c i). J e d n a k w ie le in n y c h
cech z w ią z a n y c h je s t ze śc ie ż k a m i,
Labirynt
d la te g o n a tu r a ln y s p o s ó b n a p o z n a n ie
ta k ic h w ła śc iw o ś c i to p r z e c h o d z e n ie
m ię d z y w ie rz c h o łk a m i w z d łu ż k r a w ę
d z i g ra fu . P ra w ie w sz y stk ie o m a w ia n e
Skrzyżowanie a lg o r y tm y p r z e tw a r z a n ia g ra fó w są
Alejka o p a r te n a ty m s a m y m p o d s ta w o w y m
Graf
m o d e lu a b s tra k c y jn y m , c h o ć s to s o w a
n e są ró ż n e stra te g ie . N a jp ro s ts z a je s t
o p is a n a t u k la s y c z n a m e to d a .
P r z e s z u k i w a n i e l a b i r y n t u O p ro c e s ie
D W y c o fa ć się (w y k o rz y s tu ją c n ić ) p o n a p o tk a n iu o z n a c z o n e
go sk rz y ż o w a n ia .
■ W y c o fa ć się, je ś li sk rz y ż o w a n ie n a p o tk a n e w c za sie p o w r o tu n ie p ro w a d z i d o
n ie o z n a c z o n y c h a lejek .
N ić g w a ra n tu je , że z a w sze m o ż n a z n a le ź ć d ro g ę p o w r o tu , a o z n a c z e n ia p o z w a la ją
u n ik n ą ć d w u k r o tn e g o o d w ie d z a n ia a le je k lu b s k rz y ż o w a ń . U s ta le n ie , że z b a d a n o
c a ły la b iry n t, je s t b a rd z ie j s k o m p lik o w a n e . Z p r o b le m e m ty m lep iej z m ie rz y ć się
w k o n te k ś c ie p rz e s z u k iw a n ia g ra fu . E k s p lo ro w a n ie m e t o d ą T re m a u x je s t in tu ic y j
n y m p u n k te m w y jśc ia , je d n a k w y s tę p u ją t u p e w n e s u b te ln e ró ż n ic e w z g lę d e m e k s
p lo r o w a n ia g ra fu , d la te g o p rz e c h o d z im y te r a z d o p r z e s z u k iw a n ia grafó w .
4.1 □ Grafy nieskierowane 543
R o z g r z e w k a K la sy c z n a re k u r e n c y jn a m e to d a p rz e s z u k iw a n ia g ra fó w s p ó jn y c h ( o d
w ie d z a n ia w s z y s tk ic h w ie rz c h o łk ó w i k ra w ę d z i) o d z w ie rc ie d la e k s p lo ro w a n ie la b i
r y n tu m e to d ą T re m a u x , je s t je d n a k je sz c z e
ła tw ie jsz a d o o p is a n ia . W c e lu p rz e s z u k a n ia p u b lic c l a s s D epthF irstS earch
{
g ra fu n a le ż y w y w o ła ć re k u r e n c y jn ą m e to d ę , p riv ate booleanj] marked;
k tó r a p r z e c h o d z i p o w ie rz c h o łk a c h . P rz y o d p riv ate i n t count;
w ie d z a n iu w ie rz c h o łk ó w n a le ż y : p ub lic DepthFirstSearch(Graph G, in t s)
° O z n a c z y ć w ie rz c h o łe k ja k o o d w ie d z o n y . {
marked = new b o o le a n [G .V ()];
a O d w ie d z ić ( r e k u re n c y jn ie ) w sz y stk ie s ą dfs(G , s );
sie d n ie , ale n ie o z n a c z o n e w ie rz c h o łk i. }
Jest to m e t o d a p r z e s z u k iw a n ia w g łą b (an g .
p riv ate void dfs(Graph G, i n t v)
d e p th -first search — D F S ). W im p le m e n ta c ji f
in te rfe js u A P I k la s y S e a rc h u ż y w a m y m e to d y markedjv] = true;
count++;
p o k a z a n e j p o p ra w e j s tro n ie . M e to d a p r z e c h o for (in t w : G.adj(v))
w u je ta b lic ę w a rto ś c i ty p u b o o le a n d o o z n a i f ( ! marked[w]) dfs(G , w );
}
c z a n ia w s z y s tk ic h w ie rz c h o łk ó w p o łą c z o n y c h
ze ź ró d ło w y m . M e to d a r e k u r e n c y jn a o z n a p ub lic boolean marked(int w)
{ return marked[w]; }
c za d a n y w ie rz c h o łe k i w y w o łu je s a m ą sie b ie
d la n ie o z n a c z o n y c h w ie rz c h o łk ó w z lis ty s ą p ub lic in t count()
{ return count; }
sie d z tw a . Jeśli g r a f je s t sp ó jn y , s p r a w d z a n e są
w sz y stk ie lis ty s ą s ie d z tw a . }
Przeszukiwanie w głąb
Twierdzenie A. M e to d a D F S o z n a c z a w sz y stk ie
w ie rz c h o łk i p o w ią z a n e ze ź ró d ło w y m i ro b i to
w c zasie p r o p o r c jo n a ln y m d o s u m y ic h s to p n i.
Dowód. N a jp ie rw d o w ie d ź m y , że a lg o ry tm o z n a
cza w sz y stk ie w ie rz c h o łk i p o w ią z a n e ze ź ró d ło w y m
s (i n ie o z n a c z a ż a d n y c h in n y c h ). K a żd y o z n a c z o n y
w ie rz c h o łe k je s t p o w ią z a n y z s, p o n ie w a ż a lg o ry tm
z n a jd u je w ie rz c h o łk i ty lk o p rz e z p rz e c h o d z e n ie
w z d łu ż k ra w ę d z i. Z ałó ż m y , że z s p o łą c z o n y je s t
p e w ie n n ie o z n a c z o n y w ie rz c h o łe k w. P o n ie w a ż s a m
s je s t o zn aczo n y , k a ż d a ście ż k a z s d o w m u s i o b e j
m o w a ć p rz y n a jm n ie j je d n ą k ra w ę d ź ze z b io r u o z n a
c z o n y c h w ie rz c h o łk ó w d o z b io r u w ie rz c h o łk ó w n ie
o z n a c z o n y c h (n ie c h b ę d z ie to k ra w ę d ź v -x ). Je d n a k
a lg o ry tm w y k ry łb y x p o o z n a c z e n iu v, d la te g o ta k a
k ra w ę d ź n ie m o ż e istn ie ć , p o w sta je w ię c s p rz e c z
n o ść. O g ra n ic z e n ie czaso w e w y n ik a z teg o , że o z n a
c z an ie g w a ra n tu je , iż k a ż d y w ie rz c h o łe k je s t o d w ie
d z a n y je d n o k r o tn ie (s p ra w d z e n ie o z n a c z e ń z a jm u je
czas p ro p o r c jo n a ln y d o s to p n ia w ie rz c h o łk a ).
544 R O ZD ZIA Ł 4 □ Grafy
A l e j k i j e d n o k i e r u n k o w e M e c h a n iz m w y w o ły w a n ia m e t o d i z w ra c a n ia ste ro w a n ia
w p r o g r a m ie o d p o w ia d a n ic i w la b iry n c ie . P o p r z e tw o r z e n iu w s z y s tk ic h k ra w ę d z i
p o w ią z a n y c h z w ie rz c h o łk ie m (s p ra w d z e n iu w s z y s tk ic h a le je k w y c h o d z ą c y c h ze
s k rz y ż o w a n ia ) n a le ż y z w ró c ić s te ro w a n ie (czy li z a w ró c ić ). A b y n a ry s o w a ć sy tu a c ję
o d p o w ia d a ją c ą e k s p lo ro w a n iu la b ir y n tu m e to d ą T re m a u x , tr z e b a w y o b ra z ić so b ie
la b ir y n t o b e jm u ją c y a le jk i je d n o k ie r u n k o w e (p o je d n e j w k a ż d y m k ie r u n k u ) . W te n
s a m s p o s ó b , w ja k i d w u k r o tn ie (je d e n ra z w k a ż d y m k ie r u n k u ) n a p o ty k a m y k a ż d ą
a le jk ę la b iry n tu , d w u k r o tn ie n a tr a fia m y te ż n a
t in y C G .t x t Standardowy rysunek k a ż d ą k ra w ę d ź (w y c h o d z ą c je d e n ra z z k a ż d e g o
z jej w ie rz c h o łk ó w ). P rz y e k s p lo ro w a n iu m e t o
d ą T re m a m t a lb o s p r a w d z a m y a lejk ę p ie rw s z y
0 5 ra z , a lb o w ra c a m y n ią z o z n a c z o n e g o w ie r z c h o ł
2 4
2 3
k a . W m e to d z ie D F S d la g ra f u n ie s k ie ro w a n e g o
^ ^ Rysunek z obiema krawędziami p o n a p o tk a n iu k ra w ę d z i v-w a lb o n a s tę p u je re -
k u re n c y jn e w y w o ła n ie (je śli w n ie je s t o z n a c z o
3 4
3 5 n y ), a lb o n a le ż y p o m in ą ć k r a w ę d ź (je ż e li w je s t
0 2 o z n a c z o n y ). P rz y d r u g i m n a p o tk a n iu k ra w ę d z i,
w c z a sie p r z e c h o d z e n ia w k ie r u n k u w-v, zaw sze
n a le ż y ją p o m in ą ć , p o n ie w a ż w ie rz c h o łe k d o c e
Listy sąsiedztwa
lo w y v z p e w n o ś c ią z o s ta ł ju ż o d w ie d z o n y (p rz y
p ie r w s z y m n a p o tk a n i u k ra w ę d z i).
Ś le d z e n ie d z i a ł a n i a m e t o d y D F S Ja k zw y k le
je d n y m z d o b r y c h s p o s o b ó w n a z ro z u m ie n ie a l
g o r y tm u je s t p rz e ś le d z e n ie je g o d z ia ła n ia n a m a
ły m p rz y k ła d z ie . Jest to sz c z e g ó ln ie o d c z u w a ln e
p r z y p rz e s z u k iw a n iu w g łąb . P ie rw s z ą rz e c z ą ,
o k tó re j n a le ż y p a m ię ta ć p r z y tw o r z e n iu śla d u ,
je s t to , że k o le jn o ś ć o k re ś la n ia s p ra w d z o n y c h
k ra w ę d z i i o d w ie d z o n y c h w ie rz c h o łk ó w z a le ż y
o d re p re ze n ta c ji, a n ie ty lk o o d g ra f u lu b a lg o
Spójny graf nieskierowany ry tm u . P o n ie w a ż m e to d a D F S s p ra w d z a je d y
n ie w ie rz c h o łld p o w ią z a n e ze ź ró d ło w y m , p rz y
tw o r z e n iu ś la d u ja k o p rz y k ła d u u ż y w a m y m a łe g o g ra f u s p ó jn e g o p rz e d s ta w io n e g o
p o lew ej s tro n ie . W p rz y k ła d z ie w ie rz c h o łe k 2 to p ie r w s z y w ie rz c h o łe k o d w ie d z a n y
p o 0, p o n ie w a ż w y s tę p u je ja k o p ie r w s z y n a liśc ie s ą s ie d z tw a w ie rz c h o łk a 0. D r u g ą
k w e stią , n a k tó r ą tr z e b a z w ró c ić u w a g ę , je s t to , że — j a k w s p o m n ie liś m y — m e to d a
D F S p r z e c h o d z i w z d łu ż k a ż d e j k ra w ę d z i d w u k r o tn ie i z aw sz e z n a jd u je o z n a c z o n y
w ie rz c h o łe k p o r a z d ru g i. J e d n y m z w n io s k ó w z te g o s p o s tr z e ż e n ia je s t to , że ś le
d z e n ie d z ia ła n ia m e to d y D F S z a jm u je d w u k r o tn ie w ię ce j c z a su , n iż m o ż n a sąd zić!
P rz y k ła d o w y g r a f m a ty lk o o s ie m k ra w ę d z i, tr z e b a je d n a k p rz e ś le d z ić d z ia ła n ie a lg o
r y t m u d la 16 e le m e n tó w z lis ty s ą s ie d z tw a .
4.1 □ Grafy nieskierowane 545
p u b l i c c l a s s Paths
public c la s s DepthFirstPaths
{
private boolean[] marked; // Czy wywołano już dfs() dla danego wierzchołka?
private i n t [] edgeTo; // Ostatni wierzchołek na znanej ścieżce
// do wierzchołka,
private final in t s; // Wierzchołek źródłowy.
w p o d r o z d z i a l e 1 .5 ), co powoduje umiesz- d fs ( O )
p u b lic c la s s B re a d th F irstP a th s
{
p riv a te boolean[] marked; // Czy znana j e s t n a jk ró tsz a śc ie ż k a do tego
// w ierzch o łka ?
p riv a te in t [ ] edgeTo; // O statni w ierzchołek na znanej śc ie ż c e do
// w ierzchołka,
p riv a te final in t s; // W ierzchołek źródłowy.
W tym przykładzie tablica edgeTo [] zostaje zapełniona po drugim kroku. Tu, tak jak
w metodzie DFS, po oznaczeniu wszystkich wierzchołków dalsze operacje to tylko
sprawdzanie krawędzi do już oznaczonych wierzchołków.
S z u k a n ie ś c ie ż e k m e to d ą S z u k a n ie n a jk ró ts z y c h ście ż e k
DFS (250 w ie rz ch o łk ó w ) m e to d ą BFS (250 w ie rz ch o łk ó w )
4.1 □ Grafy nieskierowane 555
p ub lic c la s s CC
public c la s s CC
{ % more tin y G .tx t
p rivate boolean[] marked;
13 v e rt ic e s , 13 edges
p rivate i n t [ ] id; 0: 6 2 1 5
p rivate in t count; 1: 0
2: 0
public CC(Graph G) 3: 5 4
{ 4: 5 6 3
marked = new b oolean[G.V ()]; 5: 3 4 0
id = new in t [ G .V( ) ] ; 6: 0 4
fo r (in t s = 0; s < G.V(); s++) 7: 8
i f ( ¡marked[s]) 8: 7
9: 11 10 12
{
10: 9
dfs(G, s );
11: 9 12
count++;
12: 11 9
}
} % java CC t in y G .tx t
lic z b a składowych: 3
p rivate void dfs(Graph G, in t v) 6 5 4 3 2 1 0
{ 8 7
marked[v] = true; 12 11 10 9
id[v] = count;
fo r (in t w : G.adj (v ) )
i f (!marked[w])
dfs(G, w);
}
public in t id (in t v)
{ return i d [ v ] ; }
public in t countQ
( return count; }
Ten klient klasy Graph umożliwia swoim klientom niezależne przetwarzanie spójnych skła
dowych grafu. Kod metody DepthFirstSearch (strona 543) pokazano po lewej stronie
w kolorze szarym. Przetwarzanie oparte jest na indeksowanej wierzchołkami tablicy i d [],
takiej że id[v] ma wartość i, jeśli v znajduje się w i-tej przetwarzanej spójnej składowej.
Konstruktor znajduje nieoznaczony wierzchołek i wywołuje rekurencyjną metodę dfs(),
aby oznaczyć oraz zidentyfikować wszystkie wierzchołki powiązane ze znalezionym. Proces
ten trwa do czasu oznaczenia i zidentyfikowania wszystkich wierzchołków. Implementacje
metod egzemplarza connected(), i d () ic ou n t() są oczywiste.
4.1 e Grafy nieskierowane 557
t in y G . t x t
m a r k e d [] id []
B 9101112
dfsCO) T
d f s ( 6) T
S p r a w d z a n ie 0
d fs(4 ) T T T 0 0
d fs(5 ) T T T T 0 0 0
d fs(3 ) T T T T T 0 0 0 0
S p r a w d z a n ie 5
S p r a w d z a n ie 4
3 G otow y
S p r a w d z a n ie 4
S p r a w d z a n ie 0
5 G otow y
S p r a w d z a n ie 6
S p r a w d z a n ie 3
4 G otow y
6 G otow y
d f s ( 2) T T T T T T 0 0 0 0 0 0
[ S p r a w d z a n ie 0
2 G otow y
d fs (l) T T T T T T T 00 00 000
| S p r a w d z a n ie 0
I G otow y
S p r a w d z a n ie 5
0 G otow y
d fs(7 ) T T T T T T T T 0 0 0 0 0 0 0
d f s ( 8) T T T T T T T T T 0 0 0 0 0 0 0 1
| S p r a w d z a n ie 7
8 G otow y
7 G otow y
d fs(9 ) T T T T T T T T T T 0 0 0 0 0 0 1 2
cif s ( 1 1 ) T T T T T T T T T T 0 0 0 0 0 0 12 2
S p r a w d z a n ie 9
d f s ( 12 ) T T T T T T T T T T TT 0 0 0 0 0 0 12 2 2
S p r a w d z a n ie 11
S p r a w d z a n ie 9
12 G otow y
I I G otow y
d f s ( 10 ) T T T T T T T T T T T T T 0 0 0 0 0 0 1 2 2 2 2
| S p r a w d z a n ie 9
10 G otow y
S p r a w d z a n ie 12
9 G otow y
Tu, jak zwykle przy stosowaniu metody DFS, za prostym kodem kryje się bardziej
skomplikowane przetwarzanie. Dlatego warto przeanalizować przykłady, prześledzić
ich działanie dla małych przykładowych grafów oraz rozwinąć kod o sprawdzanie
cykli i kolorowanie (pozostawiamy to jako ćwiczenia).
4.1 b Grafy nieskierowane 559
Zadanie Implementacja
p u b lic c la s s Cycle
i
p riv a te boolean[] marked;
p riv a te boolean hasCycle;
p u b lic Cycle(Graph G)
(
marked = new b o o le a n [G .V ()];
f o r ( in t s = 0; s < G .V (); s++)
Czy graf G i f (!m arked[s])
dfs(G , s, s ) ;
jest acykliczny?
Zakładamy, że nie }
istnieję pętle własne p riv a te void dfs(G raph G, in t v, in t u)
ani krawędzie {
równoległe. marked[v] = true ;
f o r (in t w : G .a d j(v ))
i f ( ¡marked[w])
dfs(G , w, v ) ;
e lse i f (w != u) hasCycle = true;
p u b lic TwoColor(Graph G)
(
marked = new boolean [G.V() ] ;
co lo r = new boolean[G .V( ) ] ;
fo r (in t s = 0; s < G.V(); s++)
i f (!m arked[s])
dfs(G , s ) ;
Czy graf }
jest dwudzielny
(czy można przypisać p riv a te void dfs(G raph G, in t v)
mu dwa kolory)? {
marked[v] = true ;
f o r ( in t w : G .a d j(v ))
i f ( ¡marked[w])
(
color[w ] = ¡c o lo r fv ];
dfs(G , w );
1
e lse i f (color[w ] == c o lo r[ v ] ) isTw oColorable = fa ls e ;
p u b lic boolean i s B i p a r t i t ę ()
{ return isTw oColorable; }
}
Więcej przykładów przetwarzania grafów metodą DFS
560 RO ZD ZIA Ł 4 o Grafy
p u b lic c la s s SymbolGraph
( P a t r ic k D ia l M
— ^ A lle n f o r M urder
M ~V _ L A
Enigma \ T 1 Tk . / se rr^ tta \
/ JA Kate
<
A Wi 1 son
Ete rnal su n sh in e
lbert J C Shane
/ \N
o f the S p o t le s s
Mind
7 n
I/ T— T
Film W ykonaw cy
twarzania grafów. Każdy klient może użyć metody i ndex (), aby przekształcić nazwę
wierzchołka na indeks używany przy przetwarzaniu grafu, i m etody name() w celu
przekształcenia indeksu na nazwę stosowaną w aplikacji.
Implementacja Pełną implementację klasy Symbol Graph przedstawiono na stronie 564.
Budowane są tam trzy struktury danych:
a tablica symboli s t z kluczami typu S tring (nazwami wierzchołków) i wartoś
ciami typu in t (indeksami);
° tablica keys[], która pełni funkcję indeksu odwrotnego i udostępnia nazwę
wierzchołka dla każdego indeksu całkowitoliczbowego;
o oparty na indeksach obiekt Graph g, służący do wskazywania wierzchołków.
Klasa Symbol Graph musi dwukrotnie przejść po danych w celu zbudowania wymie
nionych struktur. Wynika to głównie z tego, że do utworzenia obiektu Graph niezbęd
na jest liczba wierzchołków (V). W typowych praktycznych zastosowaniach utrzy
mywanie wartości V i E w pliku definiującym graf (wymagał tego konstruktor Graph
z początku podrozdziału) jest niewygodne. Przy korzystaniu z klasy Symbol Graph
można używać plików w rodzaju routes.txt i movies.txt oraz dodawać lub usuwać
elementy bez uwzględniania liczby różnych nazw.
S tr u k t u r y d a n y c h w g ra fie s y m b o li
564 RO ZDZIAŁ 4 Grafy
G = new G r a p h ( s t . s i z e ( ) ) ;
in = new In(stream); // Drugi przebieg,
while (in.hasN extLine()) // Tworzenie grafu
{
S t r in g [] a = in .readLine() . s p l i t ( s p ) ; // przez łączenie
in t v = s t . g e t ( a [ 0 ] ); // pierwszego wierzchołka
fo r (in t i = 1; i < a.length; i++) // z każdego wiersza
// z wszystkimi
G.addEdge(v, s t .g e t ( a [ i ] ) ) ; // pozostałymi wierzchołkami.
}
Ten klient klasy Graph umożliwia klientom definiowanie grafów za pomocą łańcuchów zna
ków określających nazwy wierzchołków zamiast przy użyciu indeksów całkowitoliczbowych.
Klient przechowuje zmienne egzemplarza — s t (tablicę symboli łączącą nazwy z indeksami),
keys (tablicę łączącą indeksy z nazwami) i G (graf, gdzie nazwy wierzchołków to liczby cał
kowite). W celu utworzenia tych struktur klient wykonuje dwa przebiegi po definicji grafu
(każdy wiersz zawiera łańcuch znaków i listę sąsiadujących łańcuchów, rozdzielonych ogra
nicznikiem sp).
4.1 n Grafy nieskierowane 565
Stopnie oddalenia
public c la s s DegreesOfSeparation
{
public s t a t ic void m ain(String[] args)
{
Symbol Graph sg = new Symbol Graph (args [0], a r g s [ l ] ) ;
Graph G = s g . G ( ) ;
S t r in g source = a r g s [2];
i f (is g .c o n ta in s (s o u rc e ))
{ S tdO ut.println(source + " nie ma w b a z ie . " ) ; return; }
in t s = s g .in d e x (s o u rc e );
BreadthFirstPaths bfs = new BreadthFirstPaths(G, s ) ;
Ten klient klas Symbol Graph i BreadthFi rstPaths znajduje najkrótsze ścieżki w grafach.
W przypadku pliku movies.txt umożliwia grę w Kevina Bacona.
[j PYTANIA I ODPOWIEDZI
p. Dlaczego nie połączyliśmy wszystkich algorytmów w klasie Graph .java?
O. To prawda, można dodać metody obsługi zapytań (oraz wszystkie potrzebne pola
i metody prywatne) do podstawowej definicji typu ADT Graph. Choć takie podej
ście ma pewne zalety związane z abstrakcją danych, m a też poważne wady, ponieważ
dziedzina przetwarzania grafów jest znacznie rozleglejsza niż te związane z podsta
wowymi strukturam i danych omawianymi w p o d r o z d z i a l e 1 .3 . Oto najważniejsze
z tych wad:
° Istnieje tak dużo operacji do przetwarzania grafów, że nie da się ich precyzyjnie
zdefiniować w jednym interfejsie API.
0 Przy prostych zadaniach z dziedziny przetwarzania grafów trzeba korzystać
z tego samego interfejsu, co przy wykonywaniu skomplikowanych operacji.
0 Jedna metoda może korzystać z pól przeznaczonych do użytku przez inną m e
todę, co jest niezgodne z zasadami hermetyzacji, których chcemy przestrzegać.
Umieszczenie wszystkich m etod w jednej klasie nie jest niczym niezwykłym. Interfejsy
API obejmujące wiele m etod to szerokie interfejsy (zobacz stronę 109). W rozdzia
le poświęconym algorytmom przetwarzania grafów interfejs API tego rodzaju byłby
naprawdę szeroki.
P. Czy w klasie Symbol Graph rzeczywiście niezbędne są dwa przebiegi?
O. Nie. Można ponieść dodatkowy koszt na poziomie lg N i dodać bezpośrednią
obsługę m etody adj (), używając typu ST zamiast Bag. Implementację opartą na tym
pomyśle przedstawiliśmy w książce An Introduction to Programming in Java: An
Interdisciplinary Approach.
570 R O ZD ZIA Ł 4 Grafy
0 ĆWICZENIA
4.1.1. Jaka jest m inim alna liczba krawędzi w grafie o V wierzchołkach i bez równo
ległych krawędzi? Jaka jest minimalna liczba krawędzi w grafie o V wierzchołkach,
z których żaden nie jest izolowany?
ti ny G e x 2 .txt 4.1.2. Narysuj w stylu podobnym do rysunków z tekstu
12 (strona 536) listy sąsiedztwa zbudowane na podstawie pliku
16
8 4 tinyGex2.txt (po lewej) przez konstruktor klasy Graph uży
2 3 wający strum ienia wejściowego.
111
06 4.1.3. Utwórz konstruktor kopiujący dla klasy Graph.
36
10 3 Konstruktor powinien przyjmować graf Gjako dane wejścio
7 11 we oraz tworzyć i inicjować nową kopię grafu. Zmiany wpro
78 wadzone przez klienta w G nie powinny wpływać na nowo
11 8
2 0 utworzony graf.
6 2
52 4.1.4. Dodaj do klasy Graph metodę hasEdge(), która przyj
5 10 muje dwa argumenty typu i nt (v i w) oraz zwraca true, jeśli
3 10
8 1 © graf obejmuje krawędź v-w, i fal se w przeciwnym razie.
4 1
4.1.5. Zmodyfikuj klasę Graph tale, aby graf nie mógł obej
mować krawędzi równoległych ani pętli własnych.
4.1.6. Rozważmy graf o czterech wierzchołkach oraz krawędziach 0-1, 1-2, 2-3 i 3-0.
Narysuj tablicę list sąsiedztwa, która nie mogła powstać przez wywołania addEdge()
dla tych krawędzi niezależnie od kolejności ich dodawania.
4.1.7. Opracuj dla klasy Graph klienta testowego, który wczytuje graf ze strumienia
wejściowego o nazwie podanej jako argument wiersza poleceń, a następnie wyświetla
ten graf, posługując się m etodą to S tri ng().
4 .1. 8 . Opracuj implementację interfejsu API ldasy Search ze strony 540. Wykorzystaj
typ UF, tak jak opisano to w tekście.
4.1.9. Przedstaw (w taki sposób, jak na rysunku ze strony 545) szczegółowy ślad
działania wywołania dfs(0) dla grafu zbudowanego przez konstruktor Graph dla
strumienia wejściowego na podstawie pliku tinyGex2.txt (zobacz ć w i c z e n i e 4 . 1 .2 ).
Narysuj też drzewo reprezentowane przez tablicę edgeTo [].
p u b lic c la s s G raphProperties
4.1.18. Obwód grafu to długość najkrótszego cyklu. Jeśli graf jest acykliczny, obwód
to nieskończoność. Dodaj do klasy GraphProperti es metodę gi rth () zwracającą ob
wód grafu. Wskazówka: uruchom metodę BFS dla każdego wierzchołka. Najkrótszy
cykl obejmujący s to najkrótsza ścieżka z s do pewnego wierzchołka v plus krawędź
łącząca v z powrotem z s.
4 .1.19. Przedstaw (w taki sposób, jak na rysunku ze strony 557) szczegółowy ślad
działania klasy CC przy wyszukiwaniu spójnych składowych w grafie zbudowanym
przez konstruktor klasy Graph dla strum ieni wejściowych na podstawie pliku tiny-
Gex2.txt (zobacz ć w i c z e n i e 4 . 1 .2 ).
4.1 .2 2 . Uruchom program Symbol Graph dla pliku movies.txt, aby znaleźć liczbę
Bacona dla aktorów nominowanych w tym roku do nagrody Oscara.
4.1.25. Zmodyfikuj program DegreesOfSeparati on, aby jako argument wiersza po
leceń przyjmował wartość y typu i nt i pomijał filmy starsze niż y lat.
4.1 a Grafy nieskierowane 573
PROBLEMY DO ROZWIĄZANIA
0-1 0-2 0-3 1-3 1-4 2-5 2-9 3-6 4-7 4-8 5-8 5-9 6-7 6-9 7-8
0-1 0-2 0-3 1-3 0-3 2-5 5-6 3-6 4-7 4-8 5-8 5-9 6-7 6-9 8-8
0-1 1-2 1-3 0-3 0-4 2-5 2-9 3-6 4-7 4-8 5-8 5-9 6-7 6-9 7-8
4-1 7-9 6-2 7-3 5-0 0-2 0-8 1-6 3-9 6-3 2-8 1-5 9-8 4-5 4-7
Które z tych grafów obejmują cykle Eulera (w talach cyklach każda krawędź jest od
wiedzana dokładnie raz)? Które grafy obejmują cykle Hamiltona (w takich cyklach
każdy wierzchołek jest odwiedzany dokładnie raz)?
4 .1.31. Wymienianie grafów. Ile istnieje różnych grafów nieskierowanych o V wierz
chołkach i E krawędziach (bez krawędzi równoległych)?
4 .1.35. Dwuspójność. Graf jest dwuspójny, jeśli każda para wierzchołków jest połą
czona dwoma rozłącznymi ścieżkami. Punkt artykulacji w grafie spójnym to wierz
chołek, którego usunięcie (wraz z sąsiednimi krawędziami) spowodowałoby, że graf
stałby się niespójny. Udowodnij, że każdy graf bez punktów artykulacji jest dwu
spójny. Wskazówka: dla pary wierzchołków s i t oraz łączącej je ścieżki wykorzystaj
fakt, że żaden z wierzchołków w ścieżce nie jest punktem artykulacji, do utworzenia
dwóch rozłącznych ścieżek łączących s i t.
4 . 1 . 3 7 . Grafy euklidesowe.
Zaprojektuj i zaimplementuj interfejs API klasy
EuclideanGraph. Klasa m a służyć do tworzenia grafów, których wierzchołkami są
punkty w przestrzeni współrzędnych. Dołącz metodę show() i wykorzystaj w niej
bibliotekę StdDraw do rysowania grafu.
| ' EKSPERYMENTY
4.1.42. Losowe grafy euklidesowe. Napisz używającego klasy Eucl i deanGraph klienta
RandomEucl ideanGraph (zobacz ć w i c z e n i e 4 . 1 .3 7 ), tworzącego grafy losowe przez
wygenerowanie w przestrzeni V losowych punktów i późniejsze połączenie każdego
punktu z wszystkimi punktam i w prom ieniu d od środka. Uwaga: graf prawie na
pewno będzie spójny, jeśli d jest większe od wartości progowej f \ g v T f v , i prawie na
pewno będzie niespójny, jeżeli d ma mniejszą wartość.
4 .1.43. Grafy losowe oparte na siatce. Napisz używającego klasy Eucl i deanGrap klien
ta RandomGri dGraph, który generuje grafy losowe, łącząc wierzchołki uporządkowane
w siatce f v na f y z ich sąsiadami (zobacz ć w i c z e n i e 1 . 5 . 1 5 ). Wzbogać program
tak, aby dodawał R dodatkowych losowych krawędzi. Dla dużych R zmniejsz siatkę
tak, aby łączna liczba krawędzi wynosiła mniej więcej V. Dodaj wersję, w której do
datkowa krawędź łączy wierzchołki s i t z prawdopodobieństwem odwrotnie propor
cjonalnym do odległości euklidesowej między tymi wierzchołkami.
4 .1.45. Losowe grafy przedziałowe. Rozważmy zbiór V przedziałów (par liczb rze
czywistych) na osi liczb rzeczywistych. Taka kolekcja wyznacza graf przedziałowy,
w którym każdemu przedziałowi odpowiada jeden wierzchołek. Jeśli przedziały choć
częściowo się pokrywają (mają wspólne punkty), między wierzchołkami istnieje kra
wędź. Napisz program generujący w przedziale jednostkowym V losowych przedzia
łów o długości d i tworzący odpowiedni graf przedziałowy. Wskazówka: użyj drzewa
BST.
4.1 a Grafy nieskierowcine 577
4.1.50. Możliwość przypisania dwóch kolorów. Większości grafów nie m ożna przy
pisać dwóch kolorów, a m etoda DFS pozwala szybko to stwierdzić. Przeprowadź te
sty empiryczne, aby zbadać liczbę krawędzi sprawdzanych przez program TwoCol or.
Uwzględnij różne modele grafów.
4.2. GRAFY SKIEROW ANE
578
4.2 □ Grafy skierowane 579
Typ danych Digraph Przedstawiony poniżej interfejs API i kod klasy Di graph
zaprezentowany na następnej stronie są prawie takie same, jak dla klasy Graph
(strona 538).
public c la s s Digraph
D ig ra p h (in t V) Tworzy digraf o V wierzchołkach i bez krawędzi
D ig ra p h (In in) Wczytuje digraf ze strumienia wejściowego i n
in t V() Zwraca liczbę wierzchołków
in t E() Zwraca liczbę krawędzi
void addEdge(int v, in t w) Dodaje do digrafu krawędź v->w
Wierzchołki powiązane z v krawędziami
Ite ra b le < In te g e r> a d j(i nt v)
wychodzącymi z v
Digraph re ve rse () Odwraca digraf
S t r in g t o S t r in g O Zwraca reprezentację w postaci łańcucha znaków
Odwracanie digrafu W interfejsie API klasy Di graph znalazła się dodatkowa meto
da, reverse (), zwracająca kopię digrafu po odwróceniu wszystkich krawędzi. Metoda
ta jest czasem potrzebna przy przetwarzaniu digrafów, ponieważ umożliwia klientom
znalezienie krawędzi prowadzących do każdego wierzchołka (metoda ad j () zwraca
tylko wierzchołki powiązane krawędziami wychodzącymi z każdego wierzchołka).
N a zw y sym boliczne W łatwy sposób można umożliwić klientom stosowanie
nazw symbolicznych przy korzystaniu z digrafów. Aby zaimplementować klasę
Symbol Di graph podobną do klasy Symbol Graph ze strony 564, należy zastąpić wszyst
kie wystąpienia słowa Graph słowem Di graph.
public Iterable<Integer> a d j( in t v)
V 5 T
{ return adj [ v ] ; }
3 T
public Digraph reverse()
{ ^ 0
Digraph R = new Digraph(V);
f o r (in t v = 0; v < V; v++)
for (in t w : adj (v))
R.addEdge(w, v ) ; ^ 0 -0
return R; V 7 9
} N. 11 10
A 12
Typ danych Digraph jest prawie identyczny z klasą
Graph (strona 538). Różnice polegają na tym, że tu A 4 12
metoda addEdge() wywołuje metodę add () tylko raz
S .
i dostępna jest metoda egzemplarza re v e rs e d , któ 9
ra zwraca kopię grafu z odwróconymi krawędziami.
Format danych wejściowych digrafu
Ponieważ część kodu można łatwo napisać na pod i reprezentacja w postaci list sąsiedztwa
stawie odpowiedniego kodu z ldasy Graph, pomijamy
metodę to S t r in g ( ) (zobacz tabelę na stronie 535)
i konstruktor oparty na strumieniu wejściowym (zo
bacz stronę 538).
582 RO ZD ZIA Ł 4 □ Grafy
public c la s s DirectedDFS
{
private boolean[] marked;
p ublic DirectedDFS(Digraph G, in t s)
{
marked = new boolean[G .V ()];
dfs(G, s );
}
marked[v] = true;
f o r (in t w : G.a d j(v ) ) % java DirectedDFS tin yD G .tx t 2
i f (¡marked[w]) dfs(G, w); 0 1 2 3 4 5
marked[] ad j []
dfs(O) 0 T 0 51
1 1
2 2 0 3
3 3 52
4 4 32
5 5 4
dfs(5) 0 T 0 51
1 1
2 2 0 3
3 3 52
4 4 32
5 T 5 4
d f s (4 ) 0 T 0 5 1
1 1
2 2 0 3
3 3 5 2
4 T 4 3 2
5 T 5 4
d f s (3 )
S p r a w d z a n ie 5 0 T 0 51
1 1
2 2 0 3
3 T 3 52
4 T 4 32
5 T 5 4
d f s ( 2) 0 T 0 5 1
I S p r a w d z a n ie 1 1
1 S p r a w d z a n ie 2 T 2 0 3
2 G otow y 3 T 3 5 2
3 G o to w y 4 T 4 3 2
5 T 5 4
S p r a w d z a n ie 2
4 G otow y
5 G otow y
d fs(1) 0 T 0 5 1
1 G otow y 1 T 1
0 G otow y 2 T 2 0 3
3 T 3 5 2
4 T 4 3
5 T 5 4
Ślad działania algorytmu dla przykładowego digrafu pokazano na stronie 584. Ślad
ten jest nieco prostszy niż odpowiadający mu ślad dla grafów nieskierowanych, p o
nieważ m etoda DFS jest algorytmem przetwarzania digrafów (z jedną reprezentacją
każdej krawędzi). Warto przyjrzeć się śladowi, aby utrwalić zrozumienie przeszuki
wania w głąb w digrafach.
Przywracanie pam ięci m etodą znacz Bezpośrednio
i zam iataj (ang. marle and sweep) dostępne obiekty
Określanie osiągalności z wielu źródeł
jest ważne w kontekście typowych sy
stemów zarządzania pamięcią, w tym
w wielu implementacjach Javy. Digraf,
w którym każdy wierzchołek repre
zentuje obiekt, a każda krawędź od
powiada referencji do obiektu, jest
dobrym modelem wykorzystania pa
mięci w działającym programie Javy.
W każdym momencie wykonywania
programu niektóre obiekty są dostępne
bezpośrednio, a każdy obiekt, do któ
rego nie można z nich dotrzeć, podlega
mechanizmowi przywracania pamięci.
W strategii przywracania pamięci me
todą znacz i zamiataj jeden bit na obiekt
rezerwowany jest na potrzeby mechanizmu przywracania pamięci. Mechanizm okre
sowo oznacza zbiór potencjalnie dostępnych obiektów, uruchamiając algorytm osią
galności dla digrafów (podobny do Di rectedDFS), i przechodzi przez wszystkie obiekty,
odzyskując pamięć nieoznaczonych, co pozwala wykorzystać ją na nowe obiekty.
Znajdow anie ścieżek w digrafach Algorytmy DepthFi rstP ath s ( a l g o r y t m 4.1 ze
strony 548) i BreadthFi rstP ath s ( a l g o r y t m 4.2 ze strony 552) również są przezna
czone głównie do przetwarzania digrafów. Także tu identyczne interfejsy API i kod
(z nazwą Graph zmienioną na Digraph) pozwalają skutecznie rozwiązać następujące
problemy.
Z n a jd o w a n ie ścieżek skierow an ych z je d n e g o źró d ła . Dla digrafu i wierzchołka
źródłowego s zapewnij obsługę zapytań w postaci: Czy istnieje ścieżka skierowana
z s do danego wierzchołka docelowego v? Jeśli tak, należy znaleźć taką ścieżkę.
Z n a jd o w a n ie n ajkrótszych ścieżek skierow an ych z je d n e g o źró d ła . Dla digrafu
i wierzchołka źródłowego s zapewnij obsługę zapytań w postaci: Czy istnieje ścież
ka skierowana z s do danego wierzchołka docelowego v? Jeśli tak, należy znaleźć
najkrótszą ścieżkę tego rodzaju (o minimalnej liczbie krawędzi).
W witrynie i w ćwiczeniach w końcowej części podrozdziału rozwiązania tych prob
lemów nazywamy DepthFi rstDi rectedPaths oraz BreadthFi rstDi rectedPaths.
586 R O ZD ZIA Ł 4 □ Grafy
( A lg o ry tm y
A lg e b ra lin io w a —(A n a liz a m a te m a ty c z n a )
/ T e o re ty c z n e
\ n a u k i k o m p u te r o w e /
rz r r- s / W p ro w a d z e n ie d o \
v a z Y a n y c J \ n a u k k o m p u te ro w y c h /
( S z tu c z n a in te lig e n c ja ) - ( R o b o ty k a )
( P ro g ra m o w a n ie z a a w a n s o w a n e
l)
( U c z e n ie m a s z y n o w e J — « - ( Sieci n e u ro n o w e
( B io lo g ia o b lic z e n io w a ~ )
( Obliczenia naukowe
Cykle w digrafach Jeśli zadanie x trzeba ukończyć przed zadaniem y, zadanie y przed
zadaniem z, a zadanie z przed zadaniem x, ktoś musiał popełnić błąd, ponieważ nie
można uwzględnić wszystkich tych ograniczeń jednocześnie. Ogólnie jeśli w problemie
szeregowania z ograniczeniami pierwszeństwa występuje cykl skierowany, rozwiązanie
nie istnieje. Aby wykryć takie błędy, trzeba rozwiązać następujący problem.
p u b lic c la s s D irectedCycle
Ite ra b l e<Intege r> cyc! e () Zwraca wierzchołki z cyklu (jeśli cykl istnieje)
p ublic c la s s D irectedCycle
{
private boolean[] marked;
private in t [] edgeTo;
p rivate Stack<Integer> cycle; // Wierzchołki w cyklu ( j e ś l i ten
// istn ie je ).
p rivate boolean[] onStack; // Wierzchołki na s t o s ie wywołań
// rekurencyjnych.
p ublic DirectedCycle(Digraph G)
{
onStack = new boolean[G .V()];
edgeTo = new i nt[G.V ()];
marked = new b oolean[G .V ()];
f o r (in t v = 0; v < G.V(); v++)
i ł (!marked[v]) dfs(G, v ) ;
}
private void d f s ( D i graph G, in t v) v w x c y c le
3 5 3 3
{ 3 5 4 4 3
onStack[v] = true; 3 5 4 5 43
marked[v] = true; 3 5 4 3 54 3
for (in t w : G.adj(v))
i ł (t h is . h a s C y c le Q ) return; Ślad procesu wyznaczania cyklu
e lse i f (¡marked[w])
{ edgeTo [w] = v; dfs(G, w); }
e lse i f (onStackfw])
{
cycle = new S ta c k < In te g e r> ();
fo r (in t x = v; x != w; x = edgeTofx])
c y c le .p u s h (x );
cycle.push(w );
c y c le .p u s h (v );
}
onStack[v] = fa lse ;
}
publ i c c la s s Topological_____________
Konstruktor używany do sortowania
Topological (Digraph G) topologicznego
boolean i S DAG () Czy &jest grafem DAG?
Iterabl e<Integer> order () Zwraca wierzchołki w porządku
topologicznym
Dowód. Jeśli digraf obejmuje cykl skierowany, nie występuje w nim porządek
topologiczny, jednak algorytm, który wkrótce omówimy, wyznacza porządek
topologiczny dla dowolnego grafu DAG.
Na następnej stronie pokazano ślad działania klasy DepthFi rstOrder dla przykładowego
grafu DAG. Można w łatwy sposób zaimplementować metody pre () , post () i reverse-
Post() przydatne w zaawansowanych algorytmach przetwarzania grafów. Przykładowo,
metoda order () w klasie Topol ogi cal obejmuje wywołanie metody reversePost ().
( i
pre p ost re v e rse P o st
dfs COD 0
dfs(5) O 5 Kolejka Kolejka Stos
dfs(4) 0 5 4 /
4 Gotowy / 4 / 4 /
5 Gotowy 4 5 5 4
dfs Cl) 0 5 4 1
1 Gotowy 4 5 1 15 4
dfs(6) 0 5 4 1 6
dfs(9) 054169
dfs C U D 0 5 4 1 6 9 11
dfs(12) 0541691112
12 Gotowy 4 5 1 12 12 1 5 4
11 Gotowy 4 5 1 12 11 11 12 1 5 4
dfs(10) 0 5 4 1 6 9 1 1 1 2 10
10 Gotowy 4 5 1 12 11 10 10 11 12 1 5 4
Sprawdzani e 12
9 Gotowy 4 5 1 12 11 10 9 9 10 11 12 1 5 4
Sprawdzanie 4
6 Gotowy 4 5 1 12 U 10 9 6 6 9 10 11 12 1 5 4
0 Gotowy 4 5 1 1 2 1 1 10 9 6 0 0 6 9 10 1 1 1 2 1 5 4
Sprawdzanie 1
dfs(2) 0 5 4 1 6 9 1 1 1 2 10 2
Sprawdzanie 0
dfs(B) 0 5 4 1 6 9 1 1 12 10 2 3
Sprawdzanie 5
3 Gotowy 4 5 1 1 2 1 1 10 9 6 0 3 3 0 6 9 10 11 12 1 5 4
2 Gotowy
Sprawdzanie 3 4 5 1 12 11 10 9 6 0 3 2 2 3 0 6 9 10 11 1 2 1 5 4
Sprawdzanie 4
Sprawdzanie 5
Sprawdzanie 6
dfs(73 0 5 4 1 6 9 1 1 1 2 10 2 3 7
Sprawdzanie 6
2 Gotowy 4 5 1 1 2 1 1 10 9 6 0 3 2 7 7 2 3 0 6 9 10 1 1 1 2 1 5 4
dfs(8) 0 5 4 1 6 9 11 12 10 2 3 7
Sprawdzanie 7
8 Gotowy 4 5 1 1 2 1 1 10 9 6 0 3 2 7 8 8 7 2 3 0 6 9 10 l i t 2 1 5 4
Sprawdzanie 9
Sprawdzanie 10
Sprawdzanie 11
t
Odwrócony
Sprawdzanie 12 postorder
Wyznaczanie porządków (preorder, postorder i odwrócony postorder) w digrafie przy przeszukiwaniu w głąb
___
592 RO ZD ZIA Ł 4 Grafy
public c la s s DepthFirstOrder
{
private boolean[] marked;
public DepthFirstOrder(Digraph G)
{
pre = new Queue<Integer>();
post = new Queue<Integer>();
reversePost = new S ta c k < In te g e r> ();
marked = new boolean[G.V() ];
post.enqueue(v);
re ve rse P ost.p u sh (v);
}
public c la s s Topological
i
private Iterable<Integer> order; // Porządek topologiczny.
public Topological(Digraph G)
{
DirectedCycle cyclefinder = new DirectedCycle(G);
i f (¡cyclefinder.hasCycleO)
{
DepthFirstOrder dfs = new DepthFirstOrder(G);
order = d f s . r e v e r s e P o s t ( ) ;
}
}
f o r (in t v : top.order(j)
S td O u t.p rin tln (sg.n am e (v));
}
)
Ten klient klas DepthFi rstOrder i Di rectedCycl e zwraca porządek topologiczny dla grafu
DAG. Klient testowy rozwiązuje problem szeregowania z ograniczeniami pierwszeństwa dla
typu Symbol Di graph. Metoda egzemplarza order() zwraca nuli, jeśli dany digraf nie jest
grafem DAG; w przeciwnym razie zwraca iterator udostępniający wierzchołki w porządku
topologicznym. Kod klasy Symbol Di graph pominięto, ponieważ jest dokładnie taki sam, jak
kod klasy Symbol Graph (strona 564), przy czym we wszystkich miejscach słowo Graph należy
zastąpić słowem Di graph.
594 RO ZD ZIA Ł 4 o Grafy
% more jo b s . tx t
Algorytm y/Teoretyczne nauki komputerowe/Bazy danych/O bliczenia naukowe
Wprowadzenie do nauk komputerowych/zaawansowane Programowanie/Algorytmy
Zaawansowane programowanie/Obliczenia naukowe
O b licze n ia naukowe/Biologia obliczeniow a
Teoretyczne nauki komputerowe/Biol ogia obliczeniow a/Sztuczna in t e lig e n c ja
Algebra 1 i niowa/Teoretyczne nauki komputerowe
A n a liza matematyczna/Algebra lin iow a
Sztuczna in t e lig e n c ja / S ie c i neuronowe/Robotyka/Uczenie maszynowe
Uczenie maszynowe/Sieci neuronowe
Potrzebny jest poniższy interfejs API. Jest to przeznaczony dla digrafów odpowiednik
klasy CC (strona 555).
public c la ss SCC
Zakładamy, że v
jest osiągalny z s, v musi być \ G R musi
dfs(s)
dlatego G musi d fs C v ) „gotowy" d fs(D obejmować
obejmować przed s. Inaczej ścieżkę z s do v
dfs(v) ścieżkę z s do v ,, , wywołanie dfsfv)
v Gotowy-«— , '
7 dfs(v)
; / znalazłoby \ '
v Gotowy dfsCs) / sięp rzed \ _ v Gotowy
J dfs(s) w G \ ■
s Gotowy s Gotowy s Gotowy
i ! i
Niemożliwe, ponieważ G B
obejmuje ścieżkę z v do s
Dowód poprawności algorytmu Kosaraju
4.2 Grafy skierowane 599
public c la s s KosarajuSCC
{
private boolean[] marked; // Oznaczone w ierzchołki,
private i n t [] id; // Identyfikatory składowych,
private in t count; // Liczba s iln y c h składowych.
public KosarajuSCC(Digraph G)
{
marked = new b oolean[G .V ()];
id = new i n t [ G . V ( ) ] ;
DepthFirstOrder order = new D e p th F irs tO rd e r(G .re v e rs e Q );
f o r (in t s : order.reversePost())
i f (!marked[s])
{ dfs(G, s ) ; count++; }
i % java KosarajuSCC tin yD G .tx t
lic z b a składowych: 5
p rivate void dfs(Digraph G, in t v) l
0 5 4 3 2
{
11 12 9 10
marked [v] = true;
6
id[v] = count; 8 7
fo r (in t w : G.adj(v))
i f (! marked [w])
dfs(G, w);
publ ic in t i d ( int v)
{ return i d [ v ] ; }
public in t count()
{ return count; }
}
Algorytm Kosaraju to skrajny przykład metody, której kod łatwo napisać, ale trud
no zrozumieć. Kod jest wprawdzie tajemniczy, ale jeśli prześledzisz krok po kroku
dowód poniższego twierdzenia na podstawie rysunku ze strony 598, przekonasz się,
że algorytm jest poprawny.
f dfs(l)
dfs(O )
dfs(6) V 1 Gotowy
I dfs(7:> /T J n m r*
dfs(5)
I dfsW
| sprawdzanie 7
/ ^
/ (7) dfs(4)
8 Gotow y / J dfs(3)
1 7 Gotowy / /CN Sprawdzanie 5
6 Gotowy dfs(2)
dfs(2) / V \ i Sprawdzanie 0
dfs(4) / ^ -n \ \ 1 Sprawdzanie 3
d f s ( ll) / no) \ \ 2 Gotowy
df s(9) \ \ \ 3 Gotowy
dfs(12) \ 1 JL \ \ \ Sprawdzanie 2
S p ra w d z a n ie 1 1 \ \ ( 1 2 ) ) \ \ 4 Gotowy Silnie
dfs(10) 5 Gotowy spójne
| sprawdzani Sprawdzanie 1 składowe
10 Gotowy V,0 Gotowy ,
12 Gotowy Sprawdzanie 2
sprawdzanie 8 Sprawdzanie 4
Sprawdzanie 6 Sprawdzanie 5
9 Gotowy Sp ra w d za n i e 3_________
11 Gotowy C dfs(1 1 ) ^ '
Sprawdzanie 6 Sprawdzanie 4
dfs(5) dfs(1 2 )
dfs(3) dfs(9)
I Sprawdzanie 4 I Sprawdzanie 11
I Sprawdzanie 2 | dfs(1 0 )
3 Gotowy i I Sprawdzanie 12
Sprawdzanie 0 1 10 Gotowy
5 Gotowy 9 Gotowy
4 Gotowy 12 Gotowy
Sprawdzanie 3 y n Gotowy „
2 Gotowy Sprawdzanie 9
0 Gotowy Sprawdzanie 12
dfs(l) Sprawdzanie 10
Sprawdzanie 0 C dfs(6)
1 Gotowy Sprawdzanie 9
Sprawdzanie 2 Sprawdzanie 4
Sprawdzanie 3 Sprawdzanie 0
Sprawdzanie 4 V 6 Gotowy J
Sprawdzanie 5 Odwrócony porządek f d f s (7) >
Sprawdzanie 6 postorder na potrzeby Sprawdzanie 6
Sprawdzanie 7 drugiego wywołania d f s ( ) dfs(8)
Sprawdzanie 8 (należy czytać od dołu) | sprawdzanie 7
Sprawdzanie 9 I Sprawdzanie 9
Sprawdzanie 10 8 Gotowy
Sprawdzanie 11 7 Gotowy _______
Sprawdzanie 12 Sprawdzanie 8
m
602 R O ZD ZIA Ł 4 a Grafy
a lg o ry tm k o s a ra ju r o z w i ą z u j e o p is a n y p o n iż e j o d p o w ie d n ik p r o b le m u o k r e
ś la n ia p o łą c z e ń w g ra fa c h n ie s k ie ro w a n y c h , p r z e d s ta w io n e g o po raz p ie rw s z y
w r o z d z i a l e i . i p o n o w n ie p rz y to c z o n e g o w p o d r o z d z i a l e 4.1 ( s t r o n a 546).
Osiągalnośćpo raz w tóry Za pom ocą klasy CC dla grafów nieskierowanych na pod
stawie tego, że wierzchołki v i wsą połączone, można wywnioskować, iż istnieje ścież
ka z v do w i (ta sama) ścieżka z w do v. Przy użyciu klasy KosarajuCC na podstawie
faktu, że v i w są silnie połączone, m ożna wywnioskować, iż istnieje ścieżka z v do
w i (inna) ścieżka z w do v. Co jednak z param i wierzchołków, które nie są silnie połą
czone? Możliwe, że istnieje ścieżka z v do wlub z w do v, a nie obie z nich.
Osiągalność dla dowolnej pary. Dla digrafu zapewnij obsługę pytań w postaci:
Czy istnieje ścieżka skierowana z danego wierzchołka v do innego wierzchołka w?
W grafach nieskierowanych analogiczny problem jest równoznaczny z problemem
określania połączeń. W przypadku digrafów ten problem różni się od problemu
określania silnych połączeń. W implementacji klasy CC zastosowano wstępne prze
twarzanie w czasie liniowym, aby zapewnić odpowiadanie w stałym czasie na tego
rodzaju zapytania dla grafów nieskierowanych. Czy można uzyskać podobną wydaj
ność dla digrafów? Nad tym na pozór niewinnym pytaniem eksperci zastanawiali się
przez dziesięciolecia. Aby lepiej zrozumieć trudność zadania, przyjrzyj się rysunkowi
na stronie 604, stanowiącemu ilustrację następującego podstawowego zagadnienia.
4.2 ei Grafy skierowane 603
604 RO ZD ZIA Ł 4 □ Grafy
p u b lic c la s s T ra n sitiv e C lo s u re
[ PYTANIA I ODPOWIEDZI
O. Tak, jednak pętla własna nie jest konieczna, aby wierzchołek był osiągalny z niego
samego.
608 ROZDZIAŁ 4 a Grafy
I ĆWICZENIA
4.2.1 . Jaka jest maksymalna liczba krawędzi w digrafie o V wierzchołkach i bez kra
wędzi równoległych? Jaka jest m inim alna liczba krawędzi w digrafie o V wierzchoł
kach, z których żaden nie jest izolowany?
4.2.5. Zmodyfikuj klasę Digraph tak, aby krawędzie równoległe i pętle własne były
niedozwolone.
p u b lic c la s s Degrees
4.2.9. Napisz metodę, która sprawdza, czy dana permutacja wierzchołków grafu
DAG jest porządkiem topologicznym tego grafu.
4.2.10. Na podstawie grafu DAG ustal, czy istnieje porządek topologiczny, którego
nie można uzyskać przez zastosowanie algorytmu opartego na DFS niezależnie od
kolejności wybierania sąsiednich wierzchołków. Udowodnij odpowiedź.
4.2.11. Opisz rodzinę digrafów rzadkich, w których liczba cykli skierowanych roś
nie wykładniczo względem liczby wierzchołków.
4.2.12. Ile krawędzi istnieje w domknięciu przechodnim digrafu będącego prostą
ścieżką skierowaną o V wierzchołkach i V - 1 krawędziach?
4.2.14. Udowodnij, że silnie spójne składowe grafu GRsą takie same, jak w grafie G.
4.2.16. Co się stanie po uruchom ieniu algorytmu Kosaraju dla grafu DAG?
4.2.17. Odwrócony porządek postorder dla odwrotności grafu jest taki sam, jak po
rządek postorder dla grafu — prawda czy fałsz?
4.2.18. Oblicz zapotrzebowanie pamięciowe klasy Digraph o V wierzchołkach i E
krawędziach, posługując się modelem kosztów pamięciowych z p o d r o z d z i a ł u 1 .4 .
610 ROZDZIAŁ 4 n Grafy
! PROBLEMY DO ROZWIĄZANIA
4 .2.2 1 . Najbliższy wspólny przodek w grafach DAG. Na podstawie grafu DAG i dwóch
wierzchołków, v i w, znajdź najbliższego wspólnego przodka (ang. lowest common
ancestor — LCA) tych wierzchołków. LCA wierzchołków v i w to taki ich przodek,
który nie ma potomków będących przodkami v i w. Wyznaczanie przodka LCA jest
przydatne w językach programowania (przy wielodziedziczeniu), w analizie danych
genealogicznych (przy znajdowaniu poziomu chowu wsobnego w grafie reprezentu
jącym rodowód) i w innych obszarach. Wskazówka-, zdefiniuj wysokość wierzchołka
v w grafie DAG jako długość najdłuższej ścieżki z korzenia do v. W śród wierzchoł
ków będących przodkam i v i wprzodkiem LCA jest ten o największej wysokości.
4.2.22. Najkrótsza ścieżka przez przodka. Na podstawie grafu DAG i dwóch wierz
chołków, v i w, znajdź najkrótszą ścieżkę przez przodka między nimi. Ścieżka przez
przodka między v i wobejmuje wspólnego przodka x, a składa się z najkrótszej ścieżki
z v do x i najkrótszej ścieżki z wdo x. Najkrótsza ścieżka przez przodka to taka ścieżka
przez przodka, której łączna długość jest zminimalizowana. Rozgrzewka: znajdź graf
DAG, w którym najkrótsza ścieżka przez przodka prowadzi przez wspólnego przod
ka x, który nie jest przodkiem LCA. Wskazówka: uruchom dwukrotnie metodę BFS
— raz dla v i raz dla w.
4.2.23. Silnie spójna składowa. Opisz działający w czasie liniowym algorytm do wy
znaczania silnie spójnej sIdadowej, obejmującej dany wierzchołek v. Na podstawie
tego algorytmu opisz prosty algorytm kwadratowy do wyznaczania silnie spójnych
składowych digrafu.
4.2.28. Wyliczanie grafów DAG. Podaj wzór na liczbę grafów DAG o V wierzchoł
kach i E krawędziach.
4.2.29. Wyrażenia arytmetyczne. Napisz klasę przetwarzającą grafy DAG, które re
prezentują wyrażenia arytmetyczne. Użyj tablicy indeksowanej wierzchołkami do
przechowywania wartości odpowiadających każdemu wierzchołkowi. Zakładamy, że
wartości odpowiadające liściom są znane. Opisz rodzinę wyrażeń arytmetycznych
cechujących się tym, że rozmiar drzewa wyrażenia rośnie wykładniczo względem
odpowiedniego grafu DAG (tak więc czas wykonania program u dla grafu DAG jest
proporcjonalny do logarytmu czasu wykonania dla drzewa).
612 ROZDZIAŁ 4 a Grafy
i EKSPERYMENTY
4.2.33. Losowe digrafy proste. Napisz program RandomDi graph, który przyjmuje war
tości V i E z wiersza poleceń i tworzy — z takim samym prawdopodobieństwem
— każdy z możliwych prostych digrafów o V wierzchołkach i E krawędziach.
E K S P E R Y M E N T Y (ciąg dalszy)
Graf ważony (inaczej graf z krawędziami ważonymi) oparty jest na modelu, w którym
z każdą krawędzią powiązane są wagi {koszty). Takie grafy są naturalnym modelem
w wielu obszarach. Na mapie lotów, gdzie krawędziom odpowiadają trasy, wagi mogą
reprezentować odległości lub ceny. W obwodzie elektrycznym, gdzie krawędziom
odpowiadają kable, wagi mogą reprezentować długość kabla, jego cenę lub czas prze
syłania sygnału. Naturalnym celem jest wtedy minimalizacja kosztów. W tym pod
rozdziale omawiamy modele nieskierowanych grafów ważonych i badamy algorytmy
dotyczące pewnego problemu.
ti nyEWG.txt
M inim alne drzewo rozpinające. Na podstawie
"■ *- 8
nieskierowanego grafu ważonego znajdź m ini
malne drzewo rozpinające (ang. minimum span-
4 5 0.35 Krawędź drzewa
4 7 0.37 ning tree — MST).
' M S T (czarna)
5 7 0.28
0 7 0.16
15 0.32 Definicja. Przypominamy, że drzewo rozpi
04 0.38
nające grafu to spójny podgrafbez cykli, obej
2 3 0.17
1 7 0.19 mujący wszystkie wierzchołki. Minimalne
0 2 0.26
drzewo rozpinające grafu ważonego to drze
1 7 0.36
1 3 0.29 wo rozpinające, którego waga (suma wag
2 7 0.34 krawędzi) jest nie większa niż waga innych
Krawędź spoza
6 2 0.40
3 b 0.52 drzewa M S T (szara) drzew rozpinających.
6 0 0.58
6 4 0.93
Graf ważony i jego drzewo MST W tym podrozdziale badamy dwa klasyczne al
gorytmy wyznaczania drzew MST — algorytm
Prima i algorytm Kruskala. Algorytmy
Zastosowanie Wierzchołek Krawędź
te są łatwe do zrozumienia i nietrud
Obwód Komponent Kabel ne do zaimplementowania. Należą
do najstarszych i najlepiej poznanych
Linie lotnicze Lotnisko Trasa lotu
algorytmów spośród opisanych w tej
Sieci energetyczne Elektrownia Linie przesyłowe książce. Zastosowanie w nich współ
czesnych struktur danych przynosi
Analiza obrazu Cechy Relacja bliskości
istotne korzyści. Ponieważ drzewa
charakterystyczne
MST mają wiele ważnych zastoso
Typowe zastosowania drzew M ST
wań, algorytmy do rozwiązywania
omawianego problemu są badane przynajmniej od lat 20 . ubiegłego wieku (począt
kowo w kontekście sieci energetycznych, później w ramach sieci telefonicznych).
Obecnie algorytmy wyznaczania drzew MST odgrywaj ą istotną rolę w proj ektowaniu
wielu rodzajów sieci (komunikacyjnych, elektrycznych, hydraulicznych, kom putero
wych, drogowych, kolejowych, powietrznych i wielu innych), a także przy badaniu
sieci biologicznych, chemicznych i fizycznych występujących w naturze.
616
4.3 o Minimalne drzewa rozpinające 617
Definicja. Przekrój (ang. cut) grafu to podział jego wierzchołków na dwa niepuste
rozłączne zbiory. Krawędź przekroju (ang. Crossing edge) dla danego przekroju to kra
wędź, która łączy wierzchołek z jednego zbioru z wierzchołkiem z drugiego zbioru.
Typ danych dla grafów ważonych Jak można przedstawić grafy ważone?
Prawdopodobnie najprostszym sposobem jest rozwinięcie podstawowej reprezenta
cji grafu z p o d r o z d z i a ł u 4 . 1 . W reprezentacji opartej na macierzy sąsiedztwa m a
cierz może obejmować wagi krawędzi zamiast wartości logicznych. W reprezentacji
opartej na listach sąsiedztwa można zdefiniować węzeł obejmujący zarówno wierz
chołek, jak i pole z wagą. Węzły te są umieszczane na listach sąsiedztwa (jak zwykle
koncentrujemy się na grafach rzadkich i opracowanie reprezentacji opartej na listach
sąsiedztwa pozostawiamy jako ćwiczenia). To klasyczne podejście jest atrakcyjne, tu
jednak stosujemy inną metodę. Jest ono tylko nieco bardziej skomplikowane, spra
wia, że programy są znacznie przydatniejsze w ogólnym kontekście, i wymaga ogól
niejszego interfejsu API, umożliwiającego przetwarzanie obiektów typu Edge.
In te rf e js API k ra w ę d z i w a ż o n e j
p u b lic c la s s EdgeWeightedGraph
Ten interfejs API jest bardzo podobny do interfejsu API klasy Graph (strona 534).
Dwie ważne różnice polegają na tym, że nowa ldasa jest oparta na Hasie Edge i obej
muje dodatkową metodę edges() (przedstawiona po prawej stronie), która um oż
liwia Hientom iterowanie po wszystldch Hawędziach grafu (z pominięciem pętli
własnych). Pozostała część implementacji ldasy EdgeWeightedGraph, przedstawiona
na stronie 623, przypomina implementację gra
fów nieslderowanych bez wag z p o d r o z d z i a ł u p u b lic ite ra b le < E d g e > e d g e s()
public in t e it h e r Q
{ return v; }
public S t r in g t o S t r in g Q
{ return String.form at("%d-%d % .2 f", v, w, weight); }
}
public c la s s EdgeWeightedGraph
{
private final in t V; // Liczba wierzchołków,
private in t E; // Liczba krawędzi,
private Bag<Edge>[] adj; // L i s t y sąsiedztwa.
public EdgeWeightedGraph(int V)
{
t h i s . V = V;
th is.E = 0 ;
adj = (Bag<Edge>[]) new Bag[V];
fo r (in t v = 0; v < V; v++)
adj [v] = new Bag<Edge>();
Porównywanie kraw ędzi według wag Interfejs A P I określa, że w klasie Edge na
leży zaimplementować interfejs Comparable i umieścić kod m etody compareTo().
Naturalna kolejność krawędzi w grafie ważonym jest wyznaczana przez wagi. Dlatego
implementacja metody compareTo() jest prosta.
K rawędzie równoległe Podobnie jak w implementacjach grafów nieskierowanych,
tak i tu dopuszczalne są krawędzie równoległe. Inna możliwość to opracowanie bar
dziej skomplikowanej implementacji klasy EdgeWei ghtedGraph, gdzie takie krawędzie
są niedopuszczalne (na przykład przez zachowanie krawędzi o minimalnej wadze ze
zbioru krawędzi równoległych).
Pętle własne Pętle własne są dozwolone. Jednak w implementacji metody edges()
w klasie EdgeWei ghtedGraph nie uwzględniamy pętli własnych, choć mogą one wy
stępować w danych wyjściowych lub w strukturze danych. Nie ma to wpływu na al
gorytmy dla drzew MST, ponieważ drzewa tego rodzaju nie obejmują pętli własnych.
W zastosowaniach, w których takie pętle są istotne, potrzebne mogą być odpowied
nie modyfikacje w kodzie.
p u b lic c la s s MST
K lient testowy Jak zwykle tworzymy przykładowe grafy i rozwijamy klienta testowe
go do testowania implementacji. Przykładowego klienta pokazano poniżej. Program
wczytuje krawędzie ze strum ienia wejściowego, tworzy graf ważony, wyznacza drze
wo MST grafu oraz wyświetla krawędzie drzewa MST i jego wagę.
% more mediumEWG.txt
250 1273
244 246 0.11 71 2
239 240 0.1 0616
238 245 0.0 6142
235 238 0 .07048
233 240 0 .07634
232 248 0.1 0223
231 248 0.1 0699
229 249 0 .10098
228 241 0.0 1473
226 231 0 .0 76 38
. . . [1263 i n n e kr awędzie]
% j a v a MST mediumEWG.txt
0 225 0.0 2383
49 225 0 .0 3 3 14
44 49 0.02107
44 204 0.0 17 7 4
49 97 0.0 3121
202 204 0.04207
176 202 0 .04299
176 191 0.0 2089
68 176 0.0 4396
58 68 0.0 4795
. . . [239 in n y ch kraw ęd zi]
10.46351
Drzewo MST
Graf euklidesowy o 250 węzłach (i 1273 krawędziach) oraz odpowiadające mu drzewo WIST
628 ROZDZIAŁ 4 a Grafy
Krawędź
Krawędź przekroju
niewybieralna
(kolor czerwony)
Twierdzenie L. Algorytm Prima wyznacza drze
(kolor szary)
wo MST dla dowolnego spójnego grafu ważonego.
i
Dowód. Bezpośrednio wynika z t w i e r d z e n i a k .
Rosnące drzewo wyznacza przekroje bez czarnych
krawędzi. Algorytm pobiera krawędź przekroju
\ \
Krawędź przekroju o minimalnej wadze, dlatego po kolei koloru
o minimalnej wadze
musi występować je krawędzie na czarno na podstawie algorytmu
Krawędź w drzewie M ST
drzewa
zachłannego.
(kolor czarny I
•i pogrubienie)
Przedstawiony wcześniej jednozdaniowy opis algo
Algorytm Prima - wyznaczanie drzew MST
rytm u Prima pozostawia bez odpowiedzi kluczowe
pytanie — jak można w wydajny sposób znaleźć krawędź przekroju o minimalnej
wadze? Zaproponowano kilka metod. Niektóre z nich omawiamy po opracowaniu
kompletnego rozwiązania, opartego na wyjątkowo prostym podejściu.
S tru ktu ry danych W implementacji algorytmu Prima posługujemy się kilkoma
prostymi i znanymi strukturam i danych. Wierzchołki drzewa, krawędzie drzewa
i krawędzie przekroju reprezentujemy w następujący sposób.
■ Wierzchołki drzewa. Używamy indeksowanej wierzchołkami tablicy marked[]
z wartościami logicznymi, w której marked [v] ma wartości tru e, jeśli v znajduje
się w drzewie.
■ Krawędzie w drzewie. Stosujemy jedną z dwóch struktur danych — kolejkę mst
do zapisywania krawędzi drzewa MST lub indeksowaną wierzchołkami tablicę
edgeTo[] z obiektami typu Edge, w której edgeTo[v] to obiekt Edge łączący v
z drzewem.
° Krawędzie przekroju. Korzystamy z kolejki priorytetowej Mi n PQ<Edge>, w której
krawędzie są porównywane według wag (zobacz stronę 622).
Wymienione struktury danych umożliwiają udzielenie bezpośredniej odpowiedzi na
podstawowe pytanie: „Która krawędź przekroju ma m inim alną wagę?”.
Tworzenie zbioru kraw ędzi przekroju Przy dodawaniu krawędzi do drzewa za
wsze trzeba dodać do niego także wierzchołek. Aby utworzyć zbiór krawędzi prze
kroju, należy dodać do kolejki priorytetowej wszystkie krawędzie z danego wierz
chołka do wszystkich wierzchołków spoza drzewa (m ożna je ustalić za pom ocą
tablicy marked []). Trzeba jednak zrobić coś więcej. Każda krawędź, która łączy
dodany wierzchołek z wierzchołkiem z drzewa i już znajduje się w kolejce priory-
4.3 Q Minimalne drzewa rozpinające 629
tetowej, staje się niewybieralna (nie jest wtedy krawędzią przekroju, ponieważ łączy
dwa wierzchołki drzewa). W zachłannej im plementacji algorytm u Prim a można
usunąć takie krawędzie z kolejki priorytetowej. Najpierw omawiamy jednak leniwą
implementację, w której krawędzie pozostają w kolejce priorytetowej. Sprawdzanie
wybieralności odkładamy do m om entu usuwania krawędzi.
Po prawej stronie pokazano ślad dzia
0-7 0 16
łania algorytmu dla małego przykładowe 0 - 2 0 26
* Oznaczanie
go grafu tinyEWG.txt. Na każdym rysun now ych 0-4 0 38
elementów 6 -0 0 58
ku znajduje się graf i kolejka priorytetowa
po odwiedzeniu wierzchołka (po dodaniu
go do drzewa i przetworzeniu krawędzi Krawędzie przekroju
na liście sąsiedztwa danego wierzchołka). (uporządkowane
według wagi)
Uporządkowaną zawartość kolejki priory
tetowej pokazano obok grafu, przy czym
nowe krawędzie są oznaczone gwiazdka 6-0 0.58
0-2 0.26
mi. Algorytm tworzy drzewo MST w na 5-7 0.28
stępujący sposób. 1-3 0.29
1-5 0.32
° Dodaje 0 do drzewa MST, a wszyst 2-7 0.34
kie krawędzie z listy sąsiedztwa 1-2 0.36
4-7 0.37
tego wierzchołka — do kolejki
0-4 0.38
priorytetowej. 0-6 0.58
D Dodaje 7 i krawędź 0-7 do drzewa
MST, a wszystkie krawędzie z listy
sąsiedztwa tego wierzchołka — do
kolejki priorytetowej. Krawędzie 5-7 0.28
niewybieralne l1 - 3 0 . 2 9
0 Dodaje 1 i krawędź 1-7 do drzewa (kolorszary) 'y 1-5 0.32
MST, a wszystkie krawędzie z listy 2-7 0 .34
1- 2 0 . 36
sąsiedztwa tego wierzchołka — do
4-7 0.37
kolejki priorytetowej. 0-4 0.38
6 - 2 0.40
° Dodaje 2 i krawędź 0-2 do drzewa
3-6 0.52
MST, a krawędzie 2-3 i 6-2 — do 6-0 0.58
kolejki priorytetowej. Krawędzie 2-7
i 1-2 stają się niewybieralne.
° Dodaje 3 i krawędź 2-3 do drzewa 1-2 0 . 3 6
MST, a krawędź 3-6 — do kolejki 4-7 0.37
0-4 0.38
priorytetowej. Krawędź 1-3 staje się 6 - 2 0.40
niewybieralna. 3-6 0.52
6-0 0.58
n Usuwa krawędzie niewybieralne 1-3, 6-4 0.93
1-5 i 2-7 z kolejki priorytetowej.
° Dodaje 5 i krawędź 5-7 do drzewa
MST, a krawędź 4-5 — do kolejki
priorytetowej. Krawędź 1-5 staje
się niewybieralna.
Ślad działania algorytmu Prima (wersja leniwa)
630 ROZDZIAŁ 4 a Grafy
Czas w ykonania Jak szybki jest algorytm Prima? Na podstawie wiedzy o cechach
kolejek priorytetowych nietrudno odpowiedzieć na to pytanie.
public c la s s LazyPrimMST
{
p rivate boolean[] marked; // Wierzchołki drzewa MST.
p rivate Queue<Edge> mst; // Krawędzie drzewa MST.
p rivate MinPQ<Edge> pq; // Krawędzie przekroju (i niewybieralne).
public LazyPrimMST(EdgeWeightedGraph G)
{
pq = new Mi nPQ<Edge>();
marked = new b oolean[G .V ()];
mst = new Queue<Edge>();
public c la s s PrimMST
{
private Edge[] edgeTo; // Najkrótsza krawędź z wierzchołka
// drzewa.
private doublej] distTo; // distTo[w] = edgeTo[w].weight()
private booleanj] marked; // true, j e ś l i v znajduje s ię w drzewie,
private IndexMinPQ<Double> pq; // Wybieralne krawędzie przekroju.
public PrimMST(EdgeWeightedGraph G)
{
edgeTo = newEdge CG. V ()];
distTo = newdouble[G.V () ];
marked = newb oolean[G .V ()];
fo r (in t v = 0; v < G.V(); v++)
di stTo[v] = Double.POSITIVE_INFINITY;
pq = new IndexMinPQ<Double>(G.V () );
di s tT o [0] = 0.0;
p q .in se rt(0 , 0.0); // Inicjowanie pq za pomocą 0 i wagi 0.
while (!pq.isEmpty())
v i s i t ( G , p q .d e lM in ( )); // Dodawanie najbliższego wierzchołka do
// drzewa.
}
d o w ó d w z a s a d z i e i d e n t y c z n y z dowodem t w i e r d z e n i a m
© \
znacza drzewo MST dla dowolnego spójne
© 0 -7 0.1 6 go grafu ważonego.
2-3 0.17
1-7 0.19 Dowód. Wynika bezpośrednio z t w i e r d z e
© 0-2
5-7
0.2 6
0.28
n iak. Jeśli następna rozważana krawędź
1-3 0.29 nie tworzy cyklu względem czarnych kra
1-5 0.32
© © 2-7 0.34
4 -5 0.35
wędzi, to łączy przekrój wyznaczony przez
zbiór wierzchołków powiązanych z jednym
1-2 0.36 z wierzchołków krawędzi przez krawędzie
© 4-7 0.37
0-4 0.38 drzewa i dopełnienie tego zbioru. Ponieważ
6 -2 0.40 krawędź nie tworzy cyklu, jest jedyną napot
3-6 0.52
© 6-0 0.58 kaną do tej pory krawędzią przekroju, a po
6-4 0.93 nieważ krawędzie analizowane są według
X wag, jest to krawędź przekroju o minimalnej
Niepotrzebna
krawędź wadze. Tak więc algorytm po kolei pobie
(kolor szary)
ra krawędź przekroju o minimalnej wadze
©
Szare wierzchołki określają
(czyli działa w sposób zachłanny).
przekrój wyznaczony przez
. wierzchołki powiązane
zjednymz wierzchołków Algorytm Prima tworzy drzewo MST krawędź
czerwonej krawędzi
po krawędzi, znajdując w każdym kroku nową
© krawędź dołączaną do jednego rosnącego drze
wa. Algorytm Kruskala także tworzy drzewo
MST krawędź po krawędzi, natomiast wyszu
kuje krawędź łączącą dwa drzewa w lesie ros
nących drzew. Zaczynamy od niepełnego lasu
V drzew o jednym wierzchołku i wykonujemy
Ślad działania algorytmu Kruskala
operację łączenia dwóch drzew (za pomocą
najkrótszej możliwej krawędzi) do momentu,
w którym pozostaje tylko jedno drzewo — drze
wo MST.
4.3 a Minimalne drzewa rozpinające 637
40%
60%
Algorytm Kruskala
(250 wierzchołków)
w
public c la s s KruskalMST
{
private Queue<Edge> mst;
public KruskalMST(EdgeWeightedGraph G)
{
mst = new Queue<Edge>();
MinPQ<Edge> pq = new MinPQ<Edge>(G.edges());
UF uf = new UF(G. V ( ) );
; PYTANIA I ODPOWIEDZI
O. Nie, w żadnym razie. Takich grafów dotyczy trudniejszy problem z obszaru prze
twarzania grafów — wyznaczanie drzewa o minimalnym koszcie.
4.3 n Minimalne drzewa rozpinające 643
ĆWICZENIA
4.3.1. Udowodnij, że można przeskalować wagi przez dodanie do każdej z nich dodat
niej stałej lub pomnożenie ich przez taką stałą i że nie wpływa to na drzewo MST.
4.3.5. Wykaż, że algorytm zachłanny działa poprawnie nawet wtedy, kiedy wagi kra
wędzi nie są różne.
4.3.8. Udowodnij tak zwaną właściwość cyklu — dla dowolnego cyklu w grafie wa
żonym (o różnych wagach krawędzi) krawędź o maksymalnej wadze w cyklu nie
należy do drzewa MST grafu.
4.3.10. Opracuj implementację klasy EdgeWei ghtedGraph dla grafów gęstych opartą
na reprezentacji w postaci macierzy sąsiedztwa (dwuwymiarowej tablicy wag). Nie
zezwalaj na występowanie krawędzi równoległych.
4.3.12. Załóżmy że krawędzie w grafie mają różne wagi. Czy najkrótsza krawędź
musi należeć do drzewa MST? Czy najdłuższa krawędź może należeć do drzewa
MST? Czy krawędź o minimalnej wadze z każdego cyklu należy do drzewa MST?
Udowodnij odpowiedź na każde pytanie lub przedstaw kontrprzykład.
4.3.13. Przedstaw kontrprzykład pokazujący dlaczego opisana strategia nie zawsze
wyznacza drzewo MST. Oto ta strategia: zacznij od dowolnego wierzchołka trakto
wanego jak drzewo MST o jednym wierzchołku. Następnie dodaj do niego V-1 kra
wędzi, zawsze pobierając następną krawędź o minimalnej wadze przyległą do wierz
chołka dodanego w ostatnim kroku do drzewa MST.
644 ROZDZIAŁ 4 0 Grafy
4.3.14. Wyznaczono drzewo MST dla grafu ważonego G. Załóżmy, że z grafu G usu
wamy krawędź, której brak nie prowadzi do utraty spójności. Opisz, jak wyznaczyć
drzewo MST nowego grafu w czasie proporcjonalnym do E.
4.3.15. Mamy drzewo MST dla grafu ważonego G i nową krawędź e. Opisz, jak zna
leźć drzewo MST nowego grafu w czasie proporcjonalnym do U.
4.3.16. Mamy drzewo MST dla grafu ważonego G i nową krawędź e. Napisz program
określający zakres wag krawędzi e, przy których znajdzie się ona w drzewie MST.
4.3.17. Zaimplementuj metodę to S trin g O dla klasy EdgeWeightedGraph.
4.3.18. Przedstaw ślady przebiegu procesu wyznaczania drzewa MST dla grafu
z ć w i c z e n i a 4 .3 .6 . Użyj leniwej wersji algorytmu Prima, zachłannej wersji algoryt
m u Prima oraz algorytmu Kruskala.
4.3.19. Załóżmy, że korzystasz z implementacji kolejki priorytetowej opartej na li
ście posortowanej. Jakie jest tempo wzrostu czasu wykonania dla najgorszego przy
padku przy korzystaniu z algorytmu Prima i algorytmu Kruskala dla grafów o V
wierzchołkach i E krawędziach? Kiedy takie podejście jest odpowiednie (jeśli w ogóle
występują taicie sytuacje)? Odpowiedź uzasadnij.
Rozwiązanie:
public Iterable<Edge> edges()
(
Bag<Edge> mst = new Bag<Edge>();
fo r (in t v = 1; v < edgeTo.length; v++)
mst.add(edgeTo[v]);
return mst;
}
4.3 a Minimalne drzewa rozpinające 645
| PROBLEMY DO ROZWIĄZANIA
4.3.24. Algorytm odwróć-usuń. Opracuj kod, który wyznacza drzewo MST w nastę
pujący sposób: zacznij od grafu obejmującego wszystkie krawędzie. Następnie wie
lokrotnie przejdź po krawędziach w porządku malejącym według wag. Dla każdej
krawędzi sprawdź, czy jej usunięcie prowadzi do powstania niespójnego grafu. Jeśli
nie, krawędź należy usunąć. Udowodnij, że algorytm wyznacza drzewo MST. Jaki jest
stopień wzrostu liczby porównań wag krawędzi wykonywanych przez kod?
4.3.27. Animacje. Napisz program kliencki, który generuje animacje pracy algo
rytmów wyznaczania drzew MST. Uruchom program dla pliku mediumEWG.txt.
Program ma wygenerować rysunki podobne do tych ze stron 633 i 636.
4.3.31. Wagi drzew MST. Opracuj implementacje m etody w eight() dla klas
LazyPrimMST, PrimMST i KruskalMST, wykorzystując leniwą strategię, z iterowaniem po
krawędziach drzewa MST w momencie wywołania m etody wei ght () przez klienta.
Następnie opracuj inne implementacje, oparte na strategii zachłannej, z przechowy
waniem bieżącej sumy przy wyznaczaniu drzewa MST.
4.3.32. Określony zbiór. Mamy spójny graf ważony G i określony zbiór krawędzi S
(bez cykli). Opisz sposób wyznaczania minimalnego drzewa rozpinającego dla grafu
G, przy czym drzewo m a obejmować wszystkie krawędzie z S.
I EKSPERYMENTY
4.3.34. Losowe rzadkie grafy ważone. Napisz generator losowych rzadldch grafów
ważonych oparty na rozwiązaniu ć w i c z e n i a 4 .1 .4 1 . Aby przypisać wagi krawę
dziom, zdefiniuj typ ADT dla losowych digrafów ważonych i napisz dwie implemen
tacje — jedną generującą wagi o rozkładzie równomiernym i jedną generującą wagi
0 rozkładzie Gaussa. Opracuj program kliencki do generowania losowych rzadkich
grafów ważonych na podstawie obu rozkładów wag i odpowiednio dobranych war
tości V i E, tak aby można przeprowadzić empiryczne testy na grafach o różnych
rozkładach wag.
4.3.35. Losowe euklidesowe grafy ważone. Zmodyfikuj rozwiązanie ć w i c z e n i a
4 .1.42 przez zapisanie odległości między wierzchołkami jako wagi każdej krawędzi.
4.3.37. Grafy ważone w świecie rzeczywistym. Znajdź w internecie duży graf ważo
ny. Może to być mapa z odległościami, połączenia telefoniczne z kosztami lub plan
lotów z cenami. Napisz program RandomReal EdgeWeightedGrap, tworzący graf wa
żony przez wybranie V losowych wierzchołków i E krawędzi z wagami z podgrafu
opartego na tych wierzchołkach.
4.3.45. Zewnętrzne drzewa MST. Opisz, jak wyznaczyć drzewo MST grafu, który
jest tak duży, że w danym momencie w pamięci mieści się tylko U krawędzi.
650
4.4 a Najkrótsze ścieżki 651
Typy danych dla digrafów ważonych Opracowany przez nas typ danych dla
krawędzi skierowanych jest prostszy niż typ dla krawędzi nieskierowanych, ponie
waż krawędzie skierowane prowadzą w jednym kierunku. Zamiast metod e ith e r()
io th e r() z klasy Edge, tu występują m etody from() i to ().
p ub lic c la s s DirectedEdge
p u b lic c la s s EdgeWeightedDigraph
public c la ss DirectedEdge
{
private final in t v; // Krawędź źródTowa.
private final in t w; // Krawędź docelowa,
private final double weight; // Waga krawędzi.
public in t from()
{ return v; }
public in t to()
{ return w; }
public S trin g t o S t r in g O
{ return String.form at("%d->%d % .2 f", v, w, weight); }
}
Powyższa implementacja klasy Di rectedEdge jest prostsza niż implementacja dla nieskiero-
wanych krawędzi ważonych (klasa Edge z p o d r o z d z i a ł u 4 .3 ; zobacz stronę 622), ponieważ
dwa wierzchołki są tu odróżniane od siebie. W klientach do dostępu do dwóch wierzchoł
ków obiektu e typu Di rectedEdge służy idiomatyczny kod v = e . t o () , w = e.fromO;.
4.4 Najkrótsze ścieżki 655
public c la s s EdgeWeightedDigraph
{
private final in t V; // Liczba wierzchołków,
private in t E; // Liczba krawędzi,
private Bag<DirectedEdge>[] adj; // L i s t y sąsiedztwa.
p ublic EdgeWeightedDigraph(int V)
{
t h is .V = V;
th is.E = 0 ;
adj = (Bag<DirectedEdge>[]) new Bag[ V ] ;
fo r (in t v = 0; v < V; v++)
adj [v] = new Bag<DirectedEdge>();
}
public Iterable<Edge> a d j( in t v)
{ return adj [ v ] ; }
t i nyEW D.t x t
8
15
4 5 0.35
5 4 0.35
4 7 0.37
5 7 0.28
7 5 0.28
5 1 0.32
0 4 0.38
0 2 0.26
7 3 0.39
1 3 0 .2 9
2 7 0 .3 4
6 2 0.40
3 6 0.52
6 0 0.58
6 4 0.93
p u b lic c la s s SP
w przykładzie po lewej) i nie trzeba wprowadzać zmian, albo krawędź v->w prowadzi
do krótszej ścieżki do w (tak jak w przykładzie po prawej) i należy zaktualizować
struktury edgeTo[w] i distTo[w] (co może spowodować, że niektóre inne krawędzie
staną się niewybieralne, a inne — wybieralne). Nazwa relaksacja związana jest z gu
mową taśm ą rozciągniętą na ścieżce łączącej dwa wierzchołki. Relaksacja krawędzi
przypomina zwolnienie napięcia gumowej taśmy przez przeciągnięcie jej wzdłuż
krótszej ścieżki (jeśli jest to możliwe). Mówimy, że krawędź e umożliwia relaksację,
jeśli m etoda rei ax() zmienia wartości di stT o [e .to ()] i ed g eT o [e.to ()].
/
1 w^lal■***>+niAiinrUinrilnl mu/nrl II_>.1111ioct IKMlbinr^Ina
-1
Czarn
edgeT o[]
Relaksacja wierzchołka
4.4 a Najkrótsze ścieżki 661
p u b lic double d is t T o ( in t v)
{ return d is t T o [ v ] ; }
p o w ia d a ją c e w ie rz c h o łk o m d rz e w a to d łu g o ś c i Czerwony-
edgeTo[] di stT o[]
n a jk ró tsz y c h ścieżek , a d la k a ż d e g o w ie rz c h o łk a 0 0.00
1
w z k o le jk i p rio ry te to w e j w a rto ś ć d is tT o [w ] to 2 0->2 0.26 0.26
3
w aga n a jk ró tsz e j śc ieżk i z s d o w, k tó r a o b e jm u je 4 0->4 0.38 0.38
ty lk o p o ś r e d n ie w ie rz c h o łk i z d rz e w a i k o ń c z y
się k ra w ę d z ią p rz e k r o ju edgeT o[w ], W a rto ść ?\ Indeks t
Priorytet
d is tT o [ ] w ie rz c h o łk a o n a jn iż s z y m p r io r y te Czarny -
cie to w a g a n a jk ró tsz e j ścieżk i, n ie m n ie js z a n iż w drzewie SPT 0 0.00
Im p le m e n ta c ja a lg o r y tm u D ijk s try w k la s ie Di j k s t r a S P ( a l g o r y t m 4 .9 ) to k o d o d
z w ie rc ie d la ją c y je d n o z d a n io w y o p is a lg o r y tm u . N a p is a n ie te g o k o d u je s t m o ż liw e
d z ię k i d o d a n iu d o m e to d y r e l a x ( ) je d n e j in s tr u k c ji o b s łu g u ją c e j d w a p rz y p a d k i
— a lb o w ie rz c h o łe k to () p o w ią z a n y z k ra w ę d z ią n ie z n a jd u je się je s z c z e w k o lejc e
p rio ry te to w e j (w te d y n a le ż y u ż y ć m e to d y i n s e r t ( ) i d o d a ć g o d o k o le jk i), a lb o je s t
ju ż w k o le jc e (w te d y tr z e b a z m n ie js z y ć je g o p r i o r y te t za p o m o c ą m e to d y c h a n g e () ).
Dowód. T a k i sa m , ja k d la a lg o r y tm u P r im a (z o b a c z t w i e r d z e n i e n ).
ja ic w s p o m n i e l i ś m y , i n n y s p o s ó b m y ś l e n i a o a lg o r y tm ie D ijk s tr y p o le g a n a p o
r ó w n a n iu g o z a lg o r y tm e m P r im a d o w y z n a c z a n ia d r z e w M S T ( p o d r o z d z i a ł 4 . 3 ,
s tr o n a 6 3 4 ). O b a a lg o r y tm y tw o rz ą d rz e w o z k o r z e n ie m p rz e z d o d a w a n ie k r a w ę
d z i d o ro s n ą c e g o d rz e w a . A lg o ry tm P r im a d o d a je n a s tę p n y w ie rz c h o łe k s p o z a
d rz e w a n a jb liż s z y d r z e w u . A lg o ry tm D ijk s try d o d a je n a s tę p n y w ie rz c h o łe k s p o z a
d rz e w a n a jb liż s z y źr ó d łu . T a b lic a m a rk e d [] n ie je s t p o tr z e b n a , p o n ie w a ż w a r u n e k
!m arked[w ] je s t r ó w n o z n a c z n y w a r u n k o w i m ó w ią c e m u , że d i s tT o [w] to n ie s k o ń
c z o n o ść . U jm ijm y to in a c z e j — p o z a s to s o w a n iu g ra fó w i k ra w ę d z i n ie s k ie ro w a n y c h
o ra z p o m in ię c iu re fe re n c ji d o d is tT o [ v ] w m e to d z ie r e l a x ( ) k o d a l g o r y t m u 4 .9
staje się im p le m e n ta c ją a l g o r y t m u 4 .7 — z a c h ła n n ą w e rs ją a lg o r y tm u P r im a (!).
P o n a d to n ie t r u d n o je s t o p ra c o w a ć le n iw ą w e rsję a lg o r y tm u D ijk s try , p o d o b n ą d o
k la s y LazyPrimMST ( s tr o n a 6 3 1 ).
O d m ia n y O p r a c o w a n a p rz e z n a s im p le m e n ta c ja a lg o r y tm u D ijk s try p o o d p o w ie d
n ic h m o d y f ik a c ja c h n a d a je się d o ro z w ią z a n ia in n y c h o d m ia n p ro b le m u , ta l a c h ja k
p o n iż s z a .
R o z w ią z a n ie te g o p r o b le m u je s t n a ty c h m ia s to w e , je ś li g r a f n ie s k ie ro w a n y p o t r a k t u
je m y j a k d ig ra f. N a p o d s ta w ie g ra fu n ie s k ie ro w a n e g o n a le ż y u tw o rz y ć d ig r a f w a ż o n y
o ty c h s a m y c h w ie rz c h o łk a c h i d w ó c h k ra w ę d z ia c h s k ie ro w a n y c h (p o je d n e j w k a ż
d y m k ie r u n k u ) , o d p o w ia d a ją c y c h k a ż d e j k ra w ę d z i g ra fu . Is tn ie je z a le ż n o ś ć je d e n d o
je d n e g o m ię d z y ś c ie ż k a m i d ig r a fu a ś c ie ż k a m i g ra fu , a k o s z ty śc ie ż e k są ta k ie sam e.
O b a p r o b le m y w y z n a c z a n ia n a jk r ó ts z y c h śc ie ż e k są a n a lo g ic z n e .
4.4 Najkrótsze ścieżki 667
public cl a ss DijkstraSP
{
p r i v a t e Di rect edEdge[] edgeTo;
p r i v a t e d o u b l e [] d i s t T o ;
p r i v a t e IndexMinPQ<Double> pq;
p u b l i c D i jk s tr aS P ( Ed g e We i ghtedDigraph G, i n t s)
{
edgeTo = new D i r e c t e d E d g e [ G . V ( ) ] ;
d i s t T o = new d o u b l e [ G . V ( ) ] ;
pq = new I ndex Mi nPQ<Doubl e>( G. V( ) ) ;
f o r ( i n t v = 0; v < G. V( ) ; v++)
di s tTo[ v] = D o u b l e . PO S I T I VE _ I NF I N I T Y ;
d i s t T o [ s ] = 0.0;
p q . i n s e rt (s , 0.0);
whi l e ( ! p q . i sE mpt y ( ) )
r el ax( G, p q .del Mi n ())
}
Z a s k a k u ją c o z w ię z ła im p le m e n ta c ja , p r z e d s ta w io n a p o n iż e j p o le w e j s tro n ie , r o z
w ią z u je p r o b le m n a jk r ó ts z y c h śc ie ż e k d la w s z y s tk ic h p a r o ra z p o tr z e b u je n a to
c z a s u i p a m ię c i w ilo śc i p r o p o r c jo n a ln e j d o T U lo g U. K o d tw o rz y ta b lic ę o b ie k tó w
Di j k s tra S P — p o je d n y m d la k a ż d e g o w ie rz c h o łk a ja k o ź ró d ła . P rz y o d p o w ia d a n iu
n a z a p y ta n ia k lie n tó w ź r ó d ło w y k o rz y s ty w a n e je s t d o d o s tę p u d o o d p o w ie d n ie g o
o b ie k tu z n a jk r ó ts z y m i ś c ie ż k a m i z je d n e g o ź ró d ła , a n a s tę p n ie w ie rz c h o łe k d o c e lo
w y je s t p rz e k a z y w a n y ja k o a r g u m e n t z a p y ta n ia .
n a r y s u n k a c h n a n a s t ę p n e j s t r o n i e p o k a z a n o tw o rz e n ie p rz e z a lg o r y tm D ijk s try
d rz e w a S P T d la k ilk u ró ż n y c h ź ró d e ł g ra f u e u k lid e s o w e g o z d e fin io w a n e g o w p lik u
te s to w y m m e d iu m E W D .tx t (z o b a c z s t r o
pub lic c la s s D ij k s t r a A llP a ir s S P n ę 6 5 7 ). P rz y p o m n ijm y , że lin ie w g rafie
{ re p r e z e n tu ją k ra w ę d z ie s k ie ro w a n e w o b u
p r i v a t e Di j k s t r a S P [] a l l ;
k ie r u n k a c h . T ak że t u r y s u n k i są ilu s tra c ją
Di j k s t r a A l 1 Pai r s S P ( E d g e W e ig h t e d D ig r a p h G) c ie k a w e g o d y n a m ic z n e g o p ro c e s u .
1 D a le j o m a w ia m y a lg o r y tm y w y z n a c z a
all = new D i j k s t r a S P f G . V ()]
n ia n a jk r ó ts z y c h śc ie ż e k w a c y k lic z n y c h
f o r ( i n t v = 0; v < G. V ( ) ; v++)
a l l [v] = new D i j k s t r a S P ( G , v ) ; g ra fa c h w a ż o n y c h . P r o b le m te n m o ż n a
1 ro z w ią z a ć w c z asie lin io w y m (sz y b c ie j n iż
z a p o m o c ą a lg o r y tm u D ijk s try ). N a s tę p n ie
I t e r a b l e < E d g e > p a t h ( i n t s, in t t)
{ return a l l [ s ] . p a t h T o ( t ) ; }
ro z w a ż a m y te n s a m p r o b le m w k o n te k ś c ie
d ig r a fó w w a ż o n y c h o w a g a c h u je m n y c h ,
do ub le d i s t ( i n t s, i n t t) d la k tó r y c h a lg o r y tm D ijk s tr y n ie d z ia ła .
{ return a l l [ s ] . d i s t T o ( t ) ; }
1
Twierdzenie S. P rz e z re la k s a c ję w ie rz c h o łk ó w w p o r z ą d k u to p o lo g ic z n y m
m o ż n a ro z w ią z a ć p r o b le m w y z n a c z a n ia n a jk r ó ts z y c h śc ie ż e k z je d n e g o ź ró d ła
w w a ż o n y m g ra fie D A G w c za sie p r o p o r c jo n a ln y m d o E + V.
Dowód. R e la k sa c ja k a ż d e j k ra w ę d z i v->w je s t w y k o n y w a n a d o k ła d n ie ra z , w c z a
sie re la k s a c ji v, p o c z y m d i stT o [w] <= d i stT o [v] + e . w e i g h t ( ) . N ie ró w n o ś ć
ta je s t s p e łn io n a d o m o m e n tu z a k o ń c z e n ia p r a c y a lg o r y tm u , p o n ie w a ż w a rto ś ć
di s tT o [ v ] n ig d y się n ie z m ie n ia (z u w a g i n a p o r z ą d e k to p o lo g ic z n y ż a d n a k r a
w ę d ź p ro w a d z ą c a d o v n ie je s t p r z e tw a r z a n a p o re la k s a c ji v), a w a rto ś ć d i s tT o [w]
m o ż e ty lk o m a le ć (re la k s a c ja m o ż e p ro w a d z ić ty lk o d o z m n ie js z e n ia w a rto ś c i
di s tT o [] ) . D la te g o p o d o d a n iu d o d rz e w a w s z y s tk ic h w ie rz c h o łk ó w d o s tę p n y c h
z s w a r u n k i o p ty m a ln o ś c i n a jk r ó ts z y c h śc ie ż e k są s p e łn io n e i m o ż n a z a s to s o
w a ć t w i e r d z e n i e Q. O g ra n ic z e n ie c z a su d z ia ła n ia je s t o c z y w iste — z g o d n ie
z t w i e r d z e n i e m G ze s tr o n y 5 9 5 s o r to w a n ie to p o lo g ic z n e d z ia ła w c z a sie p r o
p o r c jo n a ln y m d o E + V , a d r u g i p rz e b ie g , z w ią z a n y z re la k sa c ją , k o ń c z y p ro c e s
p rz e z j e d n o k r o tn ą re la k s a c ję k a ż d e j k ra w ę d z i, c o ta k ż e z a jm u je cz a s p r o p o r c jo
n a ln y d o E + V.
4.4 □ Najkrótsze ścieżki 671
° D o d a je d o d rz e w a 3 i k ra w ę d ź 3 -> 6 , ale ju ż
5 -> 7
n ie 3 -> 7 , p o n ie w a ż je s t n ie w y b ie ra ln a .
n D o d a je d o d rz e w a 6 o ra z k ra w ę d z ie 6->2 i 6->0, Czerwona krawędź -
dodawana do drzewa
ale ju ż n ie 6-> 4, p o n ie w a ż je s t n ie w y b ie ra ln a .
° D o d a je d o d rz e w a 4 i k ra w ę d ź 4-> 0, a le ju ż n ie l- > 3
4-> 7 , p o n ie w a ż je s t n ie w y b ie r a ln a . K ra w ę d ź 5 -> 4
° D o d a je d o d rz e w a 2. 3 -> 6
5 -> 7
N ie p r z e d s ta w io n o d o d a w a n ia 2 d o d rz e w a . Z w ie rz
c h o łk a o s ta tn ie g o w p o r z ą d k u to p o lo g ic z n y m n ie
4 -> 0
w y c h o d z ą ż a d n e k ra w ę d z ie . 5 -> l
6 ->2
Im p le m e n ta c ja ( a l g o r y t m 4.10 ) to p ro s te z a sto l- > 3
5 -> 4
so w an ie o m ó w io n e g o ju ż k o d u . Z a k ład a m y , że k la sa
3 -> 6
Topological o b e jm u je p rz e c ią ż o n e m e to d y d o s o r 5 -> 7
V
to w a n ia to p o lo g ic z n e g o , k o rz y sta ją c e z in te rfe jsó w Szara krawędź
- niewybieralna 4 -> 0
A P I M as EdgeWei ghtedDi graph i Di rectedEdge z teg o 5 -> l
p o d ro z d z ia łu (z o b a c z ć w ic z e n ie 4 .4 . 12 ). Z au w ażm y , 7 -> 2
l-> 3
że w tej im p le m e n ta c ji ta b lic a lo g ic z n a marked [] n ie 5 -> 4
public c la s s AcyclicSP
{
private DirectedEdge[] edgeTo;
private doublet] di stT o ;
public AcyclicSP(EdgeWeightedDigraph G, in t s)
{
edgeTo = new Di rectedEdge[G.V() ];
distTo = new double[G.V()];
% j a v a A c y c l i c S P tinyE W D AG.txt 5
5 do 0 ( 0 . 7 3 ) : 5- > 4 0 . 3 5 4 - > 0 0. 3 8
5 do 1( 0 . 3 2 ) : 5 - > l 0.32
5 do 2(0 .62): 5->7 0.28 7 - > 2 0. 3 4
5 do 3( 0 . 6 2 ) : 5 - > l 0 . 3 2 l - > 3 0. 29
5 do 4 ( 0 . 3 5 ) : 5 - > 4 0.35
5 do 5( 0 . 0 0 ) :
5 do 6( 1 . 1 3 ) : 5 - > l 0 . 3 2 l - > 3 0. 2 9 3 - > 6 0.52
5 do 7 ( 0 . 2 8 ) : 5- >7 0.28
4.4 b Najkrótsze ścieżki 673
t w i e r d z e n i e s m a d u ż e z n a c z e n ie , p o n ie w a ż s ta n o w i k o n k r e tn y p rz y k ła d , w k tó r y m
b r a k c y k li z n a c z n ie u p ra s z c z a p ro b le m . P rz y w y z n a c z a n iu n a jk r ó ts z y c h śc ie ż e k m e
to d a o p a r ta n a s o r to w a n iu to p o lo g ic z n y m je s t sz y b s z a o d a lg o r y tm u D ijk s tr y o c z y n
n ik p r o p o r c jo n a ln y d o k o s z tó w o p e ra c ji n a k o le jc e p rio ry te to w e j w ty m a lg o ry tm ie .
P o n a d to d o w ó d t w i e r d z e n i a s n ie z a le ż y o d teg o , c z y k ra w ę d z ie są n ie u je m n e , d la
teg o d la w a ż o n y c h g ra fó w D A G m o ż n a u s u n ą ć to o g ra n ic z e n ie . D a le j o m a w ia m y
s k u tk i m o ż liw o ś c i w y s tę p o w a n ia k ra w ę d z i o u je m n y c h w a g a c h . R o z w a ż a m y p rz y
ty m z a s to s o w a n ie m o d e lu n a jk r ó ts z y c h śc ie ż e k d o ro z w ią z a n ia d w ó c h in n y c h p r o b
lem ó w , z k tó r y c h je d e n p o c z ą tk o w o w y d a je się b y ć d o ś ć o d le g ły o d d z ie d z in y p r z e
tw a rz a n ia grafów .
N a j d ł u ż s z e ś c ie ż k i R o z w a ż m y p r o b le m z n a jd o w a n ia n a jd łu ż s z e j ś c ie ż k i w w a ż o
n y c h g ra f a c h D A G , w k tó r y c h w a g i k ra w ę d z i m o g ą b y ć d o d a tn ie i u je m n e .
O m ó w io n y w c z e śn ie j a lg o r y tm z a p e w n ia sz y b k ie ro z w ią z a n ie te g o p ro b le m u .
Twierdzenie T. P ro b le m w y z n a c z a n ia n a jd łu ż s z y c h śc ie ż e k w w a ż o n y c h g r a
fa c h D A G m o ż n a ro z w ią z a ć w c z a sie p r o p o r c jo n a ln y m d o E + V.
Dowód. P rz y w y z n a c z a n iu n a jd łu ż s z y c h ś c ie ż e k n a le ż y u tw o rz y ć k o p ię d a n e g o
w a ż o n e g o g ra f u D A G , w k tó re j w sz y stk ie k ra w ę d z ie m a ją w a g i o z m ie n io n y m
z n a k u . N a jk r ó ts z a śc ie ż k a w k o p ii je s t n a jd łu ż s z ą śc ie ż k ą o ry g in a łu . A b y p r z e
k s z ta łc ić ro z w ią z a n ie p r o b le m u w y z n a c z a n ia n a jk r ó ts z y c h ś c ie ż e k n a ro z w ią z a n ie
p r o b le m u z n a jd o w a n ia n a jd łu ż s z y c h śc ie ż ek , n a le ż y o d w ró c ić z n a k i w a g w w y n i
ku. C z a s w y k o n a n ia m o ż n a u s ta lić b e z p o ś r e d n io n a p o d s ta w ie t w i e r d z e n i a s.
W y k o rz y s ta n ie te j tr a n s f o r m a c ji d o o p ra c o w a n ia k la s y A c y c lic L P , k tó r a z n a jd u
je n a jd łu ż s z e śc ie ż k i w w a ż o n y m g ra fie D A G , je s t p ro s te . Jeszcze ła tw ie js z y s p o s ó b
n a z a im p le m e n to w a n ie tej k la s y to s k o p io w a n ie k o d u k la s y A c y c lic S P , z m ie n ie n ie
w a rto ś c i d o in ic jo w a n ia e le m e n tó w ta b lic y d i s tT o [ ] n a D o u b le . NEGAT IV E_ IN FINI TY
i z m o d y fik o w a n ie n ie r ó w n o ś c i w m e to d z ie r e l a x ( ) . W o b u s y tu a c ja c h u z y s k u je m y
w y d a jn e ro z w ią z a n ie p r o b le m u w y z n a c z a n ia n a jd łu ż s z y c h śc ie ż e k w w a ż o n y c h g r a
fach D A G . W a rto p o ró w n a ć w y d a jn o ś ć te g o ro z w ią z a n ia z n a jle p s z y m z n a n y m a lg o
r y tm e m w y s z u k iw a n ia n a jd łu ż s z y c h śc ie ż e k p r o s ty c h d la o g ó ln y c h d ig r a fó w w a ż o
n y c h (w k tó r y c h w a g i k ra w ę d z i m o g ą b y ć u je m n e ), k tó r y d la n a jg o rs z e g o p r z y p a d k u
d z ia ła w cz a sie w y k ła d n ic z y m (z o b a c z r o z d z i a ł 6 .)! W y g lą d a n a to , że m o ż liw o ś ć
w y s tę p o w a n ia c y k li p o w o d u je w y k ła d n ic z y w z ro s t tr u d n o ś c i p ro b le m u .
674 RO ZD ZIA Ł 4 o Grafy
6 7 1 ). W ty m p rz y k ła d z ie a lg o r y tm tw o rz y
0
d rz e w o n a jd łu ż s z y c h śc ie ż e k (a n g . lon g est- 1 5->l
p a th s tree — L P T ) z w ie rz c h o łk a 5 w o p is a n y 3 l->3
4 5->4
p o n iż e j sp o s ó b .
° S tosuje m e to d ę D FS d o u sta le n ia p o rz ą d 7 5->7
k u to p o lo g ic z n e g o 5 1 3 6 4 7 0 2.
n D o d a je d o d rz e w a 5 i w sz y stk ie w y c h o 0
1 5->l
d z ą c e z n ie g o k ra w ę d z ie .
3 l->3
° D o d a je d o d rz e w a 1 i k ra w ę d ź l-> 3 . 4 5->4
5
D D o d a je d o d r z e w a 3 o r a z k r a w ę d z ie 6 3->6
7 3->7
3-> 6 i 3 -> 7 . K ra w ę d ź 5->7 sta je się n ie -
w y b ie ra ln a . 0 6 -> 0
1 5->l
* D o d a je d o d rz e w a 6 o ra z k ra w ęd z ie 6->2, 2 6 -> 2
6-> 4 i 6-> 0. 3 l->3
4 6->4
n D o d a je d o d rz e w a 4 o ra z k ra w ę d z ie 4->0 5
6 3->6
i 4-> 7. K ra w ę d z ie 6-> 0 i 3->7 s ta ją się 7 3->7
n ie w y b ie ra ln e .
0 4->0
■ D o d a je d o d rz e w a 7 i k ra w ę d ź 7-> 2. 1 5->l
2 6 -> 2
K ra w ę d ź 6-> 2 s ta je się n ie w y b ie ra ln a . 3 l->3
4 6->4
° D o d a je d o d r z e w a 0 , a le n ie k r a w ę d ź 5
6 3->6
0 - > 2 , p o n ie w a ż je s t n ie w y b ie ra ln a .
7 4->7
■ D o d a je 2 d o d rz e w a (n ie p o k a z a n o n a
Obecnie niewybieralne
r y s u n k u ).
A lg o ry tm w y z n a c z a n ia n a jd łu ż sz y c h ście ż e k
p rz e tw a rz a w ie rz c h o łk i w tej sam ej k o le jn o śc i,
co a lg o ry tm z n a jd o w a n ia n a jk ró tsz y c h śc ie
żek, je d n a k d aje z u p e łn ie o d m ie n n y w y n ik .
0 4->0
1 5->l
2 7->2
3 l->3
4 6->4
6 3->6
7 4->7
S z e r e g o w a n ie r ó w n o le g ły c h z a d a ń W p o s z u k iw a n iu p rz y k ła d o w e g o z a s to s o w a n ia
w ra c a m y d o p r o b le m ó w szere g o w a n ia , p o ra z p ie r w s z y o m ó w io n y c h w p o d r o z d z i a l e
4 .2 ( s tr o n a 5 8 6 ). R o z w a ż m y n a s tę p u ją c y p r o b le m z te g o o b s z a r u (r ó ż n ic e w p o r ó w
n a n iu z p r o b le m e m ze s tr o n y 5 8 7 w y r ó ż n io n o k u rs y w ą ).
R ó w n o le g łe s z e r e g o w a n ie z o g r a n ic z e n ia m i p ie r w s z e ń s tw a . Ja k n a p o d s ta w ie
z b io r u z a d a ń o o k re ślo n y m cza sie tr w a n ia i o g ra n ic z e ń p ie r w s z e ń s tw a (o k re ś la ją
cy ch , że p r z e d ro z p o c z ę c ie m p e w n y c h z a d a ń tr z e b a u k o ń c z y ć in n e ) u sz e re g o w a ć
z a d a n ia n a id e n ty c zn y c h p ro ceso ra ch (tylu , ile je s t p o tr z e b n e ), t a k a b y z o s ta ły w y k o
n a n e b e z n a r u s z a n ia o g ra n ic z e ń w m o ż liw ie n a jk r ó ts z y m c za sie ?
Ograniczenie
D efin icja . M e to d a śc ie żk i k r y ty c z n e j p r z y sz e re g o w a n iu ró w n o le g ły m d z ia
ła w n a s tę p u ją c y s p o s ó b — n a le ż y z a c z ą ć o d u tw o r z e n ia w a ż o n e g o g ra f u D A G
o ź ró d le s, u jś c iu t o ra z d w ó c h w ie rz c h o łk a c h d la k a ż d e g o z a d a n ia (w ie r z c h o łk u
p o c z ą tk o w y m i k o ń c o w y m ). D o k a ż d e g o z a d a n ia n a le ż y d o d a ć k ra w ę d ź z w ie r z
c h o łk a p o c z ą tk o w e g o d o w ie rz c h o łk a k o ń c o w e g o o w a d z e ró w n e j c z a so w i tr w a
n ia z a d a n ia . D la k a ż d e g o o g ra n ic z e n ia p ie r w s z e ń s tw a v->w n a le ż y d o d a ć k r a
w ę d ź o w a d z e z e ro z w ie rz c h o łk a k o ń c o w e g o o d p o w ia d a ją c e g o v d o w ie rz c h o łk a
p o c z ą tk o w e g o o d p o w ia d a ją c e g o w. P o n a d to n a le ż y d o d a ć k ra w ę d z ie o w a d z e
z e ro ze ź r ó d ła d o w ie rz c h o łk a p o c z ą tk o w e g o k a ż d e g o z a d a n ia i z w ie rz c h o łk a
k o ń c o w e g o k a ż d e g o z a d a n ia d o u jśc ia . N a s tę p n ie tr z e b a z a p la n o w a ć k a ż d e z a d a
n ie n a cz as ró w n y d łu g o ś c i n a jd łu ż s z e j ś c ie ż k i ze ź ró d ła .
public c la s s CPM
% more j o b s P C . t x t
10
public s t a t ic void m ain(String[] args)
41 . 0 1 7 9
{ 51.0 2
in t N = S t d l n . r e a d l n t ( ) ; S t d ln . r e a d L i n e Q ; 50 0
EdgeWeightedDigraph G; 36.0
G = new EdgeWeightedDigraph(2*N+2); 38 .0
45 .0
in t s = 2*N, t = 2*N+1; 21-0 38
f o r (in t i = 0; i < N; i++) 32-° 38
| 32.0 2
% j a v a CPM < j o b s P C . t x t
C za sy r o z p o c z ę c ia :
Ta im p le m e n ta c ja m e to d y ścieżki k ry ty c zn e j, p rz e z n a c z o n a 0 : 0.0
1: 41 .0
d o szereg o w an ia zad ań , re d u k u je p ro b le m b e z p o śre d n io d o
2: 123.0
p ro b le m u w y zn a c z a n ia n a jd łu ż sz y c h ścieżek w w ażo n y c h
3: 91 .0
g rafach D A G . P ro g ra m tw o rz y d ig ra f w a ż o n y (m u si być to 4: 70.0
g ra f D A G ) n a p o d sta w ie specyfikacji p ro b le m u szereg o w a 5: 0. 0
n ia zad a ń , zg o d n ie z m e to d ą ścieżki k ry ty c z n e j, a n a stę p n ie 6: 70 .0
7: 4 1 . 0
używ a k lasy A cycl i cLP (zo b acz t w i e r d z e n i e t ) d o z n a le
8: 91 .0
zien ia d rz e w a n ajd łu ż szy c h ścieżek i w y św ietlen ia ich d łu 9: 41 .0
gości (czyli czasów ro zp o c zę c ia k ażd eg o zad a n ia ). Czas z a k o ń c z e n ia : 17 3.0
678 R O ZD ZIA Ł 4 □ Grafy
O ryginał
Twierdzenie U. M e to d a śc ie ż k i k ry ty c z n e j p o z w a la ro z w ią z a ć w c z asie
Zadanie Rozpoczęcie
lin io w y m p r o b le m s z e re g o w a n ia ró w n o le g łe g o z o g r a n ic z e n ia m i p ie r w
0 0.0 s z e ń s tw a .
1 41.0
2 123.0 Dowód. D la c z e g o m e to d a śc ie ż k i k ry ty c z n e j d z ia ła ? P o p ra w n o ś ć a l
3 91.0 g o r y tm u w y n ik a z d w ó c h fak tó w . P o p ie rw s z e , k a ż d a śc ie ż k a w g ra fie
4 70.0 D A G to c ią g p o c z ą tk ó w i z a k o ń c z e ń z a d a ń o d d z ie lo n y c h o g r a n ic z e n ia
5 0.0
m i p ie r w s z e ń s tw a o w a d z e z e ro . D łu g o ś ć k a ż d e j śc ie ż k i ze ź ró d ła s d o
6 70.0
d o w o ln e g o w ie rz c h o łk a v w g ra fie to d o ln e o g ra n ic z e n ie c z a s u r o z p o
7 41.0
8 91.0 c z ę c ia (i z a k o ń c z e n ia ) z a d a n ia re p r e z e n to w a n e g o p rz e z v, p o n ie w a ż n a
9 41.0 ty m s a m y m k o m p u te r z e n ie m o ż n a u z y sk a ć w y n ik u le p s z e g o n iż p rz e z
u s z e re g o w a n ie z a d a ń je d n o p o d r u g im . D łu g o ś ć n a jd łu ż s z e j ś c ie ż k i z s
2 nie później
d o u jś c ia t to d o ln e o g ra n ic z e n ie c z a s u z a k o ń c z e n ia w s z y s tk ic h z a d a ń .
niż 12,0 po 4
P o d ru g ie , w sz y stk ie c z a sy ro z p o c z ę c ia i z a k o ń c z e n ia o d p o w ia d a ją c e
Zadanie Rozpoczęcie
n a jd łu ż s z y m ś c ie ż k o m są realne. K a ż d e z a d a n ie ro z p o c z y n a się p o z a k o ń
0 0.0 c z e n iu w s z y s tk ic h z a d a ń , k tó r y c h je s t n a s tę p n ik ie m w e d łu g o g ra n ic z e ń
1 41.0 p ie rw s z e ń s tw a . Jest ta k , p o n ie w a ż cza s r o z p o c z ę c ia to d łu g o ś ć n a jd łu ż
2 123.0
sze j śc ie ż k i ze ź r ó d ła d o d a n e g o w ie rz c h o łk a . D łu g o ś ć n a jd łu ż s z e j ś c ie ż
3 91.0
k i z s d o t to g ó rn e o g ra n ic z e n ie c z a s u z a k o ń c z e n ia w s z y s tk ic h z a d a ń .
4 1 11 . 0
5 0.0
W y d a jn o ś ć lin io w a a lg o r y tm u w y n ik a b e z p o ś r e d n io z t w i e r d z e n i a t.
6 70.0
7 41.0 S z e r e g o w a n ie z a d a ń r ó w n o le g ły c h z u w z g lę d n i e n ie m w z g lę d n y c h te r m i n ó w
8 91.0 g r a n ic z n y c h K o n w e n c jo n a ln e te r m in y g ra n ic z n e są w y z n a c z a n e w z g lę d e m
9 41.0
c z a su ro z p o c z ę c ia p ie rw s z e g o z a d a n ia . Z ałó żm y , że w p ro b le m ie sz e re g o w a n ia
z a d a ń m o ż n a z a sto so w a ć d o d a tk o w y ro d z a j o g ra n ic z e ń i o k re ślić , że z a d a n ie
2 nie później
niż 70,0 po 7 m u s i się ro z p o c z ą ć p r z e d u p ły w e m o k re ś lo n e g o c z a
Zadanie Czas Względem
su w z g lę d e m in n e g o z a d a n ia . T ak ie o g ra n ic z e n ia są
Zadanie Rozpoczęcie
2 12.0 4
c zę sto p o tr z e b n e w p ro c e s a c h p ro d u k c y jn y c h k r y
0 0.0 2 70.0 7
ty c z n y c h ze w z g lę d u n a czas i w w ie lu in n y c h s y tu a
1 41.0 4 80.0 0
cjac h , je d n a k z n a c z ą c o u tr u d n ia ją ro z w ią z a n ie p r o b
2 123.0 Terminy graniczne
le m u sz ere g o w a n ia . P rz y k ła d o w o załó żm y , ja k p o k a uwzględniane przy
3 91.0
z a n o p o lew ej, że trz e b a d o d a ć o g ra n ic z e n ie , z g o d n ie szeregowaniu zadań
4 111.0
5 0.0 z k tó r y m z a d a n ie 2 m a się ro z p o c z ą ć n ie p ó ź n ie j n iż
6 70.0 12 je d n o s te k c z a su p o ro z p o c z ę c iu z a d a n ia 4. T en te r m in je s t w isto c ie o g r a n i
7 53.0 c z e n ie m c z a su ro z p o c z ę c ia z a d a n ia 4. N ie m o ż e się o n o ro z p o c z ą ć w cześn iej
8 91.0
n iż 12 je d n o s te k c z a su p rz e d u ru c h o m ie n ie m z a d a n ia 2. W p rz y k ła d z ie w p la
9 41.0
n ie je s t m ie jsc e n a d o tr z y m a n ie te r m in u . M o ż n a p rz e s u n ą ć czas ro z p o c z ę c ia
4 nie później z a d a n ia 4 n a 111, czyli 12 je d n o s te k c z a su p r z e d p la n o w a n y m c z a se m r o z p o
niż 80,0 po 0
c z ęc ia z a d a n ia 2. Z au w aż m y , że g d y b y z a d a n ie 4 b y ło d łu g ie , z m ia n a s p o w o
N iem o żliw e! d o w a ła b y o p ó ź n ie n ie c z a su z a k o ń c z e n ia całe g o p ro je k tu . T ak że p o d o d a n iu
Względne d o p la n u te r m in u , z g o d n ie z k tó r y m z a d a n ie 2 m u s i ro z p o c z ą ć się n ie p ó ź n ie j
terminy graniczne n iż 70 je d n o s te k c z a su p o u r u c h o m ie n iu z a d a n ia 7, w p la n ie je s t m ie jsc e n a
przy szeregowaniu
zadań z m ia n ę c z a su ro z p o c z ę c ia z a d a n ia 7 n a 53 b e z k o n ie c z n o ś c i p rz e k ła d a n ia z a
4.4 □ Najkrótsze ścieżki 679
d a ń 3 i 8 . Jeśli je d n a k d o d a m y te r m in , w e d le k tó re g o z a d a n ie 4 m u s i się ro z p o c z y n a ć
nie p ó ź n ie j n iż 80 je d n o s te k p o z a d a n iu 0, p la n s ta n ie się n ie w y k o n a ln y . O g ra n ic z e n ia
określające, że z a d a n ie 4 tr z e b a u ru c h o m ić n ie p ó ź n ie j n iż 80 je d n o s te k c z a su p o z a
d a n iu 0, a z a d a n ie 2 — n ie p ó ź n ie j n iż 12 je d n o s te k c z a su p o z a d a n iu 4, o z n a c z a ją , że
z a d a n ie 2 n ie m o ż e ro z p o c z ą ć się p ó ź n ie j n iż 93 je d n o s tk i c z a su p o z a d a n iu 0. J e d n a k
z a d a n ie 2 ro z p o c z y n a się n ie w cz e śn ie j n iż 123 je d n o s tk i c z a su p o z a d a n iu 0. W y n ik a
to z ła ń c u c h a 0 (41 je d n o s te k ) p r z e d 9 (2 9 je d n o s te k ) p r z e d 6 (21 je d n o s te k ) p rz e d 8
(32 je d n o s tk i) p rz e d 2. D o d a w a n ie n o w y c h te r m in ó w p ro w a d z i, o czy w iście, d o zw ie lo
k ro tn ie n ia m o ż liw o śc i i p o w o d u je p rz e k s z ta łc e n ie ła tw e g o p ro b le m u w tru d n y .
Twierdzenie V. S z e re g o w a n ie z a d a ń ró w n o le g ły c h ze w z g lę d n y m i te r m in a m i
g ra n ic z n y m i to p r o b le m w y z n a c z a n ia n a jk r ó ts z y c h śc ie ż e k w d ig ra fa c h w a ż o
n y c h (z m o ż liw y m i c y k la m i i w a g a m i u je m n y m i).
W ty m p rz y k ła d z ie p o k a z a n o , że w a g i u je m n e m o g ą o d g ry w a ć k lu c z o w ą ro lę w m o
d e la c h p ra k ty c z n y c h sy tu a c ji. Jeśli m o ż n a z n a le ź ć w y d a jn e ro z w ią z a n ie p ro b le m u
w y z n a c z a n ia n a jk ró ts z y c h śc ie ż e k o b e jm u ją c y c h u je m n e w a g i, m o ż n a te ż z n a le ź ć
w y d a jn e ro z w ią z a n ie p r o b le m u s z e re g o w a n ia ró w n o le g ły c h z a d a ń ze w z g lę d n y
m i te r m i n a m i g ra n ic z n y m i. Ż a d e n z o m ó w io n y c h w c z e śn ie j a lg o r y tm ó w n ie je s t
tu o d p o w ie d n i. A lg o ry tm D ijk s try w y m a g a , a b y w a g i b y ły d o d a tn ie (lu b z e ro w e ),
a a l g o r y t m 4 . i o w y m a g a , ż e b y d ig r a f b y ł a c y k lic zn y . D a le j w y ja śn ia m y , ja k p o ra d z ić
so b ie z u je m n y m i w a g a m i w d ig ra fa c h , k tó r e m o g ą o b e jm o w a ć cy k le.
-70
P r ó b a n u m e r I P ie rw s z y p o m y s ł, k tó r y s a m się n a rz u c a , p o le g a n a z n a le z ie n iu k r a
w ę d z i o n a jm n ie js z e j (n a jb a rd z ie j u je m n e j) w a d z e i d o d a n iu w a rto ś c i b e z w z g lę d n e j
tej w a g i d o w s z y s tk ic h k ra w ę d z i w c e lu p r z e k s z ta łc e n ia d ig r a fu n a w e rsję b e z w a g
u je m n y c h . To n a iw n e p o d e jś c ie w o g ó le n ie z a d z ia ła , p o n ie w a ż n a jk r ó ts z e śc ie ż k i
w n o w y m g ra fie n ie b ę d ą o d p o w ia d a ć n a jk r ó ts z y m ś c ie ż k o m w je g o p ie r w o tn e j w e r
sji. Im w ię c e j k ra w ę d z i ś c ie ż k a o b e jm u je , ty m w ię k sz e s z k o d y p o w o d u je ta k ie p r z e
k s z ta łc e n ie (z o b a c z ć w i c z e n i e 4 .4 . 1 4 ).
P r ó b a n u m e r I I D r u g i n a rz u c a ją c y się p o m y s ł p o le g a n a p ró b ie z a a d a p to w a n ia a l
g o r y tm u D ijk s try . P o d s ta w o w y p r o b le m z ty m p o d e jś c ie m p o le g a n a ty m , że a lg o
r y t m w y m a g a s p r a w d z e n ia śc ie ż e k w k o le jn o ś c i ro s n ą c e j w e d łu g ic h o d le g ło ś c i o d
ź ró d ła . W d o w o d z ie p o p r a w n o ś c i a lg o r y tm u w t w i e r d z e n i u r z a ło ż o n o , że d o d a n ie
k ra w ę d z i d o ś c ie ż k i p o w o d u je jej w y d łu ż e n ie . J e d n a k k a ż d a k ra w ę d ź o w a d z e u je m
n ej p ro w a d z i d o sk ró c e n ia śc ie żk i, d la te g o z a ło ż e n ie je s t t u n ie u z a s a d n io n e (z o b a c z
ć w i c z e n i e 4 .4 . 1 4 ).
4.4 o Najkrótsze ścieżki 681
tinyEWDnc.txt C y k le u j e m n e P rz y r o z w a ż a n iu d i-
g rafó w , w k tó r y c h m o g ą w y s tę p o w a ć
15
4 5 0.35
k ra w ę d z ie o w agach u je m n y c h , n a j
5 4 - 0.66 k ró ts z e ś c ie ż k i n ie m a ją z n a c z e n ia , je ś li
4 7 0.37
w d ig ra fie is tn ie je c y k l o u je m n e j w a d z e .
0.28
7 5 0.28 R o z w a ż m y n a p r z y k ła d d ig r a f w id o c z
51 0.32 n y p o lew ej s tro n ie , n ie m a l id e n ty c z n y
0 4 0.38
0.26 z p ie r w s z y m p rz y k ła d e m . W y ją tk ie m
0.39 je s t to , że k ra w ę d ź 5-> 4 m a w a g ę - 0 .6 6 .
0.29
0.34 W a g a c y k lu 4 -> 7 -> 5 -> 4 w y n o s i tu :
0.40
0.52 0 .3 7 + 0 .2 8 - 0 .6 6 = - 0 .0 1
0.58
0.93 M o ż n a w ie lo k r o tn ie p r z e c h o d z ić p rz e z
Najkrótsza ścieżka z 0 do 6 te n c y k l i g e n e ro w a ć d o w o ln ie k ró tk ie
0->4->7->5->4->7->5 . . .••>.1">3 >6 ścieżk i! Z a u w a ż m y , że n ie w sz y stk ie k r a
Digraf ważony z ujemnym cyklem w ę d z ie w c y k lu s k ie ro w a n y m m u s z ą m ie ć
u je m n e w ag i. W a ż n a je s t s u m a w ag.
T eraz z a łó ż m y , że p e w ie n w ie rz c h o łe k n a śc ie ż c e
Szary wierzchołek
z s d o o s ią g a ln e g o w ie rz c h o łk a v z n a jd u je się w c y -nieosiągalny z s
k lu u je m n y m . W te d y z a ło ż e n ie is tn ie n ia n a jk ró ts z e j
śc ie ż k i z s d o v p o w o d u je s p rz e c z n o ś ć , p o n ie w a ż
m o ż n a w y k o rz y s ta ć c y k l d o u tw o r z e n ia ś c ie ż k i o w a
Biały wierzchołek
d ze m n ie js z e j n iż d o w o ln a w a rto ś ć . O z n a c z a to , że
' - osiągalny z s
jeśli is tn ie ją c y k le u je m n e , p r o b le m w y z n a c z a n ia n a j
k ró ts z y c h śc ie ż e k je s t źle p o sta w io n y .
Czarny obrys
- istnieje
Twierdzenie W. N a jk ró ts z a ś c ie ż k a z s d o v najkrótsza
ścieżka z s
w d ig ra fie w a ż o n y m is tn ie je w te d y i ty lk o w ted y ,
je śli o b e c n a je s t p r z y n a jm n ie j je d n a s k ie ro w a n a
śc ie ż k a z s d o v o r a z ż a d e n w ie rz c h o łe k n a tej
ście ż c e n ie n a le ż y d o c y k lu s k ie ro w a n e g o .
Dowód. Z o b a c z w c z e śn ie jsz e o m ó w ie n ie
i ć w i c z e n i e 4 .4 . 2 9 .
ja k w g rafach z k ra w ę d z ia m i o d o d a tn ic h w ag ach .
682 R O ZD ZIA Ł 4 □ Grafy
P ró b a n u m e r III N ie z a le ż n ie o d w y s tę p o w a n ia c y k li u je m n y c h is tn ie je n a jk ró ts z a
ś c ie ż k a p ro s ta łą c z ą c a ź r ó d ło z k a ż d y m o s ią g a ln y m z n ie g o w ie rz c h o łk ie m . D la c z e g o
n ie z d e fin io w a ć n a jk r ó ts z y c h śc ie ż e k w ta k i s p o s ó b , a b y w y z n a c z a ć śc ie ż k i p ro s te ?
N ie ste ty , n a jle p s z y z n a n y a lg o r y tm ro z w ią z u ją c y te n p r o b le m d z ia ła d la n a jg o rsz e g o
p r z y p a d k u w c z a sie w y k ła d n ic z y m (z o b a c z r o z d z i a ł 6 .). O g ó ln ie u z n a je m y ta k ie
p ro b le m y za „ z b y t t r u d n e d o ro z w ią z a n ia ” i b a d a m y p ro s ts z e w e rsje.
p o d s u m o w a n ie — c h o ć w y z n a c z a n ie n a jk r ó ts z y c h śc ie ż e k w d ig r a fa c h z c y k la
m i s k ie ro w a n y m i to źle p o s ta w io n y p r o b le m i n ie m o ż n a s k u te c z n ie ro z w ią z a ć g o
p rz e z z n a le z ie n ie n a jk r ó ts z y c h ś c ie ż e k p ro s ty c h , w p ra k ty c e m o ż n a z id e n ty fik o w a ć
cy k le u je m n e . P rz y k ła d o w o , w p ro b le m ie sz e re g o w a n ia z a d a ń z te r m i n a m i g r a
n ic z n y m i m o ż n a o c z e k iw a ć , że c y k le u je m n e b ę d ą w y s tę p o w a ć s to s u n k o w o r z a d
ko. O g ra n ic z e n ia i te r m in y g ra n ic z n e w y n ik a ją z o g ra n ic z e ń św ia ta rz e c z y w is te g o ,
d la te g o k a ż d y c y k l u je m n y p r a w d o p o d o b n ie w y n ik a z b łę d u w u ję c iu p ro b le m u .
S e n s o w n y m s p o s o b e m p o s tę p o w a n ia je s t w y k ry c ie c y k li u je m n y c h , n a p ra w ie n ie
b łę d ó w i z n a le z ie n ie u s z e r e g o w a n ia d la p r o b le m u p o z b a w io n e g o c y k li u je m n y c h .
W in n y c h s y tu a c ja c h z n a le z ie n ie c y k lu u je m n e g o je s t c e le m o b lic z e ń . O p is a n e d alej
p o d e jś c ie , o p ra c o w a n e p rz e z R. B e llm a n a i L. F o rd a p o d k o n ie c la t 50. u b ie g łe g o w ie
k u , to p r o s ty i s k u te c z n y p u n k t w y jśc ia d o p o r a d z e n ia so b ie z o b o m a p ro b le m a m i.
R o z w ią z a n ie d z ia ła te ż d la d ig r a fó w o w a g a c h d o d a tn ic h .
4.4 * Najkrótsze ścieżki 683
Dowód. D la d o w o ln e g o w ie rz c h o łk a t o sią g a ln e g o z s n a le ż y ro z w a ż y ć k o n k r e t
n ą n a jk ró ts z ą ścieżk ę z s d o t — v 0-> v 1- > . . . -> v k, g d z ie v 0 to s, a vk to t . P o n ie w a ż
nie w y stę p u ją cy k le u je m n e , ta k a śc ie ż k a istn ieje , a k n ie je s t w ię k sz e n iż V - 1.
P rz e z in d u k c ję n a i p o k a z u je m y , że p o i - ty m p rz e b ie g u a lg o r y tm w y z n a c z a n a j
k ró ts z ą ścieżk ę z s d o v . P rz y p a d e k p o d s ta w o w y (i = 0 ) je s t try w ia ln y . P rz y z a ło
ż e n iu , że tw ie rd z e n ie je s t p ra w d z iw e d la i , v0-> V j-> .. . ->v. to n a jk ró ts z a śc ie ż k a z s
d o vf, a d i stT o [ v .] to jej d łu g o ść . W i -ty m p rz e b ie g u p rz e p ro w a d z a m y re la k sa c ję
k aż d e g o w ie rz c h o łk a , w ty m v., ta k w ięc d i s tT o [ v .+1] m a w a rto ś ć n ie w ię k sz ą n iż
d i s t T o f y ] p lu s w a g a v .-> v i+r P o i - t y m p rz e b ie g u d is tT o [ v .+1] m u s i b y ć ró w n e
di s tT o [ v .] p lu s w a g a v .-> v .+r W a rto ść n ie m o ż e być w ięk sza, p o n ie w a ż w i -ty m
p rz e b ie g u w y k o n u je m y re la k sa c ję k a ż d e g o w ie rz c h o łk a , w ty m v., o ra z n ie m o ż e
być m n ie js z a , p o n ie w a ż s ta n o w i d łu g o ś ć n a jk ró tsz e j ście ż k i — v0- > V j-> .. . - >vj+r
T ak w ię c a lg o ry tm w y z n a c z a n a jk ró ts z ą śc ie ż k ę z s d o v 1+1 p o ( i +1) p rz e b ie g a c h .
Dowód. K a ż d y z V p rz e b ie g ó w p o w o d u je re la k s a c ję E k ra w ę d z i.
M e to d a t a je s t b a r d z o o g ó ln a , p o n ie w a ż n ie n a r z u c a k o le jn o ś c i re la k s a c ji k ra w ę d z i.
D alej o g r a n ic z a m y u w a g ę d o m n ie j o g ó ln e j m e to d y , w k tó re j re la k s a c ja je s t w y k o n y
w a n a d la w s z y s tk ic h k ra w ę d z i (w d o w o ln y m p o rz ą d k u ) w y c h o d z ą c y c h z d o w o ln e g o
w ie rz c h o łk a . P o n iż s z y k o d d o w o d z i p r o s to ty te g o p o d e jś c ia :
f o r ( i n t p a s s = 0 ; p a s s < G. V( ) ; p a ss+ + )
f o r (v = 0 ; v < G. V( ) ; v++)
f o r (D ire c te d E d g e e : G. a d j ( v ) )
re la x (e );
N ie ro z w a ż a m y s z c z e g ó ło w o tej w e rsji, p o n ie w a ż z a w s z e p o w o d u je re la k s a c ję V E
k ra w ę d z i, a p r o s ta m o d y fik a c ja sp ra w ia , że w ty p o w y c h z a s to s o w a n ia c h a lg o r y tm
je s t z n a c z n ie w y d ajn iejsz y .
684 RO ZD ZIA Ł 4 o Grafy
Źródło A lg o r y tm B e llm a n a -F o r d a o p a r ty n a k o le jc e M o ż
e d g e T o []
I n a ła tw o z g ó ry ok reślić, że w iele k ra w ę d z i w d a n y m
p rz e b ie g u n ie u m o ż liw ia w y k o n a n ia u d a n e j re la k sa
l- > 3 cji. Jed y n e k ra w ęd z ie m o g ą c e sp o w o d o w a ć z m ia n ę
n © - © \
w ta b lic y di stT o [] w y c h o d z ą z w ierzc h o łk a , k tó reg o
© '- = := ^ ® * w a rto ś ć w tej ta b lic y zm o d y fik o w a n o w p o p rz e d n im
p rzeb ieg u . D o śle d z e n ia ta k ic h w ie rz c h o łk ó w u ż y w a
Na czerwono oznaczono
wierzchołki znajdujące się m y k o lejk i F IF O . P o lew ej stro n ie p o k a z a n o , ja k alg o
w kolejce w danym kroku
e d g e T o [] ry tm d z ia ła d la sta n d a rd o w e g o p rz y k ła d u z d o d a tn i
m i w ag am i. P o lew ej stro n ie r y s u n k u w id o c z n a jest
zaw a rto ść k o lejk i w d a n y m p rz e b ie g u (n a c z e rw o n o )
i w n a s tę p n y m p rz e b ie g u (n a c z a rn o ). P o czątk o w o
3 -> 6
w kolejce z n a jd u je się ź ró d ło . D rz e w o S P T m o ż n a
w y zn aczy ć w o p isa n y p o n iż e j sp o só b .
edgeTo [] ■ R e la k sa c ja k ra w ę d z i l- > 3 i u m ie s z c z e n ie
6->0 3 w k o lejce.
6->2 ■ R e la k sa c ja k ra w ę d z i 3-> 6 i u m ie s z c z e n ie
l->3
6->4 6 w k o lejce.
■ R e la k sa c ja k ra w ę d z i 6 -> 4 , 6 -> 0 i 6-> 2 o ra z
3->6
u m ie s z c z e n ie 4, 0 i 2 w k o le jce .
■ R e la k sa c ja k ra w ę d z i 4->7 i 4 -> 5 o ra z u m ie s z
edgeT o []
6->0 c z e n ie 7 i 5 w k o le jc e . N a s tę p n ie re la k sa c ja
6 ->2 k ra w ę d z i 0 -> 4 i 0 -> 2 , k tó r e są n ie w y b ie ra ln e ,
1->3
i re la k s a c ja k ra w ę d z i 2-> 7 (o r a z z m ia n a k o lo
6->4
4->5 r u k ra w ę d z i 4 -> 7 ).
\ 3->6
Krawędź 2->7 ■ R elak sacja k ra w ę d z i 7->5 (o ra z z m ia n a k o lo ru
o zmienionym kolorze k ra w ę d z i 4-> 5), p rz y c z y m n ie n a leż y u m ie s z
edge T o []
6~>0
czać 5 w kolejce, p o n ie w a ż ju ż się ta m zn ajd u je.
D alej n a stę p u je relak sacja k ra w ę d z i 7->3, k tó ra
6->2
1->3 je s t n ie w y b ie ra ln a . P o te m m a m iejsce re la k sa
6->4
7->5
c ja k ra w ę d z i 5 -> l, 5->4 i 5->7 (są n ie w y b ie ra l
3->6 n e ), p o c z y m k o lejk a staje się p u sta.
2->7
I m p le m e n ta c j a Z a im p le m e n to w a n ie a lg o ry tm u
edgeTo []
0 6-> 0 B e llm a n a -F o rd a w te n sp o s ó b w y m a g a z a sk a k u ją c o
1 n ie w ie le k o d u , co p o k a z a n o w a l g o r y t m i e 4 . 1 1 .
2 6 -> 2
3 l- > 3 R o z w ią z a n ie o p a rte je s t n a d w ó c h d o d a tk o w y c h
4 6->4
7->5 s tru k tu r a c h d a n y ch :
3->6 ■ k o le jc e q z w ie r z c h o łk a m i p rz e z n a c z o n y m i
2 -> 7
d o re la k sa c ji;
Ślad działania algorytmu Bellmana-Forda
■ in d e k s o w a n e j w ie rz c h o łk a m i ta b lic y onQ[]
z w a rto ś c ia m i ty p u b o o le a n , o k re ś la ją c y m i,
k tó r e w ie rz c h o łk i z n a jd u ją się w k o le jc e ( p o
z w a la to u n ik n ą ć d u p lik a tó w ).
4.4 0 Najkrótsze ścieżki 685
Z a c z y n a m y o d u m ie s z c z e n ia w k o le jc e ź r ó d ła s. N a s tę p n ie w c h o d z im y w p ę tlę , k tó r a
p o b ie r a w ie rz c h o łe k z k o le jk i i p rz e p r o w a d z a re la k sa c ję . W c e lu d o d a w a n ia w ie rz
c h o łk ó w d o k o le jk i ro z b u d o w a liś m y im p le m e n ta c ję m e to d y r e l a x ( ) ze s tr o n y 6 5 8 ,
ab y u m ie s z c z a ła w k o le jc e w ie rz c h o łe k d o c e lo w y k a ż d e j k ra w ę d z i, d la k tó re j w y k o
n a n o u d a n ą re la k s a c ję (n o w ą w e rsję p o k a z a n o w k o d z ie p o p ra w e j s tro n ie ) . U ż y te
s t r u k tu r y d a n y c h g w a ra n tu ją , że:
■ W k o le jc e z n a jd u je się ty lk o je d n a p r i y a t e VQid r e i ax(Ed g e W e ig h t e d D ig r a p h G, in t v)
k o p ia k a ż d e g o w ie rz c h o łk a .
° K a ż d y w ie rz c h o łe k , k tó re g o w a r f o r (D i re c t e d E d g e e : G. a d j ( v )
{
to ś c i ed g eT o [] i d i stT o [] z m ie n iły
in t w = e .to ();
się w p e w n y m p rz e b ie g u , z o s ta n ie if (dis tT o Jw ] > d i s t T o [ v ] + e . w e i g h t O )
p r z e tw o r z o n y w n a s tę p n y m . 1
dis t T o Jw ] = di stT o [v] + e . w e i g h t O ;
W c elu u z u p e łn ie n ia im p le m e n ta c ji t r z e
edgeTo[w] = e;
b a z a g w a ra n to w a ć , że a lg o r y tm z a k o ń c z y i f (! onQ[w])
d z ia ła n ie p o V p rz e b ie g a c h . J e d n y m ze {
q.enqueue(w);
s p o s o b ó w n a o s ią g n ię c ie te g o c e lu je s t
onQ[w] = t r u e ;
b e z p o ś r e d n ie ś le d z e n ie lic z b y p rz e b ie g ó w .
}
W o p raco w an ej p rzez nas im p le m e n 1
ta c ji k la s y B e llm a n F o rd S P (a lg o ry tm if ( c o s t + + % G. V () == 0)
findNegativeC ycle();
4 .1 1 ) w y k o rz y s ta liś m y in n e p o d e jś c ie ,
}
o m ó w i o n e s z c z e g ó ł o w o n a s t r o n i e 689. }
T e c h n ik a p o le g a n a w y k ry w a n iu cy k li
ujem nych W p o dzbio rze kraw ędzi d igra fu Relaksacja w algorytmie Bellmana-Forda
zapisanych w edgeT o [] i k o ń c z y działanie
po znalezieniu takiego cyklu.
Twierdzenie Y. O p a r ta n a k o le jc e im p le m e n ta c ja a lg o r y tm u B e llm a n a -F o r d a
ro z w ią z u je p r o b le m w y z n a c z a n ia n a jk r ó ts z y c h ś c ie ż e k z d a n e g o ź r ó d ła s (lu b
z n a jd u je c y k l u je m n y o s ią g a ln y z s) d la d o w o ln e g o d ig r a fu w a ż o n e g o o V w ie rz
c h o łk a c h w c z a sie p r o p o r c jo n a ln y m d o E V i p r z y u ż y c iu d o d a tk o w e j p a m ię c i
w ilo śc i p r o p o r c jo n a ln e j d o V (d la n a jg o rs z e g o p r z y p a d k u ) .
Dowód. Jeśli n ie is tn ie je c y k l u je m n y o s ią g a ln y z s, a lg o r y tm k o ń c z y d z ia ła n ie
p o re la k s a c ja c h o d p o w ia d a ją c y c h p rz e b ie g o w i ( V - 1 ) g e n e ry c z n e g o a lg o r y tm u
o p is a n e g o w t w i e r d z e n i u x ( p o n ie w a ż w sz y stk ie n a jk r ó ts z e śc ie ż k i m a ją m n ie j
n iż V - 1 k ra w ę d z i). Jeżeli z s o s ią g a ln y je s t c y k l u je m n y , k o le jk a n ig d y n ie z o s ta
n ie o p r ó ż n io n a . P o re la k s a c ja c h o d p o w ia d a ją c y c h V -te m u p rz e b ie g o w i o g ó ln e g o
a lg o r y tm u o p is a n e g o w t w i e r d z e n i u x ta b lic a edgeTo [] o b e jm u je śc ie ż k ę z c y
k le m (łą c z y p e w ie n w ie rz c h o łe k w z n im s a m y m ), a c y k l te n m u s i b y ć u je m n y ,
p o n ie w a ż śc ie ż k a z s d o d ru g ie g o w y s tą p ie n ia w m u s i b y ć k ró ts z a n iż śc ie ż k a
z s d o p ie rw s z e g o w y s tą p ie n ia w, a b y w z n a la z ł się w śc ie ż c e p o r a z d ru g i. D la
n a jg o rs z e g o p r z y p a d k u a lg o r y tm d z ia ła ta k , ja k a lg o r y tm o g ó ln y , i w k a ż d y m z V
p rz e b ie g ó w w y k o n u je re la k s a c ję w s z y s tk ic h E k ra w ę d z i.
686 RO ZD ZIA Ł 4 Grafy
public BellmanFordSP(EdgeWeightedDigraph G, in t s)
{
distTo = new double[G.V()];
edgeTo = new DirectedEdge[G .V()];
onQ = new boolean[G .V ()];
queue = new Queue<Integer>();
for (in t v = 0; v < G.V(); v++)
di stTo[v] = Double.POSITI VE_INF I NI TY;
di stTo [s] = 0.0;
queue.enqueue(s);
onQ [s] = true;
while (¡queue.is Empty() && !this.hasN egativeC ycle())
{
in t v = queue.dequeue();
onQ[v] = fa lse ;
re la x(v);
}
}
private void r e la x ( in t v)
// Zobacz stronę 685.
O p a r ty n a k o le jc e a lg o r y tm B e llm a n a -F o r d a je s t s k u te c z n ą i w y Przebiegi
d a jn ą m e to d ą ro z w ią z y w a n ia p r o b le m u w y z n a c z a n ia n a jk r ó t
szy ch śc ie ż e k , c z ę sto s to s o w a n ą w p ra k ty c e (n a w e t w te d y , k ie d y
w a g i są d o d a tn ie ) . N a r y s u n k u p o p ra w e j s tr o n ie p o k a z a n o , że
ro z w ią z a n ie d la p rz y k ła d u o 2 5 0 w ie rz c h o łk a c h m o ż n a z n a le ź ć
w 14 p rz e b ie g a c h i w y m a g a to m n ie j p o r ó w n a ń d łu g o ś c i ś c ie ż e k
n iż w a lg o r y tm ie D ijk stry .
W a g i u j e m n e N a n a s tę p n e j s tr o n ie p o k a z a n o ś la d d z ia ła n ia
Krawędzie z kolejki
a lg o r y tm u B e llm a n a -F o r d a d la d ig r a fu o w a g a c h u je m n y c h .
oznaczono na czerwono
Z a c z y n a m y o d ź ró d ła q, a n a s tę p n ie w y z n a c z a m y d rz e w o S P T
w o p is a n y p o n iż e j sp o s ó b .
° R e la k s a c ja k ra w ę d z i 0-> 2 i 0 -> 4 o ra z u m ie s z c z e n ie 2 i 4
w k o le jc e .
° R e la k sa c ja k ra w ę d z i 2-> 7 i u m ie s z c z e n ie 7 w k o le jc e , a n a
s tę p n ie re la k s a c ja k ra w ę d z i 4 -> 5 i u m ie s z c z e n ie 5 w k o
lejce. P o te m n a s tę p u je re la k s a c ja n ie w y b ie ra ln e j k ra w ę d z i
4-> 7.
° R e la k sa c ja k ra w ę d z i 7-> 3 i 5 - > l o ra z u m ie s z c z e n ie 3 i 1
w k o le jc e . P o te m m a m ie js c e re la k s a c ja n ie w y b ie r a ln y c h
k r a w ę d z i 5-> 4 i 5->7.
D R e la k s a c ja k ra w ę d z i 3-> 6 i u m ie s z c z e n ie 6 w k o le jce .
P o te m n a s tę p u je re la k s a c ja n ie w y b ie ra ln e j k ra w ę d z i l-> 3 .
D R e la k sa c ja k ra w ę d z i 6 -> 4 i u m ie s z c z e n ie 4 w k o le jce .
T a k r a w ę d ź m a w a g ę u je m n ą i d a je k ró ts z ą śc ie ż k ę d o 4,
d la te g o k ra w ę d z ie p r z y w ie rz c h o łk u 4 tr z e b a p o n o w n ie
p o d d a ć re la k s a c ji (p o r a z p ie r w s z y z ro b io n o to w p r z e
b ie g u 2 ). O d le g ło ś c i d o 5 i 1 n ie są ju ż p o p ra w n e , je d n a k
z m ie n i się to w p ó ź n ie js z y c h p rz e b ie g a c h .
° R e la k s a c ja k ra w ę d z i 4 -> 5 i u m ie s z c z e n ie 5 w k o le jc e .
P o te m n a s tę p u je re la k s a c ja k ra w ę d z i 4 -> 7 , k tó r a n a d a l je s t
n ie w y b ie r a ln a .
a R e la k sa c ja k ra w ę d z i 5 - > l i u m ie s z c z e n ie 1 w k o le jc e.
P o te m n a s tę p u je re la k sa c ja n ie w y b ie ra ln y c h k ra w ę d z i 5->4
i 5-> 7.
° R e la k sa c ja k ra w ę d z i l- > 3 , k tó r a n a d a l je s t n ie w y b ie ra ln a .
P o w o d u je to o p ró ż n ie n ie k o le jk i.
D rz e w o n a jk r ó ts z y c h śc ie ż e k d la te g o p rz y k ła d u to je d n a d łu g a
śc ie ż k a z 0 d o 1. R e la k sa c ja k ra w ę d z i z 4, 5 i 1 o d b y w a się d w u
k ro tn ie . P o n o w n e z a p o z n a n ie się z d o w o d e m t w i e r d z e n i a x
w ty m k o n te k ś c ie to d o b r y s p o s ó b n a le p s z e z ro z u m ie n ie r o z
w ią z a n ia .
Algorytm Bellmana-Forda
(250 wierzchołków)
688 R O ZD ZIA Ł 4 o Grafy
tinyEWDn.txt
4->5 0.35 edgeTo[] d istT o []
0
5->4 0.35
1
4->7 0 .3 7 2 0- > 2 0.26
5->7 0.28 3
7->5 0.28 4 0~>4 0.38
5 - > l 0.32 5 4->5 0.73
0-> 4 0.38 6
0->2 0.26 Źródło 7 2->7 0.60
7->3 0.39
l- > 3 0 .2 9 edgeTo[] d istT o []
2->7 0.34 0
1 5-> l 1.05
6->2 - 1 . 2 0
2 0- > 2 0.2 6
3->6 0.52 3 7->3 0.99
6->0 -1 .4 0 4 0 -> 4 0.38
6->4 -1 .2 5 5 4- > 5 0.73
6
7 2- > 7 0.60
edgeTo[] d istT o []
0
1 5->l 1 .0 5
2 0 -> 2 0.26
3 7 ->;3 0.99
4 0- >4 0.38
5 4- >5 0.73
6 3->6 1.51
7 2~>7 0.60
edgeTo[] d istT o []
0
1 5->l 1 .,05
2 0-> 2 0 ., 26
3 7- 7 0. Ju ż nie sq
4 6->4 0 .,26 wybieralne!
5 4->5 0. ,73
6 3 -> 6 1 ., 51
7 2-> 7 0 ., 60
edgeTo[] d istT o []
0
1 5->l 1.05
2 0~>2 0.26
3 7->3 0.99
4 6- > 4 0.26
5 4->5 0.61
6 3-->6 1.51
7 2-> 7 0.60
edgeTo[] distT o[
0
1 5->l 0.93
2 0->2 0.26
3 7->3 0.99
4 6->4 0.26
5 4->5 0.61
6 3->6 1.51
7 2->7 0.60
W ykryw anie cykli ujem nych Opracowana przez nas implementacja klasy
Bel ImanFordSP wykrywa cykle ujemne, aby uniknąć pętli nieskończonej. Można
zastosować służący do wykrywania cykli kod, aby zapewnić klientom możliwość
sprawdzania i wyodrębniania cykli ujemnych. W tym celu dodajemy do interfejsu
API klasy SP (strona 656) następujące metody.
Ite rab le <D ire cte d Ed ge > ne gative Cycle() Zwraca cykl ujemny
( n u l i , jeśli nie ma takich cykli)
Zaimplementowanie tych m etod nie jest trudne, czego dowodem jest kod pokazany
poniżej. Po wykonaniu kodu konstruktora z klasy Bel ImanFordSP wiadomo (z do
wodu t w i e r d z e n i a y ), że digraf ma dostępny ze źródła cykl ujemny wtedy i tylko
wtedy, jeśli kolejka jest niepusta po V-tym przebiegu po wszystkich krawędziach.
Ponadto podgraf z krawędziami z tablicy edgeTo [] musi obejmować cykl ujemny.
Zgodnie z tym w celu zaimplementowania m etody negativeCycle() tworzymy di
graf ważony z krawędzi z tablicy edgeTo [] i szukamy cyklu w tym digrafie. Do wy
krywania cyklu służy wersja klasy Di rectedCycl e z p o d r o z d z i a ł u 4 .2 , dostosowana
do digrafów ważonych (zobacz ć w i c z e n i e 4 .4 .1 2 ). Koszty sprawdzania zmniejszamy
w następujący sposób:
° Przez dodanie zmiennej egzemplarza
p r i v a t e v o i d fin d N e g a t iv e C y c le ()
cycle i metody prywatnej findNegati -
{
veCycle(), która ustawia zmienną cycle i n t V = e d g e T o .l e n g t h ;
na iterator po krawędziach, jeśli znalezio Edg eW eight edD igrap h s p t ;
s p t = new E d g e W e ig h t e d D ig r a p h ( V ) ;
no cykl ujemny (lub na n u li, jeżeli go nie f o r ( i n t v = 0; v < V; v++)
wykryto). i f (edgeTof v] != n u l l )
0 Przez wywoływanie m etody findNega- s p t . a d d E d g e ( e d g e T o [ v ] );
pozwala pobrać taki cykl. Dodanie możliwości p u b lic Iterable<Edge> nega tive Cy cle ()
wykrywania dowolnych cykli ujemnych w di { return cycle; }
grafie także jest prostym rozwinięciem rozwią
zania (zobacz Ć W IC Z E N IE 4 .4 .43 ). Metody do wykrywania cykli ujemnych używane
w algorytmie Bellmana-Forda
690 R O ZD ZIA Ł 4 0 Grafy
tinyEWDnc..txt queue
edgeTo[] d istT o []
4->5
5->4
0.35
0. 66
\
4->7 0.37
5->7 0.28
7->5 0.28 4 0- >4 0 . 38
5->l 0.32 5 4->5 0.73
0->4 0.38
Zródto 7 2->7 0.60
0 -> 2 0.26
7->3 0.39
T3
edgeTo []
■
1—
o
l/l
M
1->3 0.29
2->7 0.34 1 5->l 1.05
6 -> 2 0.40 2 0->2 0.26
3->6 0.52 3 7->3 0.99
6->0 0.58 4 5->4 0.07 ^ Długość ścieżki
6->4 0.93 5 4- >5 0.73 0->4->5->4
6
7 2 -> 7 0.60
e d ge T o [] d ist T o []
0
1 5 ->1 1.05
2 0 -> 2 0.26
3 7->3 0.99
0- >4 0.07
5 4->5 0.42
6 3->6 1.51
7 2->7 0.44
public c la s s Arbitrage
{
public s t a t ic void m ain(String[] args)
{
in t V = S t d l n . r e a d l n t ( ) ;
S t r in g [ ] name = new S trin g [V ] ;
EdgeWeightedDigraph G = new EdgeWeightedDigraph(V);
fo r (in t v = 0; v < V; v++)
{
name[v] = S td In .r e a d S tr in g () ;
fo r (in t w = 0; w < V; w++)
{
double rate = Stdln.readDoubleQ ;
DirectedEdge e = new DirectedEdge(v, w, -M a t h .lo g (ra te ));
G.addEdge(e);
}
}
T en k lie n t klasy Bel 1manFordSP w y szu k u je m o żliw o ści d o a rb itra ż u n a p o d sta w ie tab eli k u r
sów w y m ian y w alut. W ty m celu tw o rz y p e łn y g ra f re p re z e n tu ją c y tę tab elę, a n a stę p n ie k o
rzy sta z a lg o ry tm u B e llm a n a -F o rd a d o z n a le z ien ia cy k lu u je m n e g o w grafie.
Twierdzenie Z. P ro b le m a r b itr a ż u to o d p o w ie d n ik p r o b le m u w y k ry w a n ia c y k li
u je m n y c h w d ig ra fa c h w a ż o n y c h .
Dowód. N a le ż y z a s tą p ić k a ż d ą w a g ę jej lo g a r y tm e m z o d w r ó c o n y m z n a k ie m .
P o te j z m ia n ie o b lic z e n ie w a g śc ie ż e k p rz e z p o m n o ż e n ie w a g k ra w ę d z i w p ie r w o t
nej w e rsji o d p o w ia d a d o d a n iu ic h w p rz e k s z ta łc o n y m p ro b le m ie . K a ż d y ilo c z y n
w ,...w Ł o d p o w ia d a s u m ie - l n ( w ,) - ln ( w ,) - ... - l n ( i y j . P rz e k s z ta łc o n e w a g i
k ra w ę d z i m o g ą b y ć u je m n e lu b d o d a tn ie , śc ie ż k a z v d o w u m o ż liw ia w y m ia n ę
z w a lu ty v n a w a lu tę w, a k a ż d y c y k l u je m n y o z n a c z a m o ż liw o ś ć a rb itra ż u .
W o p is a n y m p rz y k ła d z ie m o ż liw e są w sz y stk ie tr a n s a k c je , d la te g o d ig r a f je s t g ra fe m
p e łn y m , ta k w ię c k a ż d y cy k l u je m n y je s t o s ią g a ln y z d o w o ln e g o w ie rz c h o łk a . O g ó ln ie
n a g ie łd a c h n ie k tó re k ra w ę d z ie m o g ą b y ć n ie o b e c n e , d la te g o p o tr z e b n y je s t je d n o -
a rg u m e n to w y k o n s t r u k to r o p is a n y w ć w i c z e n i u 4 .4 .4 3 . N ie je s t z n a n y w y d a jn y a l
g o ry tm d o w y s z u k iw a n ia n a jle p szej o k a z ji d o a r b itr a ż u (n a jb a rd z ie j u je m n e g o c y k lu
w d ig ra fie ), p r z y c z y m s a m g r a f n ie m u s i b y ć b a r d z o d u ży , a b y p o tr z e b n a b y ła b a r d z o
d u ż a m o c o b lic z e n io w a d o ro z w ią z a n ia te g o p r o b le
-lnC .7 41 ) -lnC l. 36 6) -l n(.995)
m u . J e d n a k n a js z y b sz y a lg o r y tm d o w y s z u k iw a n ia
ja k ie jk o lw ie k m o ż liw o ś c i a r b itr a ż u je s t b a r d z o w a ż \ \ )
.2998 - .3119 + .0050 = -.0071
ny. H a n d la r z p o s ia d a ją c y ta k i a lg o r y tm p r a w d o p o
d o b n ie z d o ła w y k o rz y s ta ć w ie le m o ż liw o śc i, z a n im
d r u g i p o d w z g lę d e m s z y b k o ś c i a lg o r y tm z n a jd z ie
ja k ą k o lw ie k o k azję .
P R Z E K S Z T A Ł C E N IE Z D O W O D U T W IE R D Z E N IA Z je s t
p rz y d a tn e ta k ż e n ie z a le ż n ie o d a rb itra ż u , p o n ie w a ż
re d u k u je p r o b le m w y m ia n y w a lu t d o p r o b le m u w y
z n a c z a n ia n a jk r ó ts z y c h ście ż ek . P o n ie w a ż fu n k c ja
lo g a r y tm ic z n a je s t m o n o to n ic z n a i z m ie n ia m y z n a k
jej w y n ik u , ilo c z y n je s t m a k s y m a ln y , k ie d y s u m a
je s t m in im a ln a . W ag i k ra w ę d z i m o g ą b y ć u je m n e
lu b d o d a tn ie , a n a jk r ó ts z a śc ie ż k a z v d o w o k re ś la Cykl ujemny reprezentujący
n a jle p sz y s p o s ó b w y m ia n y w a lu ty v n a w a lu tę w. okazję do arbitrażu
694 RO ZD ZIA Ł 4 b Grafy
Perspektywa W ta b e li p o n iż e j p r z e d s ta w io n o p o d s u m o w a n ie w a ż n y c h c e c h o p i
sa n y c h w p o d r o z d z ia le a lg o r y tm ó w w y z n a c z a n ia n a jk r ó ts z y c h śc ie ż e k . P ie rw s z y p o
w ó d w y b o r u je d n e g o z a lg o r y tm ó w z w ią z a n y je s t z p o d s ta w o w y m i c e c h a m i u ż y w a
n e g o d ig ra fu . C z y o b e jm u je w a g i u je m n e ? C z y m a cy k le? C z y w y s tę p u ją w n im cy k le
u je m n e ? T a k ż e in n e w ła śc iw o ś c i d ig ra fó w w a ż o n y c h m o g ą b y ć b a r d z o z ró ż n ic o w a
n e, d la te g o je ś li m o ż n a z a s to s o w a ć k ilk a a lg o ry tm ó w , w y b ó r je d n e g o z n ic h w y m a g a
p r z e p r o w a d z e n ia e k s p e ry m e n tó w .
U w a g i h is to r y c z n e P ro b le m y w y z n a c z a n ia n a jk r ó ts z y c h śc ie ż e k in te n s y w n ie b a d a
n o o d la t 50. u b ie g łe g o w ie k u . H is to r ia a lg o r y tm u D ijk s try d o w y z n a c z a n ia n a jk r ó t
sz y c h śc ie ż e k je s t p o d o b n a d o h is to r ii a lg o r y tm u P r im a d o o b lic z a n ia d rz e w M S T
(i p o w ią z a n a z n ią ). N a z w a a lg o r y tm D ijk s tr y je s t p o w s z e c h n ie s to s o w a n a z a ró w
n o d o a b s tra k c y jn e j m e to d y tw o rz e n ia d rz e w S T P p rz e z d o d a w a n ie w ie rz c h o łk ó w
w k o le jn o ś c i ic h o d le g ło ś c i o d ź ró d ła , ja k i d o jej im p le m e n ta c ji, b ę d ą c e j o p ty m a l
n y m a lg o r y tm e m d la re p r e z e n ta c ji w p o s ta c i m a c ie rz y s ą s ie d z tw a . E.W . D ijk s tra o b a
ro z w ią z a n ia p rz e d s ta w ił w p r a c y z 1959 r o k u (w y k a z a ł te ż , że za p o m o c ą te g o s a
m e g o p o d e jś c ia m o ż n a w y z n a c z y ć d rz e w o M S T ). P o p ra w a w y d a jn o ś c i d la g ra fó w
rz a d k ic h w y n ik a z p ó ź n ie js z y c h u s p r a w n ie ń w im p le m e n ta c ja c h k o le je k p r i o r y te
to w y c h (te c h n ik i te n ie są s p e c y fic z n e d la p r o b le m u w y z n a c z a n ia n a jk r ó ts z y c h ś c ie
żek ). Z w ię k sz e n ie w y d a jn o ś c i a lg o r y tm u D ijk s tr y to je d n o z n a jw a ż n ie js z y c h z a s to
s o w a ń ty c h te c h n ik . P rz y k ła d o w o , z a p o m o c ą s t r u k tu r y d a n y c h n a z y w a n e j k o p c e m
F ibonacciego o g ra n ic z e n ie d la n a jg o rs z e g o p r z y p a d k u m o ż n a z m n ie js z y ć d o E + V
lo g V . A lg o ry tm B e llm a n a -F o r d a o k a z a ł się p r z y d a tn y w p ra k ty c e i z n a la z ł w ie le z a
4.4 □ Najkrótsze ścieżki 695
s to so w a ń , s z c z e g ó ln ie w z a k re s ie o g ó ln y c h d ig ra fó w w a ż o n y c h . C h o ć d la ty p o w y c h
z a s to s o w a ń czas w y k o n a n ia a lg o r y tm u B e llm a n a -F o r d a je s t zazw y czaj lin io w y , d la
n a jg o rsz e g o p r z y p a d k u w y n o s i V E . O p ra c o w a n ie a lg o r y tm u lin io w e g o (d la n a jg o r
szego p r z y p a d k u ) d o w y z n a c z a n ia n a jk ró ts z y c h ś c ie ż e k w g ra fa c h rz a d k ic h p o z o s ta je
k w e stią o tw a rtą . P o d s ta w o w y a lg o r y tm B e llm a n a -F o r d a z o s ta ł o p ra c o w a n y w la ta c h
50. u b ie g łe g o w ie k u p rz e z L. F o rd a i R. B e llm a n a . M im o b a r d z o d u ż e j p o p r a w y w w y
d a jn o ś c i, ja k ą z a o b s e r w o w a n o d la w ie lu in n y c h p ro b le m ó w z d z ie d z in y g rafó w , n ie
is tn ie ją n a ra z ie a lg o r y tm y o le p sz e j w y d a jn o ś c i d la n a jg o rs z e g o p r z y p a d k u d la d ig r a
fów z k ra w ę d z ia m i o w a g a c h u je m n y c h (ale b e z c y k li u je m n y c h ).
696 RO ZD ZIA Ł 4 ■ Grafy
| PYTANIA I ODPOWIEDZI
ĆWICZENIA
4.4.1. D o d a n ie stałe j d o w a g i k a ż d e j k ra w ę d z i n ie z m ie n ia ro z w ią z a n ia p r o b le m u
w y z n a c z a n ia n a jk r ó ts z y c h śc ie ż e k z je d n e g o ź r ó d ła — p r a w d a cz y fałsz?
4.4.4. N a ry s u j d rz e w o S P T d la ź ró d ła 0 w d ig ra fie w a ż o n y m u z y s k a n y m p r z e z u s u
n ię c ie w ie rz c h o łk a 7 z g ra f u z p lik u tin y E W D .tx t (z o b a c z s tr o n ę 6 5 6 ). P rz e d s ta w r e
p r e z e n ta c ję d rz e w a S P T o p a r t ą n a o d n o ś n ik a c h d o ro d z ic ó w . W y k o n a j ć w ic z e n ie d la
te g o sa m e g o g ra f u z o d w r ó c o n y m i k ra w ę d z ia m i.
4.4.6. P rz e d s ta w ś la d p ro c e s u w y z n a c z a n ia d rz e w a S P T d la d ig r a fu z ć w i c z e n i a
4 .4.5 za p o m o c ą z a c h ła n n e j w e rsji a lg o r y tm u D ijk stry .
4 .4 . 8 . Ś red n ica d ig r a fu to d łu g o ś ć m a k s y m a ln e j s p o ś r ó d n a jk r ó ts z y c h ś c ie ż e k łą
c z ą c y c h p a r y w ie rz c h o łk ó w . N a p is z ld ie n ta k la s y Di j k s tra S P , k tó r y o k re ś la ś re d n ic ę
d ig r a fu ty p u EdgeWei g h ted D i g ra p h o n ie u je m n y c h w a g a c h .
P ro v id e n c e W esterly N ew L ondon N o rw ic h
P ro v id e n c e - 53 54 48
W esterly 53 - 18 101
N ew L o n d o n 54 18 - 12
N o rw ic h 48 101 12 -
698 RO ZD ZIA Ł 4 □ Grafy
ĆWICZENIA (ciągdalszy)
4 .4 .1 0 . P rz y jm ijm y , że k ra w ę d z ie d ig r a fu z ć w i c z e n i a 4 .4 .4 są n ie s k ie ro w a n e , a k a ż
d a k ra w ę d ź o d p o w ia d a k ra w ę d z io m o ró w n y c h w a g a c h w o b u k ie r u n k a c h z d ig r a fu
w a ż o n e g o ze w s p o m n ia n e g o ć w ic z e n ia . W y k o n a j ć w i c z e n i e 4 .4 .6 d la u z y sk a n e g o
w te n s p o s ó b d ig r a fu w a ż o n e g o .
4 .4 .1 1 . W y k o rz y s ta j m o d e l k o s z tó w p a m ię c io w y c h z p o d r o z d z i a ł u 1 .4 d o u s ta le
n ia ilo śc i p a m ię c i p o tr z e b n e j w k la s ie EdgeWei g h ted D i g ra p h d o p rz e d s ta w ie n ia g ra fu
0 V w ie rz c h o łk a c h i E k ra w ę d z ia c h .
4 .4 .1 2 . Z a a d a p tu j k la s y Di re c te d C y c l e i T o p o io g i c a l z p o d r o z d z i a ł u 4 .2 ta k , a b y
k o rz y s ta ły z in te rfe js ó w A P I EdgeWei gh ted D i g ra p h i Di re c te d E d g e , p r z e d s ta w io n y c h
w ty m p o d ro z d z ia le . Z a im p le m e n tu j w te n s p o s ó b k la s y EdgeWei g h te d C y c le F in d e r
1 EdgeWei ghtedTopologi c a l .
4 .4 .1 3 . P rz e d s ta w ( ta k ja k w ś la d a c h w te k ś c ie ) p ro c e s w y z n a c z a n ia p rz e z a lg o r y tm
D ijk s try d rz e w a S P T d la d ig r a fu u z y s k a n e g o p rz e z u s u n ię c ie k ra w ę d z i 5->7 z p lik u
tin y E W D .tx t (z o b a c z s tr o n ę 65 6 ).
4 . 4 . 1 4 . P rz e d s ta w śc ie ż k i, k tó r e z o s ta n ą o d k r y te p rz e z d w a o p is a n e n a s tr o n ie 680
p r ó b n e ro z w ią z a n ia w p rz y k ła d o w y m g ra fie z p lik u tin y E W N d .tx t p o k a z a n y m n a
o w ej s tro n ie .
4 .4 .1 6 Z a łó ż m y , że p rz e k s z ta łc iliś m y o b ie k t EdgeWei g h te d G ra p h na o b ie k t
EdgeWei g h ted D i g ra p h , tw o rz ą c w ty m o s ta tn im d w a o b ie k ty Di re c te d E d g e (p o j e d
n y m w k a ż d y m k ie r u n k u ) d la k a ż d e g o o b ie k tu Edge z p ie rw s z e g o o b ie k tu (ja k o p is a
n o to w k o n te k ś c ie a lg o r y tm u D ijk s tr y w p y t a n i a c h i o d p o w i e d z i a c h n a s tro n ie
6 9 6 ). N a s tę p n ie s to s u je m y a lg o r y tm B e llm a n a -F o r d a . W y ja śn ij, d la c z e g o to p o d e j
ście d o p ro w a d z i d o s p e k ta k u la r n e j p o ra ż k i.
O d p o w ie d ź: cza s w y k o n a n ia a lg o r y tm u m o ż e w z ro s n ą ć d o w y k ła d n ic z e g o . O p is z n a
p rz y k ła d , ja k a lg o r y tm z a d z ia ła d la p e łn e g o d ig r a fu w a ż o n e g o , w k tó r y m w sz y stk ie
k ra w ę d z ie m a ją w a g ę - 1 .
4 .4 .1 9 Z n a jd ź c y k l o n a jn iż sz e j w a d z e (n a jle p s z ą o k a z ję d o a r b itr a ż u ) w p r z y k ła
d zie p r z e d s ta w io n y m w te k śc ie .
4 .4 .2 0 Z n a jd ź ta b e lę k u r s ó w w y m ia n y w a lu t w in te r n e c ie lu b w g azecie. W y k o rz y s ta j
ją d o u tw o r z e n ia ta b e li a rb itra ż u . U w a g a : u n ik a j ta b e l o p ra c o w a n y c h (w y lic z o n y c h )
n a p o d s ta w ie k ilk u w a rto ś c i — n ie d a ją o n e w y s ta rc z a ją c o p re c y z y jn y c h in f o rm a c ji
o k u rs a c h , a b y b y ły ciek a w e. D o d a tk o w e z a d a n ie : p o d b ij g ie łd ę w y m ia n y w a lu t!
4 .4 .2 1 . P rz e d s ta w (ta k ja k w ś la d a c h w te k ś c ie ) p ro c e s w y z n a c z a n ia d rz e w a S P T
p rz e z a lg o r y tm B e llm a n a -F o r d a d la d ig r a fu w a ż o n e g o z ć w i c z e n i a 4 .4 . 5 .
700 R O ZD ZIA Ł 4 □ Grafy
4 . 4 . 2 2 . W a g i w ie rz c h o łk ó w . P o k a ż , że p ro c e s w y z n a c z a n ia n a jk r ó ts z y c h ście ż e k
w d ig ra fie w a ż o n y m o n ie u je m n y c h w a g a c h w w ie rz c h o łk a c h (w a g a ś c ie ż k i to s u m a
w a g w ie rz c h o łk ó w ) m o ż n a p rz e p r o w a d z ić , tw o rz ą c d ig r a f w a ż o n y , w k tó r y m ty lk o
k ra w ę d z ie m a ją w ag i.
4 . 4 . 2 5 . N a jk r ó ts z a śc ie żk a m ię d z y d w o m a p o d z b io r a m i. D la d ig r a fu z k ra w ę d z ia m i
o d o d a tn ic h w a g a c h i d w ó c h o k re ś lo n y c h p o d z b io r ó w w ie rz c h o łk ó w , S i T, z n a jd ź
n a jk r ó ts z ą śc ie ż k ę z d o w o ln e g o w ie rz c h o łk a z S d o d o w o ln e g o w ie rz c h o łk a z T.
A lg o ry tm p o w in ie n d la n a jg o rs z e g o p r z y p a d k u d z ia ła ć w c z a sie p ro p o r c jo n a ln y m
d o E lo g V.
4 . 4 . 2 7 . N a jk r ó ts z e śc ie żk i w g ra fa c h e u k lid e so w y c h . Z a a d a p tu j in te rfe js y A P I, ab y
p rz y s p ie sz y ć d z ia ła n ie a lg o r y tm u D ijk s try w sy tu a c ji, k ie d y w ia d o m o , że w ie rz c h o łk i
są p u n k ta m i w p rz e s trz e n i.
4 .4 .2 8 . N a jd łu ż s z e śc ie żk i w g ra fa ch D A G . O p ra c u j im p le m e n ta c ję k la s y A cycl i cLP
ta k , a b y ro z w ią z y w a ła p r o b le m w y z n a c z a n ia n a jd łu ż s z y c h ś c ie ż e k w w a ż o n y c h g r a
fa c h D A G , ja k o p is a n o to w t w i e r d z e n i u t.
4 .4 .2 9 . O g ó ln a o p ty m a ln o ś ć . D o k o ń c z d o w ó d t w i e r d z e n i a w p rz e z p o k a z a n ie , że
je ś li is tn ie je śc ie ż k a s k ie ro w a n a z s d o v, a ż a d e n w ie rz c h o łe k n a śc ie ż c e z s d o v n ie
z n a jd u je się w c y k lu u je m n y m , to is tn ie je n a jk r ó ts z a ś c ie ż k a z s d o v ( w s k a z ó w k a :
z o b a c z t w i e r d z e n i e p).
4.4 n Najkrótsze ścieżki 701
4 . 4 .3 0 N a jk r ó ts z e śc ie żk i d la w szy stk ic h p a r w g ra fa ch z c y k la m i u je m n y m i. O p ra c u j
in te rfe js A P I p o d o b n y d o te g o z a im p le m e n to w a n e g o n a s tr o n ie 6 6 8 , słu ż ą c e g o d o
w y z n a c z a n ia n a jk r ó ts z y c h śc ie ż e k d la w s z y s tk ic h p a r w g ra fa c h b e z c y k li u je m n y c h .
O p ra c u j im p le m e n ta c ję o p a r tą n a w e rsji a lg o r y tm u B e llm a n a -F o r d a . A lg o r y tm m a
o k re ś la ć w a g i pi [ v ] , ta k ie że d la d o w o ln e j k ra w ę d z i v->w w a g a k ra w ę d z i p lu s ró ż n ic a
m ię d z y pi [v] a pi [w] je s t n ie u je m n a . N a s tę p n ie w y k o rz y sta j te w a g i d o z m ia n y w a g
g ra f u ta k , a b y m o ż n a b y ło w y k o rz y s ta ć a lg o r y tm D ijk s try d o z n a le z ie n ia w sz y stk ic h
n a jk r ó ts z y c h śc ie ż e k w g ra fie ze z m o d y f ik o w a n y m i w a g a m i.
4 .4 .3 3 . N a jk r ó ts z e śc ie żk i w siatce. N a p o d s ta w ie m a c ie rz y N n a N d o d a tn i c h lic zb
c a łk o w ity c h w y z n a c z n a jk r ó ts z ą ście ż k ę z e le m e n tu ( 0 , 0 ) d o e le m e n tu ( N - 1 , N - 1 ),
g d z ie d łu g o ś ć ś c ie ż k i to s u m a lic z b c a łk o w ity c h n a ścieżce. P o n o w n ie w y k o n a j ć w i
c z e n ie , ale ty m r a z e m p rz y jm ij, że m o ż n a p o r u s z a ć się ty lk o w p ra w o i w d ó ł.
4 .4 .3 4 . N a jk r ó ts z a śc ie żk a m o n o to n ic z n a . D la d ig r a fu w a ż o n e g o z n a jd ź n a jk r ó ts z ą
śc ie ż k ę m o n o fo n ic z n ą z s d o k a ż d e g o in n e g o w ie rz c h o łk a . Ś c ie ż k a je s t m o n o t o n ic z
n a , je ś li w a g a k a ż d e j k ra w ę d z i n a śc ie ż c e je s t śc iśle ro s n ą c a lu b m a le ją c a . Ś c ież k a
p o w in n a b y ć p r o s ta (b e z p o w ta rz a ją c y c h się w ie rz c h o łk ó w ). W s k a z ó w k a : p r z e p r o
w a d ź re la k s a c ję k ra w ę d z i w k o le jn o ś c i ro s n ą c e j i z n a jd ź n a jle p s z ą śc ie ż k ę , a n a s tę p n ie
w y k o n a j re la k s a c ję k ra w ę d z i w p o r z ą d k u m a le ją c y m i w y z n a c z n a jle p s z ą ścież k ę.
4 . 4 . 3 5 . N a jk r ó ts z a śc ie żk a b ito n ic z n a . D la d ig r a fu z n a jd ź n a jk r ó ts z ą śc ie ż k ę b ito-
n ic z n ą z s d o k a ż d e g o in n e g o w ie rz c h o łk a (jeśli ta k a is tn ie je ). Ś c ie ż k a je s t b ito n ic z n a ,
je ż e li is tn ie je w ie rz c h o łe k p o ś r e d n i v, ta k i że k ra w ę d z ie z s d o v są śc iśle ro s n ą c e ,
a k ra w ę d z ie n a śc ie ż c e z v d o t — śc iśle m a le ją c e . Ś c ie ż k a p o w in n a b y ć p r o s ta (b e z
p o w ta rz a ją c y c h się w ie rz c h o łk ó w ).
702 RO ZD ZIA Ł 4 □ Grafy
4 . 4 . 3 7 . K r a w ę d zie k ry ty c z n e . O p ra c u j a lg o r y tm d o w y s z u k iw a n ia k ra w ę d z i, k tó r y c h
u s u n ię c ie p o w o d u je m a k s y m a ln e z w ię k sz e n ie d łu g o ś c i n a jk ró ts z y c h śc ie ż e k z p e w
n e g o d a n e g o w ie rz c h o łk a d o in n e g o o k re ś lo n e g o w ie rz c h o łk a w d ig ra fie w a ż o n y m .
4 . 4 . 3 9 . L e n iw a im p le m e n ta c ja a lg o r y tm u D ijk s try . O p ra c u j im p le m e n ta c ję o p is a n e j
w te k ś c ie le n iw e j w e rsji a lg o r y tm u D ijk stry .
4 .4 .4 0 . D r z e w o S P T z w ą s k im g a rd łe m . W y k a ż , że d rz e w o M S T d la g ra f u n ie s k ie -
ro w a n e g o je s t o d p o w ie d n ik ie m d rz e w a S P T z w ą s k im g a rd łe m — d la k a ż d e j p a r y
w ie rz c h o łk ó w v i w o k re ś lo n a je s t łą c z ą c a je śc ie ż k a , w k tó re j n a jd łu ż s z a k ra w ę d ź je s t
ta k k ró tk a , ja k to m o ż liw e .
4 . 4 . 4 1 . W y s z u k iw a n ie d w u k ie r u n k o w e . O p ra c u j k la s ę d o ro z w ią z y w a n ia p ro b le m u
n a jk ró ts z y c h śc ie ż e k ze ź ró d ła d o u jś c ia o p a r t ą n a k o d z ie a l g o r y t m u 4 .9 , je d n a k tu
k o le jk ę p r io r y te to w ą n a le ż y z a in ic jo w a ć z a ró w n o ź ró d łe m , ja k i u jś c ie m . R o z w ią z a n ie
to p ro w a d z i d o r o z r a s ta n ia się d rz e w a S P T o d k a ż d e g o w ie rz c h o łk a . G łó w n y m z a d a
n ie m je s t p re c y z y jn e o k re ś le n ie , co z ro b ić p r z y z e tk n ię c iu się o b u d rz e w SPT.
4 .4 .4 2 . N a jg o rs zy p r z y p a d e k (w a lg o r y tm ie D ijk s try ). O p is z ro d z in ę g ra fó w o V
w ie rz c h o łk a c h i E k ra w ę d z ia c h , d la k tó re j cz as w y k o n a n ia a lg o r y tm u D ijk s try je s t
ta k i ja k d la n a jg o rs z e g o p rz y p a d k u .
4.4 n Najkrótsze ścieżki 703
4 .4 .4 3 . W y k r y w a n ie cy kli u je m n y c h . Z a łó ż m y , że d o a l g o r y t m u 4 .1 1 d o d a n o k o n
s tru k to r , k tó r y r ó ż n i się o d p ie r w o tn e g o ty lk o ty m , że n ie p rz y jm u je d ru g ie g o a r
g u m e n tu i in ic ju je w sz y stk ie e le m e n ty ta b lic y d i s tT o [ ] w a rto ś c ią 0. W y k a ż , że je śli
k lie n t k o rz y s ta z te g o k o n s tr u k to r a , m e to d a h a s N e g a tiv e C y c le ( ) z w ra c a t r u e w te
d y i ty lk o w ted y , je ż e li g r a f m a c y k l u je m n y ( m e to d a n e g a ti v e C y c le ( ) z w ra c a te n
cykl).
O d p o w ied ź: ro z w a ż d ig r a f u tw o rz o n y n a p o d s ta w ie p ie r w o tn e g o p rz e z d o d a n ie d o
w sz y stk ic h p o z o s ta ły c h w ie rz c h o łk ó w n o w e g o ź r ó d ła z k ra w ę d z ią o w a d z e 0. P o j e d
n y m p rz e b ie g u w sz y stk ie e le m e n ty ta b lic y d i s tT o [] m a ją w a rto ś ć 0, a w y s z u k iw a n ie
cy k lu u je m n e g o o s ią g a ln e g o z d a n e g o ź ró d ła p rz e b ie g a a n a lo g ic z n ie d o s z u k a n ia c y
k lu u je m n e g o w d o w o ln y m m ie js c u p ie r w o tn e g o g ra fu .
4 .4 .4 4 . N a jg o rs zy p r z y p a d e k (w a lg o r y tm ie B e llm a n a -F o rd a ). O p is z ro d z in ę grafów ,
d la k tó r y c h a l g o r y t m 4 .1 1 d z ia ła w c z a sie p r o p o r c jo n a ln y m d o V E.
4 .4 .4 5 . S z y b k a w ersja a lg o r y tm u B e llm a n a -F o rd a . O p ra c u j a lg o r y tm , k tó r y ła m ie
lin io w o -lo g a ry tm ic z n ą b a rie rę c z a su w y k o n a n ia w p ro b le m ie w y z n a c z a n ia n a jk r ó t
szy ch ś c ie ż e k z je d n e g o ź ró d ła w o g ó ln y c h d ig r a fa c h w a ż o n y c h d la s p e c ja ln e g o p r z y
p a d k u , w k tó r y m w a g i to lic z b y c a łk o w ite o w a rto ś c i b e z w z g lę d n e j n ie w ię k sz e j n iż
p e w n a stała.
4 .4 .4 6 . A n im a c ja . N a p is z k lie n ta , k tó r y g e n e ru je d y n a m ic z n e a n im a c je d z ia ła n ia
a lg o r y tm u D ijk stry .
704 R O ZD ZIA Ł 4 □ Grafy
| EKSPERYMENTY
T esto w a n ie w szy s tk ic h a lg o r y tm ó w i b a d a n ie k a żd e g o p a r a m e tr u w k a ż d y m m o d e lu
g ra fó w je s t n ie w y k o n a ln e . D la k a żd e g o z w y m ie n io n y c h d a le j p r o b le m ó w n a p is z k lie n
ta, k tó r y ro z w ią z u je p r o b le m d la d o w o ln eg o d ig ra fu w ejściow ego. N a s tę p n ie w y b ie r z
je d e n z o p isa n ych w c ze śn ie j g e n e ra to ró w d o p r z e p r o w a d z e n ia e k s p e r y m e n tó w d la d a
nego m o d e lu grafów . W y k o r z y s ta j w ła sn ą ocen ę sy tu a c ji p r z y d o b o rz e e k s p e r y m e n tó w
(m o ż e s z o p rze ć się n a w y n ik a c h w c ze śn ie jszy c h p o m ia r ó w ). N a p is z w y ja śn ie n ie w y n i
k ó w i w n io sk i, k tó re m o ż n a z n ich w yciągnąć.
4 .4 .5 3 . P ro g n o zy . O sz a c u j z d o k ła d n o ś c ią d o 10 ra z y r o z m ia r n a jw ię k s z e g o g ra fu
s p e łn ia ją c e g o z a le ż n o ś ć E = \Q V , d la k tó re g o a lg o r y tm D ijk s tr y p o tr a f i w y z n a c z y ć
w sz y stk ie n a jk r ó ts z e ś c ie ż k i w 10 s e k u n d za p o m o c ą T w o jeg o k o m p u te r a i s y s te m u
o p e ra c y jn e g o .
4 . 4 . 5 5 . A lg o r y tm Jo h n so n a . O p ra c u j im p le m e n ta c ję k o le jk i p rio ry te to w e j o p a r t ą n a
k o p c u z w ę z ła m i o d d z ie c ia c h . Z n a jd ź n a jle p s z ą w a rto ś ć d d la r ó ż n y c h m o d e li d i
g ra fó w w a ż o n y c h .
4 . 4 . 5 6 . M o d e l p r o b le m u a rb itra ż u . O p ra c u j m o d e l d o g e n e ro w a n ia lo s o w y c h p r o b
le m ó w a rb itra ż u . C e le m je s t g e n e ro w a n ie ta b e l ja k n a jb a rd z ie j z b liż o n y c h d o ta b e l
u ż y ty c h w ć w i c z e n i u 4 .4 . 2 0 .
K i z n a n e ap lik a c je są o p a rte n a p rz e tw a r z a n iu ła ń c u c h ó w zn ak ó w . W ty m r o z
d z iale o m a w ia m y k la sy c z n e a lg o ry tm y d o ro z w ią z y w a n ia p ro b le m ó w o b lic z e
n io w y c h z w y m ie n io n y c h p o n iż e j o b szaró w .
P r z e t w a r z a n ie in f o r m a c ji P rz y w y s z u k iw a n iu s tr o n W W W o b e jm u ją c y c h d a n e s ło
w o k lu c z o w e k o rz y s ta m y z a p lik a c ji d o p rz e tw a r z a n ia ła ń c u c h ó w zn a k ó w . W e w s p ó ł
c z e sn y m św iecie p ra k ty c z n ie w szy stk ie in f o rm a c je są z a p is a n e w fo r m ie s e k w e n c ji ła ń
c u c h ó w zn ak ó w , a a p lik a c je d o ic h p r z e tw a r z a n ia o d g ry w a ją n ie z w y k le w a ż n ą ro lę.
B a d a n ia n a d g e n o m e m N a u k o w c y z a jm u ją c y się b io lo g ią o b lic z e n io w ą p r a c u ją n a d
k o d e m g e n e ty c z n y m , w k tó r y m k o d D N A je s t z r e d u k o w a n y d o b a r d z o d łu g ic h ła ń c u
c h ó w s k ła d a ją c y c h się z c z te re c h z n a k ó w — A, C, T i G. W o s ta tn ic h la ta c h o p ra c o w a n o
ro z b u d o w a n e b a z y d a n y c h z k o d a m i o p is u ją c y m i r ó ż n o r o d n e ży w e o rg a n iz m y , d la
te g o p rz e tw a r z a n ie ła ń c u c h ó w z n a k ó w je s t w a ż n y m a s p e k te m w s p ó łc z e s n y c h b a d a ń
w d z ie d z in ie b io lo g ii o b lic z e n io w e j.
S y s t e m y k o m u n i k a c j i W r a m a c h p rz e s y ła n ia w ia d o m o ś c i te k s to w e j lu b w ia d o m o
ści e -m a il a lb o p o b ie r a n ia k s ią ż k i e le k tro n ic z n e j ła ń c u c h z n a k ó w je s t p rz e k a z y w a n y
z je d n e g o m ie js c a w in n e . A lg o ry tm y p r z e tw a r z a n ia ła ń c u c h ó w z n a k ó w o p ra c o w a n o
p o c z ą tk o w o w ła ś n ie n a p o tr z e b y a p lik a c ji w y k o n u ją c y c h te z a d a n ia .
S y s t e m y p r o g r a m o w a n i a P ro g r a m y to ła ń c u c h y z n a k ó w . K o m p ila to ry , in te r p r e te r y
i in n e a p lik a c je p rz e k s z ta łc a ją c e p r o g r a m y n a in s tru k c je m a s z y n o w e to n ie z w y k le
w a ż n e a p lik a c je , w k tó r y c h s to su je się z a a w a n s o w a n e te c h n ik i p r z e tw a r z a n ia ł a ń
c u c h ó w z n ak ó w . W s z y s tk ie ję z y k i p is a n e są p r z e d s ta w ia n e za p o m o c ą ła ń c u c h ó w
zn a k ó w , a n a s tę p n y m p o w o d e m ro z w ija n ia a lg o r y tm ó w p r z e tw a r z a n ia ła ń c u c h ó w
z n a k ó w b y ła te o r ia ję z y k ó w fo r m a ln y c h (je st to d z ie d z in a n a u k i o p is u ją c a z b io r y ła ń
c u c h ó w z n a k ó w ).
707
708 RO ZD ZIA Ł 5 a Łań cuch y znaków
O to p la n te g o ro z d z ia łu . N a jp ie r w o m a w ia m y p o d s ta w o w e c e c h y ła ń c u c h ó w zn ak ó w ,
a d a lej, w p o d r o z d z i a ł a c h 5.1 i 5 . 2 , w r a c a m y d o in te rfe js ó w A P I s łu ż ą c y c h d o s o r
to w a n ia i w y s z u k iw a n ia , p r z e d s ta w io n y c h w r o z d z i a ł a c h 2 . i 3 . A lg o ry tm y , w k tó
ry c h w y k o rz y s ta n o s p e c y fic z n e c e c h y k lu c z y w p o s ta c i ła ń c u c h ó w z n a k ó w , są s z y b
sze i b a rd z ie j e la s ty c z n e o d w c z e śn ie j o p is a n y c h a lg o ry tm ó w . W p o d r o z d z i a l e 5.3
o m a w ia m y a lg o r y tm y w y s z u k iw a n ia p o d ła ń c u c h ó w , w ty m s ły n n y a lg o r y tm p r z y p i
s y w a n y K n u th o w i, M o r ris o w i i P ra tto w i. W p o d r o z d z i a l e 5 .4 w p ro w a d z a m y w y
r a ż e n ia reg u la rn e. N a ic h p o d s ta w ie o m a w ia m y p ro b le m d o p a s o w y w a n ia do w zo rca ,
k tó r y s ta n o w i u o g ó ln ie n ie p r o b le m u w y s z u k iw a n ia p o d ła ń c u c h ó w , o ra z p ro g r a m
grep — k lu c z o w e n a rz ę d z ie d o w y sz u k iw a n ia . K la sy c z n e a lg o r y tm y z te g o o b s z a r u
o p a rte są n a p o w ią z a n y c h z a g a d n ie n ia c h — ję z y k a c h fo r m a ln y c h i a u to m a ta c h s k o ń
czo n ych . p o d r o z d z i a ł 5.5 p o ś w ię c a m y w a ż n e m u z a g a d n ie n iu — k o m p re sji d a n y c h .
P ró b u je m y tu m a k s y m a ln ie z m n ie js z y ć r o z m ia r ła ń c u c h ó w zn a k ó w .
Z n a k i O b ie k t S t r i n g to c ią g z n ak ó w . Z n a k i są ty p u c h a r i p rz y jm u ją j e d n ą z 2 16
m o ż liw y c h w a rto ś c i. P rz e z d z ie s ię c io le c ia p r o g r a m iś c i s to s o w a li z n a k i k o d o w a n e za
p o m o c ą 7 -b ito w e g o k o d u A S C II (ta b e lę k o n w e rs ji p r z e d s ta w io n o n a s tr o n ie 8 2 7 ) lu b
8 -b ito w e g o r o z s z e rz o n e g o k o d u A S C II, je d n a k w w ie lu w s p ó łc z e s n y c h z a s to s o w a
n ia c h p o tr z e b n e są 1 6 -b ito w e z n a k i U n ic o d e .
N i e z m i e n n o ś ć O b ie k ty S t r i ng są n ie z m ie n n e , d la te g o m o ż n a je sto so w a ć w in s tr u k
c ja c h p rz y p is a n ia o ra z ja k o a r g u m e n ty i w a rto ś c i z w ra c a n e m e t o d b e z o b a w o z m ia n ę
w a rto ś c i.
I n d e k s o w a n i e N a jc z ę śc ie j w y k o n y w a n ą o p e ra c ją je s t w y o d rę b n ia n ie określonego
z n a k u z ła ń c u c h a . S łu ż y d o te g o m e t o d a c h a r A t( ) k la s y S t r i n g Javy. O c z e k u je m y ,
że m e to d a w y k o n a z a d a n ie w s ta ły m czasie, ta k ja k b y ła ń c u c h z n a k ó w b y ł z a p is a n y
w ta b lic y c h a r [ ] . Jak o p is a n o w r o z d z i a l e i., je s t to u z a s a d n io n e o c z e k iw a n ie .
D łu g o ś ć W Javie o p e ra c ja w y z n a c z a n ia d łu g o śc i ła ń c u c h a z n a k ó w je s t z a im p le m e n
to w a n a w m e to d z ie length() k la s y String. T a k ż e tu o c z e k u je m y , że m e t o d a 1ength()
z a k o ń c z y d z ia ła n ie w s ta ły m czasie. O c z e k iw a n ie to je s t u z a s a d n io n e , c h o ć w n ie k t ó
ry c h ś r o d o w is k a c h p ro g r a m is ty c z n y c h tr z e b a z a c h o w a ć s ta ra n n o ś ć .
P o d ła ń c u c h M e to d a s u b s t r i ng () Javy to im p le m e n ta c ja o p e ra c ji w y o d rę b n ij określony
p o d ła ń c u c h . O c z e k u je m y , że m e to d a b ę d z ie d z ia ła ć w s ta ły m czasie, ta k ja k w s ta n d a r
d o w ej im p le m e n ta c ji w Javie. Jeśli n ie z n a s z m e to d y s u b s t r i ng () i p r z y c z y n , d la któ ry c h
d zia ła w s ta ły m czasie, k o n ie c zn ie p r z e c z y ta j o m ó w ie n ie sta n d a rd o w e j im p le m e n ta c ji
ła ń cu ch ó w z n a k ó w w Javie w p o d r o z d z ia le 1.2 (z o b a c z s tro n y 92 i 216 ).
R O ZD ZIA Ł 5 Q Łań cu ch y znaków 709
Z łą c z a n ie W Javie o p e ra c ja u tw ó rz
s . 1 e n gth O
n o w y ła ń cu ch z n a k ó w p r z e z d o łą czen ie
1
je d n e g o ła ń cu ch a do drugiego je s t w b u 0 1 2 3 4 5 6 7 8 9 10 11 12
d o w a n a ( o p a r ta n a o p e ra to rz e +) i d z ia ła — ► A T T A C K A T D A W N
w czasie p r o p o r c jo n a ln y m d o d łu g o ś c i
f
s.charAt(3) \\
w y n ik u . U n ik a m y tw o rz e n ia ła ń c u c h a
s . s u b s t r in g ( 7 , 1 1 )
z n a k ó w p rz e z d o d a w a n ie z n a k ó w je d e n
p o d ru g im , p o n ie w a ż w Javie czas w y Podstawowe operacje klasy S t r in g działające w czasie stałym
k o n a n ia ro ś n ie w te d y kw a d ra to w o . D o
w y k o n y w a n ia d o łą c z a n ia w Javie słu ż y
ld a sa S t r i ngBui 1 d e r.
T a b lic e z n a k ó w T y p S t r i n g w Javie n ie je s t ty p e m p ro s ty m . S ta n d a r d o w a im p le
m e n ta c ja o b e jm u je o p is a n e w c z e śn ie j o p e ra c je , p rz y s p ie sz a ją c e p is a n ie k o d u k lie n ta .
J e d n a k w ie le o m a w ia n y c h a lg o r y tm ó w m o ż e d z ia ła ć n a re p r e z e n ta c ji n is k o p o z io m o -
w ej, n a p r z y k ła d n a ta b lic y w a rto ś c i ty p u c h a r. W w ie lu k lie n ta c h ta k a r e p r e z e n ta c ja
je s t p re f e ro w a n a , p o n ie w a ż w y m a g a m n ie j p a m ię c i i cz a su . D la k ilk u o m a w ia n y c h
a lg o r y tm ó w k o s z t p rz e k s z ta łc a n ia z je d n e j re p r e z e n ta c ji n a d r u g ą b y łb y w y ż sz y
n iż k o s z t w y k o n a n ia a lg o r y tm u . Ja k p o k a z a n o w ta b e li p o n iż e j, ró ż n ic e w k o d z ie
d o p r z e tw a r z a n ia o b u r e p r e z e n ta c ji są n ie w ie lk ie ( m e to d a s u b s t r i ng () je s t b a rd z ie j
sk o m p lik o w a n a , d la te g o ją p o m ija m y ), ta k w ię c z a s to s o w a n ie je d n e j lu b d ru g ie j r e
p r e z e n ta c ji n ie p rz e s z k a d z a w z ro z u m ie n iu a lg o ry tm u .
p o z n a n i e w y d a j n o ś c i o m a w i a n y c h o p e r a c j i je s t k lu c z e m d o z r o z u m ie n ia w y
d a jn o ś c i k ilk u a lg o r y tm ó w p r z e tw a r z a n ia ła ń c u c h ó w z n a k ó w . N ie w s z y s tk ie ję z y
k i p r o g r a m o w a n ia u d o s tę p n ia ją im p le m e n ta c je k la s y S t r i n g o p r z e d s ta w io n y c h tu
c e c h a c h z o b s z a r u w y d a jn o ś c i. P rz y k ła d o w o , w p o w s z e c h n ie s to s o w a n y m ję z y k u
C o p e r a c ja p o b ie r a n ia p o d ła ń c u c h a i o k r e ś la n ia d łu g o ś c i ła ń c u c h a z n a k ó w z a jm u
je c z a s p r o p o r c jo n a l n y d o lic z b y z n a k ó w w ła ń c u c h u . Z a a d a p to w a n ie o p is y w a n y c h
a lg o r y tm ó w d o ta k ic h ję z y k ó w z a w sz e je s t m o ż liw e (tr z e b a z a im p le m e n to w a ć ty p
A D T p o d o b n y d o ty p u S t r i ng Javy), p r z y c z y m z w ią z a n e je s t to z r ó ż n y m i t r u d n o ś
c ia m i i m o ż liw o ś c ia m i.
W te k ś c ie k o r z y s ta m y g łó w n ie z ty p u d a n y c h S t r i ng i s w o b o d n ie s to s u je m y i n
d e k s o w a n ie o ra z o k re ś la n ie d łu g o ś c i, a c z a se m w y o d r ę b n ia n ie p o d ła ń c u c h ó w i z łą
cz a n ie . W a d e k w a tn y c h s y tu a c ja c h u d o s tę p n ia m y w w itr y n ie o d p o w ie d n i k o d o p a r ty
n a ta b lic a c h w a rto ś c i ty p u c h a r. W z a s to s o w a n ia c h , g d z ie w y d a jn o ś ć o d g ry w a k r y
ty c z n ą ro lę , p o d s ta w o w ą k w e stią p r z y w y b o rz e je d n e g o z d w ó c h k lie n tó w je s t cz ę sto
k o s z t d o s tę p u d o z n a k u (w ty p o w y c h im p le m e n ta c ja c h Jav y in s tr u k c ja a [ i ] d z ia ła
z n a c z n ie sz y b c iej n iż s . c h a rA t ( i )).
A lfa b e ty W n ie k tó r y c h a p lik a c ja c h u ż y w a n e są ła ń c u c h y z n a k ó w o p a r te n a o g r a
n ic z o n y m a lfa b e c ie . W ta k ic h s y tu a c ja c h c z ę sto w a r to z a s to s o w a ć k la s ę Al p h a b e t. Jej
in te rfe js A P I p r z e d s ta w io n o p o n iż e j.
p u b l i c c l a s s A lp ha be t
T e n in te rfe js A P I je s t o p a r ty n a k o n s tr u k to r z e , k tó r y p rz y jm u je a r g u m e n t w p o s ta c i
P -z n a k o w e g o ła ń c u c h a z n a k ó w o k re ś la ją c e g o a lfa b e t, o ra z n a m e to d a c h to C h a r ( )
i t o I n d e x ( ) , p rz e k s z ta łc a ją c y c h (w s ta ły m cz a sie ) d a n e m ię d z y z n a k a m i a w a r to ś
c ia m i ty p u i n t z p r z e d z ia łu o d 0 d o R - l . In te rfe js o b e jm u je te ż m e to d ę c o n t a i n s ( ) ,
s łu ż ą c ą d o s p ra w d z a n ia , c z y d a n y z n a k z n a jd u je się w a lfa b e c ie , o ra z m e to d y R()
i 1 gR () d o w y s z u k iw a n ia lic z b y z n a k ó w w a lfa b e c ie i lic z b y b itó w p o tr z e b n y c h d o
ic h r e p r e z e n to w a n ia . D o s tę p n e są te ż m e to d y t o I n d i c e s Q i to C h a r s ( ) d o p r z e
k s z ta łc a n ia m ię d z y ła ń c u c h a m i z n a k ó w a lfa b e tu a ta b lic a m i w a rto ś c i ty p u i n t. D la
w y g o d y w ta b e li w g ó rn e j c z ę śc i n a s tę p n e j s tr o n y p rz e d s ta w ia m y te ż w b u d o w a n e
alfab ety , z k tó r y c h m o ż n a k o rz y s ta ć za p o m o c ą k o d u w ro d z a ju Alphabet.UNICODE.
Z a im p le m e n to w a n ie k la s y A lp h a b e t to p ro s te z a d a n ie (z o b a c z ć w i c z e n i e 5 . 1 . 1 2 ).
N a s tr o n ie 711 p r z e d s ta w io n o p rz y k ła d o w e g o k lie n ta tej klasy.
T a b lic e i n d e k s o w a n e z n a k a m i J e d n ą z n a jw a ż n ie js z y c h p rz y c z y n s to s o w a n ia k la s y
Al p h a b e t je s t to , że w y d a jn o ś ć w ie lu a lg o r y tm ó w m o ż n a z w ię k sz y ć p rz e z z a s to s o w a
n ie ta b lic in d e k s o w a n y c h z n a k a m i. W y m a g a to p o w ią z a n ia z k a ż d y m z n a k ie m in f o r
m a c ji, k tó r e m o ż n a p o b r a ć za p o m o c ą je d n e g o d o s tę p u d o ta b lic y . D la ty p u S t r i ng
R O ZD ZIA Ł 5 D Łań cu ch y znaków 711
BINARY 2 1 01
DNA 4 2 ACTG
OCTAL 8 3 01234567
DECIMAL 10 4 0123456789
HEXADECIMAL 16 4 0 123456 7 8 9 A B C D E F
PROTEIN 20 5 A C D E F G H IK L M N P Q R S T V W Y
LOWERCASE 26 5 a b c d e fg h ijk lm n o p q rstu v w x y z
UPPERCASE 26 5 A B C D E F G H IJK L M N O P Q R S T U V W X Y Z
A B C D E F G H IJK L M N O P Q R S T U V W X Y Z
BASE64 64 6
a b cd efg h ijld m n o p q rstu v w x y zO 1 2 3 456789+ /
A SC II 128 7 Znaki ASCII
EXTENDED_ASCII 256 8 Znaki z rozszerzonego zestawu ASCII
UNIC0DE16 65536 16 Znaki Unicode
Standardowe alfabety
p u b l i c c l a s s Count
{
p u b l i c s t a t i c v o i d main ( S t r i ng[ ] args)
{
A lp h a b e t a l p h a = new A 1 p h a b e t ( a r g s [0 ] ) ;
in t R = alpha.R ();
int[] count = new i nt [ R ] ;
i n t [] a = a lp h a . t o l n d i c e s ( s ) ;
for (in t i = 0; i < N; i++)
c o u n t[a[i]]++;
W ty m k o n te k ś c ie R to p o d s ta w a s y s te m u lic z b o w e g o . K ilk a o m a w ia n y c h a lg o r y tm ó w
c z ę sto n a z y w a n y c h je s t m e to d a m i p o z y c y jn y m i, p o n ie w a ż d z ia ła ją c y fra p o cy frze.
% more p i . t x t
3141592653
5897932384
6264338327
9502884197
... [100 000 c y f r l i c z b y p i]
m i m o z a l e t s to s o w a n ia w a lg o r y tm a c h p rz e tw a r z a n ia ła ń c u c h ó w z n a k ó w ty p u d a
n y c h w ro d z a ju k la s y Al phabet (z w ła sz c z a d la m a ły c h a lfa b e tó w ), w k sią ż c e n ie r o z
w ija m y w ła s n y c h o p a r ty c h n a o g ó ln e j k la s ie Al phabet im p le m e n ta c ji d la ła ń c u c h ó w
z n ak ó w . W y n ik a to z n a s tę p u ją c y c h p rz y c z y n :
■ W w ię k sz o ś c i k lie n tó w u ż y w a n y je s t ty p S t r i ng.
■ K o n w e rs ja n a in d e k s y i z n ic h c z ę sto z n a jd u je się w p ę tli w e w n ę trz n e j o ra z
z n a c z n ie s p o w a ln ia d z ia ła n ie k o d u .
■ K o d je s t b a rd z ie j sk o m p lik o w a n y , a ty m s a m y m i tr u d n ie js z y d o z ro z u m ie n ia .
D la te g o u ż y w a m y ty p u S t r i ng, w k o d z ie k o r z y s ta m y ze sta łe j R = 256 i p o d a je m y
R ja k o p a r a m e t r w a n a liz a c h . W o d p o w ie d n ic h m ie js c a c h o m a w ia m y w y d a jn o ś ć
o g ó ln y c h a lfa b e tó w . P e łn e im p le m e n ta c je o p a r t e n a k la s ie Al phabet z n a jd u ją się
w w itr y n ie .
w w i e l u z a s t o s o w a n i a c h s o r t o w a n i a k lu c z e w y z n a c z a ją c e p o r z ą d e k są ł a ń c u
c h a m i z n a k ó w . W ty m p o d r o z d z ia le o m a w ia m y m e to d y , w k tó r y c h w y k o rz y s ta n o
s p e c y fic z n e c e c h y ła ń c u c h ó w z n a k ó w d o o p ra c o w a n ia te c h n i k s o r to w a n ia k lu c z y
w tej p o s ta c i. T e c h n ik i te są w y d a jn ie js z e o d m e to d s o r to w a n ia d o o g ó ln e g o u ż y tk u ,
o p is a n y c h w r o z d z i a l e 2 .
R o z w a ż a m y t u d w a z a s a d n ic z o o d m ie n n e p o d e jś c ia d o s o r to w a n ia ła ń c u c h ó w
z n ak ó w . O b a to u z n a n e sp o so b y , o d d z ie s ię c io le c i p r z y d a tn e p ro g r a m is to m .
P ie rw s z e p o d e jś c ie p o le g a n a s p r a w d z a n iu z n a k ó w w k lu c z a c h w k o le jn o ś c i o d
p ra w e j d o lew ej. T ego ro d z a ju m e to d y n a z y w a n e są s o r to w a n ie m ła ń c u c h ó w z n ak ó w ,
p o c z ą w s z y o d n a jm n ie j z n a c z ą c e j cyfry. U ż y c ie p o ję c ia cy fra z a m ia s t z n a k w y n ik a
ze s to s o w a n ia tej sa m e j p o d s ta w o w e j m e to d y d o lic z b r ó ż n e g o ro d z a ju . Jeśli ła ń c u c h
z n a k ó w p o tr a k tu je m y ja k lic z b ę o p o d s ta w ie 2 5 6 , s p r a w d z a n ie z n a k ó w o d p ra w e j
d o lew ej o d p o w ia d a s p r a w d z a n iu n a jp ie rw n a jm n ie j z n a c z ą c y c h cyfr. T o p o d e jś c ie
je s t m e to d ą s to s o w a n ą z w y b o r u w a p lik a c ja c h s o r tu ją c y c h ła ń c u c h y z n a k ó w , je ś li
w sz y stk ie k lu c z e m a ją tę s a m ą d łu g o ś ć .
D ru g ie p o d e jś c ie o p a r te je s t n a s p r a w d z a n iu z n a k ó w w k lu c z a c h w k o le jn o ś c i o d
lew ej d o p ra w e j. N a jp ie r w a n a liz o w a n e są tu n a jb a rd z ie j z n a c z ą c e z n a k i. T eg o r o
d z a ju m e to d y n a z y w a n e są s o r to w a n ie m ła ń c u c h ó w z n a k ó w , p o c z ą w s z y o d n a jb a r
d z ie j z n a c z ą c e j cyfry. W p o d r o z d z ia le o m a w ia m y d w ie m e to d y te g o ro d z a ju . Są o n e
a tra k c y jn e , p o n ie w a ż n ie w y m a g a ją s p r a w d z a n ia w s z y s tk ic h z n a k ó w w e jśc io w y c h .
T e c h n ik i te p rz y p o m in a ją s o r to w a n ie szy b k ie , p o n ie w a ż d z ie lą s o r to w a n ą ta b lic ę n a
n ie z a le ż n e fra g m e n ty , co p o z w a la re k u r e n c y jn ie z a k o ń c z y ć s o r to w a n ie p rz e z z a s to
s o w a n ie tej sa m e j m e to d y d o p o d ta b lic . R ó ż n ic a p o le g a n a ty m , że tu p r z y p o d z ia
le u w z g lę d n ia n y je s t ty lk o p ie r w s z y z n a k k lu c z a s o r to w a n ia , n a to m ia s t p o r ó w n a n ia
w s o r to w a n iu s z y b k im d o ty c z ą c a łe g o k lu c z a . P ie rw s z a z o p is y w a n y c h m e t o d d z ieli
d a n e w e d łu g w a rto ś c i k a ż d e g o z n a k u . D r u g a d z ie li d a n e n a tr z y c z ę śc i — z k lu c z a m i
s o r to w a n ia , w k tó r y c h p ie r w s z y z n a k je s t m n ie js z y o d p ie rw s z e g o z n a k u k lu c z a o s io
w ego, ró w n y m u lu b w ię k sz y o d n ie g o .
P rz y a n a liz o w a n iu s o r to w a n ia ła ń c u c h ó w z n a k ó w w a ż n a je s t lic z b a z n a k ó w w a l
fa b e c ie . C h o ć k o n c e n tr u je m y się n a ła ń c u c h a c h z n a k ó w z r o z s z e rz o n e g o z e s ta w u
A S C II (R = 2 5 6 ), ro z w a ż a m y ta k ż e ła ń c u c h y z n a k ó w z d u ż o m n ie js z y c h a lfa b e tó w
( n a p rz y k ła d se k w e n c je w g e n o m ie ) i z n a c z n ie w ię k sz y c h z b io ró w z n a k ó w (ta k ic h ja k
o b e jm u ją c y 6 5 5 3 6 z n a k ó w z e sta w U n ic o d e , k tó r y je s t m ię d z y n a r o d o w y m s t a n d a r
d e m k o d o w a n ia ję z y k ó w n a tu r a ln y c h ) .
714
5.1 h Sortowanie łańcuchów znaków 715
k lu c z a u sta lić p o z y c ję in d e k s u , o d k tó re g o w p o s o r
to w a n y c h d a n y c h w y stę p u ją e le m e n ty o ty m k luczu . count[]
W p rz y k ła d z ie p o ja w ia ją się tr z y e le m e n ty o k lu c z u 1
i p ięć e le m e n tó w o k lu c z u 2 , d la te g o e le m e n ty o k lu
c z u 3 z a jm u ją w p o so rto w a n e j ta b lic y p o z y c je o d 8 .
O g ó ln ie w c elu o trz y m a n ia in d e k s u p o c z ą tk o w e g o e le
m e n tó w o k lu c z u o d a n e j w a rto ś c i n a le ż y z su m o w a ć
liczb ę w y stą p ie ń m n ie js z y c h w a rto śc i. D la k ażd ej w a r
14 20
to śc i k lu c z a r s u m a liczb w y stą p ie ń d la w a rto ś c i k lu
Liczba kluczy mniejszych niż 3
czy m n ie js z y c h n iż r +1 je s t ró w n a su m ie liczb w y stą (początkowy indeks trójek
p ie ń w a rto ś c i k lu c z y m n ie jsz y c h n iż r p lu s c o u n t [ r ] . w danych wyjściowych)
D lateg o m o ż n a ła tw o p rz e jść o d lew ej d o pra w ej w celu Przekształcanie liczby wystąpień
p rz e k s z ta łc e n ia ta b lic y c o u n t [] n a ta b lic ę in d e k s ó w d o na indeksy początkowe
w y k o rz y sta n ia p rz y s o rto w a n iu d a n y c h .
19 3 8 14 19 w illia m s
a [ i8 ] 3 Thompson 4 a u x [18] w g ru p y , je d n a k z a c h o w u ją
3 8 14 20 a [19] wi 1 son 4 wi I son 4 a u x [19] tę s a m ą w z g lę d n ą k o le jn o ść .
3 8 14 20
Rozdzielanie danych (wyróżniono rekordy o kluczu 3)
5.1 Q Sortowanie łańcuchów znaków 717
Przed
ł t t
count[0] count[l] count[2]
K o p io w a n ie z p o w r o t e m P o n ie w a ż w y k o n a liś m y s o r to w a n ie p r z e z p rz e n ie s ie n ie
e le m e n tó w d o ta b lic y p o m o c n ic z e j, o s ta tn im k r o k ie m je s t sk o p io w a n ie p o s o r to w a
n y c h w y n ik ó w z p o w r o te m d o p ie r w o tn e j tab licy .
Dowód. W y n ik a b e z p o ś re d n io z k o d u . Z a in ic jo w a n ie ta b lic y w y m a g a N + R + 1
d o s tę p ó w d o tablicy. P ierw sz a p ę tla zw ięk sza lic z n ik p rz y k a ż d y m z N p o w tó rz e ń (co
d aje 2N d o stę p ó w d o tab licy ). D ru g a p ę tla w y k o n u je R o p e ra c ji d o d a w a n ia {2R d o
stę p ó w d o tablicy ). T rzecia p ę tla N ra z y zw ięk sza lic z n ik i N ra z y p rz e n o s i d a n e (3N
d o s tę p ó w d o tab licy ). C z w a rta p ę tla N ra zy p rz e n o s i d a n e (2N d o stę p ó w d o tablicy).
O b ie o p e ra c je p rz e n o s z e n ia z a ch o w u ją w z g lę d n ą k o le jn o ść ró w n y c h so b ie kluczy.
S o rto w a n ie p rz e z z lic z a n ie je s t n ie z w y k le w y d a jn e
in t N = a.length;
w s y tu a c ja c h , k ie d y k lu c z a m i są m a łe lic z b y c a ł
k o w ite . P ro g r a m iś c i c z ę sto n ie p a m ię ta ją o tej m e S trin g!] aux = new S t r i n g [ N ] ;
to d z ie . Z r o z u m ie n ie jej d z ia ła n ia je s t p ie r w s z y m in t[] coun t = new i n t [ R + l ] ;
k r o k ie m n a d r o d z e d o z r o z u m ie n ia s o r to w a n ia ł a ń
// W yz naczanie l i c z b y powtórzeń,
c u c h ó w z n ak ó w . Z g o d n ie z t w i e r d z e n i e m a s o r to f o r ( i n t i = 0; i < N; i+ + )
w a n ie p rz e z z lic z a n ie n a r u s z a d o ln e o g ra n ic z e n ie N c o u n t [ a [ i ] . k e y ( ) + 1] ++ ;
// P r z e k s z t a ł c a n i e l i c z b w yst ąp ie ń
lo g N u d o w o d n io n e d la s o r to w a n ia . Jak to m o ż liw e ?
// na i n d e k s y ,
t w i e r d z e n i e i w p o d r o z d z i a l e 2 .2 d o ty c z y d o ln e - f o r ( i n t r = 0; r < R; r++)
go o g ra n ic z e n ia lic z b y p o tr z e b n y c h p o r ó w n a ń (k ie d y c o u n t [ r + 1 ] += c o u n t [ r ] ;
// R o z d z i e l a n i e rekordów,
d o s tę p d o d a n y c h o d b y w a się ty lk o za p o m o c ą m e
f o r ( i n t i = 0; i < N; i+ + )
to d y co m p a re T o ()). S o rto w a n ie p rz e z z lic z a n ie nie a u x [ c o u n t [ a [ i ] . key ( ) ] + + ] = a[i];
w y m a g a p o r ó w n a ń ( d o s tę p d o d a n y c h o d b y w a się // K opiow an ie z powrotem,
w y łą c z n ie p o p r z e z m e to d ę key ( ) ) . Jeśli R n ie r ó ż n i f o r ( i n t i = 0; i < N; i+ + )
a [ i] = au x [i];
się w ię c e j n iż o s ta ły c z y n n ik o d N , o tr z y m u je m y s o r
to w a n ie d z ia ła ją c e w c z asie lin io w y m .
Sortowanie przez zliczanie — a[].key to
liczba całkowita z przedziału [O, R)
718 RO ZD ZIA Ł 5 ■ Łań cu ch y znaków
IP to ła ń c u c h y o stałej lic z b ie zn ak ó w .
S o rto w a n ie ta k i c h ła ń c u c h ó w z n a k ó w m o ż n a w y k o n a ć z a p o m o c ą s o r to w a n ia
p r z e z z lic z a n ie , co p o k a z a n o w a l g o r y t m i e 5 . 1 (k la s a LSD) i p r z e d s ta w io n y m p o d
n im p rz y k ła d z ie n a n a s tę p n e j s tr o n ie . Jeśli k a ż d y ła ń c u c h z n a k ó w m a d łu g o ś ć W ,
n a le ż y p o s o r to w a ć je W ra z y z a p o m o c ą s o r to w a n ia p r z e z z lic z a n ie , u ż y w a ją c k a ż
d ej p o z y c ji ja k o k lu c z a i p r z e c h o d z ą c o d p ra w e j d o le w e j. P o c z ą tk o w o n ie ła tw o
się p r z e k o n a ć , że m e t o d a t a tw o r z y p o s o r to w a n ą ta b lic ę . R z e c z y w iśc ie , t e c h n i k a ta
w o g ó le n ie z a d z ia ła , o ile im p le m e n ta c ja s o r to w a n ia p r z e z z lic z a n ie n ie b ę d z ie s t a
b iln a . W a r to o ty m p a m i ę ta ć i w r a c a ć d o p r z y k ła d u w c z a sie a n a liz o w a n ia d o w o d u
p o p r a w n o ś c i.
Dowód. K lu c z o w e je s t to , a b y im p le m e n ta c ja s o r to w a n ia p rz e z z lic z a n ie b y ła
sta b iln a , o c z y m w s p o m n ia n o w t w i e r d z e n i u a . P o p o s o r to w a n iu (s ta b iln y m )
k lu c z y w e d łu g i o s ta tn ic h z n a k ó w w ia d o m o , że d w a d o w o ln e k lu c z e w y s tę p u ją
w o d p o w ie d n ie j k o le jn o ś c i w ta b lic y (w e d łu g ty lk o ty c h z n a k ó w ) a lb o z u w a g i
n a to , iż p ie r w s z y z i k o ń c o w y c h z n a k ó w je s t w n ic h r ó ż n y (w te d y p o r z ą d e k
je s t w y z n a c z o n y p rz e z s o r to w a n ie w e d łu g te g o z n a k u ), a lb o d la te g o , że p ie r w s z y
z i k o ń c o w y c h z n a k ó w je s t ta k i s a m (w te d y k o le jn o ś ć je s t z a p e w n ia n a d z ię k i s ta
b iln o ś c i). P rz e z in d u k c ję je s t to p ra w d z iw e ta k ż e d la i -1 .
5.1 Sortowanie łańcuchów znaków 719
p ublic c la s s LSD
{
public s t a t i c void s o r t ( S t r i n g [ ] a, in t W)
{ // Sortowanie a[] według W pierwszych znaków,
in t N = a.length;
in t R = 256;
S t r i n g [] aux = new String[N ] ;
a w 0 A AA In n y m s p o s o b e m u ję c ia d o w o d u je s t z a s ta n o w ie n ie się n a d d a ls z y
76 7 A A 2 m i k ro k a m i. Jeśli z n a k i, k tó r y c h je sz c z e n ie s p r a w d z o n o , są w o b u
0 A AA A 3
7 A AA A 4 k lu c z a c h id e n ty c z n e , r ó ż n ic a m ię d z y k lu c z a m i m o ż e d o ty c z y ć ty lk o
AK A 2 A 5 sp r a w d z o n y c h ju ż z n a k ó w , d la te g o k lu c z e u p o rz ą d k o w a n o p ra w id ło w o
7W A 2 A 6
0 D 72 A 7 i — ze w z g lę d u n a s ta b iln o ś ć — to się n ie z m ie n i. N a to m ia s t je ż e li n ie
*6 ❖ 2 A 8 sp r a w d z o n e z n a k i ró ż n ią się o d sieb ie, z n a k i ju ż s p r a w d z o n e n ie m a ją
AW 7 3 A 9
z n a c z e n ia , a w d a ls z y c h p rz e b ie g a c h p a r a z o s ta n ie p o p r a w n ie u p o r z ą d
A A A3 A 10
0 9 A 3 AW k o w a n a n a p o d s ta w ie w a ż n ie js z y c h ró ż n ic .
79 ❖ 3 A D S o rto w a n ie p o z y c y jn e m e to d ą LSD to te c h n ik a s to s o w a n a w d a w
08 ❖ 4 A K
A 9 A 4 7 A n y c h m a s z y n a c h d o s o r to w a n ia k a r t p e rf o ro w a n y c h , o p ra c o w a n y c h n a
AK 74 72 p o c z ą tk u X X w ie k u i w y p rz e d z a ją c y c h w y k o rz y s ta n ie k o m p u te r ó w d o
0 4 A 4 7 3
A 5 A 5 74 k o m e r c y jn e g o p r z e tw a r z a n ia d a n y c h o k ilk a d z ie się c io le c i. M a s z y n y
AD 0 5 7 5 te p o tr a fiły ro z d z ie la ć k a r ty p e rf o ro w a n e m ię d z y 10 k o s z y k ó w w e d łu g
V 3 A 5 76
A 2 7 5 77 w z o rc a d z iu r e k w w y b ra n y c h k o lu m n a c h . Jeśli w o k re ś lo n y m z b io rz e
A10 76 78 k o lu m n k a r t ta lii z a p is a n e b y ły n u m e r y , o p e r a to r m ó g ł p o s o r to w a ć
A 9 A 6 79
k a r ty p rz e z p r z e tw o r z e n ie ic h w m a s z y n ie n a p o d s ta w ie c y fr y p ie r w
7 7 A 6 710
A 4 06 7 W szej o d p ra w e j i p ó ź n ie js z e p rz e tw o r z e n ie w y jśc io w e j ta lii w e d łu g n a
7 4 77 7 D
stę p n e j o d p ra w e j c y fr y i ta k d a le j — d o m o m e n tu d o ta r c ia d o p ie r w
A 10 A 7 7 K
AA A 7 ♦ A szej cyfry. F iz y c z n e u k ła d a n ie k a r t to s ta b iln y p ro c e s , o d p o w ia d a ją c y
❖ 5 ♦ 7 ♦ 2 s o r to w a n iu p rz e z z lic z a n ie . T a w e rsja s o r to w a n ia p o z y c y jn e g o m e to d ą
A3 0 8 0 3
78 78 0 4 L S D n ie ty lk o o d g ry w a ła w a ż n ą ro lę w k o m e r c y jn y c h a p lik a c ja c h aż d o
A 2 A 8 O5 la t 70. u b ie g łe g o w ie k u , ale te ż b y ła s to s o w a n a p rz e z w ie lu o s tro ż n y c h
❖ K A 8 ❖ 6
A 4 0 9 ♦ 7 p r o g r a m is tó w (i s tu d e n tó w !), k tó r z y m u s ie li p rz e c h o w y w a ć p r o g r a m y
A 7 79 ❖ 8 n a k a r ta c h p e rf o ro w a n y c h (p o je d n y m w ie rs z u n a k a rtę ) i z a p isy w a li
7 D A 9 ♦ 9
c ią g i lic z b w lu lk u o s ta tn ic h k o lu m n a c h ta lii z p r o g r a m e m , a b y m ó c
♦ W A 9 ♦ 10
A 6 A10 ❖ W m e c h a n ic z n ie p rz y w ró c ić k o le jn o ś ć k a r t p o ic h p r z y p a d k o w y m p o
A 3 ♦ 10 ❖ D
m ie s z a n iu . M e to d a t a je s t te ż e le g a n c k im s p o s o b e m s o r to w a n ia k a r t
A 7 A 10 ♦ K
A 8 710 A A d o gry. N a le ż y ro z ło ż y ć je n a 13 s to s ó w (p o je d n y m n a k a ż d ą w a rto ś ć ),
A 10 AW A 2 w y b ie ra ć sto s y p o k o le i i ro z k ła d a ć k a r ty n a c z te ry n o w e s to s y ( p o j e d
❖ 3 7 W A 3
710 AW A 4 n y m n a k a ż d y k o lo r ). T e n s ta b iln y p ro c e s r o z d a w a n ia p o w o d u je , że
O1 ♦ W A 5 k a r ty w r a m a c h k a ż d e g o k o lo r u są u p o rz ą d k o w a n e , ta k w ię c w y b ra n ie
A D 0 D A 6
72 AD A 7 s to s ó w w k o le jn o ś c i w y z n a c z a n e j p rz e z k o lo r y p o w o d u je u tw o rz e n ie
0 2 7 D A 8 p o s o r to w a n e j ta lii.
A 5 A D A 9
7 K W w ie lu z a s to s o w a n ia c h z w ią z a n y c h z s o r to w a n ie m ła ń c u c h ó w z n a
AK A10
7 5 AK A W k ó w k lu c z e n ie m a ją ta k ie j sa m e j d łu g o ś c i (d o ty c z y to n a w e t n u m e r ó w
06 ♦ K AD
re je s tr a c y jn y c h w n ie k tó r y c h s ta n a c h ). M o ż n a d o s to s o w a ć s o r to w a n ie
A8 7 K AK
ła ń c u c h ó w z n a k ó w m e t o d ą LSD, a b y d z ia ła ło ta k ż e w t a l a c h w a r u n
Sortowanie talii kart przez
k a c h . T o z a d a n ie p o z o s ta w ia m y je d n a k ja k o ć w ic z e n ie , p o n ie w a ż d a lej
sortowanie łańcuchów
znaków metodą LSD o m a w ia m y d w ie in n e m e to d y , z a p ro je k to w a n e s p e c ja ln ie p o d k ą te m
k lu c z y o z m ie n n e j d łu g o ś c i.
5.1 □ Sortowanie łańcuchów znaków 721
Z p e rs p e k ty w y te o re ty c z n e j s o r to w a n ie ła ń c u c h ó w z n a k ó w m e t o d ą L SD m a z n a
c z e n ie , p o n ie w a ż je s t te c h n ik ą s o r to w a n ia d z ia ła ją c ą w ty p o w y c h w a r u n k a c h w c z a
sie lin io w y m . N ie z a le ż n ie o d w a rto ś c i N m e t o d a w y k o n u je W p rz e b ie g ó w p o d a n y c h .
U jm ijm y to k o n k r e tn ie .
W ty p o w y c h z a s to s o w a n ia c h R je s t z n a c z n ie m n ie js z e n iż N , d la te g o z t w i e r d z e n i a
b w y n ik a , że łą c z n y czas w y k o n a n ia je s t p r o p o r c jo n a ln y d o W N . W e jś c io w a ta b lic a N
ła ń c u c h ó w zn a k ó w , z k tó r y c h k a ż d y m a W z n a k ó w , s k ła d a się w s u m ie z W N z n a k ó w ,
ta k w ię c czas w y k o n a n ia s o r to w a n ia ła ń c u c h ó w z n a k ó w m e t o d ą L SD r o ś n ie lin io w o
w z g lę d e m w ie lk o ś c i d a n y c h w e jśc io w y c h .
722 RO ZD ZIA Ł 5 □ Łań cuch y znaków
aw AK AA ta c ji m e to d y s o r to w a n ia ła ń c u c h ó w z n a k ó w d o o g ó ln e g o u ż y tk u , s to s o
<96 AW A2 w a n e j w te d y , k ie d y ła ń c u c h y z n a k ó w n ie m a ją tej sa m e j d łu g o ś c i, z n a k i
0 A A9 A3
p rz e tw a r z a m y w k o le jn o ś c i o d lew ej d o p ra w e j. W ia d o m o , że ła ń c u c h y
¥A A5 A4
AK A2 A5 z n a k ó w r o z p o c z y n a ją c e się o d a p o w in n y w y s tę p o w a ć p r z e d ła ń c u c h a m i
¥W AA A6 z n a k ó w ro z p o c z y n a ją c y m i się lite rą b itd . N a tu r a ln y m s p o s o b e m n a z a
♦D A3 A7
A6 A4 A8 im p le m e n to w a n ie te g o ro z w ią z a n ia je s t r e k u r e n c y jn a m e to d a , n a z y w a
AW A6 A9 n a s o r to w a n ie m ła ń c u c h ó w z n a k ó w , p o c z ą w s z y o d n a jb a r d z ie j zn a c z ą c e j
AA A7 A10
♦9 A8 AW c y fr y (an g . m o st-sig n ific a n t-d ig it-first — M S D ). S to s u je m y s o r to w a n ie
<99 A10 AD p rz e z z lic z a n ie d o p o s o r to w a n ia ła ń c u c h ó w z n a k ó w w e d łu g p ie rw s z e g o
♦8 AD AK
A9 ¥6 ¥A z n a k u , a n a s tę p n ie (r e k u re n c y jn ie ) s o r tu je m y p o d ta b lic e o d p o w ia d a ją
AK ¥A ¥2 c e k a ż d e m u z n a k o w i (z w y łą c z e n ie m p ie rw s z e g o z n a k u , o k tó r y m w ia
0 4 ¥W ¥3
d o m o , że je s t ta k i s a m w e w sz y stk ic h ła ń c u c h a c h z d a n e j p o d ta b lic y ).
A5 ¥9 ¥4
AD ¥ 3 ¥ 5 S o rto w a n ie ła ń c u c h ó w z n a k ó w m e to d ą M S D , p o d o b n ie ja k s o r to w a n ie
V3 ¥7 ¥6 sz y b k ie , d z ie li ta b lic ę n a p o d ta b lic e , k tó r e m o ż n a p o s o r to w a ć n ie z a le ż n ie
A2 ¥4 ¥7
A10 ¥8 ¥8 w c e lu w y k o n a n ia z a d a n ia . T u je d n a k ta b lic a je s t d z ie lo n a n a p o d ta b lic e
A9 ¥ D ¥9 d la k a ż d e j m o ż liw e j w a rto ś c i p ie rw s z e g o z n a k u z a m ia s t n a d w ie lu b tr z y
¥ 7 ¥10 ¥10
A4 ¥2 ¥W części, co m a m ie js c e w s o r to w a n iu s z y b k im .
¥4 ¥K ¥D
♦ 10 ¥5 ¥ K K o n w e n c ja w y k r y w a n ia Sortowanie według Rekurencyjne sortowanie
AA ♦A ♦ A końca ła ń c u c h a zn a kó w wartości pierwszego znaku pod tablic (z pominięciem
♦5 ♦D ♦2 w celu podziału na podtablice pierwszego znaku)
A3 ♦9 ♦3 W so rto w a n iu ła ń c u c h ó w
¥8 ♦8 ♦4 z n a k ó w m e to d ą M S D trz e b a
A2 ♦4 ♦ 5
♦K ♦ 10 ♦6 zw ró cić szczeg ó h ią uw ag ę
A4 ♦5 ♦7 n a d o jśc ie d o k o ń c a ła ń c u
A7 ♦K ♦8 c h a znaków . A b y so rto w a n ie
¥ D ♦W ♦9
♦W ♦3 ♦ 10 d z iała ło p o p ra w n ie , pod-
A6 ♦7 ♦W tab lic a ła ń cu c h ó w , k tó ry ch
A3 ♦2 ♦D
A7 ♦6 ♦K z n a lu ju ż sp ra w d z o n o , m u s i
A8 AW AA w y stę p o w ać na p o c z ą tk u .
A10 A6 A2
♦3 AA A3 N ie n a leż y re k u re n c y jn ie
AK A4 2" ~
¥10 so rto w a ć teg o fr a g m e n
O7 AD A5
AD A10 A6 tu . A b y u łatw ić w y k o n a n ie
¥2 A9 A7 ty c h d w ó c h części obliczeń ,
❖2 A4 A8
sto su je m y p ry w a tn ą , d w u -
A5 A2 A9
¥ K A7 A10 a rg u m e n to w ą m e to d ę to -
¥5 A3 AW C h a r(), k tó ra p rze k sz ta łca
♦6 A5 AD
A8 A8 AK in d e k s o w a n y z n a k ła ń c u c h a
O k r e ś lo n y a lf a b e t K o sz t s o r to w a n ia ła ń c u c h ó w z n a k ó w m e to d ą M S D w d u ż y m
s to p n iu z a le ż y o d lic z b y z n a k ó w w a lfab e cie . M e to d ę s o r to w a n ia m o ż n a ła tw o z m o
d y fik o w a ć , a b y p rz y jm o w a ła ja k o a r g u m e n t o b ie k t Al p h a b e t, c o p o z w a la p o p ra w ić
w y d a jn o ś ć w k lie n ta c h k o rz y s ta ją c y c h z ła ń c u c h ó w z n a k ó w p o c h o d z ą c y c h ze s t o s u n
k o w o k r ó tk ic h alfa b e tó w . P o tr z e b n e są n a s tę p u ją c e z m ia n y :
■ z a p is a n ie a lf a b e tu w z m ie n n e j e g z e m p la rz a a l pha w k o n s tr u k to r z e ;
■ u s ta w ie n ie w k o n s tr u k to r z e R n a a l p h a . R ( ) ;
■ z a s tą p ie n ie w m e to d z ie c h a r A t( ) w y w o ła n ia s . c h a r A t ( d ) in s tr u k c ją a l p h a .
t o ! n d e x ( s . c h a r A t ( d ) ).
Interpretacja wartości w tablicy count[] w czasie sortowania łańcuchów znaków metodą MSD
724 R O ZD ZIA Ł 5 Łań cu ch y znaków
public c la s s MSD
{
private s t a t ic in t R = 256; // Podstawa.
private s t a t i c final in t M = 15; // Przełączenie dla małych podtablic.
private s t a t i c S t r in g [ ] aux; // Tablica pomocnicza dorozd zie lan ia .
public s t a t ic void s o r t ( S t r i n g [ ] a)
{
in t N = a.length;
aux = new S t r i n g [ N ] ;
s o rt (a , 0, N-l, 0);
i f (hi <= lo + M)
( In s e r t i o n . s o r t ( a , lo, h i, d ) ; return; }
W przykładach stosujemy łańcuchy znaków składające się z małych liter. Można też
łatwo rozwinąć sortowanie łańcuchów znaków m etodą LSD o obsługę małych liter,
jednak zwykle ma to znacznie mniejszy wpływ na wydajność niż w sortowaniu m e
todą MSD.
Dane wejściowe d
she are a he are are are are are are
sel 1 s by lo^ by- by by by by by by
seasheU s she 'x s e l l s se ash e l1s sea sea sea seas sea
by s - !1 se a s h e l1s sea s e a s h e l1s s e a s h e l! s s e a s h e l 1s s e a s h e l1s seasheTp.
the s e a sh e lI s sea se a s h e l1s se ash e l1s s e a s h e l1s s e a s h e l!s s e a s h e l1s seashells
sea sea se lls sel 1 s sel 1 s sel 1 s sel 1 s sel 1s se lls '
shore shore s e a s h e l! s se lls sel 1 s sel 1 s sel 1 s sel 1 s se lls
the sh ells she she she she she she she
s h e l 1s she shore shore shore shore shore s h e l1s s h e l ls
she se lls sh e lls sh ells s h e l 1s s h e l1s s h e l 1s shore shore
se lls s u r e ly she she she she she she she
are seashel! s, s u re ly s u re ly sure! y s u re ly s u re ly s u r e ly surely
s u re ly the hi ^ the the the the the the the
se ash e l1s the the the the the the the the
_______
5.1 ■ Sortowanie łańcuchów znaków 1T1
p u b lic s t a t i c void s o r t ( S t r i n g [ ] a, i n t l o , in t h i, i n t d)
{ // S o r t o w a n ie od a [ l o ] do a [ h i ] , poc ząwszy od d - t e g o znaku,
f o r ( in t i = lo ; i <= h i ; 1++)
f o r ( i n t j = i ; j > 1o && l e s s ( a [ j ] , a[j-l], d); j — )
exch(a, j , j - 1 ) ;
}
p r i v a t e s t a t i c bo olean l e s s ( S t r i n g v, S t r i n g w, i n t d)
( r e t u r n v . s u b s t r i n g ( d ) . c o m p a r e T o ( w . s u b s t r i n g ( d ) ) < 0; }
S o r to w a n ie p rz e z w s ta w ia n ie d la ła ń c u c h ó w z n a k ó w , w k tó ry c h p ie rw s z y c h d
z n a k ó w j e s t id e n ty c z n y c h
król kich podtablic jest w sortowaniu metodą MSD niezbędne. Aby uniknąć kosztów
ponownego sprawdzania znaków, o których wiadomo, że są równe, można użyć wersji
sortowania przez wstawianie przedstawionej w górnej części strony. Wersja ta przyjmu
je dodatkowy argument d i działa według założenia, że pierwszych d znaków wszystkich
sortowanych łańcuchów nie różni się od siebie. Wydajność tego kodu zależy od tego,
czy operacja substring () działa w stałym czasie. Tak jak w sortowaniu szybkim i sor
towaniu przez scalanie większość korzyści z usprawnienia wynika z przełączania algo
rytmów dla małych wartości, jednak tu oszczędności są znacznie większe. Na rysunku
po prawej stronie pokazano wyniki eksperymentów, w których przełączenie się na sor
towanie przez wstawianie dla podtablic o wiel
100 % ■
kości 10 lub mniejszej skraca czas wykonania
10 -krotnie w typowych zastosowaniach.
D odatkow a pam ięć Do dzielenia danych w metodzie MSD używane są dwie tablice
pomocnicze — tymczasowa tablica do rozdzielania kluczy (aux [] ) i tablica przecho
wująca liczby wystąpień przekształcane na indeksy wyznaczające podział (count [] ).
Tablica aux [] m a rozmiar N i m ożna ją utworzyć poza rekurencyjną m etodą s o rt ( ).
Tę część dodatkowej pamięci można wyeliminować kosztem stabilności (zobacz
ć w i c z e n i e 5 .1 . 1 7 ), jednak w praktycznych zastosowaniach m etody MSD zwykle nie
ma to większego znaczenia. Natom iast ważna może okazać się pamięć na tablicę
count [] (ponieważ tablicy nie można utworzyć poza rekurencyjną metodą s o rt()),
co opisano w t w i e r d z e n i u d poniżej.
M odel losowych łańcuchów zn a kó w Przy badaniu wydajności m etody MSD ko
rzystamy z modelu losowych łańcuchów znaków, w którym każdy łańcuch składa się
z niezależnych i losowych znaków, przy
Losowe Nielosowe Najgorszy
czym nie obowiązuje ograniczenie ich dłu (szybciej niż z powtórzeniami przypadek
gości. Długie równe sobie klucze w zasadzie liniowo) (prawie liniowo) (liniowo)
public c la s s Q uick3string
{
private s t a t ic in t ch arA t(Strin g s, in t d)
{ i f (d < s .le n g t h ()) return s.ch arAt(d); else return -1; }
public s t a t i c void s o r t ( S t r i n g [ ] a)
( s o rt(a , 0, a.length - 1, 0); }
in t I t = lo, gt = h i ;
in t v = c h a r A t ( a [ lo ] , d ) ;
in t i = lo + 1;
while (i <= gt)
{
in t t = c h a r A t ( a [ i ] , d ) ;
if (t < v) exch(a, lt++, i++);
else i f (t > v) exch(a, i, g t - - ) ;
else i++;
}
Aby posortować tablicę a [] z łańcuchami znaków, należy podzielić ją na trzy części według
pierwszego znaku, a następnie rekurencyjnie posortować trzy uzyskane podtablice: z łańcu
chami, w których pierwszy znak jest mniejszy niż znak uwzględniany przy podziale, z łań
cuchami z pierwszym znakiem równym znakowi podziału (w tej części pierwszy znak jest
pomijany przy dalszym sortowaniu), a także z łańcuchami o pierwszym znaku większym niż
znak podziału.
5.1 h Sortowanie łańcuchów znaków 733
cji, w których metoda MSD działa wolno. Szczególnie ważne jest to, że podział pasuje
do różnego rodzaju struktur w różnych częściach klucza. Ponadto szybkie sortowanie
łańcuchów znaków z podziałem na trzy części (podobnie jak zwykłe sortowanie szyb
kie) nie wymaga dodatkowej pamięci (potrzebny jest tylko tworzony pośrednio stos do
obsługi rekurencji), co jest istotną zaletą w porównaniu z metodą MSD, która wymaga
pamięci zarówno na liczniki wystąpień, jak i na tablicę pomocniczą.
Na rysunku w dolnej części strony pokazano wszystkie wywołania rekurencyjne,
które klasa Qui ck 3 stri ng wykonuje w przykładzie. Każda podtablica jest sortowana
za pomocą dokładnie trzech rekurencyjnych wywołań. Wyjątkiem jest sytuacja, kie
dy pomijamy rekurencyjne wywołanie po dojściu do końców równych łańcuchów
znaków w środkowej podtablicy.
W praktyce, jak zwykle, warto rozważyć różne standardowe usprawnienia imple
mentacji przedstawionej w a l g o r y t m i e 5 .3 .
Krótkie podtablice W każdym algorytmie rekurencyjnym m ożna zwiększyć wy
dajność, traktując krótkie podtablice w odm ienny sposób. Tu stosujemy sortowanie
przez wstawianie ze strony 727, gdzie pomijane są znaki, o których wiadomo, że są
równe. Zyski wynikające z tej zmiany mogą być znaczne, choć nie w takim stopniu,
jak w metodzie MSD.
O graniczony alfabet Na potrzeby obsługi specjalnych alfabetów m ożna dodać do
każdej metody argument alpha typu Alphabet i zastąpić w metodzie charAt() wy
wołanie s.charA t(d) instrukcją a lp h a .toIndex(s.charA t(d)). Tu takie rozwiązanie
nie przynosi korzyści, a dodanie wspomnianego kodu może znacznie spowolnić al
gorytm, ponieważ kod ten działa w pętli wewnętrznej.
R andom izacja Tak jak w każdym sortowaniu szybkim, tak i tu ogólnie warto wstęp
nie wymieszać tablicę lub zastosować losowy element osiowy, przestawiając pierwszą
wartość z losową. Ma to przede wszystkim chronić przed najgorszym przypadkiem
w sytuacji, kiedy tablica jest już posortowana (lub prawie uporządkowana).
Dla kluczy w postaci łańcuchów znaków sortowanie szybkie i inne m etody sorto
wania z r o z d z i a ł u 2 . odpowiadają technice MSD, ponieważ metoda compareTo()
w klasie S tri ng uzyskuje dostęp do znaków w kolejności od lewej do prawej. Oznacza
to, że m etoda compareTo () sprawdza tylko pierwsze znaki, jeśli są różne, dwa począt
kowe znaki, jeśli pierwsze są identyczne, a drugie — odm ienne itd. Przykładowo,
jeśli pierwsze znaki wszystkich łańcuchów znaków są różne, standardowe sortowanie
sprawdzi tylko je, co automatycznie gwarantuje te same zyski w wydajności, co w m e
todzie MSD. Kluczowym pomysłem, na którym oparte jest sortowanie szybkie z p o
działem na trzy części, jest podjęcie specyficznych działań, kiedy pierwsze znaki są
równe. O a l g o r y t m i e 5.3 można myśleć jak o sposobie na śledzenie w sortowaniu
szybkim pierwszych znaków, o których wiadomo, że są równe. W krótkich podtabli-
cach, w których większość porównań została już wykonana, łańcuchy znaków mają
przeważnie dużą liczbę równych łańcuchów znaków. Standardowy algorytm musi
przejść po wszystkich tych znakach w każdym porównaniu, natomiast w algorytmie
z podziałem na trzy części nie jest to konieczne.
W ydajność Rozpatrzmy sytuację, w której klucze w postaci łańcuchów znaków są
długie (i — dla uproszczenia — mają tę samą długość), przy czym większość pierw
szych znaków jest taka sama. Wtedy czas wykonania standardowego sortowania
szybkiego jest proporcjonalny do długości łańcuchów znaków razy 2N ln N, a dla
sortowania szybkiego z podziałem na trzy części jest to N razy długość łańcuchów
znaków (w celu wykrycia wszystkich równych początkowych znaków) plus 2N ln N
porównań (w celu posortowania pozostałych, krótkich kluczy). Tak więc sortowanie
szybkie z podziałem na trzy części wymaga nawet 2 ln N razy mniej porównań niż
zwykłe sortowanie szybkie. Nierzadko się zdarza, że w praktyce klucze mają podobne
cechy, co w przedstawionym tu sztucznym przykładzie.
5.1 o Sortowanie łańcuchów znaków 735
Jak podkreślono na stronie 728, warto rozważyć losowe łańcuchy znaków, jednak
do prognozowania wydajności w praktycznych sytuacjach potrzebne są bardziej
szczegółowe analizy. Badacze dokładnie przebadali opisany algorytm i udowodnili,
że — przy bardzo ogólnych założeniach i uwzględnianiu liczby porównań znaków
— żadne rozwiązanie nie może być szybsze niż sortowanie szybkie z podziałem na
trzy części o więcej niż stały czynnik. Aby docenić wszechstronność metody, warto
zauważyć, że sortowanie szybkie łańcuchów znaków z podziałem na trzy części nie
jest bezpośrednio zależne od rozmiaru alfabetu.
■Mi
736 ROZDZIAŁ 5 B Łańcuchy znaków
Sortowanie
ogólnego użytku
Sortowanie szybkie Nie Tak N \ o g 2N lo g N
przy ograniczeniach
pamięci
Sortowanie przez Stabilne sortowanie
Tak Nie N l o g 2N N
scalanie ogólnego użytku
Sortowanie szybkie
Duża liczba równych
z podziałem na trzy Nie Tak Między N a N log N log N
kluczy
części
Sortowanie Krótkie łańcuchy
łańcuchów znaków Tak Nie NW N znaków o stałej
metodę LSD długości
Sortowanie
Losowe łańcuchy
łańcuchów znaków Tak Nie Między N a Nw N + WR
znaków
metodę MSD
Sortowanie
Sortowanie szybkie
ogólnego użytku; dla
łańcuchów znaków
Nie Tak Między N a Nw W + log N łańcuchów znaków
z podziałem
z długimi wspólnymi
na trzy części
przedrostkam i
J PY T A N IA I O D P O W IE D Z I
P. Tak więc do sortowania kluczy typu S tri ng powinienem używać sortowania sy
stemowego?
O. W Javie zwykle należy tak postępować, jednak jeśli liczba łańcuchów znaków jest
bardzo duża lub potrzebujesz wyjątkowo szybkiego sortowania, możesz zastosować
w tablicach typ char zamiast S tri ng i wykorzystać sortowanie pozycyjne.
[ ] Ć W IC Z E N IA
5.1.1. Opracuj implementację sortowania, która zlicza różne wartości kluczy, a na
stępnie na podstawie tablicy symboli i sortowania przez zliczanie sortuje tablicę.
Metoda ta nie nadaje się do użytku, jeśli liczba różnych wartości kluczy jest duża.
5.1.2. Przedstaw ślad przebiegu sortowania łańcuchów znaków metodą LSD dla na
stępujących kluczy:
no i s th t i fo al go pe to co to th ai of th pa
5.1.3. Przedstaw ślad przebiegu sortowania łańcuchów znaków metodą MSD dla
następujących kluczy:
no i s th t i fo al go pe to co to th ai of th pa
no i s th ti fo al go pe to co to th ai of th pa
5.1.5. Przedstaw ślad przebiegu sortowania łańcuchów znaków m etodą MSD dla
następujących kluczy:
now i s the time f o r a ll good people to come to the aid of
j PROBLEMY DO ROZWIĄZANIA
5.1.17. Sortowanie przez zliczanie w miejscu. Opracuj wersję sortowania przez zli
czanie wymagającą stałej ilości dodatkowej pamięci. Udowodnij, że wersja jest stabil
na, lub przedstaw kontrprzykład.
740 ROZDZIAŁ 5 □ Łańcuchy znaków
U EKSPERYMENTY
5.1.18. Losowe klucze dziesiętne. Napisz metodę statyczną randomDecimal Keys, któ
ra jako argumenty przyjmuje wartości N i Wtypu i nt, a zwraca tablicę N wartości typu
S t r i ng, z których każda jest W-cyfrową liczbą dziesiętną.
5.1.22. Czasy wykonania. Przy użyciu różnych generatorów kluczy porównaj czasy
wykonania sortowania łańcuchów znaków metodą MSD i sortowania szybkiego łań
cuchów znaków z podziałem na trzy części. Przy kluczach o stałej długości uwzględ
nij też sortowanie łańcuchów znaków metodą LSD.
5.1.23. Dostępy do tablicy. Przy użyciu różnych generatorów kluczy porównaj liczbę
dostępów do tablicy przy sortowaniu łańcuchów znaków m etodą MSD i sortowaniu
szybkim łańcuchów znaków z podziałem na trzy części. Przy kluczach o stałej dłu
gości uwzględnij też sortowanie łańcuchów znaków m etodą LSD.
Ite ra b le < S t rin g > keysW ithPrefix(String s) Zwraca wszystkie klucze z przedrostkiem s
Ite ra b le < S t rin g > keysThatM atch(String s) Zwraca wszystkie klucze pasujące do s
(. pasuje do dowolnego znaku)
Ten interfejs API różni się od interfejsu API tablicy symboli przedstawionego
w r o z d z i a l e 3 . w następujących obszarach:
■ Generyczny typ Key zastąpiono typem konkretnym S tri ng.
■ Dodano trzy nowe metody: 1ongestPrefixOf (), keysWithPrefix() i keysThat-
Match().
Zachowujemy podstawowe konwencje z r o z d z i a ł u 3 . dotyczące implementacji tab
licy symboli (niedozwolone są powtórzenia oraz klucze lub wartości równe nul 1).
Jak pokazano w kontekście sortowania kluczy w postaci łańcuchów znaków, czę
sto ważna jest możliwość korzystania z łańcuchów opartych na określonym alfabecie.
Proste i wydajne implementacje, stosowane z wyboru dla małych alfabetów, okazu
ją się bezużyteczne dla dużych alfabetów, ponieważ wymagają zbyt dużo pamięci.
W wielu sytuacjach warto dodać konstruktor umożliwiający klientom określenie al
fabetu. Implementację takiego konstruktora omawiamy w dalszej części podrozdzia
łu, jednak na razie pomijamy go w interfejsie API, co pozwoli skoncentrować się na
kluczach w postaci łańcuchów znaków.
W przykładach w dalszych opisach trzech nowych m etod wykorzystano klucze
she s e l l s sea s h e l l s by the sea shore.
■ M etoda 1ongestPrefixOf () jako argument pobiera łańcuch znaków i zwraca
najdłuższy klucz z tablicy symboli będący przedrostkiem tego łańcucha. Dla
przedstawionych kluczy wywołanie 1ongestPrefixOf("shel 1 ") zwraca she,
awywołanie lon g e stP re fix O f("sh e llso rt") — shells.
■ M etoda keysWithPrefix() jako argument przyjmuje łańcuch znaków i zwraca
wszystkie klucze z tablicy symboli, których dany łańcuch jest przedrostkiem.
Dla przedstawionych kluczy wywołanie keysWithPrefix("she") zwraca she,
a wywołanie keysWi thPrefix("se") — sel 1s i sea.
■ M etoda keysThatMatch() jako argument przyjmuje łańcuch znaków i zwraca
wszystkie klucze z tablicy symboli, które pasują do tego łańcucha. Kropka (.)
w argumencie pasuje do dowolnego znaku. Dla przedstawionych kluczy wywo
łanie keysThatMatch(".he") zwraca she, a wywołanie keysThatM atch("s.. ")
— she i sea.
Implementacje i zastosowania tych operacji omawiamy szczegółowo po przedsta
wieniu podstawowych m etod związanych z tablicą symboli. Opisane operacje są re
prezentatywne, jeśli chodzi o możliwości przetwarzania kluczy w postaci łańcuchów
znaków. W ćwiczeniach omawiamy kilka innych możliwości.
Aby skupić się na głównych pomysłach, koncentrujemy się na put () , get ( ) i no
wych metodach. Zakładamy (tak jak w r o z d z i a l e 3 .) stosowanie domyślnych im
plementacji m etod con tains() i isEmpty(), a przygotowanie implementacji metod
s i ze ( ) i del ete ( ) pozostawiamy jako ćwiczenia. Ponieważ łańcuchy znaków są zgod
ne z interfejsem Comparable, możliwe (i warte zachodu) jest rozwinięcie opisanego
interfejsu API o operacje na danych uporządkowanych, zdefiniowane w r o z d z i a l e 3 .
w interfejsie API dla uporządkowanych tablic symboli. Implementacje (zwykle pro
ste) opisane są w ćwiczeniach i w kodzie w poświęconej książce witrynie.
744 ROZDZIAŁ 5 □ Łańcuchy znaków
g e tC sh e lls" ) yet("shel1" )
Zwracanie wartości
węzła powiązanego
\
Wartość w węźle odpowiadającym
ostatniemu znakowi klucza to n u li,
z ostatnim znakiem klucza
dlatego należy zwrócić n u li
getCshe") getC'shore")
Przeszukiwanie
może się zakończyć Brak odnośnika dla
w węźle pośrednim litery o, dlatego
należy zwrócić n u li
zieniem ostatniego znaku klucza lub pustego odnośnika. Na tym etapie spełniony jest
jeden z trzech warunków (przykłady pokazano na rysunku powyżej).
■ Wartość węzła odpowiadającego ostatniemu znakowi klucza jest różna od nul 1
(tak jak przy wyszukiwaniu łańcuchów shel 1 s i she po lewej stronie rysunku
powyżej). Oznacza to udane wyszukiwanie. Wartość powiązana z kluczem to
wartość w węźle odpowiadającym ostatniemu znakowi klucza.
■ Wartość węzła odpowiadającego ostatniemu znakowi klucza to nuli (tak jak przy
wyszukiwaniu łańcucha shel 1 , co pokazano w prawej górnej części rysunku po
wyżej). Oznacza to nieudane wyszukiwanie — klucz nie znajduje się w tablicy.
■ Wyszukiwanie kończy się pustym odnośnikiem (tak jak przy wyszukiwaniu
łańcucha shore, co pokazano w prawej dolnej części rysunku powyżej). Także
to oznacza nieudane wyszukiwanie.
We wszystkich sytuacjach wyszukiwanie wymaga sprawdzenia tylko węzłów na ścieżce
z korzenia do innego węzła drzewa trie.
746 ROZDZIAŁ 5 o Łańcuchy znaków
W staw ianie do drzew a trie Podobnie jak w drzewach wyszukiwań binarnych, tak
i tu wstawianie rozpoczyna się od wyszukiwania. W drzewie trie oznacza to wyko
rzystanie znaków klucza do przejścia w dól drzewa do m om entu napotkania ostat
niego znaku klucza lub odnośnika nul 1. Na tym etapie spełniony jest jeden z dwóch
warunków.
■ Napotkano odnośnik nul 1 przed dojściem do ostatniego znaku klucza. Wtedy
żaden węzeł drzewa trie nie odpowiada ostatniemu znakowi klucza, dlatego
trzeba utworzyć węzły dla każdego z nienapotkanych znaków klucza i ustawić
wartość w ostatnim z nich na wartość wiązaną z kluczem.
■ Napotkano ostatni znak klucza przed dojściem do odnośnika nul 1. Wtedy na
leży ustawić wartość węzła na wartość wiązaną z kluczem (niezależnie od tego,
czy wartość ta to nul 1 ), tak jak zwykle postępujemy w przypadku tablic asocja
cyjnych.
We wszystkich sytuacjach należy w drzewie trie sprawdzić lub utworzyć węzeł dla
każdego znaku klucza. Na następnej stronie pokazano tworzenie drzewa trie przez
standardowego używającego indeksów klienta z r o z d z i a ł u 3 . Drzewo tworzone jest
dla danych wejściowych:
she s e l l s sea s h e l ls by the sea shore
Reprezentacja w ęzłów Jak wspomnieliśmy na początku, rysunki drzew trie nie od
powiadają strukturom danych tworzonym przez programy, ponieważ pomijamy od
nośniki nul 1. Uwzględnienie odnośników nul 1 pozwala zrozumieć poniższe ważne
cechy drzew trie:
■ Każdy węzeł obejmuje R odnośników — po jednym na każdy możliwy znak.
■ Znaki i klucze są przechowywane w strukturze danych pośrednio.
Przykładowo, na rysunku poniżej pokazano drzewo trie dla kluczy składających się
z małych liter. Każdy węzeł ma tu wartość i 26 odnośników. Pierwszy odnośnik pro
wadzi do poddrzewa trie z kluczami rozpoczynającymi się od a, drugi prowadzi do
poddrzewa trie z podłańcuchami rozpoczynającymi się od b itd.
5.2 □ Drzewa trie 747
Wartość znajduje
się w węźle
odpowiadającym
ostatniemu znakowi
sells
t he
Po jednym
węźle na każdy -
znak klucza
sea
shells 3
Węzły odpowiadające
końcowym znakom klucza
nie istnieją, dlatego należy
je utworzyć i ustawić
wartość w ostatnim z nich
Ślad procesu tworzenia drzewa trie przez standardowego klienta używającego indeksów
748 ROZDZIAŁ 5 0 Łańcuchy znaków
p ublic c la s s TrieST<Value>
{
p rivate s t a t i c in t R = 256; // Podstawa,
private Node root; // Korzeń drzewa t r i e .
private s t a t ic c la s s Node
{
p rivate Object v a l ;
p rivate Node[] next = new Node[R];
}
ch a r next = p a t . c h a r A t ( d ) ;
f o r ( c h a r c = 0; c < R; C++)
if (ne xt == 1. 1 || next == c)
col 1e c t ( x . n e x t [ c ] , pre + c, p a t, q ) ;
}
p u b l i c S t r i n g 1 o n g e s t P r e f i x O f ( S t r i n g s)
(
i n t l e n g t h = s e a r c h ( r o o t , s , 0, 0 ) ;
return s . s u b s t r in g ( 0 , length);
)
Wyszukiwanie kończy się
p r i v a t e i n t s e arc h (N o d e x, S t r i n g s , i n t d, i n t le n g th ) na końcu łańcucha znaków.
( Wartość jest różna od n u li,
if (x == n u l l ) return length; dlatego należy zwrócić she
if (x.val != n u l l ) l e n g t h = d;
if (d == s . l e n g t h ( ) ) return leng th;
ch a r c = s . c h a r A t ( d ) ;
r e t u r n s e a r c h ( x . n e x t [ c ] , s , d+1, l e n g t h ) ; "shell"
de le te ("sh e lls") ;
(a ) 2 m ©o (a)2 Q ) © o
Ustawianie ( -j) ©
wartości y y i
na nuli (5 ), ( f ) ©1
Wartość jest różna od nuli, dlatego Odnośnik jest różny od nuli, dlatego
nie należy usuwać węzła (trzeba nie należy usuwać węzła (trzeba
Wartość i odnośniki to nuli, zwrócić odnośnik do niego) zwrócić oćjnośnik do niego)
dlatego należy usunąć węzeł
(i zwrócić odnośnik nuli)
Cechy drzew trie Jak zwykle, interesuje nas ilość czasu i pamięci potrzebna do
stosowania drzew trie w typowych sytuacjach. Drzewa trie gruntownie przebada
no i przeanalizowano, a ich podstawowe cechy są stosunkowo łatwe do zrozumienia
oraz wykorzystania.
Ten podstawowy fakt jest cechą charakterystyczną drzew trie. We wszystkich innych
omówionych do tej pory strukturach drzewiastych używanych do wyszukiwania
kształt tworzonego drzewa zależy zarówno od zbioru kluczy, jak i od kolejności ich
wstawiania.
Ograniczenia czasowe dla najgorszego przypadku p rzy wyszukiwaniu i wsta
wianiu Jak długo trwa znajdowanie wartości powiązanej z kluczem? W kontekście
drzew BST, haszowania i innych m etod opisanych w r o z d z i a l e 4 . odpowiedź na to
pytanie wymagała analiz matematycznych. Jednak w przypadku drzew trie udziele
nie odpowiedzi jest bardzo proste.
1 - (1 - R ' i) N + 1 - (1 - R - 2) N + . . . + 1 - (1 - R ‘) N + -
Składniki sumy są niezwykle bliskie 1 dla około lnRN wyrazów, gdzie R jest zna
cząco mniejsze niż N. Dla wszystkich wyrazów z R‘ wyraźnie większym niż N
wartości są niezwykle bliskie 0. Dla nielicznych wyrazów, w których R‘ ~ N, war
tości należą do przedziału od 0 do 1. Tak więc łączna suma wynosi około log AT.
działanie tego rodzaju jest często spotykane w praktyce i stanowi ważny powód po
wszechnego stosowania drzew trie.
Pamięć Ile pamięci potrzeba na drzewo trie? Udzielenie odpowiedzi na to pytanie
(i ustalenie, jaka ilość pamięci jest dostępna) jest kluczowe, jeśli chcemy z powodze
niem korzystać z drzew trie.
Kalifornijskie numery
4PGC938 7 256 256 milionów
tablic rejestracyjnych
256 4 miliardy
Num ery kont 024000199929932 99111 20
10 256 milionów
Adresy URL www.cs.princeton.edu 28 256 4 miliardy
Przetwarzanie tekstu s e a sh e lls 11 256 256 milionów
public c la s s TST<Value>
f
prívate Node root; // Korzeń drzewa t r i e .
prívate c la s s Node
{
char c; // Znak.
Node l e f t , mid, rig h t; // Lewe, środkowe i prawe poddrzewo t r i e .
V alue val ; // Wartość powiązana z łańcuchem znaków.
}
public Value get(String key) // Taka sama, jak dla drzew t r i e (strona 749).
W tej implementacji użyto wartości c typu char i trzech odnośników na węzeł do utworzenia
drzewa trie do wyszukiwania łańcuchów znaków, w którym poddrzewa trie obejmują klucze
0 pierwszym znaku mniejszym niż c (lewe poddrzewo), równym c (środkowe poddrzewo)
1większym niż c (prawe poddrzewo).
760 ROZDZIAŁ 5 □ Łańcuchy znaków
niem. Aby wstawić nowy klucz, należy przeszukać dane, a następnie dodać nowe wę
zły dla znaków z „ogona” klucza, tak jak w drzewach trie. Szczegółowe implementa
cje metod pokazano w a l g o r y t m i e 5 .5 .
To rozwiązanie jest odpowiednikiem zaimplementowania każdego węzła ^-kie
runkowego drzewa trie jako drzewa wyszukiwań binarnych, w którym za klucze słu
żą znaki odpowiadające odnośnikom różnym od nul 1. W a l g o r y t m i e 5.4 wykorzy
stano tablicę indeksowaną kluczami. Drzewo TST i odpowiadające m u drzewo trie
pokazano powyżej. Przez nawiązanie do opisanej w r o z d z i a l e 3 . analogii między
drzewami wyszukiwań binarnych a algorytmami sortowania m ożna stwierdzić, że
drzewa TST odpowiadają sortowaniu szybkiemu łańcuchów znaków z podziałem na
trzy części w taki sam sposób, jak drzewa BST odpowiadają sortowaniu szybkiemu,
a drzewa trie — metodzie MSD. Na rysunkach na stronach 726 i 733 pokazano struk
turę wywołań rekurencyjnych w metodzie MSD i sortowaniu szybkim łańcuchów
znaków z podziałem na trzy części. Rysunki te odpowiadają drzewom trie i TST dla
tego samego zbioru kluczy, przedstawionym na stronie 758. Pamięć na odnośniki
w drzewach trie odpowiada pamięci na liczniki w sortowaniu łańcuchów znaków.
Rozgałęzianie na trzy części zapewnia skuteczne rozwiązanie obu problemów.
się od su
Reprezentacje węzłów drzew trie
5.2 n Drzewa trie 761
Rzeczywisty poziom wykorzystania pamięci jest przeważnie niższy niż górne ogra
niczenie trzech odnośników na znak, ponieważ klucze o wspólnych przedrostkach
współużytkują węzły na wysokich poziomach drzewa.
K oszt w yszukiw ania Aby ustalić koszt wyszukiwania (i wstawiania) danych w drze
wach TST, należy pomnożyć koszt dla powiązanego drzewa trie przez koszt porusza
nia się w reprezentacji BST każdego węzła drzewa trie.
przedrostku). Ponadto dla większości znaków potrzebnych jest tylko kilka porównań
(ponieważ w większości węzłów w drzewach trie liczba wartości różnych od nuli
jest niewielka). Nieudane wyszukiwanie przeważnie wymaga tylko kilku porównań
znaków i kończy się odnośnikiem nuli w górnej części drzewa trie, a udane wyszu
kiwanie obejmuje tylko około jednego porównania na znak klucza wyszukiwania,
ponieważ większość znaków znajduje się w węzłach z jednokierunkowym i gałęziami
w dolnej części drzewa trie.
A lfabet Główną korzyścią ze stosowania drzew TST jest to, że płynnie dostosowują
się do nieregularności w kluczach wyszukiwania (takie nieregularności często wy
stępują w praktyce). Zauważmy, że nie ma powodu, aby umożliwiać tworzenie łań
cuchów znaków na podstawie alfabetu określonego przez klienta, co było niezwykle
ważne w przypadku drzew trie. Występują tu dwa główne efekty. Po pierwsze, klucze
w praktyce są oparte na dużych alfabetach, a częstotliwość występowania znaków
ze zbiorów jest daleka od równomiernej. W drzewach TST m ożna korzystać z 256-
znakowego kodowania ASCII lub 65 536-znakowego kodowania Unicode. Nie trzeba
się przy tym martwić o nadm ierne koszty węzłów o 256 lub 65 536 gałęziach ani
określać, które zbiory znaków są potrzebne. Łańcuchy znaków Unicode w alfabetach
niełacińskich mogą obejmować tysiące znaków. Drzewa TST wyjątkowo dobrze n a
dają się dla standardowych kluczy typu S tri ng Javy składających się z takich znaków.
Po drugie, klucze w praktycznych zastosowaniach często mają ustrukturyzowany for
mat, różny w poszczególnych aplikacjach. Czasem w jednej części klucza stosowane
są tylko litery, a w innej — same cyfry. W numerach kalifornijskich tablic rejestra
cyjnych drugi, trzeci i czwarty znak to duże litery (R = 26), a pozostałe znaki to cyfry
dziesiętne (R = 10). W drzewie TST dla talach kluczy niektóre węzły drzewa trie będą
reprezentowane jako 10-węzłowe drzewa BST (w miejscach, w których we wszyst
kich kluczach występują cyfry), a inne — jako 26-węzłowe drzewa BST (w miejscach,
gdzie we wszystkich kluczach są litery). Ta struktura powstaje automatycznie, bez
konieczności przeprowadzania specjalnych analiz kluczy.
Dopasowywanie przedrostków, pobieranie kluczy i dopasowywanie do symboli
wieloznacznych Ponieważ drzewo TST jest reprezentacją drzewa trie, implementacje
metod longestPrefixOf(), keys(), keysWithPrefix() i keysThatMatch() można łatwo
zaadaptować z analogicznego kodu dla drzew trie z poprzedniego podrozdziału. Jest
to wartościowe ćwiczenie, które pozwala utrwalić wiedzę na temat drzew trie i TST
(zobacz ć w i c z e n i e 5 .2 .9 ). Występują tu te same wady i zalety, co przy wyszukiwaniu
(rosnąca liniowo ilość pamięci, ale dodatkowy czynnik ln R na porównanie znaków).
Usuwanie Opracowanie m etody d e le te () dla drzew TST wymaga więcej pracy.
Każdy znak w usuwanym kluczu należy do drzewa BST. W drzewie trie m ożna usu
nąć odpowiadający znakowi odnośnik przez ustawienie odpowiedniego wpisu w tab
licy odnośników na nul 1. W drzewie TST usunięcie węzła odpowiadającego znakowi
wymaga usuwania węzłów z drzewa BST.
5.2 □ Drzewa trie 763
K tórej im p le m e n t a c j i ta b lic y s y m b o li z ła ń c u c h a m i z n a k ó w p o w i
n ie n e m u ży w a ć ? Tak jak przy sortowaniu łańcuchów znaków, tak i tu interesuje
nas wydajność omówionych m etod przeszukiwania łańcuchów znaków w porów na
niu z m etodam i do użytku ogólnego, opisanymi w r o z d z i a l e 3 . W poniższej tabeli
podsumowano ważne cechy algorytmów omówionych w tym podrozdziale (dla po
równania dołączono wiersze dotyczące drzew BST, czerwono-czarnych drzew BST
i haszowania z r o z d z i a ł u 3 .). W konkretnych zastosowaniach wartości te należy
traktować jako ogólne, a nie precyzyjne, ponieważ przy analizowaniu implementacji
tablic symboli rolę odgrywa bardzo wiele czynników (na przykład cechy kluczy i wy
konywane operacje).
Losowo
Drzewa BST cl dg W 64N
uporządkowane klucze
Drzewa wyszukiwań
2-3 (czerwono-czarne c2 (lgN )2 64N Gwarancje wydajności
drzewa BST)
Typy wbudowane
Próbkowanie liniowe i przechowywanie
W Od 32N do 128N
(tablice równoległe) skrótów w pamięci
podręcznej
Przeszukiwanie drzew
Od (8R+56JN Krótkie klucze i małe
trie (R-kierunkowe lo g rN
drzewa trie)
do (8R+56)Nw alfabety
P Y T A N IA I O D P O W IE D Z I
O. Nie.
766 ROZDZIAŁ 5 ■ Łańcuchy znaków
] Ć W IC Z E N IA
no i s th t i fo al go pe to co to th ai of th pa
pu b lic c l a s s StringSET
1 PROBLEMY DO ROZWIĄZANIA
5.2.7. Pusty łańcuch znaków w drzewie TST. Kod dla drzew TST nie obsługuje pra
widłowo pustych łańcuchów znaków. Wyjaśnij problem i zaproponuj poprawkę.
5.2.9. Dodatkowe operacje dla drzew TST. Zaimplementuj metodę keys() i dodat
kowe metody przedstawione w tym podrozdziale — longestPrefixOf(), keysWith-
Prefix() i keysThatMatch() — dla typu TST.
5.2.12. Wewnętrzne jednokierunkowe gałęzie. Dodaj do ldas Tri eST i TST kod, który
wyeliminuje wewnętrzne jednokierunkowe gałęzie.
5.2.13. Hybrydowa klasa TST z R2gałęziami w korzeniu. Dodaj do Masy TST kod do
tworzenia wielu gałęzi na dwóch pierwszych poziomach (co opisano w tekście).
5.2.17. Sprawdzanie pisowni. Napisz korzystającego zklasy TST klienta Spel IChecker,
który jako argument wiersza poleceń przyjmuje nazwę pliku zawierającego słownik
słów angielskich, a następnie wczytuje łańcuch znaków ze standardowego wejścia
i wyświetla każde słowo, które nie występuje w słowniku. Wykorzystaj zbiór łańcu
chów znaków.
5.2.18. Biała lista. Napisz korzystającego z klasy TST klienta, który rozwiązuje prob
lem przedstawiony w p o d r o z d z i a l e i . i i ponownie omówiony w p o d r o z d z i a l e
3.5 (zobacz stronę 503).
! EKSPERYM ENTY
Wzorzec — ► N E E D L E
Tekst — - I N A H A Y S T A C K N E E D L E I N A
Dopasowanie
Wyszukiwanie podłańcuchów
770
5.3 n Wyszukiwanie podiańcuchów 771
i i i+j 0 1 2 3 4 5 6 7 8 9 10
txt — - A B A c A D A B R A C
0 2 2 A B R A ■pat
1 0 1 A B R A Czerwona litera
2 1 3 A B R "" oznacza niedopasowanie
3 0 3 / A J B R A Szare litery
4 1 5 / A B r A / podano w celach
Czarne litery * poglądowych
5 0 5 pasują do tekstu A B R A
6X 4 10 A B R A
Jeśli i ma wartość M, \
należy zwrócić i Dopasowanie
Sztuczne łańcuchy znaków tego rodzaju w zasadzie nie występują w tekstach w języ
ku polskim, jednak mogą się pojawić w innych zastosowaniach (na przykład w teks
tach binarnych), dlatego należy
i j i + j 0 1 2 3 4 5 6 7 8 9
poszukać lepszego algorytmu.
txt— ► A A A A A A A A A B
Inna implementacja, przed
0 4 4 A A A A B -*— pat
stawiona w dolnej części strony,
1 4 5 A A A A B
jest pouczająca. Program, tak
2 4 6 A A A A B
3 4 7 A A A A B
jak wcześniej, przechowuje je-
4 4 g a a a a b den wskaźnik do tekstu (i) oraz
5 5 io a a a a b jeden wskaźnik do wzorca (j).
,. ,
Wyszukiwanie podłancucnow metodą
, Dopóki
r
wskaźniki rprowadzą-z do
ataku siłowego (najgorszy przypadek) pasujących znaków, Są Z W ię k -
szane. Kod wykonuje dokładnie
tę samą liczbę porównań, co poprzednia implementacja. Aby to zrozumieć, nale
ży zauważyć, że i w tym kodzie to odpowiednik wartości i +j z poprzedniego kodu
— wartość ta wskazuje koniec ciągu już dopasowanych znaków w tekście (wcześniej
i wskazywał początek ciągu). Jeśli i oraz j wskazują niedopasowane znaki, należy
cofnąć oba wskaźniki — j do początku wzorca, a i tak, aby odpowiadał przesunięciu
wzorca o jedną pozycję w prawo w celu dopasowania go względem tekstu.
p u b l i c s t a t i c i n t s e a r c h ( S t r i n g pa t, S t r i n g t x t )
1
ant j , M = p a t . l e n g t h ( ) ;
int i , N = t x t.le n g t h ( );
f o r (i = 0 , j =0; i <N && j < M; i+ + )
{
if (txt.charA t(i) == pat.charAt(j)) j++;
e l s e { i - = j ; j = 0; }
1
if (j == M) r e t u r n i - M; // Z n a l e z io n o ,
e lse r e t u r n N; // N ie z n a l e z i o n o .
Tekst
Po niedopasowaniu
szóstego znaku
Jednak cofnięcie
nie jest konieczne
Cofanie wskaźnika tekstu przy wyszukiwaniu podłańcuchów
Cofanie wskaźnika wzorca Przy wyszu j pat.charAt(j) dfa[][j] Tekst (sam wzorzec)
kiwaniu podiańcuchów metodą KMP ni A B C ABABAC
Sym ulacja determ inistycznego autom atu skończonego Przydatne jest przedsta
wianie omawianego procesu w kategoriach deterministycznego automatu skończonego
(ang. deterministic finite-state automaton — DFA). Jak wskazuje na to nazwa, tablica
dfa [] [] wyznacza taki automat. Graficzna reprezentacja automatu DFA przedstawio
na w dolnej części strony składa
pub lic in t se a rc h (S t rin g txt) się ze stanów (wartości w okrę
{ // Symulowanie d z i a t a n i a automatu DFA na ła ńcu chu t x t .
gach) i przejść (opisane linie). Dla
int i, j, N = t x t . le n g t h ( ) ;
f o r (i = 0, j = 0; i < N && j < M; i+ + ) każdego znaku wzorca istnieje
j j§ d f a [ t x t . ch a rA t ( i )] [ j ] ; jeden stan, a każdy taki stan po
i f (j — M) r e t u r n i - M; // Z n a l e z io n o ,
wiązany jest z jednym przejściem
else r e t u r n N; // Nie z n a l e z i o n o .
dla każdej litery alfabetu. Dla
omawianych tu automatów DFA
Wyszukiwanie podtańcuchów metodą KMP służących do dopasowywania
(symulowanie działania automatu DFA)
podłańcuchów jednym z przejść
jest przejście po dopasowaniu (z j do j+1 i oznaczenie za pom ocą pat.charA t (j)),
a wszystkie pozostałe — to przejścia po niedopasowaniu (w lewo). Stany odpowiadają
porównaniom znaków, po jednym na każdą wartość indeksu wzorca. Przejścia odpo
wiadają zmianie wartości indeksu wzorca. Przy sprawdzaniu w tekście znaku i , kiedy
występuje stan j , maszyna działa tak — „Zastosuj przejście do dfa [ t x t . charAt (i ) ] [j ]
i przejdź do następnego znaku, zwiększając i ”. Przy przejściu po dopasowaniu n a
leży przesunąć indeks w prawo o jedną pozycję, ponieważ d fa [p a t.c h a rA t(j)] [j]
to zawsze j+1. Przy przejściu po niedopasowaniu należy przesunąć indeks w lewo.
Maszyna wczytuje znaki tekstu jeden po drugim od lewej do prawej i po wczytaniu
każdego znaku przechodzi w nowy stan. Uwzględniliśmy też stan zatrzymania, M,
który nie ma przejść. Uruchamiamy maszynę w stanie 0. Jeśli maszyna dojdzie do
stanu M, oznacza to znalezienie w tekście podłańcucha pasującego do wzorca (m ó
wimy, że maszyna DFA rozpoznaje wzorzec). Jeżeli maszyna dojdzie do końca tekstu
przed przejściem w stan M, wia
Reprezentacja wewnętrzna domo, że wzorzec nie występuje
j o jako podłańcuch tekstu. Każdy
pat.charAt(j) A A wzorzec odpowiada maszynie
1 3
dfa[] [j] 0 0 (jest ona reprezentowana przez
0 0 tablicę d fa [] [] z przejściami).
Przejście po Metoda search() w algorytmie
niedopasowaniu Przejście po KMP to program Javy symulują
(cofnięcie) dopasowaniu
Reprezentacja graficzna (zwiększenie) cy działanie opisanej maszyny.
Aby zrozumieć wyszukiwa
nie podłańcuchów za pomocą
automatu DFA, rozważmy dwie
najprostsze wykonywane przez
Stan zatrzymania niego operacje. Na początku, po
uruchom ieniu w stanie 0 na po
Automat DFA odpowiadający łańcuchowi znaków A B A B A C czątku tekstu, automat pozosta-
5.3 b Wyszukiwanie podłańcuchów 777
3 4 5 6 7 8 9 10 11 12 13 14 15 16 — t
W czytyw anie tego zn aku A A B A C A A B A B A C A A— tX t
A ktualny stan 0 1 1 2 3 0 1 1 2 3 4 5 6 — j
Przejście d o tego stanu B A C t
A B A C Zn ale zio no wzorzec;
należy zw rócić i - M = 9
B A B A C
A B A B A C
A B A B A C
A B A B A C
A B A B A C
A B A B A £
D op asow an ie:
należy ustawić j n a
A B A B A C
d fa [tx t.c h a rA tC O ] [ j ] =
d f a [ p a t .c h a r A t ( j) ] [ j] = j+19
Ślad w yszu kiw a n ia p o d ła ń c u c h ó w m etod ą KMP (sym ulacja autom atu DFA) dla w zorca A B A B A C
0_ r i.c i
pat.charAt(j) A
W “ ©
A 1
dfa[] [j] B O
c O
ł
j 0 1 © k y f '* Kopiowanie d f a [ ] [X] do d f a [] [ j ]
p a t.c h a rA t(j) A B (o )~ a — » - © - B — * ~ © d fa [p a t.c h a rA t(j)][j] = j+ 1 ;
A 1 1 <^" X c x = d fa [p a t.c h a rA t(j)] [ x ] ;
d fa [] [j] B 0 2
C 0 0
X
1
j 0 1 2 O ©
p a t.c h a rA t(j) A B A
© k * -—O©k -. B— A—^ ©
A 1 1 3
d fa [] [j] B 0 2 0
C 0 0 0
X
1
j 0 1 2 3
p a t.c h a rA t(j) A B A B
A 1 1 3 1
d fa [][j] B 0 2 0 4
C 0 0 0 0
X
1
j 0 1 2 3 4
pat. c h a r A t ( j ) A B A B A
A 1 1 3 1 5
d fa [] [ j] B 0 2 0 4 0
C 0 0 0 0 0
X
1
j 0 1 2 3 4 5
p a t.c h a rA t(j) A B A B A C
A 1 1 3 1 5 1
©
d fa [] [j] B 0 2 0 4 0 4
C 0 0 0 0 0 6
Tworzenie automatu DFA pod kątem wyszukiwania podłańcuchów metodą KMP we wzorcu A B A B A C
780 ROZDZIAŁ 5 Łańcuchy znaków
public c la s s KMP
{
private S t r in g pat;
private i n t [ ] [ ] dfa;
p u b l i c c l a s s KMP
Ważny jest też inny parametr. Dla P-znakowego alfabetu łączny czas wykonania
(i pamięć) przy tworzeniu automatu DFA rośnie proporcjonalnie do MR. Można
usunąć czynnik R, tworząc automat DFA, w którym każdy stan obejmuje przejście
dla dopasowania i dla niedopasowania (a nie dla każdego możliwego znaku), choć
proces ten jest bardziej zawiły.
Gwarancje liniowego czasu dla najgorszego przypadku w algorytmie KMP są waż
nym osiągnięciem teoretycznym. W praktyce przyspieszenie w porównaniu z ata
kiem siłowym często jest nieistotne, ponieważ rzadko szukane są wzorce z wieloma
powtórzeniami w tekście obejmującym liczne powtórzenia. M etoda ta ma jednak
praktyczną zaletę, ponieważ nigdy nie p o
woduje cofania w danych wejściowych. Ta p u b l i c s t a t i c v o i d main ( S t r i n g[] a r g s )
cecha sprawia, że dla strum ieni wejściowych {
S t r i n g pat = a r g s [ 0 ] ;
o nieokreślonej długości (na przykład dla
Strin g txt = a r g s[l];
standardowego wejścia) wyszukiwanie pod KMP kmp = new KMP (p at );
łańcuchów m etodą KMP jest wygodniejsze Std O u t.p rin tln ("T e kst: 11 + t x t ) ;
niż stosowanie algorytmów wymagających i n t o f f s e t = k m p . s e a r c h ( t xt);
StdO ut.print("W zorzec: ") ;
cofania (te ostatnie wymagają skomplikowa f o r ( i n t i = 0; i < o f f s e t ; i+ + )
nego buforowania). Co ciekawe, jeśli cofanie StdO ut.p rint(" ");
jest proste, m ożna uzyskać efekty znacząco Std O u t.p rin tln (p at);
i j 0 1 2 B 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
Tekst— - F I N D I N A H A Y S T A C K N E E D L E I N A
0 5 N E E D L E - — Wzorzec
5 5 N E E D L E
11 4 N E E D L E
15 0 N E E D L E
\
Zwracanie i = 15
Heurystyka obsługi niedopasowania znaku przy wyszukiwaniu
podłańcuchów metodą Boyera-Wloore'a (od prawej do lewej)
5.3 o Wyszukiwanie podłańcuchów 783
public c la s s BoyerMoore
(
private i n t [] rig h t;
private S trin g pat;
BoyerMoore(String pat)
( // Obliczanie ta b lic y przeskoków,
t h i s . pat = pat;
in t M = p a t . le n g t h ( ) ;
in t R = 256;
rig h t = new i nt [R ];
for (in t c = 0; c < R; C++)
rig h t[ c ] = -1; // -1 dla znaków spoza wzorca,
for (in t j = 0; j < M; j++) // Pierwsza od prawej pozycja
rig h t[ p a t.c h a rA t (j) ] = j; // znaku we wzorcu.
}
3.4 w kontekście łańcuchów znaków i innych typów kluczy o wielu wartościach, pro
wadzi do kodu pokazanego po prawej, który w czasie proporcjonalnym do Moblicza
wartość funkcji haszującej dla M-cyfrowej liczby o podstawie R, reprezentowanej jako
tablica wartości typu char. Mjest przekazywane jako argument, dlatego — jak się okaże
— metodę m ożna zastosować zarówno do wzor
ca, jak i do tekstu. Dla każdej cyfry w liczbie p riv a t e long hash ( s t r i n g key, i n t M)
. . . , , . , . ,, { // O b licza nie s krótu dla key[ 0 . . M-1].
należy pomnożyć dotychczasową wartość h _ Q.
przez R, dodać cyfrę i obliczyć resztę z dzielenia for (i n t j = 0; j < M; j++)
przez Q. W dolnej części strony pokazano, jak h = (R * h + k ey.c h a rA t( j)) % Q;
obliczyć wartość funkcji haszującej dla wzorca. return h,
Tę samą metodę m ożna wykorzystać do obli
czenia wartości funkcji haszujących dla teks- M e t o d a H o rn e ra z a s to s o w a n a
tu, jednak koszt wyszukiwania podłańcuchów d o h a s z o w a n ia m o d u la r n e g o
obejmowałby mnożenie, dodawanie i obliczanie reszty dla każdego znaku tekstu. Dla
najgorszego przypadku daje to N M operacji, co oznacza brak poprawy w porównaniu
do ataku siłowego.
K luczow y p o m ysł Metoda Rabina-Karpa oparta jest na wydajnym obliczaniu funk
cji haszującej dla pozycji i +1 w tekście na podstawie wartości dla pozycji i . Technika
wynika bezpośrednio z prostych matematycznych wzorów. Zapis t. to wartość t x t .
charA t(i). Liczba odpowiadająca M-znakowemu podłańcuchowi tekstu tx t zaczy
nająca się od pozycji i jest równa:
x.1 =1 tR M1 + t.z+1 RM'2 + ... + t.i+ Mu-1 R°
Można założyć, że znana jest wartość h{x.) = x. mod Q. Przesunięcie o jedną pozycję
w prawo w tekście odpowiada zastąpieniu x przez:
x.j+1, = (x
v 1
- tR
i
M')R
'
+ ti + M
Należy odjąć początkową cyfrę, pomnożyć wartość przez R, a następnie dodać koń
cową cyfrę. Najważniejsze jest to, że nie trzeba przechowywać wartości liczb, a tylko
wartości ich reszt z dzielenia przez Q. Podstawową cechą operacji modulo jest to, że
jeśli obliczymy resztę z dzielenia przez Qpo każdej operacji arytmetycznej, uzyskamy
ten sam wynik, co po wykonaniu wszystkich operacji arytmetycznych i późniejszym
obliczeniu reszty. Tę cechę wy
korzystaliśmy już wcześniej, charA t(j)
przy implementowaniu ha- . 0 1 2 3 4
szowania m odularnego me- 2 g 5 3 f
todą Hornera (zobacz stronę 0 2 % 997 = 2 R Q
/ /
472). Efekt jest taki, że można 1 2 g % 997 = ( 2 n o + 6) % 997 = 26
wydajnie przesuwać się w tek-
, ’ 1 r Y . 2 2 6 5 % 997 = ( 2 6 * 1 0 + 5) % 997 = 265
scie w prawo o jedną pozycję
3 2 6 5 3 % 997 = ( 2 6 5 * 1 0 + 3) % 997 = 659
w stałym czasie niezależnie
4 2 6 5 3 5 % 997 = ( 6 5 9 * 1 0 + 5) % 997 = 613
od tego, czy Mjest równe 5,
100 czy 1000. Obliczanie wartości skrótu dla wzorca metodą Hornera
788 ROZDZIAŁ 5 0 Łańcuchy znaków
i 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
3 1 4 1 5 9 2 6 5 3 5 8 9 7 9 3
0 3 % 997 = 3
/
1 3 1 % 997 = ( 3 * 1 0 + 1) % 997 = 31
2 3 1 4 % 997 = (3 1 * 1 0 + 4 ) % 997 = 314
3 3 1 4 1 %1 997 = (3 1 4 *1 0 + 1) % 997 = 150
4 3 1 4 1 5 % 997 = (1 5 0 * 1 0 + 5) % 997 = 508 /RM^ / R
5 1 4 1 5 9 % 997 = ( ( 5 0 8 + 3 * ( 9 9 7 - 3 0 ) ) * 1 0 + 9) % 997 = 201
6 4 1 5 9 2 % 997 = ( ( 2 0 1 + 1 * (9 9 7 - 3 0 ) ) *1 0 + 2) % 997 = 715 D o p a so w a n ie
p u b l i c c l a s s R a b in K a rp
{
p r iv a t e S t r i n g pat; // Wzorzec ( p o t r z e b n y ty lk o w w ersji Las V e g a s ) .
p r i v a t e long patHash; // W a rto ść s k r ó t u d l a w zorca.
p r i v a t e i n t M; // DTugość wzorca.
p r i v a t e l o n g Q; // Duża l i c z b a p ie r w s z a .
p r i v a t e i n t R = 256; // Ro zm ia r a l f a b e t u .
p r i v a t e l o n g RM; // RA (M -1) % Q
p u b l i c R a b i n K a r p ( S t r i n g pat)
{
t h i s . pat = p a t ; // Z a p i s y w a n i e w zorca ( p o t r z e b n e t y l k o w w e r s j i
// Las V e g a s ) .
this.M = p a t . l e n g t h ( ) ;
Q = lo n g R a n d o m P r i m e O ; // Zobacz ć w i c z e n i e 5 . 3 . 3 3 .
RM = 1;
f o r ( i n t i = 1; i <= M - l ; i + + ) // O b l i c z a n i e RA(M-1) % 0 na p o t r z e b y
RM = (R * RM) % Q; // usu w an ia p oczątko w ej c y f r y .
patHa sh = h a s h ( p a t , M);
p u b l i c b o o le a n c h e c k ( i n t i ) // Monte C a r l o (z o b a c z o p i s w t e k ś c i e ) .
{ re tu rn true; ) // W w e r s j i Las Vegas n a l e ż y porównać
// pat z t x t ( i . . i - M + 1 ) .
p r i v a t e lo n g h a s h ( S t r i n g key, i n t M)
// Zobacz o p i s w t e k ś c i e ( s t r o n a 7 8 7 ) .
p riva te in t se a rc h (S trin g txt)
{ // Szu ka p a s u j ą c e g o s k r ó t u w t e k ś c i e ,
in t N = t x t .le n g t h ();
long txtHash = h a sh ( tx t, M ) ;
i f (p a tH a s h == t x t H a s h && c h e c k ( O ) ) r e t u r n 0; // D opasow anie na
// p o c z ą t k u .
f o r ( i n t i = M; i < N; i + + )
( // Usuw anie początkow ej c y f r y , dodawanie końcowej c y f r y
// i s p r a w d z a n ie d op aso w a n ia .
t x t H a s h = ( t x t H a s h + Q - R M * t x t . c h a r A t ( i - M ) % Q) % Q;
t x t H a s h = ( t x t H a s h * R + t x t . c h a r A t ( i ) ) % Q;
i f (p atH a sh == t x t H a s h )
i f ( c h e c k ( i - M + 1 )) r e t u r n i - M + 1 ; // D opasow anie.
}
r e t u r n N; // N ie z n a l e z i o n o d op aso w a n ia .
}
}
Ten algorytm wyszukiwania podłańcuchów jest oparty na haszowaniu. Oblicza w konstruk
torze wartość skrótu dla wzorca, a następnie szuka w tekście pasującego skrótu.
790 ROZDZIAŁ 5 0 Łańcuchy znaków
wartości typu 1 ong większej niż 10 20, przez co prawdopodobieństwo, że losowy klucz
ma skrót o tej samej wartości, co wzorzec, jest mniejsze niż 10'20. Jest to niezwykle
m ała wartość. Jeżeli uznasz, że to i tak za dużo, możesz ponownie uruchomić algoryt
my, aby uzyskać prawdopodobieństwo niepowodzenia poniżej 1(L40. Omawiany algo
rytm jest wczesnym i znanym przykładem zastosowania m etody Monte Cario, który
pozwala zagwarantować określony czas ukończenia, jednak może — choć z małym
prawdopodobieństwem — wygenerować błędną odpowiedź. Inna metoda sprawdza
nia dopasowania może być wolna (czasem, z bardzo małym prawdopodobieństwem,
może działać tak, jak atak siłowy), jednak gwarantuje poprawność. Algorytmy tego
typu noszą nazwę Las Vegas.
Jeśli Twoja wiara w teorię prawdopodobieństwa (lub model losowych łańcuchów zna
ków i kod używany do generowania liczb losowych) nie jest wystarczająca, możesz
dodać do m etody check() kod sprawdzający, czy tekst pasuje do wzorca. Powoduje
to przekształcenie a l g o r y t m u 5.8 w wersję Las Vegas (zobacz ć w i c z e n i e 5 .3 . 1 2 ).
Jeśli ponadto dodasz test, aby sprawdzić, czy nowy kod jest kiedykolwiek potrzebny,
możliwe, że z czasem nabierzesz zaufania do teorii prawdopodobieństwa.
waniem za pomocą „odcisków palców”, ponieważ mała ilość informacji służy do re
prezentowania potencjalnie bardzo dużego wzorca. M etoda wyszukuje „odciski pal
ców” (wartość skrótu) w tekście. Algorytm jest wydajny, ponieważ „odciski palców”
można wydajnie obliczać i porównywać.
5.3 a Wyszukiwanie podłańcuchów 791
Liczba operacji C o fa n ie
D o d a tk o w a
A lg o ry tm W ersja w danych P o p ra w n y ?
p a m ię ć
G w a r a n to w a n a Typow o w e jśc io w y c h ?
j PYTANIA I O D PO W IED ZI
P. Problem wyszukiwania podłańcuchów wydaje się trochę sztuczny. Czy naprawdę
muszę rozumieć wszystkie te skomplikowane algorytmy?
P. Dlaczego nie uprościć pracy przez przekształcenie każdego znaku na postać bi
narną i potraktowanie całego tekstu jako binarnego?
O. Pomysł ten nie jest skuteczny z uwagi na błędne dopasowania przy granicach
znaków.
5.3 Q Wyszukiwanie podłańcuchów
ĆWICZENIA
[ j PROBLEMY DO ROZWIĄZANIA
5.3.25. Przesyłanie strumieniowe. Dodaj do klasy KMP metodę search(), która jako
argument przyjmuje zmienną typu In i w podanym strum ieniu wejściowym wyszu
kuje wzorzec bez korzystania z dodatkowych zmiennych egzemplarza. Następnie
zrób to samo dla klasy Rab i n Karp.
5.3.26. Wykrywanie rotacji cyklicznej. Napisz program, który dla dwóch łańcuchów
znaków określa, czy jeden z nich jest rotacją cykliczną drugiego (na przykład przy-
kl ad i kl adprzy).
5 .3.1 dodaj metodę search(), która jako argument przyjmuje strumień wejściowy
(typu In) i wyszukuje wzorzec w podanym strumieniu wejściowym. Uwaga-, trzeba
utrzymywać bufor, w którym można umieścić przynajmniej Mwcześniejszych znaków
ze strumienia wejściowego. Zadanie polega na napisaniu wydajnego kodu do inicjowa
nia, aktualizowania i czyszczenia bufora dla dowolnego strumienia wejściowego.
5.3.31. Wzorce losowe. Ile porównań znaków jest potrzebnych, aby znaleźć losowy
wzorzec o długości 100 w danym tekście?
która jako dane wejściowe przyjmuje wzorzec, a jako w yszukiw anie podłańcucha a a b a a a bez pętli
dane wyjściowe generuje program bez pętli (taki jak
pokazany) wyszukujący dany wzorzec.
5.3.35. Metoda Boyera-Moored dla łańcuchów binarnych. Heurystyka obsługi niedo
pasowania znaków nie jest zbyt pom ocna w kontekście binarnych łańcuchów znaków,
ponieważ niedopasowanie mogą powodować tylko dwa możliwe znaki (i przeważnie
oba występują we wzorcu). Opracuj klasę do wyszukiwania podłańcuchów w łańcu
chach binarnych. Klasa ma grupować bity w „znaki”, które m ożna wykorzystać w taki
sam sposób, jak w a l g o r y t m i e 5 .7 . Uwaga: przy pobieraniu b bitów jednocześnie
potrzebna jest tablica ri ght [] o 2b elementach. Wartość b powinna być na tyle mała,
aby tablica nie była zbyt długa, a przy tym na tyle duża, aby dla większości ¿»-bitowych
fragmentów tekstu prawdopodobieństwo ich wystąpienia we wzorcu było niskie. We
wzorcu występuje M - b + 1 różnych ¿»-bitowych fragmentów (po jednym rozpoczy
nającym się od pozycji każdego bitu od 1 do M - ¿> + 1), dlatego wartość M - b + 1
powinna być znacznie niższa niż 2b. Przykładowo, jeśli 2b to około lg (4M), tablica
ri gth [] będzie w ponad % zapełniona wartościami -1. Trzeba jednak uważać, aby b
nie było mniejsze niż M l2, ponieważ w przeciwnym razie może nastąpić pominięcie
wzorca, jeśli zostanie podzielony między dwa ¿»-bitowe fragmenty tekstu.
798 ROZDZIAŁ 5 a Łańcuchy znaków
[ j EKSPERYMENTY
5.3.36. Losowy tekst. Napisz program, który jako argumenty przyjmuje liczby cał
kowite Mi N, generuje losowy binarny łańcuch o długości N, a następnie zlicza inne
wystąpienia ostatnich Mbitów tekstu. Uwaga: dla różnych wartości Modpowiednie
mogą być inne metody.
5.3.37. Metoda KMP dla losowego tekstu. Napisz klienta, który jako dane wejściowe
przyjmuje liczby całkowite M, Ni T, a następnie T razy wykonuje następujący ekspery
m ent — generuje losowy wzorzec o długości Mi losowy tekst o długości Noraz zlicza
porównania znaków potrzebne klasie KMP na znalezienie wzorca w tekście. Dopracuj
klasę KMP tak, aby udostępniała liczbę porównań, i wyświetl średnią liczbę porównań
dla T prób.
5.3.38. Metoda Boyera-Moorea dla losowego tekstu. Wykonaj poprzednie ćwiczenie
dla klasy BoyerMoore.
5.3.39. Czas działania. Napisz program, który mierzy czas wyszukiwania przez
cztery przedstawione m etody poniższego podłańcucha:
it is a f a r f a r b e tt e r thing th a t 1 do t h a n i have e v e r done
w tekście książki Tale o f Two Cities (tale.txt). Omów, w jakim stopniu wyniki potwier
dzają postawione w tekście hipotezy na temat wydajności.
w w i e l u a p l i k a c j a c h potrzebne jest wyszukiwanie podłańcuchów bez komplet
nych informacji na temat wzorca. Użytkownik edytora tekstu może chcieć określić
tylko część wzorca, podać wzorzec pasujący do kilku różnych słów lub stwierdzić,
że akceptowalny jest jeden z kilku wzorców. Biolog może szukać sekwencji genów
spełniającej pewne warunki. W tym podrozdziale opisujemy, jak w wydajny sposób
przeprowadzić tego rodzaju dopasowywanie do wzorca.
Algorytmy z poprzedniego podrozdziału wymagają podania kompletnego wzorca,
dlatego trzeba rozważyć inne rozwiązania. Podstawowe mechanizmy, które tu opisu
jemy, stanowią podstawę bardzo rozbudowanej techniki wyszukiwania łańcuchów
znaków. Pozwala ona dopasowywać skomplikowane M-znakowe wzorce do frag
mentów N-znakowych tekstów w czasie proporcjonalnym do M N dla najgorszego
przypadku i znacznie szybciej w typowych sytuacjach.
Najpierw potrzebny jest sposób na opisywanie wzorców — precyzyjny sposób
określania wspomnianych wcześniej problemów wyszukiwania niepełnych podłań
cuchów. Specyfikacja musi obejmować bardziej zaawansowane operacje podstawowe
niż stosowaną w poprzednim podrozdziale operację „sprawdź, czy i-ty znak tekstu
pasuje do j-tego znaku wzorca”. Dlatego stosujemy wyrażenia regularne, które opisują
wzorce w połączeniu z trzema naturalnymi, podstawowymi i rozbudowanymi ope
racjami.
Programiści korzystają z wyrażeń regularnych od dziesięcioleci. Z uwagi na bły
skawicznie rosnącą liczbę możliwości przeszukiwania sieci W W W zakres zastoso
wań wyrażeń regularnych jeszcze się zwiększył. Na początku podrozdziału omawia
my liczne specyficzne zastosowania. Nie tylko pokazuje to przydatność i możliwości
wyrażeń regularnych, ale też pozwala lepiej poznać ich podstawowe cechy.
Tak jak w przypadku algorytmu KMP przedstawionego w poprzednim podroz
dziale, tak i tu rozważamy trzy podstawowe operacje w kategoriach abstrakcyjnego
automatu do wyszukiwania wzorców w tekście. Następnie, tak jak wcześniej, pokazu
jemy tworzenie takiego automatu i symulowanie jego działania przez algorytm dopa
sowywania wzorców. Oczywiście, automaty do dopasowywania wzorców są zwykle
bardziej skomplikowane niż automat DFA z algorytmu KMP, jednak są mniej złożo
ne, niż m ożna by podejrzewać.
Jak widać, rozwiązanie problemu dopasowywania wzorców jest blisko związane
z podstawowymi procesami z obszaru nauk komputerowych. Przykładowo, m eto
da używana w programie do wyszukiwania łańcuchów znaków wyznaczanych przez
dany opis wzorca przypomina metodę wykorzystywaną w systemie Javy do prze
kształcania danego program u Javy na program w języku maszynowym komputera.
Ponadto omawiane jest zagadnienie niedeterminizmu, które odgrywa kluczową rolę
w poszukiwaniu wydajnych algorytmów (zobacz r o z d z i a ł 6.).
800
5.4 □ Wyrażenia regularne 801
W y ra ż e n ie r e g u la r n e P a s u je d o N ie p a s u je d o
Ogólnie język opisywany przez dane wyrażenie regularne może być bardzo duży (po
tencjalnie nieskończony). Istnieje wiele różnych sposobów na opisanie każdego języka.
Należy próbować określać zwięzłe wzorce, podobnie jak próbujemy pisać zwięzłe
programy i implementować wydajne algorytmy.
5.4 Q Wyrażenia regularne 803
Z n a c z e n ie Z a p is P rz y k ła d S k ró t d la W ję z y k u P o z a ję z y k ie m
K o n te k s t W y ra ż e n ie r e g u la r n e P a s u ją c e ciąg i
Numer telefonu \ ([0 -9 ]{3 }\)\ [0-9 ]{3 }-[0 -9]{4 } (800) 867-5309
T y p o w e w y ra ż e n ia r e g u la r n e w a p lik a c ja c h (w e rs je u p ro s z c z o n e )
5.4 n Wyrażenia regularne 805
Stan początkowy
A A A A B D
W y s z u k iw a n ie w z o rc a z a p o m o c ą a u to m a t u N F A d la w y ra ż e n ia ( ( A * B | A C ) D )
808 ROZDZIAŁ 5 b Łańcuchy znaków
Sym ulow anie działania autom atu NFA i osiągalność Aby zasymulować działanie
automatu NFA, należy śledzić zbiór stanów, które można napotkać w czasie sprawdza
nia przez automat bieżącego znaku wejściowego. Kluczowy jest tu znany proces okre
ślania osiągalności z wielu źródeł, omówiony w a l g o r y t m i e 4.4 (strona 583). Aby za
inicjować zbiór, należy znaleźć zbiór stanów osiągalnych przez e-przejścia ze stanu 0 .
Dla każdego takiego stanu należy sprawdzić, czy możliwe jest przejście po dopasowaniu
dla pierwszego znaku wejściowego. W ten sposób uzyskujemy zbiór możliwych stanów
automatu NFA po dopasowaniu pierwszego znaku wejściowego. Do tego zbioru należy
dodać wszystkie stany, które mogą wystąpić po e-przejściach z jednego ze stanów zbio
ru. Dla zbioru możliwych stanów automatu NFA bezpośrednio po dopasowaniu pierw
szego znaku wejściowego rozwiązanie problemu osiągalności z wielu źródeł w digrafie
e-przejść wyznacza zbiór stanów, które mogą prowadzić do przejść po dopasowaniu
dla drugiego znaku wejściowego. Początkowy zbiór stanów w przykładowym automacie
NFA to 0 1 2 3 4 6. Jeśli pierwszy znak to A, automat NFA może wybrać przejście po
dopasowaniu do stanu 3 lub 7. Następnie może wybrać e-przejścia z 3 do 2 lub z 3 do
4, tak więc zbiór stanów, które mogą prowadzić do przejścia po dopasowaniu dla dru
giego znaku, to 2 3 4 7. Powtarzanie tego procesu do czasu wyczerpania wszystkich
znaków tekstu prowadzi do jednego z dwóch skutków.
■ Zbiór możliwych stanów obejmuje stan akceptacji.
■ Zbiór możliwych stanów nie obejmuje stanu akceptacji.
Pierwszy ze skutków oznacza, że istnieje ciąg przejść umożliwiający automatowi NFA
dotarcie do stanu akceptacji. Należy więc poinformować o powodzeniu. Drugi sku
tek oznacza, że automat NFA zawsze zatrzymuje się dla danych wejściowych, dla-
810 ROZDZIAŁ 5 a Łańcuchy znaków
0 12 3 4 6 : Z b ió r s t a n ó w o s ią g a ln y c h p rze z E-przejścia o d p o c z ą t k u
3 7
2 3 4 7 Z b ió r s ta n ó w o s ią g a ln y c h przez E-przejścia p o d o p a s o w a n iu A
2 3 4 Z b ió r s t a n ó w o s ią g a ln y c h p rzez E-przejścia p o d o p a s o w a n iu A A
10 : Z b ió r s t a n ó w o s ią g a ln y c h p o d o p a s o w a n iu A A B D
A k cep ta cja
S y m u lo w a n ie p ra c y a u to m a tu NFA d la w y ra ż e n ia
( ( A * B | A C ) D ) i d a n y c h w e jśc io w y c h A A B D
5.4 a Wyrażenia regularne 811
tego trzeba poinformować o niepowodzeniu. Za pom ocą typu danych SET i klasy
Di rectedDFS, opisanej w kontekście rozwiązywania problemu osiągalności z wielu
źródeł w digrafie, m ożna napisać kod symulujący działanie automatu NFA (widocz
ny poniżej) przez przekształcenie przedstawionego opisu w języku polskim. Poziom
zrozumienia kodu można sprawdzić, analizując ślad na poprzedniej stronie, gdzie
pokazano pełną symulację dla omawianego przykładu.
Warto przez m om ent zastanowić się nad tym zaskakującym wynikiem. Koszt dla naj
gorszego przypadku, iloczyn długości tekstu i wzorca, jest taki sam, jak koszt dla naj
gorszego przypadku przy wyszukiwaniu podłańcuchów za pom ocą podstawowego
algorytmu, od którego zaczęliśmy p o d r o z d z i a ł 5 .3 .
p ublic boolean re c o g n i z e s ( S t r i n g tx t)
{ // Czy automat NFA rozpoznaje łańcuch t x t ?
Bag<Integer> pc = new B a g < In te g e r > ( );
DirectedDFS dfs = new DirectedDFS(G, 0);
f o r (i n t v = 0; v < G.V(); v++)
i f (dfs.marked(v)) pc.add(v);
f o r ( i n t i = 0; i < t x t . l e n g t h ( ) ; i++ )
{ // Wyznaczanie stanów automatu NFA dla t x t [ i +1].
Bag<Integer> match = new B a g < In te g e r > ( );
f o r ( i n t v : pc)
if (v < M)
i f (r e [v] == t x t . c h a r A t ( i ) || re [v] == ' . ' )
m a tch.a d d (v +l);
pc = new B a g < In te g e r > ( );
d fs '= new DirectedDFS(G, match);
f o r ( i n t v = 0; v < G.V (); v++)
i f (dfs.marked(v)) pc.add(v);
1
D om knięcie p o je d y n c ze g o znaku
G .a d d E d g e ( i, i + 1 ) ;
G .a d d E d g e ( i+ l, i ) ;
W yrażenie d om knięcia
G .a d d E d g e O p , i + 1 ) ;
G .a d d E d g e ( i+ l, I p ) ;
W yrażenie lub
G .a d d E d g e ( l p , o r+ 1 );
G .a d d E d g e f o r , i);
R eg u ły tw o rz e n ia a u to m a tu NFA
0 -H T h -
A u to m a t NFA o d p o w ia d a ją c y w z o rc o w i ( . * A B ( ( C | D * E ) F ) * G )
814 ROZDZIAŁ 5 Łańcuchy znaków
p u b l i c NFA(String regexp)
{ // T w o r z e n i e m a s z y n y NFA d l a d a n e g o w y r a ż e n i a r e g u l a r n e g o .
S t a c k < I n t e g e r > o p s = new S t a c k < I n t e g e r > ( ) ;
re = re g ex p .toCharArrayO ;
M = re.length;
G = new Di g r a p h ( M + l ) ;
% more t i n y L . t x t
AC
AD
AAA
ABD
ADD
BCD
ABCCBD
BABAAA
BABBAAA
PYTANIA I O D PO W IED ZI
O. Pierwsza wartość oznacza pusty zbiór. Druga określa pusty łańcuch znaków. Może
istnieć zbiór, który obejmuje jeden element, e, a tym samym nie jest pusty.
818 ROZDZIAŁ 5 a Łańcuchy znaków
| Ć W IC Z E N IA
Jaka jest maksymalna liczba różnych łańcuchów znaków, które można opisać
5 . 4 .3 .
za pomocą wyrażeń regularnych o M operatorach lub, ale bez operatorów dom knię
cia (dozwolone są nawiasy i złączenia)?
5 . 4 .4 . Narysuj automat NFA odpowiadający wzorcowi ( ( (A | B) * | CD* | EFG) *) *.
i PRO BLEM Y DO R O ZW IĄ ZA N IA
A u to m a t NFA o d p o w ia d a ją c y w z o rc o w i ( . 4 A B ( ( C | D | E ) F ) 1 G )
5.4 Q Wyrażenia regularne 821
5.4.18. Jeden lub więcej. Dodaj do klasy NFA obsługę operatora domknięcia +.
5.4.22 Dowód. Opracuj wersję klasy NFA, która wyświetla dowód na to, że dany łań
cuch znaków należy do języka rozpoznawanego przez automat NFA (dowodem jest
ciąg przejść między stanami prowadzący do stanu akceptacji).
5.5. KOMPRESJA DANYCH
Model ten dotyczy tak zwanej kompresji bezstratnej. Ważne jest tu, aby nie nastąpiła
utrata informacji (w tym sensie, że efekt kompresji i rozpakowania strum ienia bitów
musi co do bitu odpowiadać oryginałowi). Kompresja bezstratna jest wymagana dla
wielu typów plików, na przykład dla danych numerycznych lub kodu wykonywalne
go. Dla pewnych typów plików (takich jak obrazy, filmy lub piosenki) dopuszczalne
są m etody kompresji, w których następuje utrata pewnych informacji. Dekoder ge
neruje tu tylko przybliżoną wersję pierwotnego pliku. W metodach stratnych ocenia
się — obok współczynnika kompresji — także subiektywną jakość. W tej książce nie
omawiamy kompresji stratnej.
Podobne metody dla typów byte (8 bitów), short (16 bitów), i nt (32 bity), 1ong i doubl e (64 bity)
boolean isEmpty() Czy strumień bitów jest pusty?
void c l o s e d Zam yka strumień bitów
In te rf e js API z m e to d a m i s ta ty c z n y m i d o o d c z y tu d a n y c h
z e s tr u m ie n ia b itó w z e s ta n d a r d o w e g o w e jśc ia
Kluczową cechą tej abstrakcji jest to, że — inaczej niż w klasie Stdln — dane ze stan
dardowego wejścia nie zawsze są wyrównane względem granic bajtów. Jeśli strum ień
wejściowy obejmuje jeden bajt, klient wczyta go po jednym bicie za pomocą ośmiu
wywołań readBoolean(). M etoda cl ose() nie jest niezbędna, ale w celu eleganckiego
zakończenia pracy należy wywołać ją w kliencie, aby określić, że dalsze bity nie będą
wczytywane. Tak jak w przypadku klas Stdln i StdOut, tak i tu używamy poniższe
go uzupełniającego interfejsu API do zapisywania strum ieni bitów w standardowym
wyjściu.
p ub lic c l a s s BinaryStdOut
Metoda clo se() strum ienia wyjścia jest niezbędna. Klient musi wywołać cl ose (),
aby zagwarantować, że wszystkie bity określone we wcześniejszych wywołaniach
wri te () trafiły do strum ienia bitów i że ostatni bajt jest uzupełniony zerami, co po
woduje wyrównanie bajta w danych wyjściowych (zapewnia to zgodność z systemem
plików). Z klasami Stdln i StdOut powiązane są interfejsy API In i Out. Tu dostęp
ne są podobne klasy Binaryln i BinaryOut, umożliwiające bezpośrednie korzystanie
z plików z danymi binarnymi.
P rzykład W ramach prostego przykładu załóżmy, że istnieje typ danych, w którym
data reprezentowana jest za pomocą trzech wartości typu i nt (miesiąca, dnia i roku).
Zapisanie tych wartości w formacie 12/31/1999 za pomocą klasy StdOut wymaga
10 znaków, czyli 80 bitów. Zapisanie tych wartości bezpośrednio za pom ocą klasy
BinaryStdOut wymaga 96 bitów (32 bitów na każdą z trzech wartości typu int). Po
zastosowaniu bardziej ekonomicznej reprezentacji, w której miesiąc i dzień zapisany
jest za pom ocą typu byte, a rok — przy użyciu typu short, potrzebne są 32 bity. Klasa
BinaryStdOut pozwala też zapisać pole 4-bitowe, pole 5-bitowe i pole 12-bitowe, co
daje w sumie 21 bitów (a dokładniej — 24 bity, ponieważ pliki muszą obejmować
całkowitą liczbę 8 -bitowych bajtów, dlatego m etoda close() dodaje na końcu trzy
bity 0). Ważna uwaga: uzyskiwanie talach oszczędności samo w sobie stanowi prostą
formę kompresji danych.
Z rzu ty binarne Jak sprawdzić zawartość strum ienia bitów lub bajtów w trakcie
diagnozowania? Na to pytanie próbowali odpowiedzieć sobie pierwsi programiści
w czasach, kiedy jedynym sposobem na znalezienie błędu było sprawdzenie każdego
bitu w pamięci. Pojęcie zrzut jest stosowane od początków informatyki do opisywania
strum ieni bitów w formie czytelnej dla człowieka. Jeśli spróbujesz otworzyć plik za
I o o iio o o lo o iio o io b o io iiiio o iio iić o o iio o o lo o io iiiio o iio o o io o iiio o L O O iiio o io o iiio o ij
l 2 / 3 1 / 1 / 9 9 9 80 bitów
Trzy w artości ty p u i n t (B in a ry S td O u t) 8-bitowa reprezentacja
cyfry 9 w kodzie ASCII
B in a ry S td O u t.w rite (m o n th );
B in a ry S td O u t.w rite (d a y ) ; 32-bitowacalkowitoliczbowa
reprezentacjo wartości 31
B in a ry S td O u t.w rite (y e a r);
| o o o o o o o o l o o o o o o o o io o o o o o Q o |o o o o i i o o |o Qoooo oo: ooo oooo ojo ooo ooo o,oo oii iii ioo oooo oolo o o o o o o o lo o o o o i i i j i i Q o i i i i
12 31 1999 96 bitów
Dwie w artości ty p u c h a r Pole 4-bitow e, pole 5-bitow e
i je d n a ty p u s h o r t (Bi n a ry S td O u t) i p o le 12-bitow e (Bi n a ry S td O u t)
C ztery s p o s o b y n a u m ie s z c z e n ie d a ty w s ta n d a rd o w y m w yjściu
826 ROZDZIAŁ 5 a Łańcuchy znaków
S tand ard o w y stru m ień znaków Strum ień bitów re p rezen to w an y za pom ocą cyfr szesnastkow ych
l i Li
0100010001000001 Powiększone okno
0100001001010010 16 na 6 pikseli
0100000100100001
L ic zb a bitów: 96 L iczb a bitów: 96
czy 7-bitowego kodowania ASCII, dla T abela konwersji m iędzy kodow aniem
szesnastkow ym a kodow aniem ASCII
tego pierwsza cyfra szesnastkowa musi
być równa 7 lub mniej. Liczby szesnast
kowe rozpoczynające się od 0 lub 1 (oraz liczby 20 i 7F) odpowiadają niewyświet-
lanym znakom sterującym. Wiele znaków sterujących to pozostałości po czasach,
kiedy urządzeniami fizycznymi, takim i jak maszyny do pisania, sterowano za pom o
cą znaków ASCII. W tabeli wyróżniono kilka takich znaków, które mogą wystąpić
w zrzutach. Przykładowo, SP to znak spacji, NUL to znak pusty, LF to znak wysuwu
wiersza, a CR to znak powrotu karetki.
L i c z b a bitów: 1000000
T ru d n y d o s k o m p re s o w a n ia plik - m ilio n p s e u d o lo s o w y c h b itó w
następnej strony, rozpakowuje strum ień bitów utworzony przez metodę compress ().
Tak jak przy kompresji, m etoda wczytuje strum ień bitów i zapisuje strum ień bitów,
zgodnie z podstawowym modelem kompresji danych. Strumień bitów generowany
jako dane wyjściowe to pierwotne dane wejściowe.
832 ROZDZIAŁ 5 n Łańcuchy znaków
% more genomeTiny.txt
ATAGATGCATAGCGCATAGCTAGATGTGCTAGC
K rótki p r z y p a d e k t e s t o w y (2 6 4 b ity )
L i c z b a b it ó w : 50000
L i c z b a b it ó w : 12536
K o m p re sja i ro z p a k o w y w a n ie sek w e n c ji g e n o m u za p o m o c ą k o d o w a n ia 2 -b ito w e g o
834 ROZDZIAŁ 5 o Łańcuchy znaków
Łańcuch składa się z 15 cyfr 0, 7 cyfr 1, 7 cyfr 0, a następnie 11 cyfr 1. Można za
kodować go za pom ocą liczb 15, 7, 7 i 11. Wszystkie strum ienie bitów składają się
z naprzemiennych serii zer i jedynek. Wystarczy zakodować długość tych serii. Jeśli
dla przykładowych danych zastosujemy 4 bity do zakodowania liczb i zaczniemy od
serii cyfr 0, otrzymamy 16-bitowy łańcuch znaków:
1111011101111011
B itm apy Kodowanie długości serii jest skuteczne na przykład dla bitmap, powszech
nie stosowanych do reprezentowania obrazów i zeskanowanych dokumentów. Z uwa
gi na zwięzłość i prostotę omawiamy bitmapy o wartościach binarnych uporządko
wane w strumienie bitów przez pobranie pikseli w kolejności wyznaczanej przez
wiersze. Do wyświetlania zawartości bitmap służy program Pi ctureDump. Można
łatwo napisać program do przekształcania obrazu z jednego z wielu bezstratnych
formatów zdefiniowanych dla zrzutów ekranu lub zeskanowanych dokumentów na
bitmapę. Przykład demonstrujący skuteczność kodowania długości serii oparty jest
na zrzucie z tej książki, a konkretnie — na literze q (w różnych rozdzielczościach).
5.5 □ Kompresja danych 835
plikowana. Dopóki w strum ieniu danych Liczba bitów: 1536 " ^ 7 7 cyfrO
q
i java PictureDump 32 48 < q32x48.bin
to 1 1 4 4 /1 5 3 6 = 74%
100000110000101010010100000110000111000001 -
100010010000011000010101001010000010100001
i m ożna go odkodować jako CRRDDCRCB lub kilka innych łańcuchów znaków. Nadal
jednak 17 bitów plus 10 ograniczników to mniej niż pierwotna wersja, co wynika
głównie z tego, że nie są potrzebne bity do kodowania liter, które nie występują w tek
ście. Następny krok polega na wykorzystaniu tego, że ograniczniki nie są potrzebne,
jeśli kod żadnego znaku nie jest przedrostkiem innego. Kod o tej właściwości to kod
bezprefiksowy. Przedstawiony wcześniej kod nie jest bezprefiksowy, ponieważ 0, kod
litery A, jest przedrostkiem 00, kodu litery R. Przykładowo, jeśli zakodujemy A jako
0, B jako 1111, C jako 110, Djako 100, R jako 1110, a ! jako 101, poniższy 30-bitowy
łańcuch będzie m ożna odkodować w tyko jeden sposób:
011111110011001000111111100101
Klucz Wartość
m ocą d rzew a trie Jednym z wygodnych sposo
! 101
bów na reprezentowanie kodów bezprefiksowych A 0
jest drzewo trie (zobacz p o d r o z d z i a ł 5.2 ). Każde B 1111
Kompresja za pom ocą kodów bezprefiksowych Tablica słó w ko d o w y c h Reprezentacja w p ostac i drze w a trie
Przy kompresji drzewo trie z definicją kodu wy Klucz Wartość
korzystujemy do utworzenia tablicy kodów, co po 1010
kazano w metodzie bui 1dCode() w górnej części 0
111
strony. Metoda ta jest zwięzła i elegancka, ale nie 1011
co skomplikowana, dlatego zasługuje na staranną 100
analizę. Dla dowolnego drzewa trie tworzy tabli 110
Tworzenie drzew a trie W czasie lektury opisu procesu przydatny będzie rysunek
z następnej strony, na którym pokazano proces tworzenia drzewa trie Huffmana dla
poniższych danych wejściowych:
i t was t h e b e s t o f t i m e s i t was t h e w o r s t o f t i m e s
Kodowane znaki znajdują się w liściach. Ponadto w każdym węźle przechowywana jest
zmienna egzemplarza f req, reprezentująca liczbę wystąpień wszystkich znaków w pod-
drzewie, którego korzeniem jest dany węzeł. Pierwszy krok polega na utworzeniu lasu
drzewa o jednym węźle (liści), po jednym drzewie na każdy znak ze strumienia wejścio
wego. Do każdego drzewa przypisana jest wartość f req równa liczbie wystąpień znaku
w danych wejściowych. W przykładzie dane wejściowe obejmują 8 liter t, 5 liter s, 11
odstępów itd. Ważna uwaga — aby określić liczbę wystąpień, trzeba wczytać cały stru
mień wejściowy; kodowanie Huffmana to algorytm dwuprzebiegowy, ponieważ wyma
ga wczytania strumienia wejściowego drugi raz w celu skompresowania go. Następnie
tworzymy od dołu do góry (według liczb wystąpień znaków) drzewo trie potrzebne do
kodowania. Przy tworzeniu drzewa trie traktujemy je jak binarne drzewo trie z liczba
mi wystąpień zapisanymi w węzłach. Po zakończeniu tego procesu postrzegamy je jak
drzewo trie używane do kodowania w opisany wcześniej sposób. Proces ten przebiega
w następujący sposób — należy znaleźć dwa węzły o najmniejszej liczbie wystąpień,
a następnie utworzyć nowy węzeł, którego dwa wspomniane węzły są dziećmi (i w któ
rym liczba wystąpień jest równa sumie tych liczb w dzieciach). Operacja ta zmniejsza
liczbę drzew trie w lesie o jedno. Następnie powtarzamy proces — trzeba znaleźć dwa
węzły o najmniejszej liczbie wystąpień i w opisany wcześniej sposób utworzyć nowy
węzeł. Proces ten można zaimplementować w prosty sposób za pomocą kolejki prio
rytetowej, tak jak w metodzie bui 1dTri e() poniżej. Dla przejrzystości drzewa trie na
rysunku przedstawiono w posortowanej kolejności. Na dalszych etapach procesu po
wstają coraz większe drzewa trie, a jednocześnie w każdym kroku liczba drzew trie
w lesie zmniejsza się o jeden (dwa drzewa są usuwane, a jedno — dodawane).
Z dolnego poziom u
lewej kolum ny
1 \ 2 2 2 2 2 3 3 4 5 6 8 U
N o w y rodzic dla
tych dw óch drzew
Ostatecznie wszystkie węzły są łączone w jedno drzewo trie. Liście w tym drzewie
obejmują kodowane znaki i liczbę wystąpień znaków w danych wejściowych. Każdy
węzeł, który nie jest liściem, obejmuje sumę liczb wystąpień z dwójki dzieci. Węzły
o małej liczbie wystąpień są mocno zagłębione w drzewie trie, a węzły o dużej licz
bie wystąpień znajdują się blisko korzenia. Liczba wystąpień w korzeniu jest równa
liczbie znaków w danych wejściowych. Ponieważ utworzono binarne drzewo trie,
w którym znaki występują tylko w liściach, drzewo to wyznacza bezprefiksowy kod
dla użytych znaków. Po zastosowaniu tablicy słów kodowych utworzonej za pomocą
m etody bui 1dCode () w tym przykładzie (tablicę te pokazano w prawej części rysunku
na początku strony) otrzymujemy wyjściowy łańcuch bitów:
10111110100101101110001111110010000110101100 -
0 1001110100111100001111101111010000100011011 -
11101001011011100011111100100001001000111010 -
01001110100111100001111101111010000100101010
W przykładzie jest jeden liść o odległości 2 (SP o liczbie wystąpień 11), trzy liście o od
ległości 3 (e, s i t o łącznej liczbie wystąpień 19), trzy liście o odległości 4 (w, o oraz
i o łącznej liczbie wystąpień 10), pięć liści o odległości 5 (r, f, h, mi a o łącznej liczbie
wystąpień 9) i dwa liście o odległości 6 (LF i b o łącznej liczbie wystąpień 2). Suma
wynosi więc 2x11 + 3x19 + 4x10 + 5x9 + 6x2 = 176. Jest to, zgodnie z oczekiwania
mi, długość wyjściowego łańcucha bitów.
Kiedy trzeba wybrać węzeł, może się zdarzyć, że kilka z nich ma tę samą wagę.
Metoda Huffmana nie określa, jak dokonać wyboru w takiej sytuacji. Nie określa też
lewej i prawej pozycji dzieci. Różne wybory prowadzą do różnych kodów Huffmana,
jednak wszystkie takie kody powodują zakodowanie kom unikatu za pom ocą kodu
bezprefiksowego o optymal
nej liczbie bitów.
Zapis i odczyt drzew a trie
Jak podkreśliliśmy, podane
wcześniej oszczędności nie są
w pełni dokładne, ponieważ
skompresowanego strum ie
nia bitów nie można odkodo-
Liście wać bez drzewa trie. Dlatego
i oprócz kosztów zapisu sa
0101 0 0 0 0 0 1 0 0 1 0 1 0 0 0 1 0 001000010101010000110101010010101000010
mego łańcucha bitów trze
t t
Węz/y wewnętrzne ba uwzględnić koszt zapisu
Przechodzenie w porządku preorder w celu w skompresowanych danych
zakodowania drzewa trie jako strumienia bitów
wyjściowych także drzewa
trie. Jeśli dane wejściowe są
długie, koszt ten jest stosunkowo niski, jednak pełny system kompresji danych wy
maga tu zapisu drzewa trie w strum ieniu bitów na etapie kompresowania i odczytu
drzewa w czasie rozpakowywania. Jak zakodować drzewo trie w strumieniu bitów,
a następnie je rozpakować? Co zaskakujące, oba zadania można wykonać za pomocą
prostych procedur rekurencyjnych, opartych na przechodzeniu w porządku preorder
przez drzewo trie. Przedstawiona poniżej procedura w riteT rie() przechodzi przez
drzewo trie w takim właśnie porządku. Po dotarciu do węzła wewnętrznego zapisuje
jeden bit 0. Po dojściu do liścia zapisuje bit 1, po którym następuje 8 -bitowy kod ASCII
znaku z danego liścia. Powyżej pokazano łańcuch bitów z zakodowanym drzewem trie
Huffmana dla przykładowego łańcucha ABRACADABRA!. Pierwszy bit to 0 (odpowiada
on korzeniowi). Ponieważ potem metoda natrafia na liść z literą A, następny bit to 1, po
czym następuje 8-bitowy kod ASCII dla litery A — 0100001. Dwa dalsze bity to 0, po
nieważ metoda napotyka
dwa węzły wewnętrzne p r i v a t e s t a t i c voi d w r i t e T r i e ( N o d e x)
itd. Powiązana metoda { / / Za pi s drzewa t r i e zakodowanego j a k o ł a ńc uc h bi t ów,
i f (x.isLeaff))
readTri e () ze strony 847
(
odtwarza drzewo trie BinaryStdOut.write(true);
na podstawie łańcucha BinaryStdOut.write(x.ch);
return;
bitów. Metoda wczytuje
1
jeden bit, aby ustalić ro BinaryStd0ut.writ e ( f a l s e ) ;
dzaj następnego węzła. w riteTrie(x.left);
writeTriefx.right);
Jeśli jest to liść (bit to 1),
1
metoda wczytuje kolejny
Zapis drzewa trie jako łańcucha bitów
5.5 Q Kompresja danych 847
p r i v a t e s t a t i c Node r e a d T r i e ( )
{
i f (BinaryStdln.readBooleanf))
r e t u r n new N o d e ( B i n a r y S t d I n . r e a d C h a r ( ) , 0, n u l l , n u l l ) ;
r e t u r n new N o d e ( ' \ 0 ' , 0, r e a d T r i e ( ) , r e a d T r i e f ) ) ;
}
znak i tworzy liść. Jeżeli jest to węzeł wewnętrzny (bit to 0), metoda tworzy węzeł we
wnętrzny, a następnie rekurencyjnie tworzy jego lewe i prawe poddrzewo. Upewnij się,
że rozumiesz te metody — ich prostota może być myląca.
Im p le m e n ta c ja ko m p resji H u ffm a n a Wraz z opisanymi wcześniej metodam i
b u i l d C o d e ( ) , b u i l d T r i e ( ) , r e a d T r i e ( ) i w r i t e T r i e ( ) (oraz przedstawioną na po
czątku m etodą e x p a n d Q ) a l g o r y t m 5.10 jest kompletną implementacją kom pre
sji Huffmana. Rozwińmy omówienie, które przedstawiliśmy kilka stron wcześniej
— strum ień bitów można traktować jak strum ień 8-bitowych wartości typu c h a r
i kompresować go w następujący sposób:
■ Wczytać dane wejściowe.
■ Zapisać w tablicy liczbę wystąpień każdej wartości typu char z danych wejścio
wych.
■ Utworzyć na potrzeby kodowania drzewo trie Huffmana odpowiadające licz
bom wystąpień.
■ Utworzyć odpowiednią tablicę słów kodowych, aby powiązać łańcuch bitów
z każdą wartością typu char z danych wejściowych.
■ Zapisać drzewo trie zakodowane jako łańcuch bitów.
■ Zapisać liczbę znaków w danych wyjściowych zakodowaną jako łańcuch bitów.
n Wykorzystać tablicę słów kodowych do zapisu słowa kodowego dla każdego
znaku wejściowego.
W celu rozpakowania strum ienia bitów zakodowanego w ten sposób należy:
■ Wczytać drzewo trie (zakodowane na początku strum ienia bitów).
■ Wczytać liczbę znaków do odkodowania.
n Wykorzystać drzewo trie do odkodowania strum ienia bitów.
Kompresja Huffmana wymaga czterech rekurencyjnych m etod przetwarzania drzew
trie i siedmioetapowego procesu kompresji. Jest tym samym jednym z najbardziej
złożonych algorytmów omawianych w książce, ale też jednym z najczęściej stosowa
nych (z uwagi na jego skuteczność).
848 ROZDZIAŁ 5 Łańcuchy znaków
p u b l i c c l a s s Huffman
{
p r i v a t e s t a t i c i n t R = 256; // A l f a b e t A S C I I .
// Kod wewnętrznej k l a s y Node z n a j d z i e s z na s t r o n i e 840.
// Metody pom ocnicze i metodę e x p a n d () p r z e d s t a w i o n o w t e k ś c i e .
p u b l i c s t a t i c v o i d c o m p r e s s ()
{
// O d czy t danych w e jś c io w y c h .
S t r in g s = B in a r y S t d ln .r e a d S t r in g ();
ch a r[] in pu t = s . t o C h a r A r r a y ( ) ;
// T w o r z e n ie t a b l i c y l i c z b w y s t ą p i e ń ,
i nt □ f r e q = new i n t [ R ] ;
fo r ( in t i = 0; i < in p u t.le n g th ; i++ )
fre q [in p u t[i]]+ + ;
// T w o r z e n ie t a b l i c y kodów ( r e k u r e n c y j n i e ) .
S t r i n g [] s t = new S t r i n g [ R ] ;
b u ild C o d e (st, ro ot, " " ) ;
// Z a p i s drzewa t r i e na p o t r z e b y odkodowywania ( r e k u r e n c y j n i e ) .
w rite T rie (ro o t);
// Z a p i s l i c z b y znaków.
B i n a r y S t d O u t . w r i t e ( i n p u t. l e n g t h ) ;
L ic z b a b itó w : 45056
nych typów plików, a nie tylko dla tekstów w języku naturalnym. Starannie napisali
śmy kod metody, tak aby działała prawidłowo dla dowolnej 8-bitowej wartości w każ
dym 8-bitowym znaku. Oznacza to, że można ją zastosować do dowolnego strum ie
nia bajtów. Na rysunku w dolnej części strony pokazano kilka przykładów dotyczą
cych typów plików wspomnianych we wcześniejszej części podrozdziału. Widać tu,
że kompresja Huffmana jest konkurencyjna względem kodowania za pom ocą kodów
o stałej długości i kodowania długości serii, choć metody te zaprojektowano w taki
sposób, aby działały dobrze dla określonych typów plików. Warto zrozumieć powody
dobrego działania kodowania Huffmana w przykładowych obszarach. W przypadku
danych o genomie kompresja Huffmana „odkrywa” kod 2-bitowy, ponieważ cztery li
tery występują tu z mniej więcej równą częstotliwością, dlatego drzewo trie dla kodo
wania Huffmana jest zbalansowane, a każdemu znakowi przypisywany jest 2-bitowy
kod. Jeśli chodzi o kodowanie długości serii, 00000000 i 1 1 1 1 1 1 1 1 to prawdopodobnie
najczęściej występujące znaki, dlatego zostaną zakodowane za pom ocą dwóch lub
trzech bitów, co prowadzi do znacznej kompresji.
Compresowanie danych o genom ie i bitm ap za pom ocą kodowania Huffmana oraz wyspecjalizowanych metod
5.5 □ Kompresja danych 851
Pod koniec lat 70. i na początku lat 80. wymyślono zaskakującą alternatywę do
kompresji Huffmana. A. Lempel, J. Ziv i T. Welch opracowali jedną z najczęściej sto
sowanych m etod kompresji. Jest ona łatwa w implementacji i działa dobrze dla pli
ków różnego typu.
Podstawowy plan jest uzupełnieniem pomysłu z kodowania Huffmana. Zamiast
przechowywać tablicę słów kodowych o zmiennej długości dla wzorców o stałej dłu
gości z danych wejściowych, można przechowywać tablicę słów kodowych o stałej
długości dla wzorców o zmiennej długości. Zaskakującą dodatkową cechą tej metody
jest to, że — inaczej niż przy kodowaniu Huffmana — nie trzeba kodować tablicy.
Kompresja L Z W Aby pomóc zrozumieć pomysł, omawiamy przykład kompresji,
w którym dane wejściowe to 7-bitowe znaki ASCII, a dane wyjściowe to strum ień
8 -bitowych bajtów. W praktyce zwykle stosujemy większe wartości tych parametrów
— w opracowanych przez nas implementacjach używamy 8-bitowych danych wej
ściowych i 12-bitowych danych wyjściowych. Bajty wejściowe określamy jako znaki,
ciągi bajtów wejściowych — jako łańcuchy znaków, a bajty wyjściowe — jako sło
wa kodowe, choć w innych kontekstach pojęcia te mają nieco odm ienne znaczenie.
Algorytm kompresji LZW jest oparty na przechowywaniu tablicy symboli, która łą
czy klucze w postaci łańcuchów znaków z wartościami słów kodowych (o stałej dłu
gości). Tablicę symboli należy zainicjować za pom ocą 128 możliwych kluczy w po
staci pojedynczych znaków. Następnie trzeba powiązać klucze z 8-bitowymi słowami
kodowymi uzyskanymi przez dołączenie 0 do 7-bitowej wartości definiującej każdy
znak. Z uwagi na zwięzłość i przejrzystość stosujemy dla wartości słów kodowych za
pis szesnastkowy — 41 to słowo kodowe dla A w kodzie ASCII, 52 odpowiada literze
R itd. Słowo kodowe 80 jest zarezerwowane i oznacza koniec pliku. Pozostałe warto
ści słów kodowych (od 81 do FF) przypisujemy różnym napotkanym podłańcuchom
z danych wyjściowych. Zaczynamy od 81 i zwiększamy wartość dla każdego nowego
dodanego klucza. Przy kompresowaniu, dopóki występują niepobrane znaki w da
nych wyjściowych, wykonujemy następujące kroki.
D Znajdow anie w tablicy sym boli najdłuższego łańcucha znaków s, który jest
przedrostkiem niezakodow anego jeszcze fragm entu danych wejściowych.
n Zapisywanie 8-bitowej wartości (słowa kodowego) powiązanej z s.
■ Pobieranie jednego znaku po s z danych wejściowych.
n Wiązanie w tablicy symboli następnej wartości słowa kodowego z s + c (c do
łączonego do s), gdzie c to następny znak w danych wejściowych.
W ostatnim kroku należy przejść naprzód, aby sprawdzić następny znak z danych wej
ściowych w celu utworzenia kolejnego elementu słownika. Tak więc znak c to znak
następny (ang. lookahead). Na razie załóżmy, że po wyczerpaniu się wartości słów ko
dowych (po przypisaniu wartości FF do jednego z łańcuchów znaków) kończymy do
dawanie elementów do tablicy symboli. Dalej omawiamy inne rozwiązania.
852 ROZDZIAŁ 5 h Łańcuchy znaków
Dane wejściowe to 17 znaków ASCII po 7 bitów każdy, co w sumie daje 119 bi
tów. Dane wyjściowe to 12 słów kodowych po 8 bitów każdy — łącznie 96 bitów.
Współczynnik kompresji wynosi 82% nawet w tym krótkim przykładzie.
Reprezentacja kompresji L Z W za pom ocą drzew a trie Kompresja LZW oparta
jest na dwóch operacjach na tablicy symboli:
B znajdowaniu pasującego najdłuższego przedrostka dla danych wejściowych za
pom ocą klucza z tablicy symboli;
■ dodawaniu elementu łączącego na
stępne słowo kodowe z kluczem
utworzonym przez dołączenie znaku
następnego do danego klucza.
S truktury danych dla drzew trie p rzedsta
w ione W PO D R O Z D Z IA LE 5-2 Są doStOSO-
w ane do tych operacji. D rzew o trie rep re
zentujące om aw iany przykład pokazano
po prawej stronie. Aby znaleźć najdłuższy
pasujący przedrostek, należy przejść po
drzew ie trie, począw szy od korzenia, i d o
pasow ać etykiety węzłów do wejściowych Drzewo trfe reprezentujące tablicę kodów LZW
5.5 □ Kompresja danych 853
znaków. W celu dodania nowego słowa kodowego nowy węzeł opisany kolejnym
słowem kodowym i znakiem następnym trzeba połączyć z węzłem, w którym zakoń
czono wyszukiwanie. W praktyce z uwagi na oszczędność pamięci stosujemy drzewa
TST, opisane w p o d r o z d z i a l e 5 .2 . Warto zwrócić uwagę na różnicę w porównaniu
z drzewami trie dla kodowania Huffmana, gdzie drzewa trie są przydatne, ponie
waż żaden przedrostek słowa kodowego sam nie jest słowem kodowym. W metodzie
LZW drzewa trie są użyteczne, ponieważ każdy przedrostek klucza dla wejściowego
podłańcucha sam też jest kluczem.
R ozpakow yw anie w m etodzie L Z W Dane wejściowe przy rozpakowywaniu w m e
todzie LZW są w omawianym przykładzie ciągiem 8-bitowych słów kodowych. Dane
wyjściowe to łańcuch 7-bitowych znaków ASCII. Aby zaimplementować rozpako
wywanie, należy utworzyć tablicę symboli, w której łańcuchy znaków są powiązane
z wartościami słowa kodowego (jest to odwrotność tablicy używanej przy kom preso
waniu). Trzeba zapełnić elementy tablicy od 00 do 7F jednoznakowymi łańcuchami,
po jednym dla każdego znaku ASCII, ustawić pierwszą nieprzypisaną wartość słowa
kodowego na 81 (wartość 80 oznacza koniec pliku), ustawić wartość bieżącego łań
cucha znaków, v al, na jednoznakowy łańcuch obejmujący pierwszy znak, a następ
nie wykonywać poniższe kroki do m om entu wczytania słowa kodowego 80 (koniec
pliku):
H Zapisać bieżący łańcuch znaków, v al.
* Wczytać słowo kodowe x z danych wejściowych.
■ Ustawić s na wartość powiązaną z x w tablicy symboli.
a Powiązać w tablicy symboli następną nieprzypisaną wartość słowa kodowego
z val + c, gdzie c to pierwszy znak z s.
■ Ustawić wartość bieżącego łańcucha znaków, v al, na s.
Proces ten jest bardziej skomplikowany niż kompresowanie. Wynika to ze znaku na
stępnego. Trzeba wczytać kolejne słowo kodowe, aby pobrać pierwszy znak z powią
zanego z nim łańcucha, co powoduje desynchronizację procesu o jeden krok. Dla
pierwszych siedmiu słów kodowych metoda tylko sprawdza i zapisuje odpowiedni
znak, a następnie idzie naprzód o jeden znak i dodaje dwuznakowy element do tablicy
Dane wejściowe 41 42 52 41 43 41 44 81 83 82 88 41 80
D a n e wyjściowe A B R A C A D A B R A B R A B R A
O d w ró c o n a tablica
s ł ó w k o d ow yc h
Klucz Wartość
81 AB AB AB AB AB AB AB AB AB AB AB 81 AB
82 B R BR BR BR BR BR BR BR BR BR 82 BR
83 R A RA RA RA RA RA RA RA RA 83 RA
84 A C AC AC AC AC AC AC AC 84 AC
85 C A CA CA CA CA CA CA 85 CA
86 A D AD AD AD AD AD 86 AD
87 D A DA DA DA DA 87 DA
.88 A B R AB R AB R AB R 88 ABR
Słow o kodow e ^
z m etody L Z W 89 R A B RAB RA B 89 RAB
/
Wejściowy 8A B R A B RA 8A BRA
podlańcuch 8B ABRA 8B ABRA
p u b lic c la s s LZW
{
p rivate s t a t ic final i n t R = 256; // L i c z b a znaków w e jś c io w y c h ,
p riva te s t a t ic final i n t L = 409 6 ; // L i c z b a stów kodowych = 2^12.
p rivate s t a t ic final i n t W = 12; // S z e r o k o ś ć stó wa kodowego.
p u b l i c s t a t i c v o i d c o m p r e s s ()
{
S t r in g input = B in a r y S t d ln . r e a d S t r in g Q ;
T S T < I n t e g e r > s t = new T S T < I n t e g e r > ( ) ;
f o r ( i n t i = 0; i < R; i + + )
s t . p u t ( " " + (char) i , i ) ;
i n t code = R + l ; // R t o sło w o kodowe o z n a c z a j ą c e k o n i e c p l i k u .
w h ile ( in p u t . le n g th () > 0)
(
Strin g s = st. l o n g e s t P r e f i x O f ( i n p u t ) ; // Z najdow anie n a j d ł u ż s z e g o
// p a s u ją c e g o p r z e d r o s t k a .
B i n a r y S t d O u t . w r i t e ( s t . g e t ( s ) , W); // W y ś w i e t la n i e kodu d la s.
in t t = s . le n g t h ( ) ;
i f (t < i n p u t . l e n g t h ( ) && code < L) // Dodawanie s do t a b l i c y
// symbol i .
s t . p u t ( i n p u t . s u b s t r i n g ( 0 , t + 1 ), c o d e + + ) ;
in p u t = i n p u t . s u b s t r i n g ( t ) ; // P rze ch o d ze n ie za s w danych
// w e jś c io w y c h .
i
B i n a r y S t d O u t . w r i t e ( R , W); // Z a p i s końca p l i k u .
B in a ry Std O u t.c lo se ();
}
p u b l i c s t a t i c v o i d e x p a n d ()
// Zobacz s t r o n ę 856.
)
symboli, tak jak wcześniej. Następnie wczytuje 81 (dlatego zapisuje AB i dodaje ABR do
tablicy), 83 (dlatego zapisuje RAi dodaje RAB do tablicy), 82 (dlatego zapisuje BR i dodaje
BRAdo tablicy) i 88 (co powoduje zapisanie ABR i dodanie ABRA do tablicy). Pozostaje 41.
Ostatecznie metoda dochodzi do znaku końca pliku, 80, dlatego zapisuje A. Na końcu
procesu zapisane są, zgodnie z oczekiwaniami, pierwotne dane wejściowe. Program
buduje też tę samą tablicę kodów, co przy kompresowaniu, jednak role kluczy i warto
ści są tu odwrócone. Zauważmy, że dla tablicy można zastosować prostą reprezentację
w postaci tablicy łańcuchów znaków indeksowanej słowami kodowymi.
Skom plikow ana sytuacja W opisanym procesie występuje drobny błąd. Studenci
(i doświadczeni programiści!) często wykrywają go dopiero po opracowaniu im
plementacji na podstawie wcześniejszego opisu. Problem, pokazany w przykładzie
po prawej stronie, polega na tym, że
Kompresja
proces sprawdzania znaku następne Da n e wejściowe A B A B A B A
wczytać słowo kodowe 41, zapisać Kolejny znak d anych wyjściowych - znak następny!
p u b lic s t a t i c vo id expandQ
{
S t r i n g [] s t = new S t r i n g [ L ] ;
in t i; // N a stę pn a d o s tę p n a w a r t o ś ć s ło w a kodowego.
f o r ( i = 0; i < R; i + + ) // I n i c j o w a n i e t a b l i c y na z n a k i .
s t [i ] = " " + ( c h a r ) i ;
s t [i ++] = " // (N ieu żyw a n y ) znak n a s t ę p n y d l a końca p l i k u .
i n t codeword = B i n a r y S t d l n . r e a d l n t ( W ) ;
S t r i n g va l = s t [ c o d e w o r d ] ;
w h ile (true )
{
B in a r y S t d O u t . w r it e ( v a l); // Z a p i s b ie ż ą c e g o p o d ła ń c u c h a .
codeword = B i n a r y S t d l n . r e a d l n t ( W ) ;
i f (codeword == R) b re a k ;
S t r i n g s = s t [codew ord]; // P o b i e r a n i e n a st ę p n e g o s ło w a
// kodowego.
i f (i == codeword) // J e ś l i znak n a s t ę p n y j e s t
// niepraw idłow y,
s = val + v a l . c h a r A t ( O ) ; // n a l e ż y u tw o rz y ć sło w o kodowe na
// p o d s t a w ie p o p r z e d n i e g o .
i f (i < L)
s t [ i + + ] = va l + s . c h a r A t ( O ) ; // Dodawanie nowego elementu do
// t a b l i c y kodów.
v a l = s; // A k t u a l i z o w a n i e b ie ż ą c e g o słow a
// kodowego.
}
łania kompresji LZW, przedstawionymi wraz z program am i i w dolnej części tej stro
ny. Przez kilka dziesięcioleci od czasu wymyślenia m etody udowodniono, że jest ona
wszechstronną i skuteczną techniką kompresji danych.
i tíSvuc- ? tí
L ic zb a b itó w : 12536
PYTANIA I ODPOWIEDZI
O. Ten wymóg wynika z tego, że standardowe dane wyjściowe to strum ień bajtów,
dlatego m etoda Bi naryStdOut musi wiedzieć, kiedy ma zapisać ostatni bajt.
O. Opracowane przez nas algorytmy kompresji danych mają postać kolekcji m etod
statycznych, a nie implementacji typów danych.
OĆWICZENIA
5.5.1. Rozważmy cztery kody o zmiennej dłu Sym bol Kod 1 Kod 2 Kod 3 Kod 4
gości przedstawione w tabeli po prawej stronie. A 0 0 1 1
Które z tych kodów są bezprefiksowe? Które
B 100 1 01 01
można jednoznacznie odkodować? Dla tych
ostatnich odkoduj łańcuch 1000000000000. C 10 00 001 001
D 11 11 0001 000
5.5.2. Podaj przykład kodu, który umożliwia
jednoznaczne odkodowywanie, a który nie jest
bezprefiksowy.
Odpowiedź: każdy kod bezsufiksowy umożliwia jednoznaczne odkodowywanie.
5.5.3. Podaj przykład kodu, który umożliwia jednoznaczne odkodowywanie,
a nie jest wolny ani bezprefiksowy, ani bezsufiksowy.
Odpowiedź: {0 0 1 1 , 0 1 1 , 1 1 , 1 1 1 0 } lub {0 1 , 1 0 , 0 1 1 , 1 1 0 }.
5.5.4. Czy kody { 01, 1001, 1011, 111, 1110 } i{ 01, 1001, 1011, 111, 1110
} umożliwiają jednoznaczne odkodowywanie? Jeśli nie, podaj łańcuch znaków, który
m ożna zakodować na dwa sposoby.
5.5.8. Przedstaw efekt kodowania łańcuchów znaków ab, abab, ababab, abababab,
... (łańcuchów znaków składających się z N powtórzeń ab) za pomocą kodowania
długości serii, m etody Huffmana i LZW. Jaki jest współczynnik kompresji wyrażony
jako funkcja od N?
5.5.9. Oszacuj współczynnik kompresji uzysldwany za pom ocą kodowania długości
serii, m etody Huffmana i LZW dla losowego łańcucha znaków ASCII o długości N
(na każdej pozycji wszystkie znaki występują tu z równym prawdopodobieństwem).
5 .5.11. Jak wygląda kod Huffmana dla łańcucha znaków, którego wszystkie znaki
pochodzą z dwuznakowego alfabetu? Podaj przykład, w którym potrzebna jest m ak
symalna liczba bitów w kodzie Huffmana dla N -znakowego łańcucha ze znakami
z dwuznakowego alfabetu.
5.5.13. Załóżmy, że liczba wystąpień każdego symbolu jest równa. Opisz uzyskany
kod Huffmana.
5.5.14. Załóżmy, że liczba wystąpień każdego kodowanego znaku jest inna. Czy
drzewo w kodowaniu Huffmana jest wtedy unikatowe?
5.5.20. Podaj kod Huffmana, w którym liczba wystąpień cyfry 0 w danych wyjścio
wych jest znacznie, znacznie większa niż liczba wystąpień cyfry 1 .
Odpowiedź: jeśli znak A występuje milion razy, a znak B — tylko raz, słowo kodowe
dla Ato 0, a słowo kodowe dla B to 1.
5.5 o Kompresja danych
5.5.22. Udowodnij następujący fakt na tem at kodów Huffmana — jeśli liczba wystą
pień symbolu i jest większa niż liczba wystąpień symbolu j, długość słowa kodowego
symbolu i jest mniejsza lub równa długości słowa kodowego symbolu j.
5.5.23. Jaki będzie efekt rozbicia łańcucha znaków zakodowanego m etodą Huffmana
na pięciobitowe znaki i zakodowania tego łańcucha za pom ocą tej samej techniki?
5.5.24. Pokaż (tak jak na rysunkach w tekście) zbudowane na potrzeby kodowania
drzewo trie oraz proces kompresowania i rozpakowywania przy stosowaniu metody
LZWdla poniższego łańcucha znaków:
i t was th e b e s t o f tim e s i t was th e w o r s t o f tim e s
862 ROZDZIAŁ 5 ■ Łańcuchy znaków
| PROBLEMY DO ROZWIĄZANIA
5.5.25. Kod o stałej długości. Zaimplementuj klasę RLE. Wykorzystaj w niej kod
o stałej długości do kompresowania strum ieni bajtów ASCII za pomocą stosunkowo
niewielu znaków. Kod należy przesyłać jako część zakodowanego strum ienia bitów.
Dodaj do m etody com press () kod do tworzenia łańcucha znaków al pha z wszystkimi
różnymi znakami występującymi w wiadomości. Wykorzystaj ten łańcuch do utwo
rzenia obiektu Al phabet do zastosowania w metodzie com press (). Łańcuch znaków
al pha (znaki w kodzie 8 -bitowym i długość) należy podać przed skompresowanym
strumieniem bitów. Do m etody expand () dodaj kod wczytujący alfabet przed rozpa
kowywaniem danych.
5.5.26. Ponowne tworzenie słownika w metodzie LZW . Zmodyfikuj klasę LZW tak,
aby po zapełnieniu słownika opróżniała go i zaczynała pracę od nowa. W niektórych
zastosowaniach jest to zalecane podejście, ponieważ zapewnia lepsze dostosowanie
do zmian ogólnego charakteru danych wejściowych.
l i l i Kontekst
e w s p ó ł c z e s n y m ś w i e c i e urządzenia obliczeniowe są wszechobecne.
865
866 KONTEKST
• A
poszczególnych atomów i cząsteczek). Maxwell i Boltzmann
wykorzystali ten model do wyprowadzenia rozkładu pręd
© # kości cząsteczek wchodzących w interakcje jako funkcji
Przesunięcie czasu do f + 2dt temperatury. Einstein na podstawie tego modelu wyjaśnił
ruchy Browna pyłków kwiatowych zanurzonych w wodzie.
Prognoza (w czasie t)
dt = czas do zderzenia ze ścianą Ś c ia n a
= odległość/prędkość (r,,r.) ' przy
= (1 - s - r ) / v x=;
Cząsteczka będzie wtedy zajmować pozycję (1 - s, r + v dt), o ile wcześniej nie zderzy
się z inną cząsteczką lub poziomą ścianą. Należy więc umieścić w kolejce prioryteto
wej element o priorytecie t + d t {i odpowiednich informacjach opisujących zdarzenie
zderzenia cząsteczki ze ścianą). Obliczenia przy prognozowaniu zderzenia z innymi
ścianami wyglądają podobnie (zobacz ć w i c z e n i e 6 . i ). Obliczenia zderzenia dwóch
cząsteczek też przebiegają podobnie, ale są bardziej skomplikowane. Zauważmy, że
obliczenia często prowadzą do prognoz, zgodnie z którymi zderzenie nie nastąpi (je
śli cząsteczka oddala się od ściany lub dwie cząsteczki oddalają się od siebie). Nie
trzeba wtedy umieszczać żadnych danych w kolejce priorytetowej. Na potrzeby ob
sługi sytuacji innego rodzaju, kiedy czas prognozowanego zderzenia jest zbyt daleki,
aby go uwzględniać, dodajemy param etr 1i mit. Określa on uwzględniany przedział
czasu, dlatego można pominąć wszelkie zdarzenia, których prognozowany czas jest
późniejszy niż 1 i mi t.
E fekt zderzenia Kiedy nastąpi zderzenie, trzeba określić jego efekt, stosując wzory
fizyczne określające zachowanie cząsteczki po zderzeniu sprężystym ze ścianą lub
inną cząsteczką. W omawianym przykładzie, w którym cząsteczka zderza się z pio
nową ścianą, po wystąpieniu zderzenia prędkość cząsteczki zmienia się z (y_, v ) na
(-vv, v ). Obliczenia efektu zderzenia dla innych ścian przebiegają analogicznie, a dla
zderzenia dwóch cząsteczek wyglądają podobnie, są jednak bardziej skomplikowane
(zobacz ć w i c z e n i e 6 .1 ).
Prognoza (w czasie t)
Cząsteczki zderzają się, chyba
że jedna przejdzie po za punkt
przecięcia przed dotarciem Efekt (w czasie t + dt)
do niego drugiej Po zderzeniu prędkości obu
cząsteczek zmieniają się
4
mulacje klienta C o l i i s i o n S y s t e m .
Istotą symulacji jest typ Mi nPQ, któ
I Zderzenie zanadto
ry obejmuje uporządkowane w cza
oddalone w czasie sie zdarzenia. Dalej omawiamy im
Można przewidzieć, plementacje klas P a r t i c l e , Event
że te zdarzenia nie zajdą i Col 1 i sio n S y s te m . Działanie trzeciej cząsteczki -
zderzenie nie występuje
Unieważnione zdarzenie
872 KONTEKST
p u bl i c Event(double t , P a r t i c l e a , P a r t i c l e b)
1 / / Tworzenie nowego z d a r z e n i a , wy s t ę p u j ą c e g o w c z a s i e t i d o t y c z ą c e g o a o r a z b.
this.time = t;
this.a = a;
this.b = b;
i f ( a != n u l i ) count A = a . c o u n t ( ) ; e l s e count A = - 1 ;
i f (b != n u l i ) countB = b . c o u n t ( ) ; e l s e countB = - 1;
1
p u b l i c b o ol e a n i s V a l i d ( )
1
i f (a != n u l l && a . c o u n t ( ) != countA) r e t u r n f a l s e ;
i f (b != n u l l && b . c o u n t () != count B) r e t u r n f a l s e ;
return true;
}
}
Klasa Event służąca do symulowania ruchu cząsteczek
874 KONTEKST
* ., , . rżenia z udziałem
i f (a == n u l i ) r e t u r n ;
f o r ( i n t i = 0; i < p a r t i c l e s . l e n g t h ; i++) cząsteczki a (z in-
{ / / Umieszczani e w pq z d e r z e n i a z udzi ał em c z ą s t e c z k i p a r t i cl es [ i ] . nymi cząsteczka-
doubl e d t = a . t i m e T o H i t ( p a r t i c l e s [ i ] ) ; mi k b śdanam i)
i f ( t + d t <= l i m i t )
p q . i n s e r t ( n e w E v e n t ( t + d t , a , p a r t i cl es [ i ] )); * umieszcza zda-
} rżenie odpowiada-
do u bl e dtX = a . t i m e T o H i t Ve r t i c a l Wa l l ( ) ; ; a c e p a ¿ d e m u 7,de-
i f ( t + dtX <= l i m i t ) . , , .
pq. i n s e r t (new E v e n t ( t + dtX, a , n u l i ) ) ; rzemu w kolejce
do u bl e dtY = a . t i me To Hi t Ho r i z o n t a l Wa l 1(); priorytetowej,
if ( t + dtY <= l i m i t ) Istotą symulacji
p q . i n s e r t ( n e w E v e n t ( t + dtY, n u l i , a ) ) ; . . , . .
j Jest przedstawiona
na stronie 876 me-
Prognozow aniezderzeń z innymi cząsteczkami toda Sim ulate().
Algorytm należy
zainicjować przez wywołanie metody predi ctColl i sions () dla każdej cząsteczki,
aby zapełnić kolejkę priorytetową możliwymi zderzeniami par cząsteczka - cząstecz
ka i cząsteczka - ściana. Następnie algorytm wchodzi w główną pętlę symulacji ste
rowanej zdarzeniami. Działa ona tak:
■ Usuwa najbliższe zdarzenie (o minimalnym priorytecie t).
■ Jeśli zdarzenie jest unieważnione, pomija je.
a Przesuwa wszystkie cząsteczki do czasu t według toru wyznaczanego przez linię
prostą.
■ Aktualizuje prędkości cząsteczek uczestniczących w zderzeniach.
° Wywołuje metodę p re d ic tC o llisio n s(), aby przewidzieć przyszłe zderzenia
obejmujące cząsteczki, które uczestniczyły w zderzeniach, i wstawia do kolejki
priorytetowej zdarzenie odpowiadające każdemu prognozowanemu zderzeniu.
Symulacja sterowana zdarzeniami 875
p u b lic C o llis io n S y st e m (P a r t ic le [ ] p a r t ic le s )
{ t h is .p a r t ic le s = p a rtic le s; }
p r i v a t e v o i d p r e d i c t C o l l i s i o n s ( P a r t i c l e a, d o u b le l i m i t )
( / * Zobacz o p i s w t e k ś c i e . * / }
p u b l i c v o i d r e d r a w ( d o u b le l i m i t , d o u b le Hz)
{ // Ponowne w y ś w i e t l a n i e w s z y s t k i c h c z ą s t e c z e k .
Std D ra w .cle a rQ ;
f o r ( i n t i = 0 ; i < p a r t i cl e s . 1 e n g t h ; i + + ) p a r t i c l e s [ i ] . d r a w ( ) ;
StdDraw .show (20);
i f (t < lim it )
p q . i n s e r t ( n e w E v e n t ( t + 1.0 / Hz, n u l l , n u l i ) ) ;
)
p u b lic vo id s im u la te (d o u b le l i m i t , d o u b le Hz)
( / * Zobacz n a s t ę p n ą s t r o n ę . * / }
p u b lic s t a t i c vo id m a in ( S t r in g [ ] args)
{
StdDraw .show (O );
in t N = In te g e r.p a r se ln t (a r g s [0 ]);
P a r t i c l e [ ] p a r t i c l e s = new P a r t i c l e [ N ] ;
f o r ( i n t i = 0; i < N; i + + )
p a r t i c l e s [ i ] = new P a r t i cl e ( ) ;
C o l l i s i o n S y s t e m syste m = new C o l i i s i o n S y s t e m ( p a r t i c l e s ) ;
syste m .sim u la te (10 0 00 , 0 .5 );
}
odnośnik, które mogą znajdować się w każdym węźle. Ustalamy parametr M (zgodnie
z konwencją jest to liczba parzysta) i tworzymy drzewa wielokierunkowe. W drzewach
każdy węzeł ma najwyżej M - 1 par klucz-odnośnik (zakładamy, że M jest na tyle małe,
iż węzeł o M dzieciach zmieści się na stronie) i przynajmniej M l2 takich par (co two
rzy rozgałęzienia zapewniające, że ścieżki wyszukiwania są krótkie). Wyjątkiem jest
korzeń — może mieć mniej niż M l2 par klucz-odnośnik, przy czym ich liczba musi
wynosić przynajmniej 2. Nazwę tej struktury (b-tree) wymyślili Bayer i McCreight,
którzy w 1970 roku jako pierwsi naukowcy wpadli na pomysł wykorzystania wielokie
runkowych drzew zbalansowanych do wyszukiwania zewnętrznego. Niektórzy stosują
określenie b-drzewa tylko do opisu struktury danych tworzonej przez algorytm zapro
ponowany przez Bayera i McCreighta. Tu używamy tego określenia jak ogólnej nazwy
struktur danych opartych na wielokierunkowych zbalansowanych drzewach wyszuki
wań i stałym rozmiarze strony. Wartość Ai podajemy za pomocą terminologii „drzewo
zbalansowane rzędu M ”. W drzewach zbalansowanych rzędu 4 każdy węzeł ma najwyżej
3 i co najmniej 2 pary klucz-odnośnik. W drzewach zbalansowanych rzędu 6 każdy węzeł
ma najwyżej 5 i co najmniej 3 pary odnośników (wyjątkiem jest korzeń, który może mieć
2 pary klucz-odnośnik) itd. Przyczyna wyjątkowego traktowania korzenia dla większych
M stanie się jasna przy szczegółowym omawianiu algorytmu tworzenia drzewa.
Konwencje Aby zilustrować podstawowe mechanizmy, rozważmy implementację
uporządkowanego typu SET (z kluczami i bez wartości). Pouczającym ćwiczeniem
jest rozwinięcie tego typu do uporządkowanego typu ST w celu powiązania kluczy
z wartościami (zobacz ć w i c z e n i e 6. 1 6 ). Celem jest dodanie m etod add () i con-
ta i ns () dla zbioru kluczy, który może być bardzo duży. Stosujemy klucze uporząd
kowane, ponieważ tworzymy uogólnione drzewa wyszukiwań, które oparte są na
kluczach uporządkowanych. Rozwinięcie przedstawionej implementacji o obsługę
innych operacji na danych uporządkowanych także jest pouczającym ćwiczeniem.
Przy stosowaniu wyszukiwania zewnętrznego indeks często przechowywany jest nie
zależnie od danych. Dla drzew zbalansowanych efekt ten można uzyskać za pomocą
dwóch różnych rodzajów węzłów. Są to:
■ węzły wewnętrzne, w których kopie kluczy są powiązane ze stronami;
■ węzły zewnętrzne, obejmujące referencje do samych danych.
Węzeł o 2 dzieciach
* 1K I 1/
1 1 1 Wewnętrzny węzeł
Klucz pełniący funkcję wartownika o 3 dzieciach
Każdy czerw ony klucz jest kopią /
" I D 1H | | j. m inim alnego klucza poddrzew a - KIQijJ L
Zewnętrzny węzeł Zewnętrzny węzeł ~I I i Zewnętrzny węzeł
o 3 dzieciach o 4 dzieciach
\ /
Każdy klucz w węźle wewnętrznym powiązany jest z innym węzłem, będącym ko
rzeniem drzewa, które zawiera wszystkie klucze większe lub równe względem da
nego klucza i mniejsze niż następny największy klucz, jeśli taki istnieje. Wygodne
jest stosowanie specjalnego klucza, tak zwanego wartownika, o wartości mniejszej
niż wszystkie pozostałe klucze, i umieszczenie tego klucza w węźle korzenia powią
zanym z drzewem obejmującym wszystkie klucze. Tu tablica symboli nie zawiera
powtarzających się kluczy, ale używamy kopii kluczy (w węzłach wewnętrznych) na
potrzeby wyszukiwania. W przykładach używamy kluczy jednoliterowych i znaku
*, który reprezentuje wartownika, mającego wartość mniejszą niż wszystkie pozo
stałe klucze. Te konwencje pozwalają nieco uprościć kod, dlatego stanowią wygod
ną (i powszechnie stosowaną) alternatywę do mieszania danych z odnośnikam i
w węzłach wewnętrznych (które to rozwiązanie stosowaliśmy w innych drzewach
wyszukiwań).
W yszukiw anie i w staw ianie Wyszukiwanie w drzewach zbalansowanych oparte
jest na rekurencyjnym przeszukiwaniu jedynego poddrzewa, które może obejmować
klucz wyszukiwania. Każde wyszukiwanie kończy się w węźle zewnętrznym, który
zawiera klucz wtedy i tylko wtedy, jeśli dany klucz znajduje się w zbiorze. Ponadto
można zakończyć proces trafieniem po napotkaniu kopii klucza wyszukiwania
w węźle wewnętrznym, jednak tu zawsze kontynuujemy wyszukiwanie do m om entu
dojścia do węzła zewnętrznego, ponieważ łatwiej jest wtedy rozwinąć kod do imple
mentacji opartej na uporządkowanej tablicy symboli (ponadto dla dużego M sytuacja
natrafienia na klucz wyszukiwania w węźle wewnętrznym zdarza się rzadko). Aby
przedstawić precyzyjne informacje, rozważmy wyszukiwanie w drzewie zbalansowa-
nym rzędu 6 . Składa się ono z węzłów o 3 parach klucz-odnośnik, węzłów o 4 parach
klucz-odnośnik, węzłów o 5 parach klucz-odnośnik i czasem węzła o dwóch takich
parach w korzeniu. Przy wyszukiwaniu należy zacząć od korzenia i poruszać się m ię
dzy węzłami, znajdując dla klucza wyszukiwania odpowiedni przedział w danym
węźle i korzystając z odpowiedniego odnośnika do przejścia do następnego węzła.
Ostatecznie proces prowadzi do strony zawierającej klucze, która znajduje się w dol
nej części drzewa. Wyszukiwanie kończymy trafieniem, jeśli klucz wyszukiwania
znajduje się na danej stronie. Jeżeli klucza tam nie ma, wyszukiwanie jest nieudane.
Tu, tak jak w drzewach 2-3, można zastosować kod rekurencyjny do wstawienia n o
wego klucza w dolnej części drzewa. Jeśli nie ma miejsca na klucz, dolny węzeł zostaje
tymczasowo przepełniony (obejmuje sześć par klucz-odnośnik), po czym należy go
podzielić, przechodząc w górę drzewa po wywołaniu rekurencyjnym. Jeżeli węzeł
obejmuje sześć par klucz-odnośnik, trzeba podzielić go na węzeł o dwóch parach
połączony z dwoma węzłami o trzech parach. W innych miejscach drzewa należy
zastąpić dowolny węzeł o k parach połączony z węzłem o sześciu parach przez węzeł
0 (k+ 1 ) parach połączony z dwoma węzłami o trzech parach. Zastąpienie trójki przez
M l2 i szóstki przez Ai powoduje przekształcenie omówienia w opis wyszukiwania
1 wstawiania danych w drzewach zbalansowanych rzędu M oraz prowadzi do nastę
pującej definicji.
Drzewa zbalansowane 881
Wyszukiwanie E
Podążanie za tym
odnośnikiem, poniew aż E -
występuje m iędzy * a l<
S
Szukanie E w tym x
węźle zewnętrznym
Wstawianie A
*1 I 1/1
Reprezentacja Jak opisano, mamy dużą swobodę przy wyborze konkretnych repre
zentacji węzłów w drzewach zbalansowanych. Wybory te są ukryte za interfejsem
API Page, który łączy klucze z odnośnikami do obiektów Page i udostępnia opera
cje potrzebne do sprawdzenia przepełnienia stron, ich podziału i rozróżniania stron
wewnętrznych od zewnętrznych. Obiekt Page m ożna traktować jak przechowywaną
zewnętrznie (w pliku na komputerze lub w sieci W W W ) tablicę symboli. Pojęcia
open i close w interfejsie API dotyczą procesu wprowadzania zewnętrznej strony do
wewnętrznej pamięci i zapisywania zawartości strony w pierwotnej lokalizacji (jeśli
to konieczne). M etoda add () dla stron wewnętrznych to operacja na tablicy symboli
łącząca daną stronę z m inimalnym kluczem drzewa, którego korzeniem jest dana
strona. Metody add() i contains() dla stron zewnętrznych przypominają ich odpo
wiedniki z klasy SET. Siłą napędową każdej implementacji jest m etoda spl i t (), która
dzieli pełną stronę przez przeniesienie M l2 par klucz-wartość o pozycji większej niż
M l 2 do nowego obiektu Page i zwraca referencję do nowej strony. W ć w i c z e n i u
6.15 opisano implementację klasy Page opartą na klasie Bi narySearchST. W tej wersji
drzewa zbalansowane przechowywane są w pamięci, tak jak w innych implementa
cjach wyszukiwania. W niektórych systemach rozwiązanie to sprawdza się też przy
wyszukiwaniu zewnętrznym, ponieważ system pamięci wirtualnej może obsługiwać
referencje dyskowe. Bardziej typowe implementacje spotykane w praktyce obejmują
specyficzny dla sprzętu kod do zapisu i odczytu stron. W ć w i c z e n i u 6.19 zachęcamy
do zastanowienia się nad implementacją klasy Page za pom ocą stron WWW. W tek
ście pomijamy takie szczegóły, co pozwala podkreślić przydatność drzew zbalanso
wanych w różnorodnych kontekstach.
p u b l i c c l a s s Page<Key>
Po tych przygotowaniach napisanie kodu klasy BTreeSET (strona 884) jest zaskakują
co proste. W metodzie eon ta i ns () należy wykorzystać kod rekurencyjny, który jako
argument przyjmuje obiekt Page i obsługuje trzy przypadła:
a Jeśli strona jest zewnętrzna i klucz znajduje się na stronie, należy zwrócić true.
a Jeśli strona jest zewnętrzna, ale klucz nie znajduje się na stronie, należy zwrócić
fal se.
0 W przeciwnym razie należy rekurencyjnie wywołać metodę dla poddrzewa,
które może obejmować dany klucz.
W metodzie put () należy zastosować to samo rekurencyjne podejście, przy czym
jeśli w trakcie wyszukiwania klucz nie zostanie znaleziony, trzeba wstawić go na dole
drzewa, a następnie podzielić wszystkie pełne węzły przy przechodzeniu w górę.
Wydajność Najważniejszą cechą drzew zbalansowanych jest to, że dla rozsądnych
wartości param etru M koszt wyszukiwania jest w praktycznych zastosowaniach stały.
p u b l i c c l a s s B TreeSET<Key e x t e n d s C o m p a r a b l e < K e y »
{
p r i v a t e Page r o o t = new P a g e ( t r u e ) ;
p u b l i c B T re e S E T ( K e y s e n t i n e l )
{ a d d ( s e n t in e l); }
p u b l i c b o o le an c o n t a i n s ( K e y key)
{ return c o n ta in s( ro o t, key); }
p r i v a t e b o o le a n c o n t a i n s ( P a g e h, Key key)
{
i f ( h . i s E x t e r n a l ()) re tu rn h .c o n ta in s( k e y );
re tu rn c o n t a in s ( h . n e x t ( k e y ) , key);
}
p u b l i c v o i d a d d (K e y key)
{
put ( r o o t , k e y ) ;
i f ( r o o t . i s F u l l ())
{
Page l e f t h a l f = r o o t ;
Page r i g h t h a l f = r o o t . s p l i t ( ) ;
r o o t = new P a g e ( f a l s e ) ;
ro o t.a d d (le fth a lf);
ro o t.a d d (rig h th a lf);
}
}
p u b l i c v o i d a d d (P ag e h, Key key)
{
if ( h . is E x t e r n a l ()) { h.add(key); return; }
Page n e x t = h . n e x t ( k e y ) ;
add(next, key);
i f ( n e x t . is F u ll ())
h .a d d (n e x t.sp lit());
n e x t.c lo se ();
}
}
111
Drzewa zbalansowane 885
liczba kluczy w węźle wynosi około M ln 2, dlatego mniej więcej 44% pamięci pozo
staje niewykorzystane. Podobnie jak dla wielu innych algorytmów wyszukiwania, tak
i tu losowy model dobrze prognozuje rozkład kluczy występujący w praktyce.
z t w i e r d z e n i a b s ą n i e z w y k l e i s t o t n e i warte przemyślenia.
w n io s k i p ł y n ą c e
Wyznaczanie najdłuższego
powtarzającego się podłańcucha
przez sortowanie przyrostków
Tablice przyrostkowe 889
i t was thu
lub dla popularnej strony WWW. of wi sdom
i t was • i
W idealnych warunkach indeks
i t was ;u
łączy wszystkie możliwe podłań- epoch of
it was the best of times it was the best of times it was the
t was the best of times it was the it was the
was the best of t imes it was the of times it was the
was the best of ti mes it was the the
as the best of tim es it was the the best of times it was the
s the best of time it was the times it was the
the best of times it was the was the
the best of times it was the was the best of times it was the
he best of times i t was the as the ■select(9)
e best of times it was the as the best of times it was the-*"'
best of times it was the best of times it was the
best of times it w as the . , . e
est of times it wa t he index(9) e best of times it was the
st of times it was the es it was the
t of times it was the est of times it was the
of times it was t he f times it was the
of times it was th he
f times it was the he best of times it was the
times it was the imes it was the
times it was the it was the
imes it was the 20 0 10 it was the best of times it was the
mes it was the mes it was the
es it was the of times it was the
s it was the s it was the
it was the 1cp(20)' s the
it was the s the best of times it was the
t was the st of times it was the
was the t of times it was the
was the t was the
as the t was the best of times it was the
s the rank("th")- the
the the best of times it was the
the times it was the
he was the
e was the best of times it was the
M // f
Przedziały obejmujące " t h"
znalezione przez metodę rank()
w czasie wyszukiwania binarnego
Wyszukiwanie binarne w tablicy przyrostkowej
Tablice przyrostkowe 891
p u b l i c c l a s s SuffixArray
W przykładzie na poprzedniej stronie select (9) to "as the best of times...", in d e x (9)
to 4, a l e p (20) to 10, ponieważ " it was th e b e s t of times..." i " it was t h e " mają
wspólny przedrostek " it was t h e " o długości 10. Wywołanie r a n k ( " t h " ) zwraca war
tość 30. Ponadto zauważmy, że sel e c t( rank ( k e y ) ) to pierwszy możliwy przyrostek na
posortowanej liście przyrostków, którego key jest przedrostkiem. Wszystkie pozostałe
wystąpienia klucza key podane są bezpośrednio za pierwszym (zobacz rysunek na po
przedniej stronie). Za pomocą podanego interfejsu API można natychmiast napisać
kod kliencki pokazany na dwóch następnych stronach. Klasa LRS (strona 8 92 ) znajduje
najdłuższy powtarzający się podłańcuch w tekście podanym w standardowym wejściu.
W tym celu tworzy tablicę przyrostkową, a następnie przegląda posortowane przyrost
ki w celu znalezienia maksymalnej wartości zwróconej przez metodę 1cp (). Klasa KWIC
(strona 893) tworzy tablicę przyrostkową dla tekstu wskazanego za pomocą argumentu
wiersza poleceń, a następnie przyjmuje zapytania ze standardowego wejścia i wyświetla
wszystkie wystąpienia każdego zapytania w tekście (wraz z określoną liczbą znaków
przed każdym wystąpieniem i po nim, aby przedstawić kontekst). Nazwa KWICpochodzi
od angielskiego keyword-in-context (czyli słowo kluczowe w kontekście). Określenie to
istnieje przynajmniej od lat 60. ubiegłego wieku. Prostota i wydajność przedstawionego
kodu klienckiego w typowych zastosowaniach związanych z przetwarzaniem łańcu
chów znaków są zaskakujące. Stanowi to dowód na znaczenie starannego projektowa
nia interfejsu API (i wartość prostego, ale odkrywczego pomysłu).
892 KONTEKST
p u b l i c c l a s s LRS
{
p u b l i c s t a t i c voi d m a i n ( S t r i n g [ ] a r g s )
{
String t e x t = Stdln.readAl1();
int N = te x t. lengthO ;
SufflxArray sa = new S u f f i x A r r a y ( t e x t ) ;
String lrs =
f o r ( i n t i = 1; i < N; i++)
{
i n t length = s a . l c p ( i ) ;
i f (length > l r s . l e n g t h O )
lrs = s a .s e le c t( i).s u b s tr in g (0 , length);
}
StdOut.println(lrs);
}
}
% more tinyTale.txt
i t was t h e b e s t o f t i me s i t was t h e w o r s t o f t i mes
i t was t h e age o f wisdom i t was t h e age o f f o o l i s h n e s s
i t was t h e epoch o f b e l i e f i t was t h e epoch o f i n c r e d u l i t y
i t was t h e s e ason o f l i g h t i t was t h e s e a s on o f d a r k n e s s
i t was t h e s p r i n g o f hope i t was t h e w i n t e r o f d e s p a i r
% j a v a LRS < t i n y T a l e . t x t
s t o f t i me s i t was t h e
Tablice przyrostkowe 893
p u b l i c c l a s s KWIC
1
p u b l i c s t a t i c voi d mai n ( S t r i n g [] a r g s )
{
In i n = new I n ( a r g s [ 0 ] ) ;
i n t context = I n t e g e r . p a r s e l n t ( a r g s [1]);
wh i l e ( S t d l n . h a s N e x t L i n e O )
{
String q = S t d l n . r e a d L i n e O ;
f o r ( i n t i = s a . r a n k ( q ) ; i < N && s a . s e l e c t ( i ) . s t a r t s W i t h ( q ) ; i++)
{
i n t from = Mat h. max(0, s a . i n d e x ( i ) - c o n t e x t ) ;
i n t t o = Ma t h . mi n ( N- l , from + q . l e n g t h ( ) + 2 * c o n t e x t ) ;
StdOut.println(text.substring(from, t o ) ) ;
)
StdOut.println();
}
}
}
% j a v a KWIC t a l e . t x t 15
search
o s t g i 1e s s t o s e a r c h f o r c o n t r a b a n d
her unavailing search f o r your fat he
l e and gone i n search of her husband
t pr ovi nces in s e a r c h o f i mp o v e r i s h e
d i s p e r s i n g in search of o t h e r c a r r i
n t h a t bed and s e a r c h t h e s t r a w hold
b e t t e r thing
t i s a f a r f a r b e t t e r t h i n g t h a t i do t h a n
some s e n s e o f b e tte r things else forgotte
was c a p a b l e o f b etter things mr c a r t o n e n t
894 KONTEKST
Im plem entacja Kod na następnej stronie jest prostą implementacją interfejsu API
klasy Su f fix A r ra y . Jej zmienne egzemplarza to tablica łańcuchów znaków i (użyta
w celu skrócenia kodu) zmienna Nprzechowująca długość tej tablicy (długość łańcu
cha znaków i liczbę przyrostków). Konstruktor tworzy tablicę przyrostkową i sortuje
ją, dlatego wywołanie s e l e c t (i) jedynie zwraca wartość s u ff ix e s [i]. Także imple
mentacja metody i ndex () zajmuje jeden wiersz, jest on jednak skomplikowany i wy
nika ze spostrzeżenia, że długość łańcucha znaków z przyrostkiem jest jednoznacznym
wyznacznikiem jego początku. Przyrostek o długości Nzaczyna się na pozycji 0, przyrostek
o długości N-l — na pozycji 1, przyrostek o długości N-2 — na pozycji 2 itd. Dlatego
w wywołaniu i ndex ( i ) wystarczy zwrócić N - su ffixe s [i] , l e n g t h ( ) . Implementację
metody 1 cp() można opracować natychmiast, ponieważ statyczna metoda 1 cp() ze
strony 8 8 7 i metoda ran k () są w zasadzie takie same, jak w implementacji wyszukiwa
nia binarnego dla tablicy symboli (strona 393). Także tu prostota i elegancja implemen
tacji nie powinny ukrywać tego, że jest to skomplikowany algorytm, umożliwiający
rozwiązanie ważnych problemów (talach jak wyszukiwanie najdłuższego powtarzają
cego się podłańcucha), które bez niego wydają się być niewykonalne.
W ydajność Wydajność sortowania przyrostków wynika z tego, że w Javie wyodręb
nianie podłańcuchów wymaga stałej ilości pamięci. Każdy podłańcuch składa się ze
standardowego narzutu na obiekt, wskaźnika do pierwotnego łańcucha i długości.
Dlatego rozmiar indeksu rośnie liniowo względem rozmiaru łańcucha znaków. Jest
to nieco sprzeczne z intuicją, ponieważ łączna liczba znaków w przyrostkach wynosi
-AP/2 i jest funkcją kwadratową wielkości łańcucha znaków. Ponadto należy uwzględ
nić kwadratowe tempo wzrostu przy rozważaniu kosztów sortowania tablicy przyrost
ków. Bardzo ważne jest, aby pamiętać, że podejście to jest skuteczne dla długich łań
cuchów znaków z uwagi na ich reprezentację stosowaną w Javie. Przestawianie dwóch
łańcuchów znaków wymaga przestawienia samych referencji, a nie całych łańcuchów.
Dlatego koszt porównywania dwóch łańcuchów znaków może być proporcjonalny do
długości łańcuchów, jeśli ich wspólne przedrostki są bardzo długie, jednak większość
porównań w typowych sytuacjach kończy się po kilku znakach. Wtedy czas wykonania
sortowania przyrostków jest liniowo-logarytmiczny. Przykładowo, w wielu zastosowa
niach uzasadnione jest przyjęcie modelu losowych łańcuchów znaków.
p u b lic c l a s s SuffixArray
{
p r i v a t e final S t r i n g [] s u f f ix e s ; // T a b l i c a p r z y r o s t k o w a .
p r i v a t e final i n t N; // D ł u g o ś ć ł a ń c u c h a znaków (i t a b l i c y ) .
p u b l i c S u f f i x A r r a y ( S t r i n g s)
{
N = s . lengthO ;
s u ff ix e s = new S t r i ng [N] ;
for ( i n t i = 0; i < N; i + + )
s u ff ix e s [ i ] = s.su b strin g (i) ;
Q uick3w ay.sort(suffïxes) ;
}
p u b lic in t le n g t h O { r e t u r n N; }
p u b lic S t r in g s e le c t ( in t i) {r e t u r n s u ff ix e s [i ] ; }
p u b lic in t in d e x (in t i) {r e t u r n N - s u ff ix e s [ i ] . l e n g t h () ; }
p riva te s t a t ic i n t l c p ( S t r i n g s, S t r i n g t)
// Zobacz s t r o n ę 887.
p u b lic in t lc p ( in t i)
{ r e t u r n 1 c p ( s u f f i x e s [ i ] , s u ffix e s [ i -1] ) ; )
p u b lic in t ra n k (S tr in g key)
{ // W y s z u k iw a n ie b i n a r n e ,
i n t l o = 0, hi = N - 1;
w h ile ( l o <= h i )
{
in t mid = l o + (h i - lo ) / 2;
in t cmp = key. compareTo( s u ffix e s [mi d] ) ;
if (cmp < 0) hi = mid - 1;
e lse i f (cmp > 0 ) l o = mid + 1;
e l s e r e t u r n mid;
}
re turn lo ;
Wydajność tej implementacji interfejsu API klasy SuffixArray wynika z tego, że wartości
typu S tri ng są w Javie niezmienne, dlatego podłańcuchy to referencje o stałej wielkości,
a wyodrębnianie podłańcuchów zajmuje stały czas (zobacz opis w tekście).
896 KONTEKST
częto wraz z powstaniem drzew trie dla indeksów słów kluczowych w kontekście
w latach 60. ubiegłego wieku. Wielu naukowców przez kilka dziesięcioleci pracowało
nad wykorzystaniem opisanych algorytmów w praktyce — od przeniesienia słownika
Oxford English Dictionary do internetu przez pierwsze wyszukiwarki internetowe po
określanie sekwencji ludzkiego genomu. Historia ta pomaga lepiej zrozumieć zna
czenie projektowania i analizowania algorytmów.
898 KONTEKST
2.0 2.0
3.0 1.0
3.0 2.0
1.0 0.0
1.0 0.0
1.0 1.0
2.0 2.0
3.0 1.0
t
Poziom przepływ u
pow iązany z każdą
krawędzią
M aksym alny przepływ st. Dla sieci przepływowej st znajdź przepływ st, taki że
żaden inny przepływ z s do t nie ma większej wartości.
Taki przepływ nazywamy maksymalnym, a problem wyszukiwania go w sieci — prob
lemem przepływu maksymalnego. W niektórych zastosowaniach wystarczy ustalić
samą wartość przepływu maksymalnego, jednak zwykle chcemy poznać przepływy
(wartości przepływów krawędzi) związane z tą wartością.
Interfejsy A P I Interfejsy API FlowEdge i FlowNetwork przedstawione na stronie 902
są prostym rozwinięciem interfejsów API z r o z d z i a ł u 3 . Na stronie 908 omawiamy
implementację klasy FlowEdge opartą na dodaniu do klasy WeightedEdge ze strony
622 zmiennej egzemplarza obejmującej wartość przepływu. Przepływy mają kieru
nek, jednak podstawą klasy FI owEdge nie jest klasa Wei ghtedDi rectedEdge, ponieważ
korzystamy z ogólniejszej abstrakcji — opisanej poniżej sieci rezydualnej. Przy imple
mentowaniu sieci rezydualnej każda krawędź ma pojawiać się na listach sąsiedztwa
obu wierzchołków. Sieć rezydualna umożliwia dodawanie i odejmowanie przepływów
oraz sprawdzanie, czy krawędź jest pełna (nie można dodać przepływów) lub pusta
(nie można odjąć przepływów). Abstrakcja jest zaimplementowana na podstawie opi
sanych dalej metod residualC apacity() i addResidualFlow(). Implementacja klasy
FlowNetwork jest w zasadzie identyczna z implementacją klasy EdgeWeightedGraph
ze strony 623, dlatego jej
nie przedstawiamy. Aby p r i v a t e bo ol ean 1ocal Eq(Fl owNet wor k G, i n t v)
uprościć format plików, { / / Sprawdzani e równowagi l o k a l n e j w wi e r z c h o ł k u v.
stosujemy konwencję, doubl e EPSILON = 1E-11;
doubl e netflow = 0 . 0 ;
zgodnie z którą źródło to f o r (FlowEdge e : G. a d j ( v ) )
0, a ujście — V -l. Przy i f (v == e . f r o m O ) netflow -= e. f l ow() ;
takich interfejsach API else netflow += e. f l owf) ;
p u b l i c c l a s s FlowNetwork
Każda krawędź przekroju st jest albo krawędzią st, łączącą wierzchołek ze zbioru obej
mującego s z wierzchołldem ze zbioru obejmującego t, albo krawędzią ts, prowadzą
cą w odwrotnym kierunku. Czasem zbiór krawędzi st przekroju nazywamy zbiorem
przekroju. Przepustowość przekroju st w sieci przepływowej to suma przepustowości
krawędzi st danego przekroju. Przepływ przekroju (ang. flow across) dla przekroju st
to różnica między sumą przepływów w krawędziach st przekroju a sumą przepły
wów w jego krawędziach ts. Po usunięciu wszystkich krawędzi st (zbioru przekroju)
z przekroju st sieci nie istnieje żadna ścieżka z s do t, jednak po dodaniu dowolnej
takiej krawędzi ścieżka może ponownie zaistnieć. Przekroje są odpowiednią abstrak
Algorytmy dla sieci przepływowych 905
T w ie r d z e n ie E. W
dowolnym przepływie st
przepływ przekroju dla każdego przekroju st jest
równy wartości danego przepływu.
P rze p ływ e m
D o w ó d . Niech C będzie zbiorem wierzchołków p rzekroju jest
ró żn ica m ię d z y
obejmującym s, a C( — zbiorem wierzchołków za
p rz e p ły w e m
wierającym t. Twierdzenie wynika bezpośrednio w e jścio w ym
z indukcji na rozmiarze C(. Właściwość z twier a p rz e p ły w e m
w yjścio w ym
dzenia jest z definicji prawdziwa, jeśli Cj obejmuje
tylko t, a po przeniesieniu wierzchołka z C do C(
lokalna równowaga dla tego wierzchołka powo
duje zachowanie właściwości. Przez przenoszenie W a rto ścią p rz e p ły w u
jest p rz e p ły w
wierzchołków w ten sposób m ożna utworzyć do
w e jścio w y d o t
wolny przekrój st.
T w ie r d z e n ie F ( tw ie r d z e n ie p r z e p ły w u m a k s y m a ln e g o i p rze k r o ju m in i
m a ln e g o ). Niech / będzie przepływem st. Trzy poniższe warunki są równo
znaczne.
i. Istnieje przekrój st, którego przepustowość jest równa wartości przepływ u/
ii. P rzepływ /jest przepływem maksymalnym.
iii. Nie istnieje ścieżka powiększająca powiązana z/
Jeśli krawędź e z v do wjest pusta ( / jest równe 0), w sieci rezydualnej istnieje jedna od
powiadająca jej krawędź v->w o pojemności c . Jeżeli krawędź jest pusta ( /je s t równe
c ), w sieci rezydualnej istnieje jedna odpowiadająca jej krawędź w->v o p ojem ności/.
Jeżeli krawędź nie jest ani pusta, ani pełna, sieć rezydualna obejmuje krawędzie v->w
i w->v o odpowiednich przepustowościach. Przykład pokazano w dolnej części stro
ny. Początkowo reprezentacja sieci rezydualnej może być niezrozumiała, ponieważ
krawędzie odpowiadające przepływowi prowadzą w odwrotnym kierunku względem
przepływu. Krawędzie do przodu reprezentują pozostałą przepustowość (wartość
przepływu, jaką można dodać przy przechodzeniu daną krawędzią), a krawędzie do
tyłu reprezentują przepływ (wartość przepływu, jaką m ożna odjąć przy przechodze
niu określoną krawędzią). Na stronie 908 pokazano metody klasy FI owEdge potrzebne
do zaimplementowania abstrakcyjnej sieci rezydualnej. Te implementacje sprawiają,
że algorytmy mogą pracować na sieci rezydualnej, choć w rzeczywistości sprawdzają
przepustowości i zmieniają przepływy (przez referencje do krawędzi) w krawędziach
klienta. Metody from() i other () umożliwiają przetwarzanie krawędzi prowadzących
w obu kierunkach — wywołanie e . other (v) zwraca punkt końcowy krawędzi e, który
nie jest v. Metody residualCapTo() i addResidual FlowToO składają się na implemen-
p u b li c c l a s s FlowEdge
{
private final i n t v; // Wierzchołek źródłowy krawędzi
private final i n t w; // Wierzchołek docelowy krawędzi
private final double c a p a c it y ; // Przepustowość,
private double flow; // Przepływ.
p u b li c i n t from() { return v; }
p u b li c i n t t o ( ) { re tu rn w; }
p u b li c double c a p a c i t y () { re tu rn c a p a c it y ; }
p u b li c double flow() ( re tu rn flow; }
p u b li c i n t o t h e r ( i n t vertex)
// Taka sama, jak w k l a s i e Edge.
p u b li c double r e s i d u a l C a p a c i t y T o ( i n t vertex)
{
if (vertex == v) re tu rn flow;
e lse i f (vertex == w) re tu rn cap - flow;
e l s e throw new R u ntim eE xcep tio n("N ie sp ójn a krawędź");
}
p u b li c S t r i n g t o S t r i n g O
( re tu rn S t r in g . f o r m a t ( "% d - > % d % . 2 f % . 2 f " , v, w, c a p a c it y , flow);
m ar ke d[ s] = t r u e ; // O zna cz anie ź r ó d ł a
q.enqueue(s); // i u m ie s z c z a n ie go w k o l e j c e ,
w h i l e ( ! q . i s E m p t y ( ))
{
int v = q.dequeued;
f o r (FlowEdge e : G . a d j ( v ) )
1
in t w = e .o th e r(v );
if ( e . r e s i d u a l C a p a c i t y T o ( w ) > 0 && !marked[w])
{ // D la każdej krawędzi do nie o zn a czo ne go w i e r z c h o ł k a ( s i e c i rezydualnej):
edgeTo[w] = e; // z a p is y w a n ie o s t a t n i e j krawędzi na ś c i e ż c e ;
markedjw] = t r u e ; // o z n a c z a n ie w, ponieważ ś c i e ż k a j e s t znana,
q .e n q u e u e ( w ) ; // i dodawanie w do k o l e j k i .
1
1
1
r e t u r n m ar ked[ t ] ;
p u b li c c l a s s F o rd F u lkerson
{
p r iv a t e boolean[] marked; // Czy rezydualn y g r a f obejmuje ś cie ż k ę s - > v ?
p r iv a t e FlowEdge[] edgeTo; // O sta tn ia krawędź n a jk ró tsz e j ś c i e ż k i s->v.
p r iv a t e double value; // Bieżąca wartość przepływu maksymalnego.
p u b li c FordFulkerson(FlowNetwork G, i n t s, i n t t)
{ // Znajdowanie przepływu maksymalnego z s do t w s i e c i przepływowej G.
w hile (hasAugmentingPath(G, s, t ) )
{ // Dopóki i s t n i e j e ś c ie ż k a powiększająca, na leży j ą zastosować.
// Zw iększanie przepływu.
f o r ( i n t v = t; v != s; v = e d g e T o [ v ] , o t h e r ( v ) )
edgeTo[v] .addResidual FlowTo(v, b o t t l e ) ;
value += b o t t le ;
}
}
p u b li c double v a lu e ( ) { re t u rn value; }
p u b li c boolean i n C u t ( i n t v) { re t u rn marked[v]; }
p u b li c s t a t i c vo id m a i n ( S t r i n g [ ] a rg s)
(
FlowNetwork G = new FlowNetwork(new I n ( a r g s [ 0 ] ) ) ;
i n t s = 0, t = G.V() - 1;
FordFul kerson maxflow = new FordFul kerson(G, s, t ) ;
pozostają zarówno sztuką, jak i nauką. Sztuka polega na wyborze strategii, która jest
najwydajniejsza w danej praktycznej sytuacji. Nauka związana jest ze zrozumieniem
podstawowej natury problemu. Czy istnieją nieodkryte jeszcze struktury danych
i algorytmy, które pozwalają rozwiązać problem przepływu maksymalnego w czasie
liniowym? A może uda się udowodnić, że rozwiązanie o tej wydajności nie istnieje?
Forda-Fulkersona oparty
VE2
na najkrótszej ścieżce powiększającej
Forda-Fulkersona oparty
E2log C
na maksymalnej ścieżce powiększającej
Przy redukcji trzeba zwrócić uwagę na koszty. Można na przykład znaleźć medianę
dla zbioru liczb w czasie liniowym, jednak po redukcji do sortowania koszt będzie
liniowo-logarytmiczny. Nawet wtedy dodatkowy koszt może być akceptowalny, po
nieważ korzystamy z istniejącej implementacji sortowania. Sortowanie jest wartoś
ciowe z trzech powodów:
■ Jest przydatne samo w sobie.
■ Istnieją wydajne algorytmy do wykonywania tego zadania.
■ Do sortowania m ożna zredukować wiele problemów.
Problem o takich cechach nazywamy modelem rozwiązywania problemów. Dobrze
zaprojektowane modele rozwiązywania problemów, podobnie jak dobrze zbudowa
ne biblioteki oprogramowania, znacznie rozszerzają zakres problemów, które m oż
na wydajnie rozwiązać. Jedną z pułapek przy koncentrowaniu się na modelach roz
wiązywania problemów jest tak zwany młotek Maslowa — jeśli masz tylko młotek,
wszystko wydaje się być gwoździem. Pomysł ten jest powszechnie przypisywany A.
Maslowowi i pochodzi z lat 60. ubiegłego wieku. Przez skoncentrowanie się na kilku
modelach rozwiązywania problemów możemy stosować je jak młotek Maslowa do
rozwiązywania każdego napotkanego zadania. Uniemożliwia to odkrycie lepszych
algorytmów do rozwiązania danego problemu, a nawet nowych modeli rozwiązy
wania problemów. Choć omawiane modele są ważne, skuteczne i użyteczne w wielu
sytuacjach, warto też rozważać inne możliwości.
Redukcje do problem u w yznaczania najkrótszych ścieżek W p o d r o z d z i a l e 4.4
ponownie zetknęliśmy się z redukcją — tym razem w kontekście algorytmów wyzna
czania najkrótszych ścieżek. Rozważyliśmy między innymi opisane poniżej problemy.
W yznaczanie najkrótszych ścieżek z jedn ego źródła w grafach nieskierowanych.
Dla ważonego grafu nieskierowanego o nieujemnych wagach i wierzchołku źród
łowym s zapewnij obsługę zapytań w postaci: Czy istnieje ścieżka z s do danego
wierzchołka docelowego v? Jeśli tak, należy znaleźć najkrótszą ścieżkę tego rodzaju
(o minimalnej wadze).
Dwa ostatnie problemy wydają się nie być bezpośrednio związane z problemem wy
znaczania najkrótszych ścieżek, pokazaliśmy jednak, że m ożna je skutecznie rozwią
zać za pom ocą najkrótszych ścieżek. Przykłady te, choć ważne, są tylko ilustracją
zagadnienia. Wiele ważnych problemów (zbyt wiele, aby je tutaj omawiać) m ożna
zredukować do problemu wyznaczania najkrótszych ścieżek. Jest to skuteczny i ważny
model rozwiązywania problemów.
Redukcja 917
W Y Z N A C Z A N IE N A JK R Ó T SZY C H Ś C IE Ż E K I PR Z E PŁ Y W U M A K SY M A LN EG O
to ważne modele rozwiązywania problemów, ponieważ mają te same
cechy, które podano dla sortowania:
■ Są przydatne same w sobie.
■ Istnieją dla nich wydajne algorytmy, rozwiązujące dany problem.
B Można zredukować do nich wiele innych problemów.
To krótkie omówienie jest tylko wprowadzeniem do zagadnienia.
Jeśli uczestniczysz w kursie z badań operacyjnych, poznasz wiele in
nych problemów, które można zredukować do wymienionych i in
nych modeli rozwiązywania problemów.
Program ow alne liniowe Jedną z podstaw badań operacyjnych jest
programowanie liniowe. Technika ta związana jest z redukowaniem
danego problemu do opisanego poniżej ujęcia matematycznego.
Programowanie liniowe. Dla zbioru Ai nierówności liniowych i rów
ności liniowych obejmujących N zmiennych oraz liniowej funkcji celu
z N zmiennych znajdź wartości zmiennych, dla których wartość funk
cji celu jest maksymalna, lub określ, że rozwiązanie nie istnieje.
M aksymalizowanie
Programowanie liniowe jest niezwykle waż
w a rto ści/ + h
z uwzględnieniem nym modelem rozwiązywania problemów,
Ścieżka
ograniczeń: ponieważ: z krawędziami
0 <a< 2 a tyłu
Bardzo wiele ważnych problemów można
0 <b<3
0< c< 3
zredukować do programowania liniowego.
0< d< 1 ’ Istnieją wydajne algorytmy do rozwiązywa
0< e< 1 nia problemów z obszaru programowania
0 </< 1 liniowego.
0 - S — 2 Punkt „przydatny sam w sobie”, który wy
0< h< 3
mienialiśmy przy innych modelach rozwią Ś c ie ż k i p o w ię k sza ją c e p rzy
a = c+d
b = e+f zywania problemów, nie jest tu potrzebny, ko ja rze n iu w g ra fie d w u d zie ln ym
c+ e= g ponieważ tak wiele praktycznych problemów
d+f= h można zredukować do programowania liniowego.
Przykład z dziedziny
programowania liniowego
920 KONTEKST
Rozw iązanie
Przepływ maksymalny
Po przekształceniu na
program ow anie liniowe z dziedziny
z 0 do 5
2.0 program owania 0->2 3 .0 2 .0
3 .0 Maksymalizacja wartości
liniow ego 0- > l 2.0 2.0
3 .0 x 3S+x4Sz uwzględnieniem
1. 0 ograniczeń 1-> 4 1 .0 1 .0
1. 0 0 < x o, < 2 *01 = 2 1-> 3 3 .0 1 .0
1.0 0 < x OJ< 3 2-> 3 1 .0 1 .0
(N
X°
II
0 < x 35< 2
II
0<x„s <3
rs
II
*01 = * ,3 + *,4
*02 =*23 + *24
*t3 = *23 + *35
*.4 = *24+*45
Także to stwierdzenie dotyczy świata naturalnego i jest poparte tym, że pracę wszyst
kich znanych urządzeń obliczeniowych m ożna zasymulować za pom ocą maszyny
Turinga przy zwiększeniu kosztów o nie więcej niż czynnik wielomianowy. W ostat
nich latach obliczenia kwantowe sprawiły, że niektórzy naukowcy zaczęli wątpić
w prawdziwość rozszerzonej hipotezy Churcha-Turinga. Większość badaczy zgadza
się, że z praktycznego punktu widzenia twierdzenie to będzie jeszcze przez pewien
czas „bezpieczne”, jednak wielu naukowców ciężko pracuje nad sfalsyfikowaniem
twierdzenia.
Czas w ykonania rosnący w ykładniczo Celem teorii nierozwiązywalności jest od
dzielenie problemów, które m ożna rozwiązać w czasie rosnącym wielomianowo, od
problemów, które w najgorszym przypadku wymagają — prawdopodobnie — czasu
rosnącego wykładniczo. Algorytmy działające w czasie wykładniczym warto trakto
wać tak, jakby dla danych wejściowych o rozmiarze N działały w czasie proporcjonal
nym do 2N (co najmniej). Istota wnioskowania nie zmienia się po zastąpieniu liczby
2 dowolną wartością a > 1. Ogólnie przyjmujemy, że algorytm działający w czasie
wykładniczym nie gwarantuje rozwiązania problemu o rozmiarze na przykład 100
w sensownym czasie, ponieważ niezależnie od szybkości komputera nikt nie może
czekać na wykonanie przez algorytm 2 100 kroków. Postęp technologiczny nie m a zna
czenia w obliczu wykładniczego czasu działania. Superkomputer może być trylion
razy szybszy od liczydła, jednak żadne
z tych urządzeń nie pozwala rozwiązać p u b l i c c l a s s Lo nge st Pa th
Kluczowe jest to, że wedle obecnej wiedzy problemy te znajdują się niemal na prze
ciwnych końcach skali trudności. Wyszukiwanie wszerz pozwala rozwiązać pierwszy
problem w czasie liniowym, jednak wszystkie znane algorytmy rozwiązujące drugi
problem działają dla najgorszego przypadku w czasie wykładniczym. Kod w dolnej
części poprzedniej strony to wersja przeszukiwania w głąb wykonująca to zadanie.
Rozwiązanie to przypomina zwykłe przeszukiwanie w głąb, jednak sprawdza wszyst
kie ścieżki proste z s do t w digrafie, aby znaleźć najdłuższą z nich.
Problemy przeszukiw ania Duża rozbieżność między problemami możliwymi do
rozwiązania za pom ocą „wydajnych” algorytmów w rodzaju tych przedstawionych
w książce a problemami, które wymagają znalezienia rozwiązania wśród potencjalnie
wielkiej liczby możliwości, powoduje, że za pom ocą prostego formalnego modelu
m ożna zbadać zależności między problemami różnego typu. Pierwszy krok polega
na określeniu rodzaju analizowanego problemu.
NP to nic więcej jak precyzyjne ujęcie wszystkich problemów, które naukowcy, inży
nierowie i programiści aplikacji chcą rozwiązać za pom ocą programów, które kończą
działanie w akceptowalnym czasie.
Nierozwiązywalność
A lgorytm
Problem Dane wejściowe Opis Przykład Rozwiązanie
w ielom iano w y
Przykładow e problem y NP
Algorytm
Problem Dane wejściowe Opis Przykład Rozwiązanie
wielomianowy?
W yznaczanie Graf G
Znaleźć najkrótszą Przeszukiwanie
najkrótszych Wierzchołki 0-3
ścieżek st
ścieżkę z s do t wszerz
sit
Znaleźć permutację,
w której elementy Sortowanie
Sortowanie Tablica a 2,8 8,5 4,1 1,3 302 1
z a są w kolejności przez scalanie
rosnącej
Spełnialność Przypisanie do
M zmiennych Eliminacja x + y = 1,5 x = 0,5
równości zmiennych wartości
liniowej
N równań Gaussa 2x - y - 0 7=1
spełniających równości
Przypisanie do x - y< 1,5
Spełnialność x = 2,0
M zmiennych zmiennych wartości Algorytm 2x - z < 0
nierówności y= 1,5
liniowej
N nierówności spełniających elipsoidalny x + y > 3,5
z = 4,0
nierówności z > 4,0
Przykładowe problemy P
928 KONTEKST
czy P = NP?
Pytanie to po raz pierwszy postawił K. Gödel w słynnym, napisanym w 1950 roku
liście do J. von Neumanna. Do tej pory matematycy i badacze z dziedziny nauk kom
puterowych nie potrafią sobie z nim poradzić. Inne sposoby ujęcia tego pytania rzu
cają światło na podstawową naturę problemu.
■ Czy istnieją jakiekolwiek trudne do rozwiązania problemy przeszukiwania?
B Czy możliwe byłoby wydajniejsze rozwiązanie niektórych problemów przeszuki
wania, gdyby udało się zbudować niedeterministyczne urządzenie obliczeniowe?
Brak odpowiedzi na te pytania jest niezwykle frustrujący, ponieważ liczne ważne prob
lemy praktyczne należą do zbioru NP, natomiast nie wiadomo, czy należą do zbioru P
(najlepsze znane dla nich algorytmy deterministyczne działają w czasie wykładniczym).
Gdyby udało się udowodnić, że problem nie należy do zbioru P, można by zrezygnować
z poszukiwań wydajnych rozwiązań danego problemu. Ponieważ dowód nie istnieje,
możliwe, że uda się odkryć wydajne algorytmy. Prawie nikt nie wierzy w to, że P = NP.
Włożono wiele wysiłku w udowodnienie przeciwnej tezy, jednak zrealizowanie tego
celu nadal jest otwartym problemem badawczym w dziedzinie nauk komputerowych.
R edukcje w ielom ianow e Na stronie 915 opisano, że aby wykazać, iż problem A m oż
na zredukować do innego problemu B, należy pokazać, że możliwe jest rozwiązanie
dowolnego egzemplarza problemu A w trzech krokach:
■ przekształcając go na egzemplarz problemu B,
■ rozwiązując egzemplarz problemu B,
■ przekształcając rozwiązanie problemu B na rozwiązanie problemu A.
Jeśli m ożna wydajnie wykonać przekształcenia (i rozwiązać B), to m ożna wydajnie
rozwiązać A. W obecnym kontekście słowo wydajny używane jest w najsłabszym m oż
liwym sensie — należy rozwiązać A, rozwiązując najwyżej wielomianową liczbę eg
zemplarzy B i stosując przekształcenia wymagające najwyżej wielomianowego czasu.
W tym przypadku mówimy, że A m ożna zredukować wielomianowo do B. Wcześniej
stosowaliśmy redukcję w celu przedstawienia modeli rozwiązywania problemów.
Modele te pozwalają znacznie zwiększyć zakres problemów, które można rozwiązać
za pom ocą wydajnych algorytmów. Tu korzystamy z redukcji w inny sposób — aby
udowodnić, że problem jest trudny do rozwiązania. Jeśli wiadomo, że problem A jest
trudny do rozwiązania i m ożna go wielomianowo zredukować do B, także B musi
być trudny do rozwiązania. W przeciwnym razie gwarancje wielomianowego czasu
rozwiązania B prowadziłyby do gwarancji wielomianowego czasu wykonania A.
Nierozwiązywalność 929
C4 > 1 - x ,
c4 > 1 - x 2
W n io se k . Jeśli problem spełnialności jest tru d
ny do rozwiązania, to samo dotyczy program o c <(1 - x , ) + (1 —X ) + X
co
KONTEKST
Ścieżka H am iltona. Znajdź w grafie ścieżkę prostą, która przechodzi przez każdy
wierzchołek dokładnie raz, lub stwierdź, że taka ścieżka nie istnieje.
M odel Isinga. Na podstawie modelu Isinga dla trójwymiarowej kraty i progu ener
gii E określ, czy istnieje podgraf o energii swobodnej mniejszej niż E.
R yzyko dla portfela inwestycji o danej stopie zw rotu. Na podstawie portfela in
westycji o danym łącznym koszcie, danym zwrocie, określonym ryzyku przypisa
nym do każdej inwestycji i progu M znajdź taką alokację inwestycji, aby ryzyko
było niższe niż M.
Nierozwiązywalność 933
6.1. Uzupełnij implementację m etody predi ctCol l i s i ons () i klasy P arti cl e w opi
sany w tekście sposób. Zderzenia sprężyste między param i twardych dysków oparte
są na trzech równaniach. Dotyczą one: a) zachowania pędu liniowego, b) zachowa
nia energii kinetycznej, c) tego, że przy zderzeniu norm alna siła działa prostopadle
względem punktu zderzenia (zakładamy brak tarcia lub ruchu obrotowego). Więcej
szczegółów znajduje się w witrynie poświęconej książce.
6.2. Opracuj wersje klas CollisionSystem, P a rtic le i Event obsługujące zderzenia
między wieloma cząsteczkami. Zderzenia te są ważne przy symulowaniu rozbicia
w grze w bilard (to ćwiczenie jest trudne!).
6.3. Opracuj wersje klas Col l i s i onSystem, P arti cl e i Event działające w trzech wy
miarach.
6.6. Ruchy Browna. W 1827 roku botanik Robert Brown zaobserwował za pomocą
mikroskopu ruch zanurzonych w wodzie pyłków kwiatowych. Stwierdził, że ruchy
pyłków są losowe (są to tak zwane ruchy Browna). Badano to zjawisko, jednak prze
konujące wyjaśnienie uzyskano dopiero po przedstawieniu matematycznych wyni
ków przez Einsteina w 1905 roku. Oto wyjaśnienie Einsteina — ruch pyłków jest
powodowany przez miliony małych molekuł zderzających się z większymi cząstecz
kami. Przeprowadź symulację ilustrującą to zjawisko.
6.7. Temperatura. Dodaj do klasy P a rtic ie metodę tem perature(), która zwraca
iloczyn masy cząsteczki i kwadratu jej szybkości podzielonej przez dkB, gdzie d= 2 to
liczba wymiarów, a kB= 1,3806503x10'23 to stała Boltzmanna. Temperatura systemu to
średnia wartość takich iloczynów. Następnie dodaj metodę tem perature() do klasy
CollisionSystem i napisz kod, który okresowo wyświetla wartość temperatury, co
pozwala sprawdzić, czy jest ona stała.
935
6.9. Arbitralny kształt. Cząsteczki poruszają się bardzo szybko (szybciej niż odrzuto
wiec), jednak rozpraszają się powoli, ponieważ zderzają się z innymi cząsteczkami, co
zmienia ich kierunek. Rozwiń omawiany model o nowy kształt — dwa połączone rurką
pojemniki zawierające dwa różne rodzaje cząsteczek. Przeprowadź symulację i zmierz
procent cząsteczek każdego rodzaju w każdym pojemniku jako funkcję czasu.
6.15. Opracuj implementację klasy Page, w której każdy węzeł drzewa zbalansowa-
nego reprezentowany jest jako obiekt BinarySearchST.
6.16. Rozwiń klasę BTreeSET, aby opracować implementację BTreeST, w której klu
cze powiązane są z wartościami i obsługiwany jest kompletny interfejs API dla upo
rządkowanej tablicy symboli, obejmujący m etody min(), max(), floor(), cei 1 in g (),
deleteM in(), deleteMax(), s e le c t(), rank() i dwuargumentowe wersje metod
s iz e () i g e t ().
6.20. Drzewa B*. Zastanów się nad heurystyką podziału braci dla drzew zbalanso-
wanych (jest ona stosowana w drzewach B*). Kiedy trzeba podzielić węzeł, ponieważ
obejmuje M elementów, należy najpierw połączyć węzeł z bratem. Jeśli brat obejmuje
k elementów, a k < M - 1, należy zmienić układ elementów przez umieszczenie w bra
cie i pełnym węźle po około (M+k)l2 węzły. Jeżeli k jest większe, należy utworzyć
nowy węzeł i umieścić w każdym z trzech węzłów po około 2M/3 węzły. Ponadto
dopuszczalne jest zwiększenie korzenia do około 4M/3 elementów, a kiedy to ogra
niczenie zostanie osiągnięte, należy podzielić korzeń i utworzyć nowy o dwóch ele
mentach. Podaj ograniczenia liczby sprawdzeń potrzebnych przy wyszukiwaniu lub
wstawianiu w drzewie B* rzędu M o N elementach. Porównaj te ograniczenia z ogra
niczeniami dla drzew zbalansowanych (zobacz t w i e r d z e n i e b ). Opracuj implemen
tację wstawiania dla drzew B*.
a. abacadaba
b. m i s s i s s i p p i
c. abcdefghij
d. aaaaaaaaaa
suffix =
f o r ( i n t i = s . l e n g t h ( ) - 1 ; i >= 0; i - - )
{
suffix = s . c h a r A t ( i ) + suffix;
s u ffix e s [ i] = suffix;
}
Odpowiedź: tempo wzrostu czasu i pamięci jest kwadratowe.
6.27. W niektórych sytuacjach potrzebne jest sortowanie rotacji cyklicznych teks
tu obejmujących wszystkie znaki. Dla i od 0 do N - 1 i-ta rotacja cykliczna tekstu
o długości N to ostatnich N - i znaków, po których następuje i pierwszych znaków.
Zidentyfikuj problem w poniższym fragmencie kodu wyznaczającym wszystkie ro
tacje cykliczne.
in t N = s . l e n g t h ( ) ;
fo r (in t i = 0 ; i < N; i+ +)
ro ta tio n [i] = s . s u b s t r i n g ( i , N) + s . s u b s t r i n g ( 0 , i ) ;
6.33. Najdłuższy podłańcuch powtarzający się k razy. Napisz klienta klasy SuffixArray,
który na podstawie łańcucha znaków i liczby całkowitej k znajduje najdłuższy pod
łańcuch powtarzający się k lub więcej razy.
6.34. Długie powtarzające się podłańcuchy. Napisz klienta klasy SuffixArray, który
na podstawie łańcucha znaków i liczby całkowitej Lwyszukuje wszystkie powtarzają
ce się podłańcuchy o długości L lub większej.
6.36. Jeśli przepustowości to dodatnie liczby całkowite mniejsze niż M, jaka jest
maksymalna możliwa wartość przepływu dla dowolnej sieci s t o V wierzchołkach i E
krawędziach? Podaj dwie odpowiedzi dotyczące sytuacji z dozwolonymi i niedozwo
lonymi krawędziami równoległymi.
6.37. Podaj algorytm rozwiązujący problem przepływu maksymalnego dla przy
padku, w którym sieć tworzy drzewo po usunięciu ujścia.
6.38. Prawda czy fałsz? Jeśli prawda, podaj krótki dowód, jeżeli fałsz — przedstaw
kontrprzykład.
6.45. Obsadzanie stanowisk. Opracuj klienta klasy FordFul kerson, który rozwiązuje
problem obsadzania stanowisk. Wykorzystaj redukcję z t w i e r d z e n i a j . Użyj tablicy
symboli do przekształcenia nazw symbolicznych na liczby całkowite potrzebne w sie
ci przepływowej.
6.46 Utwórz rodzinę problemów kojarzenia w grafach dwudzielnych, w której
średnia długość ścieżek powiększających używanych przez dowolny oparty na takich
ścieżkach algorytm rozwiązujący powiązany problem wyznaczania przepływu m ak
symalnego jest proporcjonalna do E.
6.47. Połączenia st. Opracuj klienta klasy FordFul kerson, który dla nieskierowane-
go grafu G oraz wierzchołków s i t określa m inim alną liczbę krawędzi w G, których
usunięcie powoduje odłączenie t od s.
6.48. Ścieżki rozłączne. Opracuj klienta klasy FordFul kerson, który dla nieskierowa-
nego grafu G oraz wierzchołków s i t określa maksymalną liczbę rozłącznych ścieżek
z s do t.
942 KONTEKST
6 .51 . Czy może istnieć algorytm, który rozwiązuje problem NP-zupełny średnio
w czasie A/1“^ , jeśli P ^ NP? Wyjaśnij odpowiedź.
6 .56 . Przyjmij, że wiadomo, iż dwa problemy są NP-zupełne. Czy oznacza to, że ist
nieje wielomianowa redukcja między nimi?
P o d sta w y Grafy
1.1. Stos oparty na powiększaniu tablicy 4.1. Wyszukiwanie w głąb
944
KLIENTY
P o d sta w y Ł ańcuchy zn a k ó w
Białe listy Dopasowywanie do wzorca
za pomocą wyrażeń regularnych
Wartościowanie wyrażeń
Kompresja Huffmana
Określanie połączeń
Kompresja L Z W
S o rto w a n ie
K o n tek st
Porównywanie dwóch algorytmów
Symulacja zderzeń cząsteczek
M największych elementów
Zbiory oparte na drzewach zbalansowanych
Scalanie wielościeżkowe
Tablice przyrostkowe (podstawowe)
Indeksowanie plików
G rafy
Typ danych dla grafów symbolicznych
Stopnie oddalenia
Metoda PERT
Arbitraż
945
l i l i Skorowidz
przez scalenie, 18, 201, 282, 284, 289,
300, 305, 310, 313, 353, 354, 355, 736,
ADT, Patrz: dane typ abstrakcyjny przez wstawianie, 18, 262,270, 287,
akumulator, 104 308, 353, 354, 736
wizualny, 106 przez wybieranie, 18, 260, 339, 353, 354
alejka, 542, 550 przez zliczanie, 715, 717, 718
jednokierunkow a, 544 Shella, 270, 305, 353, 354
alfabet, 709, 714, 723, 733, 753, 762, systemowego Javy, 355
algorytm szybkiego, 18,217,300-315,353-356,736
A*, 362 topologicznego, 590, 670, 694
analiza, 17 z podziałem na trzy części, 731, 736
Bellmana-Forda, 18, 683, 684, 687, 694, tablicy symboli, 62
694, 695, Tremaux, 542, 544, 588
Boyera-M oorea, 771, 782, 791, U nion-Find, 558
Dijkstry, 18, 140, 362, 664, 680, 694, wyszukiwania, 18, 19, 373, 409, 437, 459,
Euklidesa, 16 479, 880, 889,
Forda-Fulkersona, 903, 904, 907, 909, 914, binarnego, 20, 201, 390, 392, 395, 397,
haszowania, Patrz: haszowanie, tablica 398,408, 426, 459, 499
z haszowaniem
podłańcuchów, 708, 770, 772, 774, 782,
Jarnika, 640, Patrz też: algorytm Prima 786, 790, 800, 804, 889
KMP, Patrz: algorytm Knutha-Morrisa-Pratta sekwencyjnego, 386, 388, 397, 426,
K nutha-M orrisa-Pratta,, 771, 774, 775,
459, 499
781,782, 791,806, z random izacją, 210, 302
kolejki priorytetowej, Patrz: kolejka zachłanny, 619
priorytetowa amortyzacja kosztów, 210, 244,487
Kosaraju, 598, 602, anomalia, Patrz: graf anomalia
Kruskala, 18, 362, 616, 636, 641,
API, Patrz: interfejs API
Las Vegas, 790
argum ent, 83
Prima, 18, 362, 616, 628, 636, 640, 641,
asercja, 119
666, 694,
atak siłowy, 772, 773, 791, 887
wersja leniwa, 629 autoboxing, 134
wersja zachłanna, 629, 632, 635,
autom at
Rabina-Karpa, 786, 787, 790, 791,
DFA, Patrz: autom at skończony
sortowania, 18, 19, 255, 265, 267, 320, 335,
deterministyczny
348, 354, 360, 714, 888,
NFA, Patrz: autom at skończony
LSD, 718, 736, niedeterm inistyczny
łańcucha znaków, 714, 718, 722,731,736,
skończony, 708, 922
MSD, 722, 725, 728, 729, 736,
deterministyczny, 776, 777
przez kopcowanie, 18, 335,338,353,354,
niedetrministyczny, 806, 809, 811, 816
948 SKOROWIDZ
E rzadld, 532
skierowany, 529, 578, 585, 588, 596,653, 900
egzemplarz, 96
acyldiczny, Patrz: graf acyldiczny
element, 362 ważony
osierocony, 149
spójny, 531, 596, 617, 636
osiowy, 302, 308
symboli, 560
entropia, 308, 312, 313
ścieżka, 531, 547, 553, 585,650, 673, 680,
Euklidesa algorytm, Patrz: algorytm Euldidesa
916,919
długość, 531, 579, 923
F krytyczna, Patrz: ścieżka krytyczna
filtrowanie na podstawie białej listy, 20 ogólna, 531
Floyd R. W., 338, 339 powiększająca, 903, 909
Ford Lester Randolph, 682, 695, Patrz też: skierowana, 579
algorytm Bellmana-Forda, algorytm waga, 650
Forda-Fulkersona ważony, 529, 616, 620, 628, 636, 650
Fredm an M.L., 640 skierowany, 529, 653, 664, 670,
Fulkerson D.R., 903, Patrz też: algorytm 680, 900
Forda-Fulkersona wierzchołek, 530, 560, 578, 628
funkcja, 34, Patrz też: m etoda statyczna sąsiadujący, 531
hashCode(), 473 źródłowy, 540
haszująca, 470, 471,474 z krawędziami ważonymi, Patrz: graf
ważony
G grep, 708
głowa, 578
Google, 514,516
Gosper R.W., 771 haszowanie, 18, 398, 470, 478, 499, 771, 786
gra w Kevina Bacona, 565 m etodą łańcuchową, 476, 480
graf, 18, 168, 362, 516, 527, 530, 531, 898 m odularne, 471, 472
acykliczny, 532, 558, 588, 668, 670 równomierne, 475
ważony, 586, 590, 594, 595, 670, 671, z adresowaniem otwartym, 481, 483
673, 676 z próbkowaniem liniowym, 481, 484,
anomalia, 530 485, 501
cyld, 531,586, 588 hermetyzacja, 108
ogólny, 531 Hoare C.A.R., 217, 307
prosty, 531 Huffmana kompresja, Patrz: dane kompresja
skierowany, 579 H uffmana
ujemny, 681, 682, 689
DAG, Patrz: graf acykliczny ważony I
dwudzielny, 533, 558 identyfikator, 23
euklidesowy, 626, 635, 668
iloczyn skalarny, 514, 515
gęsty, 532, 640
implementacja, dziedziczenie, 113
krawędź, 362, 530, 560, 578, 588, 617,
indeks, 332, 470, 508, Patrz też: tablica symboli
624, 628
odwrotny, 510
incydentna, 531
instrukcja, 22, 26
równoległa, 530
deldaracja, 22, 26
waga, 617, 624, 636
foreach, 135, 136, 138, 150
multigraf, 530
pętla, 22, 26, 27
nieskierowany, 529, 534, 616, 666
przypisania, 22, 26, 28, 81
niespójny, 531
return, 22, 26
podgraf, 531
w arunkowa, 22, 26, 27
prosty, 530
wywołanie, 22, 26
przekrój, 518, 628
interfejs
przeszukiwanie
Comparable, 408
w głąb, 18, 542, 543, 545, 554, 558, 582
dziedziczenie, 112
wszerz, 18, 550, 551, 553, 554
•ft-.
950 SKOROWIDZ