Algorytmy i struktury danych/Algorytmy tekstowe II
Algorytmy tekstowe II
W module tym zajmiemy się strukturami danych reprezentującymi zbiór wszystkich podsłów danego słowa
,zapisywany jako . Oznaczmy wszystkie wystąpienia (początkowe pozycje) słowa w słowie przez . ( jest skrótem od ang. Occurrences).Chcemy znaleźć taką reprezentację zbioru
by można było łatwo odpowiedzieć na pytanie , (co jest równoważne ) jak również rozwiązywać inne problemy tekstowe. Poza tym chcemy by rozmiar tej reprezentacji był liniowy, podczas gdy rozmiar może być kwadratowy. Spośród wielu dobrych reprezentacji najbardziej znanymi są tablice sufiksowe (oznaczane przez ) i drzewa sufiksowe.Tablice i drzewa sufiksowe
Niech
, oraz niech będzie specjalnym znakiem leksykograficznie mniejszym od każdego innego symbolu.Oznaczmy przez
sufiks tekstu x zaczynający się na pozycji i-tej.Niech
będzie pozycją od której zaczyna się k-ty leksykograficznie sufiks x.Sufiks zaczynający się na pozycji
-szej nie jest brany pod uwagę.Ciąg sufiksów posortowany leksykograficznie wygląda następująco:
Rysunek 1: Tablicą sufiksową tekstu
Oznaczmy przez długość wspólnego prefiksu -tego i następnego sufiksu w kolejności leksykograficznej. Na rysunku wartości najdłuższego wspólnego prefiksu między kolejnymi słowami są przedstawione jako zacienione segmenty. Odpowiadają one tablicy .
Tablica sufiksowa ma następująca miłą własność: Niech
, .
Wtedy
jest przedziałem w tablicy sufiksowej od do .Drzewo sufiksowe jest drzewem, w którym każda ścieżka jest etykietowana kolejnymi symbolami pewnego sufiksu, oraz każdy sufiks
jest obecny w drzewie. Gdy dwie ścieżki się rozjeżdżają tworzy się wierzchołek. Mówiąc bardziej formalnie każdej krawędzi jest przypisane jako etykieta pewne podsłowo . Krawędzie wychodzące z tego samego węzła różnią się pierwszymi symbolami swoich etykiet, patrz rysunek.
Etykiety są kodowane przedziałami w tekście : para liczb reprezentuje podsłowo Parser nie mógł rozpoznać (nieznana funkcja „\ldotsa”): {\displaystyle a_ia_{i+1}\ldotsa_j}
, zobacz prawe drzewo na rysunku. Dzięki temu reprezentacja ma rozmiar . Wagą krawędzi jest długość odpowiadającego jej słowa.
Rysunek 2: Drzewo sufiksowe dla tekstu . Na końcu jest dodany znak . Końcowe węzły zawierają informację gdzie zaczyna się sufiks, którym dochodzimy do danego węzła.
Obie reprezentacje rozwiązują szybko problem string-matchingu, oraz mają rozmiar liniowy. Niech z będzie wzorcem o długości m, a słowem długości n. Z reguły .
Szukanie podsłów
Pokażemy jak sprawdzać, czy
występuje w .Używając drzewa sufiksowego (czas
)Idziemy od korzenia w dół czytając kolejne symbole
, czasami posuwamy się po wewnętrznej etykiecie pewnej krawędzi. Zbiór wystąpień odpowiada zbiorowi liści w poddrzewie węzła do którego doszliśmy. Jeśli po drodze utknęliśmy i nie dało się dalej schodzić po drzewie to oznacza, żeUżywając tablicy sufiksowej (czas
)Możemy sprawdzić czy
jest prefiksem -tego sufiksu w czasie . Korzystając z tego wykonujemy rodzaj binarnego szukania. W ten sposób znajdujemy pierwszy sufiks, którego prefiksem jest z. Jeśli jest taki sufiks to Parser nie mógł rozpoznać (nieznana funkcja „\inSubwords”): {\displaystyle z \inSubwords(x)} . W przeciwnym wypadku z nie jest podsłowem x.Podobnie znajdujemy ostatni sufiks. Zbiór wystąpień odpowiada przedziałowi w tablicy
między obliczonymi pierwszym i ostatnim sufiksem zaczynającym się od z.Liczenie liczby podsłów
Pokażemy jak liczyć liczbę podsłów słowa
mając tablicę sufiksową lub drzewo sufiksowe. Końcowy marker nie traktujemy jako części słowa . Liczba podsłów jest równa . Jeśli wszystkie symbole słowa są różne to .Używając drzewa sufiksowego, czas
Sumujemy wagi krawędzi drzewa.
Używając tablicy sufiksowej, czas
Niech
będzie sumą elementów tablicy . Liczbę podsłów obliczamy jako
Pozostawiamy jako ćwiczenie uzasadnienie tego, że liczba podsłów jest poprawnie obliczona (korzystając z drzewa sufisowego lub z tablicy sufiksowej).
Przykład
Podobnie jak tablicę sufiksową możemy zdefiniować tablicę
odpowiadającą posortowanemu ciągowi wszystkich cyklicznych przesunięć słowa (rotacji ).Pozostawiamy jako ćwiczenie znalezienie liniowego algorytmu liczącego tablicę
, zakładając, że mamy liniowy algorytm liczenia tablicy sufiksowej.
Dygresja. Ciekawą klasą słów dla których tablice są szczególnie interesujące są słowa Fibonacciego . W tym szczególnym przypadku załóżmy, że pozycje numerujemy od zera. Dla każdego tablica jest postępem arytmetycznym (modulo długość słowa). Natomiast tablica jest postępem arytmetycznym gdy jest parzyste.
Słowa Fibonacciego definiujemy następująco: \Na przykład: Oznaczmy przez tablicę dla słowa Fibonacciego , wtedy:
Pozostawiamy jako ćwiczenie policzenie wzoru na .
Drzewa sufiksowe =>tablice sufiksowe
W celu znalezienia początkowych pozycji sufiksów w porządku leksykograficznym przechodzimy drzewo sufiksowe metodą DFS, zakładając, że dla każdego węzła lista jego synów jest w kolejności leksykograficznej etykiet krawędzi prowadzących do synów. Wystarczy sprawdzać pierwsze symbole tych etykiet.
Załóżmy, że w liściach mamy początki sufiksów, które do nich prowadzą.
Kolejność odwiedzania liści w naszym przejściu metodą DFS automatycznie generuje elementy tablicy sufiksowej.
Tablice sufiksowe =>drzewa sufiksowe
Pokażemy konstruktywnie następujący istotny fakt:
jeśli znamy tablicę sufiksową i tablicę
to drzewo sufiksowe dla danego tekstu możemy łatwo skonstruować w czasie liniowym.Przypuśćmy, że,
, a więc:
Algorytm Drzewo-Sufiksowe
for to do
wstaw nową ścieżkę o sumarycznej etykiecie do ;

