• Nie Znaleziono Wyników

Implementacja algorytmu zachłannego dla problemu najkrótszego nadsłowa

N/A
N/A
Protected

Academic year: 2021

Share "Implementacja algorytmu zachłannego dla problemu najkrótszego nadsłowa"

Copied!
3
0
0

Pełen tekst

(1)

Implementacja algorytmu zachłannego dla problemu najkrótszego nadsłowa

Tomasz Kociumaka 5 marca 2010

1 Podstawowe własności i oznaczenia

Przypomnijmy działanie algorytmu greedy. Przez ov(u, v) oznaczmy najdłuższy prefiks v, który jest też sufiksem u. Nazywać go będziemy zakładką (słów u i v). Mamy dany zbiór słów {w

1

, . . . , w

n

} taki, że żadne słowo nie jest podsłowem innego. Znajdujemy w nim 2 różne słowa u i v takie, że |ov(u, v)|

jest największa, i zastępujemy je słowem u ⊕ v, które powstało z u i v przez nałożenie ov(u, v) (tzn. jest konkatenacją u i słowa powstałego z v przez usunięcie prefiksu ov(u, v)). Tak postępujemy aż otrzymamy zbiór jednoelementowy.

Zwróćmy uwagę na pewną kluczową własność tego algorytmu.

Obserwacja. Niech W będzie zbiorem słów oraz Ov(W ) = max{|ov(x, y)| : x 6= y ∈ W }. Jeśli

|ov(u, v)| = Ov(W ), to dla dowolnego x ∈ W \ {u, v} zachodzi ov(u ⊕ v, x) = ov(v, x) oraz ov(x, u ⊕ v) = ov(x, u).

Dowód. Gdyby u ⊕ v i x miały zakładkę dłuższą niż |v|, to łatwo zauważyć, że u i x miałyby zakładkę dłuższą niż |ov(u, v)|, co nie jest możliwe. Zatem zakładką słów u ⊕ v i x musi być sufiks v, a więc zakładka v i x. Analogicznie ov(x, u ⊕ v) = ov(x, u).

Początkowy zbiór słów będziemy dalej oznaczać przez W = {w

1

, w

2

, . . . , w

n

}, a jego elementy bę- dziemy nazywali słowami bazowymi. Niech x, y będą słowami, które w pewnym momencie znajdują się w zbiorze słów rozważanych przez algorytm greedy. Powstały one przez operację ⊕ z pewnych słów bazowych. Niech więc x = x

1

⊕ x

2

⊕ . . . ⊕ x

k

, y = y

1

⊕ y

2

⊕ . . . ⊕ y

l

, gdzie x

i

, y

i

∈ W (w zapisie pomijamy nawiasowanie). Pozwala to traktować takie słowa x, y jak ciągi słów bazowych. Na mocy Obserwacji wiemy, że ov(x, y) = ov(x

k

, y

1

).

Szczególnie interesujące będą więc F i L - zbiory tych słów bazowych, które w danym momencie są na odpowiednio początku i końcu jakiegoś słowa. Dla każdego słowa bazowego w niech word(w) oznacza słowo z przetwarzanego przez algorytm zbioru, w którego skład wchodzi w.

2 Szkic implementacji

Sformułujmy teraz algorytm greedy w nowym języku. Mamy zbiór słów W , który podzielony jest na (rozłączne) ciągi, początkowo jednoelementowe. F to zbiór początków tych ciągów, a L - końców. Funkcja word przypisuje słowu bazowemu ciąg, w którym się znajduje. Przez f irst oznaczmy funkcję zwracającą pierwszy wyraz ciągu, a przez last ostatni. (Zauważmy tu, że last ◦ word jest bijekcją z F na L).

Algorytm znajduje takie f ∈ F i ` ∈ L, że word(f ) 6= word(`), a przy tym ov(`, f ) jest maksymalne.

Następnie łączy ciągi word(`) i word(f ). Powtarza te czynności dopóki wszystkie słowa bazowe nie są w jednym ciągu.

