Welcher Suchalgorithmus ist eigentlich der schnellste?

27

Ich bin seit einiger Zeit festgefahren, was der schnellste Algorithmus für die Suche nach Zeichenfolgen ist, habe viele Meinungen gehört, bin mir aber am Ende nicht sicher.

Ich habe einige Leute sagen hören, dass der schnellste Algorithmus Boyer-Moore ist und einige sagen, dass Knuth-Morris-Pratt tatsächlich schneller ist.

Ich habe nach der Komplexität bei beiden gesucht, aber sie sehen größtenteils gleich aus O(n+m). Ich habe festgestellt, dass Boyer-Moore im schlimmsten Fall eine O(nm)Komplexität im Vergleich zu Knuth-Morris-Pratt hat, die O (m + 2 * n) hat. Wobei n = Länge des Textes und m = Länge des Musters.

Soweit ich weiß, hat Boyer-Moore eine linear schlechteste Zeit, wenn ich die Galil-Regel anwenden würde.

Meine Frage: Über alles, was eigentlich der schnellste String-Suchalgorithmus ist (Diese Frage beinhaltet alle möglichen Stichalgorithmen, nicht nur Boyer-Moore und Knuth-Morris-Pratt).

Edit: Aufgrund dieser Antwort

Was ich genau suche ist:

Angesichts eines Textes Tund eines Musters muss Pich alle Auftritte von Pin finden T.

Auch die Länge von P und T ist von [1,2 000 000]und das Programm muss unter 0,15 Sekunden laufen.

Ich weiß, dass KMP und Rabin-Karp ausreichen, um das Problem zu 100% zu lösen, aber ich wollte Boyer-Moore unbedingt implementieren. Welches wäre das Beste für diese Art der Mustersuche?

Vandamon Taigi
quelle
6
Was haben Sie gefunden, als Sie diese in der Sprache Ihrer Wahl getestet haben?
Walter
4
Bei einigen Tests war Boyer-Moore besser, bei anderen war KMP besser, aber ich bin nicht sicher, ob ich die "beste" Implementierung von ihnen habe. Die Sprache der Wahl steht in den Tags: C ++ (nicht sicher, ob Sie das gesehen haben, da Sie "Sprache der Wahl" geschrieben haben). PS Ich bin mir auch nicht sicher, ob ich die besten Tests getestet habe.
Vandamon Taigi
1
stackoverflow.com/q/3183582
Robert Harvey
Knuth-Morris-Pratt mit O (m + 2 * n) ... Sie meinen O (m + n).
Jules
Wählen Sie eine mit einer anständigen algorithmischen Komplexität aus und optimieren Sie den Mist mit einem Profiler in der Hand - das hat immer bei mir funktioniert. :-D

Antworten:

38

Dies hängt von der Art der Suche ab, die Sie durchführen möchten. Jeder der Algorithmen ist für bestimmte Suchtypen besonders leistungsfähig, aber Sie haben den Kontext Ihrer Suchvorgänge nicht angegeben.

Hier sind einige typische Gedanken zu Suchtypen:

  • Boyer-Moore: Analysiert das Muster vor und vergleicht es von rechts nach links. Wenn eine Nichtübereinstimmung auftritt, wird die anfängliche Analyse verwendet, um zu bestimmen, wie weit das Muster in Bezug auf den gesuchten Text verschoben werden kann. Dies funktioniert besonders gut bei langen Suchmustern. Insbesondere kann es sublinear sein, da Sie nicht jedes einzelne Zeichen Ihres Textes lesen müssen.

  • Knuth-Morris-Pratt: analysiert das Muster ebenfalls vorab, versucht jedoch, alles, was bereits im ersten Teil des Musters vorhanden war, wiederzuverwenden, um zu vermeiden, dass das Muster erneut abgeglichen werden muss. Dies kann recht gut funktionieren, wenn Ihr Alphabet klein ist (z. B. DNA-Basen), da Sie eine höhere Wahrscheinlichkeit haben, dass Ihre Suchmuster wiederverwendbare Submuster enthalten.

  • Aho-Corasick: Benötigt viel Vorverarbeitung, tut dies jedoch für eine Reihe von Mustern. Wenn Sie wissen, dass Sie immer wieder nach denselben Suchmustern suchen, ist dies viel besser als das andere, da Sie Muster nur einmal und nicht einmal pro Suche analysieren müssen.