Rysunek 3: Wstawianie kolejnego sufiksu do drzewa sufiksowego, przed włożeniem wszystkie krawędzie ścieżki roboczej od do korzenia są skierowane w lewo.
Opiszemy w jaki sposób wstawiamy kolejny sufiks do drzewa. Operacja ta jest zilustrowana na rysunku. Załóżmy, że w każdym węźle drzewa trzymamy długość tekstu, który ten węzeł reprezentuje (jako pełna etykieta od korzenia do węzła).
Niech
będzie poprzednio wstawionym sufiksem, a ostatnio utworzonym liściem.Wtedy wstawienie
polega na znalezieniu maksymalnego wspólnego prefiksu tekstów , . Niech . Znajdujemy węzeł odpowiadający ścieżce od korzenia etykietowanej . Podstawowym trikiem jest to, że węzła szukamy nie od korzenia, ale od ostatnio utworzonego liścia . Jeśli takiego węzła nie ma (jest wewnątrz krawędzi) to go tworzymy. Następnie tworzymy nowy liść odpowiadający sufiksowi , oraz krawędź etykietowaną .
Z tablicy odczytujemy długość .
W celu obliczenia
posuwamy się ścieżką od w górę drzewa, aż znajdziemy węzeł oddalony od korzenia o .Przechodząc drzewo posuwamy się po węzłach drzewa, przeskakując (w czasie stalym) potencjalnie długie teksty na krawędziach.
Koszt operacji wstawienia jest proporcjonalny do sumy: jeden plus zmniejszenie głębokości nowego liścia w stosunku do starego. Suma tych zmniejszeń jest liniowa. jest najdłuższym wspólnym prefiksem słów i . Kluczowe znaczenie w operacji ma znajomość wartości . Wiemy kiedy się zatrzymać idąc do góry od węzła w kierunku korzenia.
Lokalnie wykonana praca w jednej iteracji jest zamortyzowana zmniejszeniem się głębokości aktualnego liścia w stosunku do poprzedniego. W sumie praca jest liniowa.
Historia algorytmu jest pokazana dla przykładowego tekstu na rysunkach.

