Etwas Hilfsinformatik für die PCI-Auditoren in meinem Publikum.
Ich gebe Ihnen eine Reihe von zufälligen ganzen Zahlen. Woran erkennt man, ob die Nummer drei drin ist?
Nun, es gibt den offensichtlichen Weg: Überprüfen Sie die Zahlen nacheinander, bis Sie die „3“ finden, oder erschöpfen Sie das Array. Lineare Suche. Bei 10 Zahlen muss davon ausgegangen werden, dass es 10 Schritte dauern kann. N Zahlen, N Schritte.
Bild 1.png
Die lineare Suche ist schlecht. Es ist schwer, schlimmer als linear zu machen. Lass es uns verbessern. Sortieren Sie das Array.
Bild 2.png
Ein sortiertes Array schlägt eine andere Strategie vor: Springen Sie in die Mitte des Arrays und prüfen Sie, ob der gesuchte Wert kleiner (links) oder größer (rechts) ist. Wiederholen Sie diesen Vorgang, indem Sie das Array jedes Mal halbieren, bis Sie den Wert gefunden haben.
Binäre Suche. Bei 10 Zahlen dauert es bis zu 3 Schritte - log2 von 10 -, um eine davon in einem sortierten Array zu finden. O (log n) Suche ist fantastisch. Wenn Sie 65.000 Elemente haben, müssen Sie nur 16 Schritte ausführen, um eines davon zu finden. Verdopple die Elemente und es sind 17 Schritte.
Aber sortierte Arrays saugen; Zum einen ist das Sortieren teurer als die lineare Suche. Daher verwenden wir die binäre Suche nicht oft. Stattdessen verwenden wir binäre Bäume.
Bild 3.png
Um einen binären Baum zu durchsuchen, fangen Sie oben an und fragen sich: "Ist mein Schlüssel kleiner als (links) oder größer als (rechts) der aktuelle Knoten?" Aber dieser Baum ist hübsch, nicht wahr?
Die Suche mit einem (ausgeglichenen) Binärbaum ist wie die Binärsuche O (log n) und variiert mit der Anzahl der Elemente im Baum. Binäre Bäume sind fantastisch: Sie erhalten eine schnelle Suche und sortierte Durchquerung, etwas, das Sie nicht aus einer Hash-Tabelle herausholen können. Binärbäume sind eine bessere Standardtabellenimplementierung als Hashtabellen. 2.
Aber binäre Bäume sind nicht der einzige baumstrukturierte Suchmechanismus. Binäre Grundversuche, auch PATRICIA-Bäume genannt, funktionieren wie binäre Bäume mit einem grundlegenden Unterschied. Anstatt an jedem Knoten größer als / kleiner als zu vergleichen, prüfen Sie, ob ein Bit gesetzt ist, und verzweigen nach rechts, wenn es gesetzt ist, und nach links, wenn es nicht gesetzt ist.
Bild 4.png
Ich lasse eine Menge darüber aus, wie Binär-Radix-Versuche funktionieren. Das ist eine Schande, weil Radix-Versuche notorisch unterbewertet sind - Sedgewick hat sie in „Algorithmen“ schändlicherweise vermasselt, und die Wikipedia-Seite für sie ist zum Kotzen. Die Leute streiten sich immer noch darüber, wie man sie nennt! Anstelle einer Erklärung von Backlinks und mit Bitpositionen beschrifteten Kanten ist hier eine winzige Ruby-Implementierung.
Hier ist, warum Radix-Versuche cool sind:
Search performance varies with the key size, not the number of elements in the tree. With 16 bit keys, you’re guaranteed 16 steps
unabhängig von der Anzahl der Elemente im Baum, ohne Ausgleich.
More importantly, radix tries give you lexicographic matching, which is a puffed-up way of saying “search with trailing wildcard”, or
"Suche nach Befehlszeilenabschluss". In einem Radixbaum können Sie schnell nach "ro *" suchen und "rom" und "romulous" und "roswell" erhalten.
3.
Ich habe dich verloren.
Lassen Sie uns dies in einen Kontext setzen. Versuche sind eine entscheidende Datenstruktur für das Internet-Routing. Das Routing-Problem sieht folgendermaßen aus:
You have a routing table with entries for “10.0.1.20/32 -> a” and “10.0.0.0/16 -> b”.
You need packets for 10.0.1.20 to go to “a”
You need packets for 10.0.1.21 to to to “b”
Dies ist ein schwieriges Problem, das mit einem einfachen Binärbaum zu lösen ist, aber mit einem Radix-Versuch fragen Sie nur nach „1010.0000.0000.0000.0000.0001.0100“ (für 10.0.1.20) und „1010“ (für 10.0.0.0 ). Die lexikografische Suche ergibt die beste Übereinstimmung für das Routing. Sie können es im obigen Ruby-Code versuchen. Fügen Sie dem Versuch * ”10.0.0.0” .to_ip hinzu und suchen Sie nach ”10.0.0.1” .to_ip.
Die Korrespondenz zwischen Routing- und Radix-Versuchen ist so stark, dass die beliebteste universelle Radix-Trie-Bibliothek (die von CPAN) tatsächlich aus GateD gestohlen wird. Übrigens ist es ein Durcheinander, und benutze es nicht.
Wenn Sie wissen, wie ein Versuch funktioniert, wissen Sie auch, wie reguläre Ausdrücke funktionieren. Versuche sind ein Sonderfall deterministischer endlicher Automaten (DFAs), bei denen Verzweigungen ausschließlich auf Bitvergleichen basieren und immer vorwärts verzweigen. Eine gute Regex-Engine verarbeitet nur DFAs mit mehr "Funktionen". Wenn meine Bilder für Sie einen Sinn ergeben, werden die Bilder in diesem hervorragenden Artikel über den NFA-DFA-Reduktionsalgorithmus von Thompson Sie auch schlauer machen. 4.
Sie sind ein Netzwerkbetreiber bei einem Backbone-ISP. Ihre Welt besteht größtenteils aus "Präfixen" - IP-Netzwerk / Netzmasken-Paaren. Die Netzmasken in diesen Präfixen sind für Sie von enormer Bedeutung. Zum Beispiel gehört 121/8 zu Korea; 121.128 / 10 gehört Korea Telecom, 121.128.10 / 24 gehört einem KT-Kunden und 121.128.10.53 ist ein Computer in diesem Kunden. Wenn Sie ein Botnetz oder einen Spam-Vorgang oder die Verbreitung von Würmern aufspüren, ist diese Netzmaskennummer für Sie ziemlich wichtig.
Ungeachtet ihrer Bedeutung ist auf einem IP-Paket nirgendwo eine "Netzmaske" eingeprägt - Netzmasken sind lediglich ein Konfigurationsdetail. Wenn Sie also den Verkehr beobachten, müssen Sie im Wesentlichen mit den folgenden Daten arbeiten:
ips.png
Erstaunlicherweise sind dies bei genügend Paketen genug Informationen, um Netzmasken zu erraten. Kenjiro Cho hat sich bei Sony eine sehr elegante Methode ausgedacht, die auf Versuchen basiert. Hier ist wie:
Nehmen Sie einen einfachen Binär-Radix-Versuch, wie er auch von Software-Routern verwendet wird. Binden Sie jedoch die Anzahl der Knoten im Baum auf 10.000. Wenn Sie bei einer Backbone-Verbindung Adressen aus IP-Headern aufzeichnen, werden Sie in wenigen Augenblicken 10.000 Knoten ausschöpfen.
Speichern Sie die Liste der Knoten in einer Liste, die in LRU-Reihenfolge sortiert ist. Mit anderen Worten, wenn Sie eine IP-Adresse mit einem Knoten abgleichen, berühren Sie den Knoten und halten Sie ihn an der Spitze der Liste. Allmählich sprudeln häufig gesehene Adressen nach oben, und selten gesehene Knoten sinken nach unten.
Bild 6.png
Nun der Trick. Wenn Ihnen die Knoten ausgehen und Sie einen neuen benötigen, fordern Sie ihn am Ende der Liste erneut an. Wenn Sie dies jedoch tun, rollen Sie die Daten vom Knoten in den übergeordneten Knoten. Gehen Sie dazu folgendermaßen vor:
Bild 5.png
10.0.1.2 und 10.0.1.3 sind Geschwister / 32s, die beiden Hälften von 10.0.1.2/31. Um sie zurückzugewinnen, führen Sie sie in 10.0.1.2/31 zusammen. Wenn Sie 10.0.1.2/31 zurückfordern müssen, können Sie es mit 10.0.1.0/31 zusammenführen, um 10.0.1.0/30 zu bilden.
Wenn Sie dies beispielsweise nach einer Minute tun, werden die herausragenden Quellen ihre Position im Baum verteidigen, indem sie an der Spitze der LRU-Liste bleiben, während Umgebungsgeräusche / 32 bis zu / 0 ansteigen. Für die obige unformatierte Liste von IPs mit einem 100-Knoten-Baum erhalten Sie dies.
Cho nennt das heuristische Aguri. 5.
Aguri ist BSD-lizensiert. Sie können es herunterladen und ein Treiberprogramm, das Pakete über pcap überwacht, von Chos alter Homepage herunterladen. 6.
Ich gehe damit irgendwohin, aber ich schreibe jetzt 1300 Wörter in diesen Beitrag, und wenn Sie eine Person mit Algorithmen sind, sind Sie jetzt müde von mir, und wenn Sie nicht sind, sind Sie müde von mir jetzt. Also, lass Aguri eintauchen und ich gebe dir etwas Cooles und Nutzloses, um es später in dieser Woche zu tun.
Dort sind zahlreiche Links verstreut. Leider behält Archive.org nicht die Bilder, sondern nur den Text, so dass einige von ihnen verloren gegangen sind. Hier sind die, die es archiviert hat: