• Nie Znaleziono Wyników

WSPÓLNE PODCIĄGI

7. Najdłuższy wspólny podciąg

7.3. Algorytmy sekwencyjne i równoległości bitowej

7.3.2. Algorytm Hunta–Szymanskiego

Algorytm Hunta i Szymanskiego [119] rozpoczyna swoje działanie od przetwarzania wstęp-nego, w którym tworzona jest tablica L rozmiaruσzawierająca dla każdego symbolu alfabetu

LCS-HS(A, B)

Wejście: A, B – ciągi, dla których wyznaczany jest podciąg LCS Wyjście: znaleziony podciąg LCS

{Przetwarzanie wstępne}

1 for i ← 1 to n do L[ai].append(i) {Obliczenia właściwe}

2 ` ← 0; Q ← pusta struktura rozwiązująca problem poprzednika 3 for j ← 1 to m do

4 for each i z listy L[bj] czytanej od końca do 5 x← Q.successor(i − 1)

6 if x istnieje then Q.remove(x)

7 else ` ← ` + 1

8 Q.insert(hi, ji)

9 M(i, j) ← Q.predecessor(i) {Wyznaczenie wyniku}

10 x← Q.predecessor(n + 1) 11 for k ← ` downto 1 do 12 sk← ax.i; x ← M(x) 13 return s1s2. . . s`

Rys. 7.3. Algorytm Hunta–Szymanskiego rozwiązujący problem LCS Fig. 7.3. Hunt-Szymanski algorithm for the LCS problem

listę jego wystąpień w ciągu A (rys. 7.3).2 Następnie rozpoczynają się obliczenia właściwe, w których główną strukturą danych jest struktura Q rozwiązująca problem poprzednika (ang.

predecessor problem), udostępniająca operacje wstawiania, usuwania i wyszukiwania następ-nika oraz poprzednastęp-nika. W strukturze tej przechowywane są pary wartości, przy czym porządek jest wyznaczany tylko według pierwszej składowej, a druga składowa pary zawiera dane dodat-kowe. Po przetworzeniu j-tego wiersza macierzy, w stukturze Q znajdują się dla każdej rangi występującej we fragmencie macierzy M(1..n, 1.. j) pary (i0, j0) o najmniejszych indeksach ko-lumn i0dopasowań o tych rangach. Na rys. 7.4 przedstawiono ilustrację działania tego algoryt-mu. W szczególności pokazano zawartość struktury Q po wykonaniu każdego przebiegu pętli zewnętrznej oraz zawartość tablicy list L dla każdego symbolu alfabetu.

W algorytmie Hunta i Szymanskiego wykorzystuje się równoważną do (7.1) regułę wyzna-czania macierzy M:

M(i, j) = max

1≤i0<i, 1≤ j0< j, ai0=bj0(M(i0, j0)) + 1. (7.2) Funkcja max z definicji zwraca 0, jeśli nie ma żadnych argumentów. Wartości tej macierzy wyznaczane są tylko dla dopasowań, a wynikiem końcowym jest największa z wartości znaj-dujących się w macierzy. W celu znalezienia podciągu LCS, a nie tylko jego długości, należy

2Opis algorytmu prezentowany w tym miejscu różni się nieco od opisu oryginalnego [119], ale istota jego działania oraz złożoności czasowa i pamięciowa są takie same.

7.3. Algorytmy sekwencyjne i równoległości bitowej 95 h1,6i h3,6i h6,6i h9,6i h1,6i h3,6i h4,7i h9,6i h10,7i h1,6i h3,6i h4,7i h5,8i h10,7i h1,6i h3,6i h4,7i h5,8i h7,9i h12,9i h1,6i h3,6i h4,7i h5,8i h7,9i h12,9i h1,6i h2,11i h4,7i h5,8i h7,9i h8,11i h1,6i h2,11i h3,12i h5,8i h6,12i h8,11i h9,12i

Rys. 7.4. Przykład działania algorytmu Hunta–Szymanskiego wyznaczania podciągu LCS dla ciągów:

ABAADACBAABC, CBCBDAADCDBA. Zaznaczone komórki oznaczają dopasowania. Strona lewa: listy wystąpień symboli w ciągu A. Środek: macierz DP z zaznaczonymi dopasowaniami (tylko dla porównania). Strona prawa: stan struktury Q po przetworzeniu każdego wiersza Fig. 7.4. An example of the Hunt–Szymanski algorithm computing the LCS for the sequences:

ABA-ADACBAABC, CBCBDAADCDBA. The marked cells denote matches. Left: lists of symbol appearances in A. Middle: the DP matrix with marked matches (just for comparison). Right:

state of Q structure after computing each row

przechowywać w komórkach macierzy reprezentujących dopasowania wskaźnik do komórki, która posłużyła do wyznaczenia wartości danej komórki, a następnie przejść tę macierz zgodnie z tymi wskaźnikami odczytując podciąg wyjściowy, który utworzą elementy ciągów z kolumn (bądź wierszy) odwiedzanych dopasowań.

Na złożoność czasową algorytmu składa się czas przetwarzania wstępnego, O(σ+ n) oraz czas obliczeń właściwych, O(m + dτQ), gdzie d to liczba dopasowań w całej macierzy, aτQto najdłuższy z czasów wykonywania operacji wstawiania, usuwania, szukania następnika i po-przednika w strukturze Q. Sumaryczny czas działania algorytmu jest O(σ+ n + m + dτQ).

Przyjmując, często uzasadnione, założenie, że σ= O(n), otrzymuje się złożoność czasową O(n + dτQ), która zależy od wyboru struktury danych Q. Użycie zrównoważonych drzew po-szukiwań binarnych (np. drzew czerwono-czarnych [29, 105, 190]), dla którychτQ= O(log `), daje algorytm o złożoności czasowej