Daher gibt es, wie in CS üblich, keine eindeutige Antwort auf die Gesamtbeste . Es geht vielmehr darum, das richtige Werkzeug für den jeweiligen Job auszuwählen.

Ein weiterer Hinweis zu Ihrer Worst-Case-Argumentation: Überlegen Sie sich, welche Suchvorgänge für diesen Worst-Case erforderlich sind, und ob diese für Ihren Fall wirklich relevant sind. Zum Beispiel O(mn)ergibt sich die schlimmste Komplexität des Boyer-Moore-Algorithmus aus einem Suchmuster und einem Text, der jeweils nur ein Zeichen enthält (wie das Finden aaain aaaaaaaaaaaaaaaaaaaaa). Müssen Sie für solche Suchvorgänge wirklich schnell sein?

Frank
quelle
Ich habe das gesamte englische Alphabet oder so zu verwenden und ich habe die Frage aktualisiert.
Vandamon Taigi
Und ja, ich muss auch für
solche
1

Ich bin zwar etwas spät dran, um diese Frage zu beantworten, aber ich denke, ich bin Z-Algorithmviel schneller als alle seine Gegenstücke. Die Komplexität im ungünstigsten Fall ist O (m + n) und es ist keine Vorverarbeitung des Musters / Texts erforderlich. Im Vergleich zu den anderen Algorithmen ist es auch sehr einfach zu codieren.

Es funktioniert auf folgende Weise.

Zum Beispiel gibt es eine Zeichenfolge S ='abaaba'. Wir sollen z(i)Werte finden für i=0 to len(S)-1. Bevor ich auf die Erklärung eingehe, möchte ich zunächst einige Definitionen festlegen.

z(i)= nein von Zeichen des Präfixes S, das mit dem Präfix von übereinstimmt s(i).

s(i)= ithSuffix von S.

Das Folgende sind die s(i)Werte für s = 'abaaba'.

s(0) = 'abaaba' = S
s(1) = 'baaba'
s(2) = 'aaba'
s(3) = 'aba'
s(4) = 'ba'
s(5) = 'a'

Die z-Werte sind jeweils

z(0) = 6 = length(S)
z(1) = 0
z(2) = 1
z(3) = 3
z(4) = 0
z(5) = 1

Weitere Informationen zum Algorithmus finden Sie unter den folgenden Links.

http://codeforces.com/blog/entry/3107

https://www.youtube.com/watch?v=MFK0WYeVEag

Jetzt ist O (N) erforderlich, um alle zWerte ohne zusätzlichen Aufwand für die Vorverarbeitung zu ermitteln. Man würde sich jetzt fragen, wie man diese Logik verwenden kann, um Muster in einer gegebenen Zeichenkette abzugleichen.

Schauen wir uns ein Beispiel an. Muster (P): aba, Text (T): aacbabcabaad.

Geben Sie dies in das Formular P $ T ein. ( $- Jedes Zeichen, das weder in einem Muster noch in einem Text vorkommt. Ich werde in Kürze auf die Bedeutung von $eingehen.)

P$T = aba$aacbabcabaad

Wir wissen len(P)= 3.

Alle z-Werte von P$Tsind

z(0) = 16 = len(P$T)
z(1) = 0
z(2) = 1
z(3) = 0
z(4) = 1
z(5) = 1
z(6) = 0
z(7) = 0
z(8) = 2
z(9) = 0
z(10) = 0
z(11) = 3
z(12) = 0
z(13) = 1
Z(14) = 1
Z(15) = 0

Nun welche z(i)= len(P). Ans = 11.Unser Muster ist also bei Ans-len(P)-1= vorhanden 7. -1ist für $Charakter.

Jetzt $ist es wichtig, warum oder welche Sonderzeichen es gibt. Betrachten P = 'aaa'und T = 'aaaaaaa'. Ohne das Sonderzeichen haben alle z(i)inkrementelle Werte. Mit den folgenden Formeln kann man die Position des Musters im Text immer noch finden:

