Znam kilka podstawowych algorytmów dopasowywania ciągów, takich jak KMP lub Boyer-Moore, ale wszystkie z nich analizują wzorzec przed wyszukiwaniem, jednak jeśli jeden znak jest pojedynczy, nie ma wiele do przeanalizowania. Czy istnieje więc lepszy algorytm niż naiwne wyszukiwanie polegające na porównywaniu każdego znaku tekstu?
algorithms
string-matching
chrześcijanin
źródło
źródło
Odpowiedzi:
Przyjmuje się, że najgorszym przypadkiem jest
O(N)
kilka bardzo dobrych mikrooptymalizacji.Naiwna metoda przeprowadza porównanie znaków i porównanie końca tekstu dla każdego znaku.
Użycie wartownika (tj. Kopii znaku docelowego na końcu tekstu) zmniejsza liczbę porównań do 1 na znak.
Na nieco kręcącym się poziomie jest:
wiedzieć, czy dowolny bajt w słowie (
x
) ma określoną wartość (n
).Podwyrażenie
v - 0x01010101UL
, ocenia na wysoki bit ustawiony w dowolnym bajcie, ilekroć odpowiedni bajtv
jest równy zero lub większy niż0x80
.Podwyrażenie
~v & 0x80808080UL
ocenia na wysokie bity ustawione w bajtach, w których bajtv
nie ma ustawionego wysokiego bitu (więc bajt był mniejszy niż0x80
).Dzięki ANDingowi tych dwóch podwyrażeń (
haszero
) wynikiem jest zestaw wysokich bitów, w którym bajtyv
były zerowe, ponieważ zestaw wysokich bitów z powodu wartości większej niż0x80
w pierwszym podwyrażeniu jest maskowany przez drugi (27 kwietnia, 1987 Alan Mycroft).Teraz możemy XOR wartość testować (
x
) słowem wypełnionym wartością bajtu, która nas interesuje (n
). Ponieważ XORing samej wartości powoduje zerowy bajt, w przeciwnym razie niezerowy, możemy przekazać wynik dohaszero
.Jest to często używane w typowej
strchr
implementacji.(Stephen M. Bennet zasugerował to 13 grudnia 2009 r. Dalsze szczegóły w dobrze znanym Bit Twiddling Hacks ).
PS
Hack przechodzi test brutalnej siły (bądź cierpliwy):
Dziękuję za uwagę.
Odpowiedź miała być tylko esejem na temat kodowania wielobajtowego / o zmiennej szerokości :-) (szczerze mówiąc, to nie jest moja specjalizacja i nie jestem pewien, czy tego szukał PO).
W każdym razie wydaje mi się, że powyższe pomysły / sztuczki można nieco dostosować do MBE (szczególnie kodowania samosynchronizującego ):
strchr
/strstr
(np. GNUlib coreutils mbschr )źródło
0x01010101UL
w jednej linii, a~0UL / 255
w następnej. Sprawia wrażenie, że muszą to być różne wartości, ponieważ w przeciwnym razie po co pisać na dwa różne sposoby?#define
s rozszerzyłoby się do( (((x) ^ (0x01010101UL * (n)))) - 0x01010101UL) & ~((x) ^ (0x01010101UL * (n)))) & 0x80808080UL )
. Czy porównanie pojedynczych bajtów nie byłoby szybsze?Każdy algorytm wyszukiwania tekstu, który wyszukuje każde wystąpienie pojedynczego znaku w danym tekście, musi odczytać każdy znak tekstu przynajmniej raz, co powinno być oczywiste. A ponieważ jest to wystarczające do jednorazowego wyszukiwania, nie może być lepszego algorytmu (przy myśleniu w kategoriach kolejności w czasie wykonywania, która w tym przypadku nazywa się „liniowa” lub O (N), gdzie N jest liczbą znaków przeszukiwać).
Jednak w przypadku rzeczywistych wdrożeń z pewnością istnieje wiele mikrooptymalizacji, które nie zmieniają w ogóle kolejności wykonywania, ale obniżają rzeczywisty czas wykonywania. A jeśli celem nie jest znalezienie każdego wystąpienia jednej postaci, ale tylko pierwszego, możesz oczywiście zatrzymać się przy pierwszym wystąpieniu. Niemniej jednak, nawet w tym przypadku, najgorszym przypadkiem jest nadal to, że postać, której szukasz, jest ostatnią postacią w tekście, więc kolejność najgorszego przypadku dla tego celu nadal wynosi O (N).
źródło
Jeśli Twój „stóg siana” zostanie przeszukany więcej niż raz, podejście oparte na histogramie będzie niezwykle szybkie. Po zbudowaniu histogramu wystarczy wyszukać wskaźnik, aby znaleźć odpowiedź.
Jeśli potrzebujesz tylko wiedzieć, czy szukany wzór jest obecny, prosty licznik może pomóc. Można go rozszerzyć o pozycję (pozycje), w których każda postać znajduje się w stogu siana, lub pozycję pierwszego wystąpienia.
źródło
Jeśli musisz szukać znaków w tym samym łańcuchu więcej niż raz, możliwe jest podzielenie łańcucha na mniejsze części, możliwie rekurencyjnie, i użycie filtrów Bloom dla każdej z tych części.
Ponieważ filtr rozkwitu może z całą pewnością stwierdzić, czy znak nie znajduje się w części ciągu „reprezentowanej” przez filtr, możesz pominąć niektóre części podczas wyszukiwania znaków.
Jako przykład: dla następującego ciągu można podzielić go na 4 części (każdy o długości 11 znaków) i wypełnić dla każdej części filtr Bloom (być może 4 bajty) znakami tej części:
Możesz przyspieszyć wyszukiwanie, np. Dla postaci
a
: Używając dobrych funkcji skrótu dla filtrów kwitnienia, powiedzą ci, że - z dużym prawdopodobieństwem - nie musisz szukać ani w pierwszej, ani w drugiej, ani w trzeciej części. W ten sposób oszczędzasz się przed sprawdzaniem 33 znaków i zamiast tego musisz tylko sprawdzić 16 bajtów (dla 4 filtrów Blooma). Jest to nadalO(n)
, tylko ze stałym (ułamkowym) współczynnikiem (aby to zadziałało, musisz wybrać większe części, aby zminimalizować koszty obliczeń funkcji skrótu dla szukanego znaku).Korzystanie z rekurencyjnego, drzewiastego podejścia powinno zbliżyć cię do
O(log n)
:W tej konfiguracji należy (ponownie, zakładając, że mieliśmy szczęście i nie otrzymaliśmy fałszywego wyniku pozytywnego z jednego z filtrów) do sprawdzenia
dostać się do ostatniej części (gdzie trzeba sprawdzić 3 znaki, aż do znalezienia
a
).Stosując dobry (lepszy jak wyżej) schemat podziału, powinieneś uzyskać z tym całkiem niezłe wyniki. (Uwaga: filtry Bloom u nasady drzewa powinny być większe niż blisko liści, jak pokazano w przykładzie, aby uzyskać niskie prawdopodobieństwo fałszywie dodatnich wyników)
źródło
Jeśli ciąg ma być przeszukiwany wiele razy (typowy problem „wyszukiwania”), rozwiązaniem może być O (1). Rozwiązaniem jest zbudowanie indeksu.
Np .:
Mapa, gdzie klucz jest znakiem, a wartość jest listą indeksów tego znaku w ciągu.
Dzięki temu jedno wyszukiwanie mapy może dać odpowiedź.
źródło