Zaawansowane algorytmy i struktury danych/Wykład 2: Różnice pomiędzy wersjami
Linia 159: | Linia 159: | ||
Dla każdego <math>i</math> tworzymy nazwę kompozycję slowa <math>x[i..i+2^{k=1}-1]</math> jako <center><math>NAZWA_k[i], NAZWA_k[i+2^k]</math></center> | |||
Każda taka kompozycja jest parą liczb naturalnych. Sortujemy te pary za pomocą ''radix-sort'' i w tensposób otrzymujemy tablicę, która koduje (w porządku leksykograficznym) każdą parę liczbą naturalną(pozycją w porządku leksykograficznym). Warością <math>NAZWA_{k+1}[i]</math> jest kod pary <math>(NAZWA_k[i],NAZWA_k[i+2^k])</math>. | |||
Zauważmy, że tablica sufiksowa odpowiada tablicy <math>NAZWA_{\lceil \log n \rceil}</math>. Możemy to podsumować następująco: | |||
1. słownik ''DBF(x)'' możemy skonstruować w czasie <math> O(n \log n) </math>i pamięci <math>O(n \log n)</math> (jest to również rozmiar słownika). <br> | |||
2. Tablicę sufiksową możemy otrzymać,stosując algorytm KMR, w czasie <math>O(n \log n)</math> i pamięci <math>O(n)</math>. Potrzebujemy pamiętać jedynie ostatnie dwie tablice w każdej iteracji. | |||
== Kosntrukcja tablicy ''SUF'' w czasie ''O(n)'': algorytm KS == | |||
Opiszemy teraz algorytm Karkainena-Sandersa ( w skrócie ''KS'') będący optymalizowaną 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 <math>n \log n</math>, mający liczne zastosowania. | |||
Główną częścią algorytmu KS jest obliczanie częściowej tablicy sufiksowej w sposób rekurencyjny.Rozważmy dwa zbiory pozycji tekstu <math>x</math>:</math></center>M\ =\ \{1\le i \le n:\ (i-1)\ \textrm{modulo}\ 3 \in \{0,1\}\}\ \ N\ =\ \{1,2,..,n\}-M<center><math><!--%<math>M</math> nazwiemy zniorem ozycji pierwszorzędnych, a <math>N</math> drugorzędnych.-->Przez <math>SUF[M]</math>, oznaczmy tablicę sufiksowądla pozycji ze zbioru <math>M</math>, podobnie zdefiniujmy <math>SUF[N]</math>. \myskip <math>SUF[M]</math> daje posortowany ciąg sufiskówzaczynających się na pozycjach ze zbioru <math>M</math>.<!--%--><!--%------------------------------------------------------------------------>\paragraph{Redukcja liczenia <math>SUF[M]</math> do liczenia tablicysufiksowej rozmiaru <math>\frac{2}{3}n</math>}\mbox{ \ } | Główną częścią algorytmu KS jest obliczanie częściowej tablicy sufiksowej w sposób rekurencyjny.Rozważmy dwa zbiory pozycji tekstu <math>x</math>:</math></center>M\ =\ \{1\le i \le n:\ (i-1)\ \textrm{modulo}\ 3 \in \{0,1\}\}\ \ N\ =\ \{1,2,..,n\}-M<center><math><!--%<math>M</math> nazwiemy zniorem ozycji pierwszorzędnych, a <math>N</math> drugorzędnych.-->Przez <math>SUF[M]</math>, oznaczmy tablicę sufiksowądla pozycji ze zbioru <math>M</math>, podobnie zdefiniujmy <math>SUF[N]</math>. \myskip <math>SUF[M]</math> daje posortowany ciąg sufiskówzaczynających się na pozycjach ze zbioru <math>M</math>.<!--%--><!--%------------------------------------------------------------------------>\paragraph{Redukcja liczenia <math>SUF[M]</math> do liczenia tablicysufiksowej rozmiaru <math>\frac{2}{3}n</math>}\mbox{ \ } | ||
Posortujmy leksykograficznie wszystkie podsłowa długości 3 w słowie <math>x</math> korzystając z rdix-sort. Każdemutakiemu słowu przyporządkujmy nazwę będącą jego pozycją w posortowanym leksykograficznie ciągu, oznaczmy<math>kod(z)</math> kod podsłowa długości 3. Zakładamy, że <math>x</math> ko"nczy się dodatkowo dwoma symbolami <math>\#</math> alerozważamy tylko podsłowa zaczynające się w <math>x</math>. Dla uproszczenia załóżmy, że 3 jest dzielnkiem n. | Posortujmy leksykograficznie wszystkie podsłowa długości 3 w słowie <math>x</math> korzystając z rdix-sort. Każdemutakiemu słowu przyporządkujmy nazwę będącą jego pozycją w posortowanym leksykograficznie ciągu, oznaczmy<math>kod(z)</math> kod podsłowa długości 3. Zakładamy, że <math>x</math> ko"nczy się dodatkowo dwoma symbolami <math>\#</math> alerozważamy tylko podsłowa zaczynające się w <math>x</math>. Dla uproszczenia załóżmy, że 3 jest dzielnkiem n. |
Wersja z 10:59, 21 sie 2006
Zaawansowane algorytmy tekstowe II
W module tym zajmiemy się strukturami danych reprezentującymi zbiór wszystkich podsłów danego słowa ,zapisywany jako . Oznaczmy wszsytkie 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 problemytekstowe. Poza tym chcemy by rozmiar tej eprezentacji 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.
Niech
, oraz niech
będzie specjalnym znakiemleksykograficznie 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-tyleksykograficznie 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 jest ciąg Parser nie mógł rozpoznać (błąd składni): {\displaystyle SUF\ =\ [4,\ 2,\ 5,\ 7,\ 9,\ 12,\ 3,\ 1,\ 6,\6,\ 8,\ 11,\ 10]} Wartości najdłuższego wspólnego prefiksu między kolejnymi słowami są przedstawione jakozacienione segmenty. Odpowiadają one tablicy .
Oznaczmy przez długść wspólnego prefisku -tego i następnego sufiksuw kolejności leksykograficznej.
Tablica sufiksowa ma następująca miłą własność: Niech
, .
Wtedy jest przedziałem w tablicy sufiksowej od do .
Drzewo sufiskowe jest drzewem, w którym każda ścieżka jest etykietowana kolejnymi symbolamipewnego 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 pewnepodsł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 zank . 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śći n. Z reguły .
Szukanie podsłów
Pokażemy jak sprawdzać, czy występuje w .
Używając drzewa sufiskowego, czas
Idziemy od korzenia w dół czytając kolejnesymbole , czasami posuwamy się po wewnętrznej etykiecie pewnej krawędzi. Zbiór wystąpie"n odpowiadazbiorowi 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, że
Używając tablicy sufiksowej , czas Możemy sprawdzić czy jest prefiksem -tego sufiksu w czasie . Korzystając z tego wykonujemy rodzaj{\em binary search}, 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 przeciwym wypadku z nie jest podsłowem x. Podobnie znajdujemy ostatni sufiks. Zbiór wystąpie"nodpowiada 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 Parser nie mógł rozpoznać (błąd składni): {\displaystyle |Subwords(x)|={\choose {n} 2}} .
Używając drzewa sufiskowego, 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ą posortowanemuciągowi wszystkich cyklicznych przesunięć słowa (rotacji ).
Pozostawiamy jako ćwiczenie znalezienie liniowego algorytmu liczącego tablicę , zakładając, że mamy liniowy algorytm liczeniatablicy sufiksowej.
Dysgresja. 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 pozycjenumerujemy 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 znalezenia początkowych pozycji sufiksów w porządku leksykograficznym przechodzimy drzewo sufiksowemetodą DFS, zakładając, że dla każdego węła lista jego synów jest w kolejności leksykograficznej etykietkrawędzi prowadzących do synów. Wystarczy sprawdzać piewsze 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 konstruktywanie 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-Susfiksowe
drzewo reprezentujące (jedna krawędź);
for to do
wstaw nową ścieżkę o sumarycznej etykiecie do ;