Rysunek 4: Pierwsze 6 iteracji algorytmu Drzewo-Sufiksowe dla tekstu .

Rysunek 5: Ostatnie 6 iteracji algorytmu Drzewo-Sufiksowe dla tekstu .
Obliczanie tablicy lcp
Niech
będzie poyccją w porządku leksykograficznym. W naszym przykładowym słowie mamy:Niech
.Załóżmy, dla uproszczenia, że
. Obliczamy tablice następująco:
Algorytm Oblicz-lcp
for
oblicz korzystając z faktu, że ;
koszt iteracji
for to do
Pozostawimay jako ćwiczenie dowód tego, że
.
Jeśli
to obliczamy sprawdzając symbol po symbolu (od lewej do prawej) zgodność prefiksów odpowiednich słów startując od pozycji . W ten sposób sumaryczny koszt jest liniowy. W każdej iteracji cofamy się o jeden, a potem idziemy do przodu (sprawdzając kolejne symbole). Jest to analiza typu jeden krok do tyłu i kilka do przodu. Liczba iteracji jest liniowa, więc liczba kroków do tyłu też. Ponieważ odległość do celu jest liniowa, to suma kroków też jest liniowa.Słownik podsłów bazowych i konstrukcja tablicy sufiksowej w czasie O(n log n)
Opiszemy uproszczoną wersję algorytmu Karpa-Millera-Rosenberga (w skrócie algorytmu KMR) rozwiązywania problemów tekstowych metodą słownika podsłów bazowych. Ustalmy pewnie tekst
długości .Załóżmy, że dodatkowym symbolem jest , leksykograficznie najmniejszy symbol. Przez segment -bazowy rozumiemy segment tekstu długości lub kończący się na .Teoretycznie możemy założyć, że po symbolu
mamy bardzo dużo takich symboli na prawo i każdy segment startujący w ma dokładnie długość .
Słownik podsłów bazowych (w skrócie DBF(x), od ang. dictionary of basic factors) składa się z tablic
, , , .
Zakładamy, że
jest pozycją słowa na posortowanej liście (bez powtórzeń) wszystkich podsłów długości słowa . Jeśli długość wystaje poza koniec to przyjmujemy że są tam (wirtualnie) same symbole . Poniżej przedstawiamy przykład słownika podsłów bazowych .
Algorytm liczenia tablic Nazwa jest bardzo prosty. Załóżmy od razu, że symbole są ponumerowane leksykograficznie. Wtedy
jest zasadniczo równa tekstowi .
Rysunek6: Słowo rozmiaru otrzymuje najpierw nazwę-kompozycję: kombinacją nazw (będących liczbaminaturalnymi z przedziału ) dwóch podsłów długości .
Opis jednej iteracji
)
Dla każdego
tworzymy nazwę kompozycję slowa jakoKażda taka kompozycja jest parą liczb naturalnych. Sortujemy te pary za pomocą algorytmu radix-sort i w ten sposób otrzymujemy tablicę, która koduje (w porządku leksykograficznym) każdą parę liczbą naturalną (pozycją w porządku leksykograficznym). Wartością
jest kod pary .
Zauważmy, że tablica sufiksowa odpowiada tablicy . Możemy to podsumować następująco:
1. słownik DBF(x) możemy skonstruować w czasie i pamięci (jest to również rozmiar słownika).
2. Tablicę sufiksową możemy otrzymać,stosując algorytm KMR, w czasie i pamięci . (Potrzebujemy pamiętać jedynie ostatnie dwie tablice w każdej iteracji.)
Konstrukcja tablicy SUF w czasie O(n): algorytm KS
Opiszemy teraz algorytm Karkainena-Sandersa ( w skrócie KS) będący zoptymalizowaną wersją algorytmu KMR liczenia tablicy sufiksowej. Zauważmy, że algorytm KMR liczy znacznie więcej niż tablica sufiksowa, ponieważ liczy słownik podsłów bazowych wielkości
(mający liczne iine zastosowania, ale jako całość być moe niepotrzbny przy liczeniu tablicy sufisksowej)Główną częścią algorytmu KS jest obliczanie częściowej tablicy sufiksowej w sposób rekurencyjny. Rozważmy dwa zbiory pozycji tekstu
:N składa się z co trzeciej pozycji, a więc N = {3,6,9,....}, M jest zbiorem pozostałych pozycji.
Przez
, oznaczmy tablicę sufiksową dla pozycji ze zbioru , podobnie zdefiniujmy .daje posortowany ciąg sufiksów zaczynających się na pozycjach ze zbioru .
Dla początkowego przykładowego tekstu
mamy
Redukcja liczenia
do liczenia tablicy sufiksowej rozmiaru
Posortujmy leksykograficznie wszystkie podsłowa długości 3 w słowie korzystając z radix-sort. Każdemu takiemu słowu przyporządkujmy nazwę będącą jego pozycją w posortowanym leksykograficznie ciągu, oznaczmy otrzymaną nazwę podsłowa długości 3. Zakładamy, że ko"nczy się dodatkowo dwoma symbolami ale rozważamy tylko podsłowa zaczynające się w . Dla uproszczenia załóżmy, że 3 jest dzielnkiem n.
Tworzymy nowe słowo
w następujący sposób:
Symbol # jest tutsj (jak poprzednio) leksykograficznie najmniejszy.
Jeśli mamy tablicę sufiksową dla słowa to można łatwo policzyć w czasie liniowym.
Pozostawiamy to jako ćwiczenie.
Algorytm Parser nie mógł rozpoznać (nieznana funkcja „\Large”): {\displaystyle {\Large KS} } (Karkkainen-Sanders)
1.
;2. obliczamy tablicę sufiksową dla x' rekurencyjnie;
3. obliczamy
wczasie liniowym, znając tablicę sufiksową dla x';4. obliczamy
w czasie liniowym (bez rekursji)znając ;5. scalamy posortowane ciągi
w tablicę sufiksową dla całego słowasłowa
Krok 1 algorytmu sprowadza się do radix-sortu, podobnie jak w algorytmie KMR.Kroki 3,4 są proste i pozostawiamy czytelnikowi jako ćwiczenie.
Najważniejszy jest krok scalania. Mamy dwie posortowane listy sufiksów i trzeba je scalić w jedną posortowaną listę. Zasadniczym problemem jest implementacja operacji porównania leksykograficznego dwóch (długich) sufiksów w czasie stałym. Jeśli oba sufiksy są typu
lub oba są typu to porównanie jest w czasie stałym bo mamy posortowane listy takich sufiksów.Pokażemy na przykładzie kluczową operację porównania sufiksu typu M z sufiksem typu N w czasie stałym.
Przykład
Nierównośc
jest równoważna temu że zachodzi co najmniej jeden z warunków:1.
2.
3.
Jednakże , zatem i , są typu M i można je porównać w czasie stałym.
Rozwiązaniem jest
. Tak więc mamy liniowy algorytm liczenia tablicy sufiksowej. Daje to również liniowy algorytm konstrukcji drzewa sufiksowego.Istnieje kilka interesujących algorytmów, które konstruują drzewo sufiksowe w czasie liniowym, bez korzystania z tablicy sufiksowej (algorytmy Weinera, McCreighta, Ukkonena).