Załóżmy, że otrzymaliśmy zbiór ciągów, . Chciałbym wiedzieć, czy którykolwiek z tych ciągów jest podciągiem dowolnego innego ciągu w kolekcji. Innymi słowy, chciałbym algorytm dla następującego zadania:
Dane wejściowe:
Wyjście: takie, że jest podłańcuchem i , lub None, jeśli nie takie exist
Czy istnieje na to wydajny algorytm?
Jeśli zamienimy „podłańcuch” na „przedrostek”, istnieje skuteczny algorytm (posortuj ciągi, a następnie wykonaj skanowanie liniowe, aby porównać sąsiednie ciągi; sortowanie zapewni, że podciągi są sąsiadujące). Ale trudniejsze wydaje się sprawdzenie, czy dowolny ciąg znaków jest podciągiem dowolnego innego ciągu. Naiwny algorytm polega na iteracji wszystkich par , ale wymaga to testów podciągowych . Czy istnieje bardziej wydajny algorytm?
Myślę, że moglibyśmy nazwać to „testowaniem podciągów na wszystkich parach” lub coś w tym rodzaju.
Moim ostatecznym celem jest przycięcie kolekcji, aby żaden ciąg nie był podciągiem innych elementów, poprzez usunięcie każdego z nich, który jest podciągiem czegoś innego w kolekcji.
Odpowiedzi:
Możesz zbudować drzewo sufiksów w czasie liniowym i sprawdzić, czy istnieje wewnętrzny węzeł, który odpowiada pełnemu ciągowi (stały czas na węzeł).
Bardziej szczegółowo załóżmy, że otrzymujemy ciągi .s1, ... ,sn∈Σ∗
Budowanie (uogólnionego) drzewa sufiks z o parami różne znaczniki zaciskowe .s1$1,s2)$2), ... ,sn$n n $1, ... ,$n∉ Σ
Za pomocą algorytmu Ukkonen można to zrobić w czasie liniowym; liniowy w sumie wszystkich długości łańcucha.
Zakładając, że etykieta liście z , jeżeli stanowią one przyrostek z , przemierzać drzewo i znaleźć te liście oznaczonych , czyli liści, które odpowiadają w pełni smyczki.( i , j ) si[j..|si|] si n (i,0)
To zajmuje czas liniowy w rozmiarze drzewa, który sam jest liniowy w rozmiarze wejściowym.
Liście potomne elementu nadrzędnego (do którego prowadzi krawędź oznaczona ) reprezentują wszystkie dopasowania z zestawu; wynika to z podstawowego niezmiennika drzewek sufiksów. Znajdź dowolne dopasowanie, schodząc do dowolnego liścia (ale ).(i,0) $i (i,0)
To znowu zajmuje czas liniowy.
Oddzielne znaczniki końcowe nie są tak naprawdę konieczne; jeden używany do zakończenia wszystkich ciągów jest wystarczający, o ile pozwala się na wiele etykiet na liść.
źródło