Rysunek 3: Wstawianie kolejnego sufiksu do drzewa sufiskowego, 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ść tesktu, który ten węzeł reprezentuje (jako pełna etykieta od korzenia do węzła).
Niech będzie poprzednio wstawionym sufiksem, a ostatniopoprzednio 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 gotworzymy. 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łachdrzewa, przeskakując potencjalnie długie teksty na krawędziach. \myskipKoszt operacji wstawienia jest proporcjonalny do sumy: jeden plus zmniejszenie głębokości nowego liścia w stosunku do starego. Suma tychzmniejszeń 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 iteracjijest zamortyzowana zmniejszeniem się głębokości aktualnego liścia w stosunku do porzedniego.
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 .
Oblicanie tablicy lcp
Niech będzie poycją 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 to do
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ść pefiksów odpowiednich słów startującod pozycji . W ten sposób sumaryczny koszt jest liniowy. W każdej iteracji cofamy się o jeden, a potemidziemy do przodu (sprawdzając kolejne symbole). Jest to analiza typu jeden krok do tyłu i kilka doprzodu. 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.
Konstrukcja tablicy SUF w czasie O(n log n): algorytm KMR
Opiszemy uproszczoną wersję algorytmu Karpa-Millera-Rosenberga (w skrócie algorytmu KMR) rozwiązywania problemów tekstowych metodą słownika bazowego. Ustalmy pewnie tekst długości .Załóżmy, że dodatkowym symbolem jest , leksykograficznie najmniejszy symbol. Przez -bazowysegment rozumiemy segment długości lub ko"nczący się na . Teoretyczniemożemy założyć, że po symbolu mamy bardzo dużo takich symboli na prawo i każde 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
jako
Każda taka kompozycja jest parą liczb naturalnych. Sortujemy te pary za pomocą radix-sort i w tensposób otrzymujemy tablicę, która koduje (w porządku leksykograficznym) każdą parę liczbą naturalną(pozycją w porządku leksykograficznym). Waroś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.
Kosntrukcja tablicy SUF w czasie O(n): algorytm KS
Opiszemy teraz algorytm Karkainena-Sandersa ( w skrócie KS) będący optymalizowaną 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 zastosowania.
Główną częścią algorytmu KS jest obliczanie częściowej tablicy sufiksowej w sposób rekurencyjny.Rozważmy dwa zbiory pozycji tekstu
:</math>M\ =\ \{1\le i \le n:\ (i-1)\ \textrm{modulo}\ 3 \in \{0,1\}\}\ \ N\ =\ \{1,2,..,n\}-M
Posortujmy leksykograficznie wszystkie podsłowa długości 3 w słowie korzystając z rdix-sort. Każdemutakiemu słowu przyporządkujmy nazwę będącą jego pozycją w posortowanym leksykograficznie ciągu, oznaczmy kod podsłowa długości 3. Zakładamy, że ko"nczy się dodatkowo dwoma symbolami alerozważ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:</math>y1\ =\ \ kod(a_1a_2a_3)\cdot kod(a_4a_5a_6) \ldots kod(a_{n-2}a_{n-1}a_n)
y2\ =\ kod(a_2a_3a_4)\cdot kod(a_5a_6a_7) \ldots kod(a_{n-1}a_{n}a_{n+1})
compress(x)\ =\ y1\ \#\ y2
\begin{center}\begin{minipage}{12cm}\noindent Algorytm KS;
- ; \item obliczamy tablicę sufiksową dla x' rekurencyjnie; \item obliczamy wczasie liniowym, znając tablicę sufiksową dla x'; \item obliczamy w czasie liniowym (bez rekursji)znając ; \item scalamy posortowane ciągi w tablicę sufiksową dla całego słowasłowa
\myskip\end{minipage}\end{center} Krok 1 algorytmu sprowadza się do {\em radix-sort}u, podobnie jak w algorytmie KMR.Kroki 3,4 są proste i pozostawiamy cztelnikowi 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ównanialeksykograficznego dwóch sufiksów w czasie stałym. Jeśli oba sufiksy są typu luboba są typu to porównanie jest w czasie stałym bo mamy posortowane listy takich sufiksów.
Pokażemy na przykładzie jak porównać sufiks typu M z sufiksem typu N. \myskip Przykład.\\Nierównośc jest równoważna alternatywie:</math>(a_2<a_{12}) \ \textrm{lub}\(a_2=a_{12},\ a_3<a_{13}) \ \textrm{lub}\ (a_2=a_{12},\ a_3=a_{13},\ sufiks_4<sufiks_{14})
T(n) \ =\ T(\frac{2}{3}\cdot n + 1) + O(n)
Istnieje kilka interesujących algorytmów, które konstruują drzewo sufiksowe w czasie liniowym, bezkorzystania z tablicy sufiksowej (algorytm Weinera, McCreighta, Ukkonena).