Powiemy jeszcze, że sufiks słowa ` ∈ W jest zakładką, jeśli ` ∈ L oraz dla pewnego f ∈ F (takiego, że word(`) 6= word(f )) jest zakładką ` i f . W każdym kroku szukamy więc najdłuższej zakładki. Łatwo widać, że jeśli jakiś sufiks w danym momencie nie jest zakładką, to nigdy już nią nie będzie.

Tym samym możemy przejść wszystkie sufiksy słów z W w kolejności malejącej długości. Jeśli natra- fimy na zakładkę, to ją ’realizujemy’, czyli łączymy odpowiednie ciągi, a jeśli nie, po prostu przechodzimy do następnego sufiksu.

1

(2)

3 Struktury danych i preprocessing

Zbudujemy tablicę sufiksową SA słowa w

1

$w

2

$ . . . $w

n

$ wraz z tablicą RAN K i LCP . De facto tablica ta odpowiada posortowanej tablicy wszystkich sufiksów słów bazowych. W szczególności znajdują się w niej całe słowa bazowe (posortowane). Dzięki temu możemy łatwo zbudować drzewo trie dla tych słów.

(Zawsze schodzimy skrajnie lewą krawędzią lub tworzymy nową). Co więcej dla każdej pozycji j w słowie w

i

będziemy trzymać wskaźnik do węzła w drzewie odpowiadającego j-temu prefiksowi słowa w

i

(aby móc uzyskać dostęp do niej w czasie stałym).

Ponadto przechodząc jednokrotnie (od tyłu) tablicę SA będziemy na podstawie LCP mogli dla każdego sufiksu s znaleźć długość najdłuższego wspólnego prefiksu z (leksykograficznie) najmniejszym nie mniejszym niż s słowem bazowym. (Po prostu na pozycjach odpowiadających słowom bazowym wpisujemy ich długość, a na pozostałych – minimum z LCP i wyniku dla kolejnego w tablicy SA sufiksu). Dzięki tej wartości dla każdego sufiksu będziemy w stanie szybko wskazać odpowiadający mu pewien węzeł z trie lub stwierdzić, że takiego nie ma.

Zbióry L będziemy przechowywać jako funkcję charakterystyczną. Ponadto w każdym węźle drzewa trie stworzymy listę f Lst słów z F , którym odpowiadają liście znajdujące się w poddrzewie danego węzła. Funkcję word zaimplementujemy jako tablicę, przy czym jej wartości będą poprawne tylko dla argumentów z L ∪ F , gdyż tylko z nich będziemy korzystać.

4 Główny krok

Przedstawimy teraz implementację przetwarzania sufiksu ` słowa s. Po pierwsze sprawdzamy czy ` ∈ L.

Jeśli tak, to szukamy odpowiadającego mu węzła w drzewie trie. Jeżeli jest taki węzeł, (czyli jest to prefiks jakiegokolwiek słowa z W ) to patrzymy na listę f Lst w tym węźle. Jeśli jest ona pusta lub jej jedynym elementem jest takie f

0

, że word(f

0

) = word(`), to ` nie jest zakładką. W przeciwnym razie na tej liście (na pierwszej lub drugiej pozycji) znajduje się takie f , że zachodzi word(f ) 6= word(`).

Słowa word(`) i word(f ) są już dobrymi kandydatami do złączenia. Łączymy więc odpowiednie ciągi (przy okazji zapamiętujemy |s| = ov(`, f ); będzie to potrzebna informacja, gdy będziemy chcieli faktycznie dokonać operacji ⊕), aktualizujemy funkcję word dla początku i końca otrzymanego ciągu oraz zbiory L i F . Musimy usunąć f ze wszystkich list f Lst. Aby uczynić to szybko, pamiętamy dla każdego słowa iteratory do odpowiednich miejsc we wszystkich listach f Lst. Alternatywnie możemy pamiętać zbiór F jako funkcję charakterystczną i usuwać zbędne wartości z f Lst dopiero gdy na takie natrafimy przeglądając te listy.

5 Usuwanie podsłów

Zakładaliśmy, że żadne słowo w zbiorze W nie jest podsłowem innego. Należałoby więc na samym początku usunąć takie słowa. Mając tablicę sufiksową i LCP możemy to uczynić w prosty sposób.

Wystarczy bowiem sprawdzić, czy LCP tego słowa i następnego lub poprzednego (sufiksu) w tablicy jest mniejsze niż długość słowa. Jeśli nie, to należy słowo usunąć. Należy przy tym uważać na słowa, które na wejściu pojawiają się wielokrotnie – chcemy usunąć ich wszystkie wystąpienia poza jednym.

Następnie możnemy po prostu usunąć odpowiednie sufiksy z tablicy SA odpoiwednio zaktualizować LCP .

6 Oszacowanie złożoności

Niech N będzie sumą długości słów w W . Pokażemy, że poza konstrukcją tablicy sufiksowej cały algorytm działa w czasie O(N ) niezależnie od rodzaju alfabetu. Tworzenie tablicy LCP i naszych dodatkowych opartych na niej wskaźników działa w takiej złożoności. Drzewo trie dla posortowanego ciągu słów konstruuje się również w O(N ). Dane słowo bazowe jest na co najwyżej tylu listach f Lst, ile wynosi jego długość, a uzupełniamy je przechodząc od liścia do korzenia w trie, więc sumarycznie jest to O(N ).

Posortowanie sufiksów (a właściwie słów z W ) po długości możemy wykonać w czasie proporcjonalnym do sumy ich ilości i długości najdłuższego, co także jest O(N ). Przetwarzanie danego sufiksu odbywa się w czasie stałym o ile pominie się aktualizację list f Lst. Odpowiedni element takiej listy usuwamy w O(1), więc ten koszt jest amortyzowany przez tworzenie tych list. Cała główna faza działa więc w O(N ), gdyż tyle jest sufiksów. Wreszcie stworzenie wynikowego nadsłowa, czyli dokonanie operacji ⊕ na kolejnych elementach otrzymanego ciągu ma także łączny koszt O(N ), ponieważ zapamiętywaliśmy wartości |ov| między kolejnymi słowami.

2

(3)

Ostatecznie algorytm działa w czasie O(N + S), gdzie S jest czasem potrzebnym do zbudowania ta- blicy sufiksowej. Dla alfabetów typu integer mamy zatem złożoność O(N ), dla dowolnych z porównaniem w czasie stałym O(N log Σ), gdzie Σ jest rozmiarem alfabetu.

3

Cytaty

Powiązane dokumenty

Sprawdź, czy arkusz zawiera 14 ponumerowanych stron. Ewentualny brak zgłoś przewodniczącemu zespołu nadzorującego badanie. W rozwiązaniach zadań przedstaw tok rozumowania

[r]

Metoda rozwiązywania równania różniczkowego cząstkowego po- legająca na sprowadzeniu równania do postaci kanonicznej a następnie na rozwiązaniu równania w sposób

Udowodnił niemożliwość rozwiązania równania algebraicznego stopnia wyższego niż cztery przez pierwiastniki, prowadził badania w dziedzinie teorii szeregów i całek

Dla poprawienia czytelności na ogół będziemy zapisywać system dedukcyjny S jako parę (AX, R), gdzie AX jest pewnym zbiorem aksjomatów, a R pewnym zbiorem reguł wnioskowania,

x-tyle kupiono długopisów y- tyle kupiono ołówków 3∙x – tyle wydano na długopisy 2∙y – tyle wydano na ołówki Tworzymy układ równań:. { 3 x +2 y=24

Niech F oznacza liczbę losowań, w których wyciągnięto monetę fałszywą, K-liczbę

[r]