O(n + d log `). (7.3)

Można też zauważyć, że element wstawiany do Q ma zawsze albo taką samą rangę jak element chwilę wcześniej usunięty, albo o 1 większą niż najwyższa z rang znajdujących się w Q. Dzięki temu jako struktury Q można użyć posortowanej tablicy i przeszukiwać ją w sposób binarny, co daje dokładnie taką samą złożoność czasową jak w (7.3). Kolejną możliwością jest użycie drzew van Emde Boasa [209, 210], dla których τQ= O(log log n), a złożoność czasowa algorytmu LCS-HSjest

O(n + d log log n). (7.4)

W obu przypadkach czas dodatkowych operacji na strukturze M w celu uzyskania podciągu wynikowego jest O(d), a więc nie wpływa on na złożoność czasową całego algorytmu.

Pozostaje jeszcze do rozważenia przypadek, w którym σ=ω(n), wtedy znaczący wpływ na złożoność czasową algorytmu zaczyna mieć faza przetwarzania wstępnego. Aby zachować złożoność pamięciową na akceptowalnym w praktyce poziomie, można w miejsce tablicy L za-stosować np. zrównoważone drzewo poszukiwań binarnych indeksowane symbolem alfabetu, w węzłach którego będą znajdowały się listy indeksów dla każdego symbolu alfabetu, wtedy czas przetwarzania wstępnego jest O(n logn). Ponadto, podczas rozpoczynania przetwarzania każdego wiersza należy znaleźć w tym drzewie listę dopasowań dla symbolu opisującego ten wiersz. Sumarycznie wnosi to do złożoności czasowej składnik O(m logn). Wobec tego suma-ryczna złożoność czasowa jest w zależności od użytej struktury danych do reprezentacji Q:

O(n log n + d log `) lub O(n log n + d log log n). (7.5) Złożoność pamięciowa tego algorytmu zależy od wyboru reprezentacji macierzy M. Naj-prostszym sposobem jest użycie listy przeszukiwanej od końca w wierszu12. algorytmu LCS-HS. Zajmie onaΘ(d) słów. Do tego należy dodać jeszcze zajętość pamięci pozostałych struktur danych, czyliΘ(n) słów. Sumarycznie złożoność pamięciowa wynosi zatemΘ(n + d) słów.

Algorytm Hunta–Szymanskiego sprawdza się najlepiej w sytuacjach, w których liczba do-pasowań, d, jest istotnie mniejsza niż nm. Sytuacje takie mają miejsce często. W przypadku pesymistycznym jednak d =Θ(nm), co powoduje, że złożoność czasowa tego algorytmu może być gorsza niż dla algorytmu programowania dynamicznego.

Przetwarzanie tylko komórek reprezentujących dopasowania i wyznaczanie dla nich rang jest popularnym sposobem, na którym opiera się wiele bardzo wydajnych algorytmów wyzna-czania podciągu LCS, np. algorytm Kuo–Crossa [135], który okazał się najszybszy w testach porównawczych przeprowadzonych przez Bergrotha i in. [32]. Idea ta jest stosowana także w al-gorytmach dla problemów pokrewnych problemowi LCS. Często podejście takie jest nazywane metodą Hunta–Szymanskiego.