Digitales Suchen / Digitale Suchbäume Verschiedene Suchverfahren laufen in der Weise ab, dass sie die Suchschlüssel bitweise untersuchen, anstatt in jedem Schritt vollständige Vergleiche zwischen Schlüsseln durchzuführen. Diese Methoden, die digitale Suchverfahren (radix-searching methods) genannt werden, arbeiten mit den Bits der Schlüssel selbst, im Gegensatz zu der transformierten Variante der Schlüssel, die beim Hashing verwendet wird. Ebenso wie im Falle der digitalen Sortierverfahren gilt, dass diese Methoden von Nutzen sein können, wenn die Bits der Suchschlüssel leicht zugänglich und die Werte der Suchschlüssel gut verteilt sind. G.Heyer 1 Algorithmen und Datenstrukturen Die Hauptvorteile sind, dass • sie ohne Komplikationen, die bei ausgeglichenen Bäumen auftreten, ein annehmbares Verhalten im ungünstigsten Fall gewährleisten, • sie einen einfachen Weg zur Behandlung von Schlüsseln mit variabler Länge bieten, • einige von ihnen Einsparungen von Speicherplatz ermöglichen, indem sie einen Teil des Schlüssels innerhalb der Suchstruktur speichern und dass • sie einen sehr schnellen Zugriff auf die Daten realisieren können, der sowohl mit binären Suchbäumen als auch mit Hashing vergleichbar ist. G.Heyer 2 Algorithmen und Datenstrukturen Die Nachteile sind, dass • Daten mit einer gewissen Systematik zu entarteten Bäumen mit schlechtem Verhalten führen können und • dass einige der Verfahren den Platz sehr ineffizient ausnutzen. Wie die digitalen Sortierverfahren sind auch diese Methoden dazu bestimmt, spezielle Eigenschaften der Architektur eines Computers auszunutzen. G.Heyer 3 Algorithmen und Datenstrukturen Digitale Suchbäume Das einfachste digitale Suchverfahren ist die Suche mit digitalen Bäumen; der Algorithmus ist genau der gleiche wie der für die Suche in einem Binärbaum, mit dem Unterschied, dass in dem Baum nicht entsprechend dem Ergebnis des Vergleiches zwischen den Schlüsseln verzweigt wird, sondern entsprechend den Bits des Schlüssels. Auf der ersten Ebene wird das führende Bit benutzt, auf der zweiten Ebene das zweite führende Bit usw., bis ein äußerer Knoten vorgefunden wird. Das Programm hierfür ist praktisch das gleiche wie für die Suche in einem Binärbaum. Der einzige Unterschied ist, dass die Vergleiche der Schlüssel durch Aufrufe der Funktion bits ersetzt werden, die beim digitalen Sortieren verwendet wurde. G.Heyer 4 Algorithmen und Datenstrukturen unsigned bits ( unsigned x, int k, int j ) { return (x >> k ) & ~ (~ 0 << j) ; } j sind Bits, die k Stellen von rechts in x erscheinen. Die Funktion kann in Maschinensprache effizient implementiert werden, indem um k Bits nach rechts geschoben wird und dann alle Bits außer den rechts befindlichen j Bits auf 0 gesetzt werden. int digitalsearch ( int v) { struct node *x = head; int b = maxb; z -> key = v ; while ( v != x -> key) x = ( bits (v, b--,1)) ? x -> r : x -> l ; return x -> info; } G.Heyer 5 Algorithmen und Datenstrukturen Datenstruktur: die gleiche wie die für elementare binäre Suchbäume Die Konstante maxb ist die Anzahl der Bits in den Schlüsseln, die zu sortieren sind. Für das Programm wird angenommen, dass das erste Bit in jedem Schlüssel ( das (maxb + 1) -te von rechts) 0 ist, so dass die Suche bei head beginnt, welches eine Verkettung ist, die auf den Kopfknoten eines Baumes mit dem Schlüssel 0 und mit einer auf den Suchbaum zeigenden linken Verkettung zeigt. Daher ist die Prozedur zur Initialisierung für dieses Programm die gleiche wie für die Suche in einem Binärbaum, abgesehen davon, dass man mit head -> l = z anstatt mit head -> r = z beginnt. Hier wird vorausgesetzt, dass alle Schlüssel, die in der Datenstruktur auftreten, unterschiedlich sind. G.Heyer 6 Algorithmen und Datenstrukturen A 00001 S 10011 E 00101 R 10010 C 00011 H 01000 I 01001 N 01110 G 00111 X 11000 M 01101 P 10000 L 01100 G.Heyer Annahme: der i-te Buchstabe des Alphabets wird durch die aus fünf Bits bestehende Binärdarstellung von i repräsentiert. Um Konsistenz mit bits zu erreichen, betrachten wir die Bits von rechts nach links mit 0 bis 4 durchnumeriert. ( demnach ist Bit 0 das einzige von 0 verschiedene Bit von A, und Bit 4 ist das einzige von 0 verschiedene Bit von P) 7 Algorithmen und Datenstrukturen Ein digitaler Suchbaum A 0 1 E 0 0 C 1 0 0 H 1 1 0 G 10 S I 1 0 0 0 L G.Heyer 0 M N 1 0 1 R P 1 0 X 1 1 1 1 8 Algorithmen und Datenstrukturen Prozedur für das Einfügen in digitale Suchbäume digitalinsert (int v, int info ) { struct node *p, *x = head; int b = maxb; while (x != z ) { p=x; x = ( bits ( v, b--, 1 )) ? x--> r : x --> l ; } x = (struct node *) malloc (sizeof *x); x --> key = v ; x --> info = info ; x --> l = z ; x --> r = z ; if ( bits ( v, b+1, 1 ) ) p --> r = x ; else p --> l = x; } G.Heyer 9 Algorithmen und Datenstrukturen Beispiel: Einfügen eines neuen Knotens, wenn der Schlüssel Z = 11010 zu dem Baum hinzugefügt wird. Durchlaufen des digitalen Suchbaums: • zweimal nach rechts wenden, da die beiden führenden Bits von Z 1 sind, • dann nach links, wo der äußere Knoten links von X liegt, wo Z eingefügt wird. Der ungünstigste Fall für Bäume, die mit digitaler Suche erzeugt werden, ist viel besser als für binäre Suchbäume, wenn die Anzahl der Schlüssel groß ist und die Schlüssel nicht lang sind. Die Länge des längsten Pfades in einem digitalen Suchbaum ist gleich der längsten Übereinstimmung in den führenden Bits zwischen zwei beliebigen Schlüsseln im Baum, und diese ist für viele Anwendungen meist relativ klein (z. B. wenn die Schlüssel aus zufälligen Bits bestehen ). G.Heyer 10 Algorithmen und Datenstrukturen Eigenschaften eines digitalen Suchbaumes: Ein Suchen oder Einfügen in einem digitalen Suchbaum erfordert durchschnittlich ungefähr lg N Vergleiche und im ungünstigsten Fall b Vergleiche, wenn der Baum aus N zufälligen Schlüsseln mit b Bits erzeugt wurde. Es ist offensichtlich, dass kein Pfad jemals länger sein kann als die Anzahl der Bits in den Schlüsseln; z. B. kann ein digitaler Suchbaum, der aus Schlüsseln mit acht Zeichen erzeugt wurde, mit beispielsweise sechs Bits pro Zeichen, keinen Pfad besitzen, der länger als 48 ist, selbst wenn es Hunderttausende von Schlüsseln gibt. Aus einer Analyse geht hervor, dass digitale Suchbäume nahezu ausgeglichen sind, da das „nächste“ Bit eines zufälligen Schlüssels mit der gleichen Wahrscheinlichkeit 0 oder 1 sein kann, so dass auf beide Seiten eines jeden Knotens jeweils die Hälfte entfällt. G.Heyer 11 Algorithmen und Datenstrukturen Digitale Such-Tries Sehr oft liegt der Fall vor, dass Suchschlüssel sehr lang sind, vielleicht zwanzig Zeichen oder mehr. In einer solchen Situation können die Kosten, die beim Vergleich eines Suchschlüssels hinsichtlich der Gleichheit mit einem Schlüssel aus der Datenstruktur entstehen, zu dominierenden Kosten werden, die nicht vernachlässigt werden können. Bei der Suche mit Hilfe digitaler Bäume wird ein solcher Vergleich bei jedem Knoten des Baumes vorgenommen. Ziel: Verringerung der Vergleiche G.Heyer 12 Algorithmen und Datenstrukturen Neue Idee: die Schlüssel nicht im Knoten des Baumes zu speichern, sondern stattdessen alle Schlüssel in äußeren Knoten des Baumes anzuordnen. Das heißt, anstatt für äußere Knoten der Struktur z zu verwenden, sieht man Knoten vor, die die Suchschlüssel enthalten. Es entstehen 2 Typen von Knoten: • innere Knoten, die nur Verkettungen zu anderen Knoten enthalten und • äußere Knoten, die Schlüssel und keine Verkettungen enthalten. (Fredkin nannte diese Methode „trie“, da sie für das Wiederauffinden „retrieval“ von Nutzen ist.) G.Heyer 13 Algorithmen und Datenstrukturen Suchen eines Schlüssels in einer solchen Struktur: Entsprechend seiner Bits abzweigen (aber kein Vergleich ) bis man an einen äußeren Knoten gelangt. Jeder Schlüssel im Baum wird in einem äußeren Knoten des Pfads gespeichert, der von dem führenden Bitmuster des Schlüssels beschrieben wird, und jeder Suchschlüssel gelangt zu einem äußeren Knoten, so dass ein vollständiger Vergleich der Schlüssel die Suche beendet. Beispiel eines (binären) digitalen Such-Tries für die Schlüssel A S E R C E A C R G.Heyer 14 S Algorithmen und Datenstrukturen Interessante Eigenschaften eines digitalen Such-Trie Die Struktur des Trie ist unabhängig von der Reihenfolge, in der die Schlüssel eingefügt werden. Für jede gegebene Menge von unterschiedlichen Schlüsseln existiert ein eindeutiger Trie. Für die Implementation dieses Verfahrens in C sind zwei Typen von Knoten erforderlich, wobei Verkettungen in inneren Knoten auf Knoten beider Typen zeigen können. Der linke Unterbaum eines binären digitalen Such-Trie enthält alle Schlüssel, die als führendes Bit 0 haben; der rechte Unterbaum enthält alle Schlüssel, die 1 als führendes Bit haben. Dies führt zu einer unmittelbaren Entsprechung zum digitalen Sortieren. Bei der Suche mit binären Tries wird die Datei in genau der gleichen Weise zerlegt wie bei Radix Exchange Sort. Diese Entsprechung ist analog zu der Entsprechung zwischen der Suche in einem Binärbaum und Quicksort. G.Heyer 15 Algorithmen und Datenstrukturen Weitere Eigenschaften von digitalen Such-Tries Ein Suchen oder Einfügen in einem digitalen Such-Trie erfordert ungefähr lg N Bitvergleiche für eine durchschnittliche Suche und b Bitvergleiche im ungünstigsten Fall, wenn der Baum aus N zufälligen Schlüsseln mit b Bits erzeugt wurde. Eine störende Eigenschaft von digitalen Tries, welche sie von den anderen Typen von Suchbäumen unterscheidet , ist die „Einweg-“Verzweigung, die für Schlüssel erforderlich ist, die eine große Anzahl Bits gemeinsam haben. Zum Beispiel erfordern Schlüssel, die sich nur im letzten Bit unterscheiden, einen Pfad, dessen Länge gleich der Länge des Schlüssels ist, gleichgültig, wie viele Schlüssel im Baum vorhanden sind. Die Zahl der inneren Knoten kann um einiges größer sein als die Zahl der Schlüssel. G.Heyer 16 Algorithmen und Datenstrukturen Ein digitaler Such-Trie, der aus N zufälligen Schlüsseln mit b Bits erzeugt wurde, besitzt im Durchschnitt ungefähr N/ln2 = 1,44 N Knoten. Beispiel: Ein Such-Trie, der aus 95 zufälligen Schlüsseln mit 10 Bits erzeugt wurde, besitzt 131 Knoten. Ziel: Die Höhe von Tries ist zwar durch die Anzahl von Bits in den Schlüsseln begrenzt, doch wäre es wünschenswert, die Möglichkeit zur Verarbeitung von Datensätzen mit sehr langen Schlüsseln (z. B. 1000 Bits oder mehr ) zu betrachten. Die Schlüssel könnten evtl. eine gewisse Einheitlichkeit aufweisen, wie dies bei Daten der Fall sein könnte, die kodierten Text darstellen. Ein Weg, um die Pfade in den Bäumen zu verkürzen, besteht in der Verwendung von deutlich mehr als zwei Verkettungen pro Knoten ( obwohl dadurch ein „Platz“-Problem infolge der Verwendung zu vieler Knoten entstehen kann; ein anderer Weg ist das „Verkürzen“ von Pfaden, die EinwegAbzweigungen zu einzelnen Verkettungen enthalten. G.Heyer 17 Algorithmen und Datenstrukturen Huffman-Codierung Der erste Schritt zur Erzeugung des Huffmann-Codes besteht darin, durch Zählen der Häufigkeit jedes Zeichens innerhalb der zu kodierenden Zeichenfolge zu ermitteln. Das folgende Programm ermittelt die Buchstabenhäufigkeiten einer Zeichenfolge a und trägt sie in ein Feld count [26] ein. Die Funktion index dient dazu, dass der Häufigkeitswert für den i-ten Buchstaben des Alphabets in dem Eintrag count [i] eingetragen wird, wobei wie üblich der Index 0 für das Leerzeichen verwendet wird. for (i = 0; i <= 26 ; i++ ) count [i] = 0; for ( i = 0; i < M ; i++ ) count [ index(a[i])] ++; Nehmen wir z. B. an, dass wir die Zeichenfolge “A SIMPLE STRING TO BE ENCODEC USING A MINIMAL NUMBER OF BITS” kodieren möchten. Häufigkeiten sind: 11 Leerzeichen, 3 A, 3 B usw. G.Heyer 18 Algorithmen und Datenstrukturen Häufigkeitstabelle zum Beispielsatz: ABCDEFGHIJKLMN OPQRSTUVWXYZ k 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 count[k] 11 3 3 1 2 5 1 2 0 6 0 0 2 4 5 3 1 0 2 4 3 2 0 0 0 0 0 Der nächste Schritt ist der Aufbau des Kodierungs-Tries entsprechend den Häufigkeiten. Während der Erzeugung des Trie betrachten wir ihn als eine binären Baum mit Häufigkeiten, die in den Knoten gespeichert sind; nach seiner Erzeugung betrachten wir ihn dann als einen Trie für die Kodierung, in der beschriebenen Weise. G.Heyer 19 Algorithmen und Datenstrukturen Zunächst wird für jede von null verschiedene Häufigkeit ein Knoten des Baumes erzeugt. Dann werden die beiden Knoten mit den kleinsten Häufigkeiten ausgewählt, und es wird ein neuer Knoten erzeugt, der diese beiden Knoten als Nachfolger hat und dessen Häufigkeit einen Wert hat, der gleich der Summe der Werte für seine Nachfolger ist. ( Falls mehr als 2 Knoten mit der kleinsten Häufigkeit vorhanden sind, ist es gleichgültig, welche benutzt werden.) Danach werden die beiden Knoten mit der kleinsten Häufigkeit in diesem Wald ermittelt und eine neuer Knoten wird auf die gleiche Weise erzeugt. In dem man in dieser Weise fortfährt, werden immer größere Unterbäume hergestellt und gleichzeitig bei jedem Schritt die Anzahl der Bäume im Wald um eins verringert (zwei werden entfernt, einer wird hinzugefügt). Am Schluß sind alle Knoten miteinander zu einem einzigen Baum verbunden. G.Heyer 20 Algorithmen und Datenstrukturen Beispiel: Erzeugung eines Huffman-Baumes 6 3 3 2 4 5 3 1 1 2 4 2 6 3 3 2 4 5 3 1 2 4 3 2 2 5 1 2 11 1 2 6 3 3 2 4 5 3 1 3 2 2 5 1 2 11 2 4 1 3 3 2 2 5 1 11 2 ... G.Heyer 21 Algorithmen und Datenstrukturen Nächste Schritte zur Erzeugung des Tries 6 3 3 4 5 3 5 2 1 4 3 4 2 4 2 2 5 2 3 1 11 2 1 Zum Schluss liegen Knoten mit geringer Häufigkeit weit unten im Baum, Knoten mit großen Häufigkeiten in der Nähe der Wurzel des Baumes. G.Heyer 22 Algorithmen und Datenstrukturen Vollständiger Baum 60 23 11 5 37 16 12 6 6 3 6 3 3 8 3 1 21 2 4 2 8 4 2 4 4 5 23 5 2 2 2 1 G.Heyer 11 10 3 1 Algorithmen und Datenstrukturen Nunmehr kann der Huffman-Code abgeleitet werden, indem die Häufigkeiten an den unteren Knoten einfach durch die zugehörigen Buchstaben ersetzt werden und der Baum dann als ein Trie für die Kodierung angesehen wird, wobei, genau wie oben „links“ einem Bit 0 und „rechts“ einem Bit 1 im Code entspricht. Der Code für N ist 000, der Code für I ist 001, der Code für C ist 110100 usw. Die kleine Zahl oberhalb jedes Knotens in diesem Baum ist der Index für das Feld count, der angibt, wo die Häufigkeit gespeichert ist. Diese Angabe wird für die Untersuchung im Programm benötigt . Folglich ist count[33] = 11, der Summe der Häufigkeitszähler für N und I. G.Heyer 24 Algorithmen und Datenstrukturen Skizze für die Huffman-Kodierung zum Beispielsatz 27 28 29 33 N 31 32 I O 37 B 36 38 A 35 G 34 40 41 42 F 30 S M U L 39 E 43 T D R C G.Heyer 25 P Algorithmen und Datenstrukturen Huffman-Kodierung ist die beste Kodierung, da Buchstaben mit großen Häufigkeiten sich näher an der Wurzel befinden und mit weniger Bits verschlüsselt werden als bei anderen Kodierungen. Eigenschaft: Die Länge der kodierten Zeichenfolge ist gleich der gewichteten äußeren Pfadlänge des Huffman-Baumes. Die „gewichtete äußere Pfadlänge“ eines Baumes ist gleich der über alle äußeren Knoten gebildeten Summe der Produkte des „Gewichts“ (zugehöriger Häufigkeitszähler) mit der Entfernung von der Wurzel. Dies ist eine Möglichkeit, die Länge der kodierten Zeichenfolge zu berechnen; sie ist äquivalent zu der über alle Buchstaben gebildeten Summe der Produkte der Häufigkeit des Auftretens eines Buchstabens mit der Anzahl der Bits bei jedem Auftreten. G.Heyer 26 Algorithmen und Datenstrukturen Eigenschaft: Kein Baum mit den gleichen Häufigkeiten bei den äußeren Knoten hat eine kleinere gewichtete äußere Pfadlänge als der Huffman-Baum. Mit Hilfe des gleichen Prozesses kann ein beliebiger Baum rekonstruiert werden, um den Huffman-Baum zu erzeugen, doch ohne bei jedem Schritt unbedingt die zwei Knoten mit dem kleinsten Gewicht auszuwählen. Mittels Induktion lässt sich beweisen, dass keine Strategie zu einem besseren Ergebnis führen kann, als die, bei der zuerst die beiden kleinsten Gewichte ausgewählt werden. G.Heyer 27 Algorithmen und Datenstrukturen