Zustand: z(i)> = len(P)und Position: Ans-len(P). In diesem Fall wird die Situation jedoch etwas knifflig und verwirrend. Ich persönlich bevorzuge die Sonderzeichentechnik.

SohamC
quelle
1
Könntest du es dir hier erklären? Links zu externen Websites können zur Ausarbeitung verwendet werden. Der Kern einer Antwort sollte jedoch in der Antwort selbst liegen, anstatt einem Link zu einer anderen Website folgen zu müssen.
Der z-Algorithmus ist im Grunde derselbe wie kmp. Ich bezweifle, dass es viel schneller geht.
Thomas Ahle
2
Ich stimme @ThomasAhle zu. Computing z ist eine Vorverarbeitung. Es ist jedoch eine gute Erklärung. O(n)Aufgrund dieser Antwort habe ich einen Weg gefunden, von der KMP-Vorverarbeitung zur Z-Vorverarbeitung zu konvertieren. Hier
leewz
-1

Verwenden Sie inhaltsadressierbaren Speicher , der in der Software in Form einer virtuellen Adressierung implementiert ist (Zeigen von Buchstaben auf Buchstaben).

Es ist ein bisschen überflüssig für einen durchschnittlichen String-Matching-Algorithmus.

CAM kann mit einer großen Anzahl von Mustern gleichzeitig übereinstimmen, bis zu 128-Buchstaben-Mustern (wenn es sich um ASCII handelt; wenn es sich nur um Unicode handelt, 64). Und es ist ein Aufruf pro Buchstabenlänge in der Zeichenfolge, mit der Sie übereinstimmen möchten, und ein zufälliger Lesevorgang aus dem Speicher pro Länge der maximalen Musterlänge. Wenn Sie also eine 100.000-Buchstaben-Zeichenfolge mit bis zu 90.000.000 Mustern gleichzeitig analysieren (was etwa 128 GiB zum Speichern einer so großen Anzahl von Mustern benötigt), sind 12.800.000 zufällige Lesevorgänge aus dem RAM erforderlich, was in 1 ms der Fall ist.

So funktioniert die virtuelle Adressierung.

Wenn ich mit 256 Startadressen beginne, die den ersten Buchstaben darstellen, zeigen diese Buchstaben auf 256 der nächsten Buchstaben. Wenn ein Muster nicht vorhanden ist, speichern Sie es nicht.

Wenn ich also weiterhin Buchstaben mit Buchstaben verbinde, ist das so, als ob 128 virtuelle Adressierungsbereiche auf virtuelle Adressierung verweisen würden.

Das wird funktionieren - aber um 900.000.000 Muster gleichzeitig abzugleichen, muss noch ein letzter Trick hinzugefügt werden - und es wird die Tatsache ausgenutzt, dass Sie anfangen, diese Buchstabenpuffer häufig wiederzuverwenden, aber später verstreut es sich. Wenn Sie den Inhalt auflisten, anstatt alle 256 Zeichen zuzuweisen, verlangsamt er sich nur geringfügig, und Sie erhalten eine 100-fache Kapazitätserhöhung, da im Grunde genommen nur 1 Buchstabe in jedem Buchstabenzeigerpuffer verwendet wird (den ich als "" bezeichnet habe). Flucht').

Wenn Sie eine Übereinstimmung mit der Zeichenfolge "Nächster Nachbar" erzielen möchten, werden viele davon parallel ausgeführt und in einer Hierarchie gesammelt, sodass Sie Ihren Fehler unbefangen weitergeben. Wenn Sie versuchen, mit nur einem Nachbarn den nächsten Nachbarn zu finden, sind Sie voreingenommen gegenüber dem Anfang des Baums.

rouncer81
quelle
4
@MagnusRobertCarlWoot vorausgesetzt, Sie haben denselben Avatar wie roucer81, ist dies entweder ein astronomischer Zufall einer Hashcode-Kollision oder Sie haben dieselbe E-Mail-Adresse. Wenn Sie dieselbe Person hinter beiden Konten haben, sollten Sie das "Kontakt" -Formular verwenden, um sie zusammenzuführen, damit Sie den Ruf, den Sie durch die Aufwertung dieser Antwort erlangt haben, richtig gutschreiben können.