Coma 2

download Coma 2

of 151

Transcript of Coma 2

Computerorientierte Mathematik II mit Java

Rolf H. M hring oTechnische Universit t Berlin a Institut f r Mathematik u

Sommersemester 2005

ii

VorbemerkungenDiese Vorlesung ist der zweite Teil des Zyklus Computerorientierte Mathematik und schliet sich direkt an die Computerorientierte Mathematik I an. Dieses Skript basiert auf meiner Vorlesung vom Sommersemester 2004. Zwei Studenten der Vorlesung, Elisabeth G nther und Olaf Maurer, haben u im Sommer 2004 eine ausgezeichnete Ausarbeitung der Vorlesung angefertigt, die von mir nur noch leicht uberarbeitet und erg nzt wurde. Das Resultat ist dieses Skript, das auch online unter a http://www.math.tu-berlin.de/coga/moehring/Coma/Skript-II-Java/ zur Verf gung steht. u Die Vorlesung umfasst folgende Punkte: Wir behandeln zun chst ein Sortierverfahren namens Bucketa sort, das durch besondere Anforderungen an die Schl sselmenge schon in linearer Zeit sortieren kann. u Dann werden B ume, insbesondere bin re B ume besprochen und wie diese zur Datenkompression a a a mit dem Huffman-Algorithmus genutzt werden k nnen. B ume nden als Suchb ume und insbesono a a dere als AVL-B ume weitere Verwendung. Wir kommen dann zu optimalen statischen Suchb umen a a und besprechen eine Alternative zum Suchen in B umen, das sogenannte Hashing. Den Abschluss a des Semesters bildet ein Kapitel uber Schaltkreistheorie und Programmierbare Logische Arrays.

iii

iv

VORBEMERKUNGEN

InhaltsverzeichnisVorbemerkungen Inhaltsverzeichnis 1 Bucketsort 1.1 Einfaches Bucketsort . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.1.1 1.1.2 1.1.3 1.2 Denition . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Implementation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Aufwandsanalyse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . iii v 1 2 2 4 4 5 6 8 9 13 15 15 15 17 22 25 26 27 29 v

Sortieren von Strings mit Bucketsort . . . . . . . . . . . . . . . . . . . . . . . . . . 1.2.1 1.2.2 1.2.3 Sortieren von Strings der L nge k . . . . . . . . . . . . . . . . . . . . . . . a Sortieren von Bin rzahlen . . . . . . . . . . . . . . . . . . . . . . . . . . . a Sortieren von Strings variabler L nge . . . . . . . . . . . . . . . . . . . . . a

1.3 2

Literaturhinweise . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

B ume und Priority Queues a 2.1 B ume . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . a 2.1.1 2.1.2 2.1.3 2.2 Grundbegriffe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Implementation von bin ren B umen . . . . . . . . . . . . . . . . . . . . . a a Traversierung von B umen . . . . . . . . . . . . . . . . . . . . . . . . . . . a

Priority Queues . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2.2.1 M gliche Implementationen einer Priority Queue . . . . . . . . . . . . . . . o

2.3 3

Literaturhinweise . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

Huffman Codes und Datenkompression

vi 3.1

INHALTSVERZEICHNIS Codierung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.1.1 3.2 3.3 Pr xcode . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . a 29 31 33 41 41 41 41 43 44 45 46 46 46 46 51 53 53 57 61 62 62 65 67 69 69 70 73 81 83 83

Der Huffman Algorithmus . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Weitere Datenkompressionsverfahren . . . . . . . . . . . . . . . . . . . . . . . . . 3.3.1 3.3.2 3.3.3 Der adaptive Huffmancode . . . . . . . . . . . . . . . . . . . . . . . . . . . Der run length code . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Der Lempel-Ziv Code . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

3.4 3.5 4

Abschlieende Bemerkungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Literaturhinweise . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

Suchb ume a 4.1 Basisoperationen in Suchb umen . . . . . . . . . . . . . . . . . . . . . . . . . . . . a 4.1.1 4.1.2 4.1.3 4.2 Suchen nach Schl ssel k . . . . . . . . . . . . . . . . . . . . . . . . . . . . u Einf gen eines Knoten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . u L schen eines Knoten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . o

Literaturhinweise . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

5

AVL-B ume a 5.1 5.2 5.3 Grunds tzliche Eigenschaften . . . . . . . . . . . . . . . . . . . . . . . . . . . . . a Rotationen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Die Basisoperationen in AVL-B umen . . . . . . . . . . . . . . . . . . . . . . . . . a 5.3.1 5.3.2 5.3.3 5.4 Suchen eines Knotens v . . . . . . . . . . . . . . . . . . . . . . . . . . . . Einf gen eines neuen Knotens v . . . . . . . . . . . . . . . . . . . . . . . . u L schen eines Knotens v . . . . . . . . . . . . . . . . . . . . . . . . . . . . o

Literaturhinweise . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

6

Optimale statische Suchb ume a 6.1 6.2 6.3 6.4 Statische Suchb ume allgemein . . . . . . . . . . . . . . . . . . . . . . . . . . . . a Optimalit t statischer Suchb ume . . . . . . . . . . . . . . . . . . . . . . . . . . . a a Konstruktion eines optimalen statischen Suchbaumes . . . . . . . . . . . . . . . . . Literaturhinweise . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

7

B-B ume a 7.1 Denition und Eigenschaften . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

INHALTSVERZEICHNIS 7.2 Basisoperationen in B-B umen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . a 7.2.1 7.2.2 7.2.3 7.3 8 Suchen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Einf gen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . u L schen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . o

vii 86 86 86 88 91 93 94 95 95 96 96 97

Literaturhinweise . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

Hashing 8.1 Hash-Funktionen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8.1.1 8.1.2 8.2 8.2.1 8.2.2 8.3 Divisionsmethode . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Multiplikationsmethode . . . . . . . . . . . . . . . . . . . . . . . . . . . . Chaining . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Offene Adressierung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

Kollisionsbehandlung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

Literaturhinweise . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 106 107

9

Schaltkreistheorie und Rechnerarchitektur 9.1 9.2

Schaltfunktionen und Schaltnetze . . . . . . . . . . . . . . . . . . . . . . . . . . . . 107 Vereinfachung von Schaltnetzen . . . . . . . . . . . . . . . . . . . . . . . . . . . . 115 9.2.1 9.2.2 9.2.3 Das Verfahren von Karnaugh . . . . . . . . . . . . . . . . . . . . . . . . . . 117 Das Verfahren von Quine und McCluskey . . . . . . . . . . . . . . . . . . . 120 Das Uberdeckungsproblem . . . . . . . . . . . . . . . . . . . . . . . . . . . 125 Addierwerke . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 128 Das Fan-In-Problem . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 130 Aufbau eines PLAs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 131 Zur Programmierung von PLAs . . . . . . . . . . . . . . . . . . . . . . . . 133

9.3

Schaltungen mit Delays . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 126 9.3.1 9.3.2

9.4

PLAs und das Prinzip der Mikroprogrammierung . . . . . . . . . . . . . . . . . . . 131 9.4.1 9.4.2

9.5

Literaturhinweise . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 135 141 142

Literaturverzeichnis Index

viii

INHALTSVERZEICHNIS

Kapitel 1

BucketsortBucketsort ist ein Sortierverfahren, das grunds tzlich anders als alle Sortierverfahren funktioniert, die a wir bisher kennen gelernt haben. Es zeichnet sich dadurch aus, dass es nicht wie die Verfahren aus Teil I der Vorlesung auf paarweisen Vergleichen von Schl sseln basiert, sondern voraussetzt, dass u die Schl sselmenge klein und bekannt ist, und dass es Objekte direkt dem richtigen Bucket (Fach) u zuordnet. Anschaulich l sst sich dieses Verfahren mit der Verteilung der Post im Postamt auf die H user einer a a Strae vergleichen. Der Brieftr ger hat eine Reihe von F chern, die den Hausnummern entsprechen. a a Er geht die Briefe der Reihe nach durch und legt jeden Brief in O(1) (also konstanter) Zeit in das Fach mit der entsprechenden Hausnummer. Dabei k nnen in einem Fach nat rlich mehrere Briefe sein, die o u aber aus Sicht der Ordnungsrelation gleich sind (da sie in das gleiche Bucket sortiert werden, haben sie ja die gleiche Nummer) und daher nicht mehr innerhalb des Fachs sortiert werden m ssen. Der u Brieftr ger entnimmt die Briefe den F chern der Reihe nach und hat sie damit nach Hausnummern a a sortiert. Bei m Hausnummern und n Briefen sortiert er also in O(m + n). Da in der Regel m n gilt, wenn Bucketsort angewendet wird, bendet sich der Algorithmus dann in der Komplexit tsordnung (2n) = a (n) und man erh lt so also einen Sortieralgorithmus, dessen Aufwand linear von der Anzahl der zu a sortierenden Schl ssel abh ngt. u a Man beachte, dass die in der CoMa I ermittelte untere Komplexit tsschranke von (n log n) nur a Sortieralgorithmen betrifft, die auf paarweisen Vergleichen beruhen. Bucketsort beruht jedoch nicht auf paarweisen Vergleichen von Schl sseln und setzt auerdem zus tzliche Informationen uber die u a Schl sselmenge voraus. Daher liegt Bucketsort nicht in der Klasse der Sortieralgorithmen, die von u dieser Schranke betroffen sind. Umgesetzt auf Datenstrukturen bedeutet dies:

Version vom 24. M rz 2006 a

1

2 Wirklichkeit F cher a Hausnummern Stapel im Fach Briefe am Anfang Briefe am Ende

KAPITEL 1. BUCKETSORT Datenstruktur Array Array-Indizes Liste an jedem Array-Index Liste Liste

1.11.1.1

Einfaches BucketsortDenition

Wir geben jetzt einen Algorithmus f r die oben erkl rte Situation an. Gegeben seien also n Obu a jekte a1 , a2 , . . . , an mit Schl sselwerten s(a1 ), s(a2 ), . . . , s(an ) in einer Liste L. O.B.d.A. seien die u Schl sselwerte Zahlen zwischen 0 und m 1, also s(a j ) {0, 1, . . . , m 1}, j = 1, . . . , n. Es gibt u dann also genau m paarweise verschiedene Schl sselwerte. u Algorithmus 1.1 (Einfaches Bucketsort) 1. Initialisiere ein Array mit m leeren Queues Qi (Buckets), je eine f r jeden Wert i = 0, 1, . . . , m1 u und je einer Referenz (head bzw. tail) auf den Anfang und das Ende der Queue Qi . 2. Durchlaufe L und f ge das Objekt a j entsprechend seines Schl sselwertes in die Queue Qs(a j ) u u ein. 3. Konkateniere die Queues Q0 , Q1 , . . . , Qm1 uber die head- und tail-Referenzen zu einer Liste L und gebe L zur ck. u Beispiel 1.1 Sei m = 5 und n = 9. Die Liste L ist gegeben durch Abbildung 1.1.head r E a1 E a2 E a3 E a4 E a5 E a6 E a7 E a8 E a9

2

r

1

r

0

r

2

r

2

r

4

r

0

r

4

r

1

Abbildung 1.1: Liste L der zu sortierenden Elemente Es wird ein Array A mit je 2 Referenzen auf head und tail der Queues Qi eingerichtet. Abbildung 1.2 zeigt die Queues nach Abarbeitung der Liste L, also am Ende von Schritt 2. Dann werden die einzelnen Listen konkateniert. Das Konkatenieren der Listen ist sehr einfach, da nur das letzte Element ( ber tail) auf das erste Element ( ber head) der n chsten nichtleeren Liste gesetzt u u a werden muss, siehe Abbildung 1.3.

1.1. EINFACHES BUCKETSORT

3

Ar E a3 E a7

A[0] A[1] A[2] A[3] A[4]

0r r r r r r r r r

r

0

r r r

E a2 E a9

1

r r

1

E a1 E a4 E a5

2

2

2

r

E a6 E a8

4

r

4

r

Abbildung 1.2: Queues nach Abarbeitung der Liste

Ar E a3 E a7

A[0] A[1] A[2] A[3] A[4]

0r r r r r r r r r

r r r

0

r r r

E a2 E a9

1

1

E a1 E a4 E a5

2

2

2

r

E a6 E a8

4

r

4

r

Abbildung 1.3: Queues nach Konkatenation der Einzellisten

4

KAPITEL 1. BUCKETSORT

1.1.2

Implementation

Eine Implementation in Java k nnte in etwa wie folgt aussehen: o class QueuePointer { public ListNode head; public ListNode tail; } QueuePointer[] A = new QueuePointer[n]; // array with head- and tail-reference in each field Das Einf gen des Knoten node mit Schl sselwert i in die i-te Queue Qi geschieht dann mit einer u u Anweisung der Form A[i].tail.setNext(node); Die Konkatenation zweier Queues erfolgt uber die Anweisung A[i].tail.setNext(A[j].head); Dabei ist j die erste nichtleere Liste nach i.

1.1.3

Aufwandsanalyse

Satz 1.1 (Aufwand von einfachem Bucketsort) Algorithmus 1.1 sortiert die Liste korrekt in O(m + n) Zeit. Beweis: Am Ende von Schritt 2 enth lt jede Queue Qi nur Objekte a j mit Schl ssel s(a j ) = i. Die a u Konkatenation der Queues in der Reihenfolge Q0 , Q1 , . . . , Qm1 liefert also eine korrekt sortierte Liste. Das Einf gen von a j in die Queue Qi mit i = s(a j ) erfolgt durch Umh ngen von Referenzen in O(1) u a Zeit. Beim Durchlaufen der Liste L sind alle Vorg nger von a j bereits aus L entfernt und der Listena zeiger current der Liste L zeigt auf a j . Die Sequenz A[i].tail.setNext(current); A[i].tail = current; current = current.getNext(); h ngt a j aus L aus und in Qi ein. Das Einf gen aller Objekte a j geschieht also in O(n) Zeit. a u Das Konkatenieren der Queues kann in einer Schleife mit O(1) Aufwand pro Queue geschehen. Wir geben ein Code-Fragment an, das die Konkatenierung durchf hrt: u

1.2. SORTIEREN VON STRINGS MIT BUCKETSORT k = 0; while ((A[k].head == NULL) && (k < m)) k++; // k is now the first nonempty list in A, if there is one i = k + 1; while (i < m) { // while-loop: O(m) // at this point k is the last list we have already concatenated // we will now look for the next nonempty list after k while ((A[i].head == NULL) && (i < m)) i++; if (i==m) break; // if (i==m), we have iterated through all nonempty lists // in A and are finished // we have found a new nonempty list, concatenate it to k A[k].tail.setNext(A[i].head); // and prepare k for the next iteration k = i; i++; } // endwhile

5

Weil das Konkatenieren pro Queue nur einen Aufwand in O(1) ben tigt und maximal m Buckets kono kateniert werden m ssen, ist der Aufwand damit insgesamt O(n) + O(m) = O(m + n). u

Falls m n gilt, ist der Aufwand in der Komplexit tsklasse O(n). Bucketsort schl gt dann also die a a untere Komplexit tsschranke f r Sortieralgorithmen, die auf paarweisen Vergleichen beruhen. Daf r a u u ben tigt Bucketsort allerdings Informationen uber die Werte der auftretenden Schl ssel, weil sonst o u m nicht klein gehalten werden kann und der Aufwand von Bucketsort nur dann in O(n) liegt, wenn m n gilt.

1.2

Sortieren von Strings mit Bucketsort

Wir wollen jetzt Bucketsort zum Sortieren von Strings gem der lexikographischen Ordnung nutzen. a Wir denieren zuerst, was wir unter lexikographisch kleiner verstehen wollen: Sei S die Menge der Zeichen und eine lineare Ordnung auf S. Seien A = a1 a2 . . . a p und B = b1 b2 . . . bq zwei Strings der L nge p bzw. q uber S. Dann heit A lexikographisch kleiner als B, in a Zeichen A lex B

6 falls einer der folgenden F lle zutrifft: a

KAPITEL 1. BUCKETSORT

1. p q und ai = bi f r i = 1, . . . , p (d.h. A ist ein Anfangsst ck von B). u u 2. Es gibt j {1, . . . , p} mit a j b j und ai = bi f r i = 1, . . . , j 1 (d.h. an der ersten Stelle j, an u der A und B verschieden sind, ist a j kleiner oder gleich b j bez glich der linearen Ordnung auf u S). Beispiel 1.2 Hall lex Hallo, Arbeit lex Album

Wir sortieren mit Bucketsort lexikographisch, indem wir f r jede Komponente einfaches Bucketsort u verwenden. Zun chst betrachten wir den Spezialfall, dass alle Strings die gleiche L nge k haben. Dies a a beinhaltet insbesondere das Sortieren k-stelliger Bin rzahlen beziehungsweise von Strings auf der a Zeichenmenge Sb = {0, 1}.

1.2.1

Sortieren von Strings der L nge k a

Die Idee besteht darin, die Strings bez glich der Stellen mit Bucketsort zu sortieren, wobei die Stelu len von hinten nach vorn durchlaufen werden. Dies gew hrleistet, dass vor dem Bucketsort bez glich a u Stelle i (also nach k i Iterationen) die Strings bereits nach den letzten Stellen i + 1, . . . , k lexikographisch sortiert sind. Diese Sortierung wird trotz der sp teren Durchl ufe erhalten, weil durch Bucketsa a ort die Elemente, die an einer Stelle i gleich sind, in der gleichen Reihenfolge eingef gt werden, in der u sie schon waren (durch die vorherige Iteration beziehungsweise von Anfang an). Diese Eigenschaft eines Sortieralgorithmus bezeichnet man als Stabilit t, Bucketsort ist ein stabiler Sortieralgorithmus. a

Algorithmus 1.2 (Bucketsort) Input: Eine Liste L mit Strings A1 , A2 , . . . , An der L nge k mit Ai = ai1 , ai2 , . . . , aik und ai j S = a {0, 1, . . . , m 1} Output: Eine Permutation B1 , . . . , Bn von A1 , . . . , An mit Bi lex B2 lex lex Bn Methode: 1. Richte eine Queue Q ein und f ge A1 , . . . , An in Q ein. 1 u 2. Richte ein Array Bucket von m Buckets ein (wie beim einfachen Bucketsort) 3. for jede Stelle r := k downto 1 do 3.1 Leere alle Buckets Bucket[i] 3.2 while Q nicht leer ist do1 Einf gen bedeutet hier immer, dass nur Referenzen auf A eingef gt werden. Das Einf gen geschieht also in O(1) und u u u i nicht wie beim zeichenweisen Einf gen des Strings in O(k). u

1.2. SORTIEREN VON STRINGS MIT BUCKETSORT Sei A j das erste Element in Q Entferne A j aus Q und f ge es in Bucket[i] mit i = a jr ein u endwhile 3.3 Konkateniere die nichtleeren Buckets in die Queue Q endfor Beispiel 1.3 Sei S = {0, 1} und 0 < 1. Seien A1 = 010, A2 = 011, A3 = 101, A4 = 100. Wir sortieren zun chst nach der letzten Komponente: 010 011 101 100 a Dadurch erhalten wir Folgendes: 0 : 010 100 1 : 011 101 Wir erhalten: 0 : 100 101 1 : 010 011 Wir erhalten: 0 : 010 011 1 : 100 101 010 100 011 101

7

Wir sortieren dann nach der zweitletzten Komponente: 010 100 011 101

100 101 010 011

Nun die letzte Iteration, also Sortierung nach der ersten Komponente: 010 100 011 101

010 011 100 101

Nach dem Durchlauf der for-Schleife stehen die Strings in folgender Reihenfolge in Q: r=3 r=2 r=1 010 100 010 100 101 011 011 010 100 101 011 101 nach letzter Stelle sortiert nach letzten 2 Stellen sortiert nach letzten 3 Stellen sortiert

Wir sehen also: Das Sortierverfahren arbeitet (bei diesem Beispiel) korrekt. Das motiviert folgenden Satz: Satz 1.2 (Aufwand von Bucketsort) Algorithmus 1.2 sortiert A1 , . . . , An lexikographisch korrekt in O((m + n) k) Zeit. Beweis: Wir beweisen folgende Invariante: Nach dem i-ten Durchlauf sind die Strings bez glich der u letzten i Zeichen lexikographisch aufsteigend sortiert. Daraus folgt dann insbesondere, dass beim Sortieren von k-stelligen Strings nach dem k-ten Durchlauf die Strings bez glich der letzten k Stellen (also allen Stellen) lexikographisch korrekt sortiert sind u und damit die Behauptung. Der Beweis der Invariante erfolgt durch vollst ndige Induktion uber die a Iterationsschritte, hier bezeichnet mit r.

8 r = 1: (einfaches Bucketsort nach letzter Komponente):

KAPITEL 1. BUCKETSORT

In diesem Fall folgt die Korrektheit aus Satz 1.1, da die Strings nach Satz 1.1 lexikographisch korrekt nach der letzten Stelle sortiert werden. r r + 1: Die Behauptung sei also f r r bewiesen. Betrachte nun die beiden Strings Ai und A j in der (r + 1)-ten u Iteration. Wir unterscheiden zwei F lle: a Fall I: Ai und A j werden in der (r + 1)-ten Iteration in unterschiedliche Buckets sortiert. Da Ai und A j in unterschiedliche Buckets sortiert werden, unterscheiden sich Ai und A j also an der gerade betrachteten Stelle. Die lexikographische Korrektheit der Sortierung folgt dann wieder aus Satz 1.1, da wir ja wieder einfaches Bucketsort an der (r + 1)sten Stelle von hinten betrachten. Daher sind sie lexikographisch korrekt sortiert. Fall II: Ai und A j werden in der (r + 1)-ten Iteration in das gleiche Bucket sortiert. Da die einzelnen Buckets durch Queues realisiert werden, werden die Strings in der Reihenfolge, in der sie im vorigen Durchgang schon waren, hinten eingef gt und in der n chsten Iteration bezieu a hungsweise in der Konkatenation wieder in dieser Reihenfolge ausgelesen. Da die Strings aber nach der r-ten Iteration schon lexikographisch korrekt sortiert waren, sie sich aber an der (r + 1)-ten Stelle von hinten nicht unterscheiden, sind sie dann nach den letzten (r + 1) Stellen lexikographisch korrekt sortiert. Da f r jedes Zeichen in dem String genau ein Durchlauf erfolgt, erfolgen genau k Durchl ufe. Da u a jeder dieser Durchl ufe (wie in Satz 1.1 bewiesen) in O(n + m) erfolgt, erfolgt die ganze Sortierung a daher in O (k (n + m)).

1.2.2

Sortieren von Bin rzahlen a

Als spezielle Anwendung gibt es das Sortieren von n k-stelligen Bin rzahlen in O(k n). In CoMa I a wurde aber bewiesen, dass zum Sortieren von n Zahlen ein Aufwand in der Gr enordnung O(n log n) o erforderlich ist. Der scheinbare Widerspruch ist aber keiner: Mit k Stellen kann man nur 2k paarweise verschiedene Bin rzahlen bilden. F r die Darstellung von n a u paarweise verschiedenen k-stelligen Bin rzahlen muss daher gelten: a 2k n k log n Also gilt f r dieses von n abh ngige k: u a n log n k n

1.2. SORTIEREN VON STRINGS MIT BUCKETSORT

9

1.2.3

Sortieren von Strings variabler L nge a

Erste Idee: Die erste Idee f r diesen Sortieralgorithmus ist es, ein Bucket hinzuzuf gen, in das die Strings sortiert u u werden, die im aktuellen Durchlauf an der betrachteten Stelle kein Zeichen haben. Weil diese Strings lexikographisch kleiner sind, steht dieses Bucket vor allen anderen Buckets. Die Sortierung erfolgt dann einfach mit Algorithmus 1.2. Beispiel 1.4 Wir betrachten die Strings bab, abc und a und wollen sie in ihre lexikographisch korrekte Reihenfolge sortieren. In der ersten Iteration erhalten wir kein Zeichen : a a: a bab abc b: bab c: abc Dann sortieren wir nach dem zweitletzten Zeichen: kein Zeichen : a a: bab a bab abc b: abc c: Und schlielich sortieren wir noch nach dem ersten Zeichen: kein Zeichen : : a a abc a abc bab b: bab c: Durch diesen Ansatz werden in jeder Iteration alle Strings betrachtet. Bezeichne max die L nge des a l ngsten dieser Strings. Dann hat dieser Sortieralgorithmus den gleichen Aufwand wie der Sortierala gorithmus 1.2 zur Sortierung von Strings der festen L nge k = max . Nach Abschnitt 1.2.1 ist also der a Gesamtaufwand dieses Algorithmus in der Klasse O ( max (n + m)). Es geht aber besser: Bezeichne total die Gesamtanzahl der Zeichen. Wir k nnen den Algorithmus so o modizieren, dass er in O( total + m) liegt. Ideen fur einen besseren Algorithmus Sei wiedermax

die L nge des Strings mit der gr ten L nge. a o a

1. Sortiere die Strings Ai nach absteigender L nge i . a 2. Verwende max -mal Bucketsort wie vorher, aber betrachte in Phase r nur die Strings Ai , f r die u i max r + 1 gilt (also die Strings, die an der aktuell betrachteten Stelle ein Zeichen haben, weil sie gen gend lang sind). u

10

KAPITEL 1. BUCKETSORT 3. Um leere Buckets zu vermeiden, bestimme vorab die n tigen Buckets in jeder Phase und kono kateniere am Ende einer Phase nur die nichtleeren Buckets. (Verringert den Aufwand zur Konkatenation auf O(#nichtleer) statt O(m).)

Algorithmus 1.3

Input: Strings (Tupel) A1 , . . . , An Ai = (ai1 , ai2 , . . . , ai i ), ai j {0, . . . , m 1}

(oder auch ein beliebiges anderes Alphabet)max

= maxi

i

Output: Permutation B1 , . . . , Bn von A1 , . . . , An mit B1 lex B2 lex lex Bn Methode: 1. Generiere ein Array von Listen NONEMPTY[] der L nge max und f r jedes , 1 max eine a u Liste in NONEMPTY[ ], die angibt, welche Zeichen an einer der -ten Stellen vorkommen und welche Buckets daher in der ( total )-ten Iteration ben tigt werden. o Dazu: 1.1 Erschaffe f r jedes ai , 1 i n, 1 i ein Paar ( , ai ) (das bedeutet: das Zeichen ai u kommt an -ter Stelle in einem der Strings vor) 1.2 Sortiere die Paare lexikographisch mit Algorithmus 1.2, indem man sie als zweistellige Strings betrachtet. 1.3 Durchlaufe die sortierte Liste der ( , ai ) und generiere im Array NONEMPTY[], sortierte Listen, wobei das Array NONEMPTY[ ], 1 max eine sortierte Liste aller ai enth lt. a Dabei lassen sich auch gleich auf einfache Weise eventuell auftretende Duplikate entfernen. 2. Bestimme L nge i jedes Strings und generiere Listen LENGTH[ ] aller Strings mit L nge (nur a a Referenzen auf die Strings in LENGTH[ ] verwalten, daher nur O(1) f r Referenzen umh ngen) u a 3. Sortiere Strings analog zu Algorithmus 1.2.3, beginnend mitmax .

Aber:

nach der r-ten Phase enth lt Q nur die Strings der L nge max r + 1; diese sind lexia a kographisch korrekt sortiert bez glich der letzten r Komponenten. u NONEMPTY [] wird benutzt, um die Listen in BUCKET[] neu zu generieren und auerdem zur schnelleren Konkatenation der Einzellisten. Dies ist n tig, weil wir nur die nichtleeren o Buckets verwalten wollen.

vor dem r + 1-ten Durchlauf wird LENGTH [ max r] am Anfang2 der Queue Q eingef gt. u Die kurzen Strings stehen dann am Anfang und damit am lexikographisch richtigen Platz, falls sie mit anderen im selben Bucket landen.2 Das

ist zwar ein wenig ungew hnlich, bereitet aber grunds tzlich keine Probleme. o a

1.2. SORTIEREN VON STRINGS MIT BUCKETSORT

11

Wir erinnern noch einmal: BUCKET[] ist ein Array von Queues, in das sortiert wird, und Q ist eine Queue, die die Strings enth lt, die zur Zeit betrachtet werden, also gen gend lang sind. a u Wir geben nun Pseudocode f r Teil 3 von Algorithmus 1.3 an. u Algorithmus 1.4 1. Leere Q 2. for j:=0 to m 1 do 2.1 leere BUCKET[j] 3. for :=max

downto 1 do

3.1 F ge LENGTH[ ] am Anfang von Q ein u 3.2 while Q nicht leer do 3.2.1 Sei Ai erster String in Q 3.2.2 l sche Ai in Q und f ge Ai in BUCKET[ai ] ein o u 3.3 for jedes j in NONEMPTY[ ] do 3.3.1 f ge BUCKET[j] am Ende von Q ein u 3.3.2 leere BUCKET[j] Beispiel 1.5 Sortieren wir nun die gleichen Strings wie vorher, also a, bab und abc. Weil wir mit Referenzen arbeiten, spielt die Stringl nge f r das Einf gen keine Rolle. a u u Teil 1 des Algorithmus erzeugt durch einfaches Durchlaufen der Strings folgende Paare: (1, a), (1, b), (2, a), (3, b), (1, a), (2, b), (3, c) in der Liste. Daraus liefert Algorithmus 1.2 dann die sortierte Liste (1, a), (1, a), (1, b), (2, a), (2, b), (3, b), (3, c) Durch einfaches Durchlaufen dieser sortierten Liste von links nach rechts werden daraus die Listen im Array NONEMPTY[] mit den richtigen Eintr gen gef llt: a uNONEMPTY [1] NONEMPTY [2]

= a, b = a, b NONEMPTY [3] = b, c

Dann werden die L ngen der einzelnen Strings bestimmt: a1

= 1,

2

= 3,

3

=3LENGTH [],

Mit Hilfe dieser Information erzeugt der Algorithmus dann das Array eine Liste aller Strings der L nge enth lt: a a

wobei

LENGTH [

]

12

KAPITEL 1. BUCKETSORT

= a = 0 / LENGTH [3] = bab, abcLENGTH [1] LENGTH [2]

Nun f hren wir den dritten Teil des Algorithmus aus, dessen Pseudocode wir angegeben haben: u Zuerst werden in Q alle Elemente von letzten Stelle sortiert:LENGTH [ max ]

=

LENGTH [3]

eingef gt und dann nach der u

BUCKET[a]

= 0 / BUCKET[b] = bab BUCKET[c] = abc

Durch das Array NONEMPTY[] wissen wir, dass wir das erste Bucket gar nicht betrachten m ssen. Es u werden daher nur die letzten beiden Listen konkateniert und die Elemente am Ende von Q eingef gt. u Q enth lt nun bab,abc. Es wird daraufhin LENGTH[2] = 0 am Anfang von Q eingef gt. a / u Daraufhin wird Q nach der zweitletzten Stelle sortiert:BUCKET[a] BUCKET[b]

= bab = abc BUCKET[c] = 0 /

Wieder wissen wir schon, dass das letzte Bucket leer ist und brauchen es daher nicht zu betrachten. Es werden die ersten beiden Listen konkateniert und am Ende von Q eingef gt. Q enth lt nun bab, abc. u a Daraufhin wird LENGTH[1] = a am Anfang von Q eingef gt, Q enth lt nun also a, bab, abc. u a Daraufhin wird Q nach der ersten Stelle sortiert: Q[a] = a, abc Q[b] = bab Q[c] = 0 / Dann werden die Listen konkateniert und wir erhalten als Ergebnis: a, abc, bab.

Zum Aufwand Um die Paare zu generieren, m ssen alle Strings durchlaufen werden und f r jedes Zeichen in jedem u u der Strings muss genau ein Paar erzeugt werden. Der Aufwand daf r ist u O( 1 +2 + + n) = O

i=1

n

i

= O(

total )

Beim Sortieren der Paare ist

max

= 2. Dieser Teil erfordert einen Aufwand von

1.3. LITERATURHINWEISE

13

2. Komponente total Elemente

1.Komponente total Elemente

O 2

+ m +Buckets

+

max total

max Buckets

O(

total

+ m)

Um NONEMPTY[] einzurichten, m ssen wir nur die sortierte Liste der Paare einmal durchlaufen, das u geht also in O( total ). Das Berechnen der L nge (O( total )) und Erzeugen des Arrays LENGTH[] (das a lmax Elemente besitzt) geht in O( total + max ) O( total ). Satz 1.3 Algorithmus 1.3 sortiert die Liste korrekt in O(total

+ m) Zeit.

Beweis: Die Korrektheit ist klar, folgt wie in Algorithmus 1.2. Zum Aufwand: Teile 1,2 Teil 3: Sei n die Anzahl der Strings, deren L nge i gr er gleich ist. Sei m die Anzahl der verschiedenen a o Symbole, die an der Stelle auftreten. m ist dann auch gleichzeitig die L nge von NONEMPTY[ ]. a Ein Durchlauf der WHILE-Schleife 3.2 hat dann als Aufwand O(n ) (weil sich in jeder Iteration n Elemente in Q benden). Ein Durchlauf der FOR-Schleife 3.3 hat einen Aufwand von O(m ) (weil wir genau m Buckets verbinden m ssen). u Der Aufwand des Pseudocodes ist dann insgesamt: O (m + n ) .=1max

O( total ) + O( total + m) + Paare generieren Paare sortieren

O(

total )

+

O(LENGTH

total )

NONEMPTY

einrichten

einrichten

O

(m=1

max

+n )

= O O( O(

m=1 total )

max

+n=1 total )

max

total ) + O(

1.3

Literaturhinweise

Die Darstellung von Bucketsort folgt [HU79]. Varianten von Bucketsort (Counting Sort, Radix Sort) und eine average vase Analyse werden in [CLRS01] behandelt.

14

KAPITEL 1. BUCKETSORT

Kapitel 2

B ume und Priority Queues a2.1 B ume a

Bisher haben wir als dynamische Datenstrukturen Listen kennengelernt. Da der Zugriff in Listen in der Regel nur sequentiell erfolgen kann, ergibt sich f r das Einf gen bzw. Suchen in einer (sortierten) u u Liste bei L nge n ein linearer Aufwand. Das heit: a

O(n) im worst case und O(n/2) im average case. Dies ist f r viele Anwendungen, in denen ein sich dynamisch andernder Datenbestand verwaltet weru den muss, zu langsam (z.B. Verwaltung von Identiern in einem Programm durch den Compiler, Autorenkatalog einer Bibliothek, Konten einer Bank, usw.). Bessere Methoden bietet unter anderem die Datenstruktur der B ume, die in diesem Kapitel erl utert wird. a a

2.1.1

Grundbegriffe

Gerichtete B ume (kurz B ume) kann man auf zwei Arten erkl ren. Eine graphentheoretische Dea a a nition 1 wurde bereits in der Coma I im Zusammenhang mit Graphen behandelt. Etwas abstrakter ist die rekursive Denition, die in der Coma I in Zusammenhang mit der Rekursion erl utert wurde. Sie a wird hier noch einmal erkl rt und in Abbildung 2.1 visualisiert: a

1 Ein

gerichteter Baum ist ein Digraph T = (V, E) mit folgenden Eigenschaften:

Es gibt genau einen Knoten r, in dem keine Kante endet (die Wurzel von T ). Zu jedem Knoten i = r gibt es genau einen Weg von der Wurzel r zu i. Dies bedeutet, dass keine zwei Wege in den gleichen Knoten einm nden. Der Graph kann sich ausgehend von der Wurzel u also nur verzweigen. Daher kommt auch der Name Baum.

15

16 Ein Baum T ist entweder leer

KAPITEL 2. BAUME UND PRIORITY QUEUES

oder er entsteht aus endlich vielen, voneinander verschiedenen B umen T1 , . . . , Tn mit Wurzeln a w1 , . . . , wn , die in T als Teilb ume unter der Wurzel w von T (einem neuen Knoten) h ngen. a a w1 wn = w rd d d d wn w1 e . . . T e T1 e n e e e e e e e

e e T1 e Tn e ... e e e e e e

Abbildung 2.1: Baum, rekursiv aufgebaut Beispiele f r die Verwendung von B umen sind: u a Darstellung von Hierarchien Auswertung arithmetischer Ausdr cke u z.B.: ((a + b) (c + d))/e + f /g (siehe Abb. 2.6, Seite 24) Rekursionsbaum Im Zusammenhang mit B umen ist die folgenden Terminologie ublich: Bl tter, innere Knoten, Wura a zel, Kinder / S hne / Br der, Vater / Eltern, Nachfolger, Vorg nger und Teilb ume. Ein Knoten v kann o u a a einen Vater und S hne haben. Die S hne eines Vaters sind Br der. Hat ein Knoten keinen Vater, ist o o u er die Wurzel des Baumes. Hat er keine S hne, ist er ein Blatt. Wenn ein Knoten verschieden von der o Wurzel ist und mindestens einen Sohn hat, ist er ein innerer Knoten. Eine besondere Rolle spielen die bin ren B ume. Sie sind entweder leer oder bestehen aus der Wurzel a a und einem linken und einem rechten bin rem Baum (den Teilb umen). Jeder Knoten hat maximal a a zwei S hne, man spricht vom linken und vom rechten Sohn. In den folgenden Abschnitten werden o wir ausschlielich bin re B ume behandeln und deshalb das Wort Baum in der Bedeutung bin rer a a a Baum verwenden. Bekannte Beispiele bin rer B ume sind der Stammbaum mit Vater, Mutter und a a einer Person als deren Nachfolger (!) oder die Aufzeichnung eines Tennisturniers, in der jedes Spiel durch einen Knoten mit dem Namen des Gewinners charakterisiert ist und die beiden vorausgehenden Spiele als dessen Nachfolger aufgef hrt sind. u Die rekursive Struktur von B umen ist von groer Bedeutung f r viele Algorithmen auf B umen. a u a Auch viele charakteristische Gr en von B umen lassen sich rekursiv beschreiben oder denieren. o a

2.1. BAUME

17

Ein Beispiel daf r ist die H he von B umen. Die H he gibt den l ngsten Weg von der Wurzel bis zum u o a o a Blatt gemessen in Anzahl der Kanten an. Sie ergibt sich wie folgt: h(T ) = 1 falls T leer max{h(T1 ), h(T2 )} + 1 sonst (2.1)

Besteht T beispielsweise nur aus einem Knoten, ergibt sich aus Gleichung (2.1) die H he von T zu o h(T ) = max{1, 1} + 1 = 0.

2.1.2

Implementation von bin ren B umen a a

Im Folgenden wird gezeigt, wie sich bin re B ume als abstrakte Datenstruktur implementieren lassen. a a Ein Baum besteht aus Knoten und Kanten zwischen den Knoten. Die Knoten sind hier Objekte der inneren Klasse BinTreeNode. F r die Kanten nutzt man die Zeigereigenschaft von Referenzobjekten. u So kennt ein BinTreeNode das Objekt, das im Knoten steht, seinen linken und seinen rechten Sohn und in manchen Implementationen auch seinen Vater. Das wird in Abbildung 2.2 deutlich. Zus tzlich sind a get und set Methoden sinnvoll sowie Methoden, die testen, ob der linke bzw. rechte Sohn vorhanden sind. class BinTreeNode { Object BinTreeNode BinTreeNode

data; lson; rson;

// saved object // left son // right son

// sometimes also usefull BinTreeNode parent; // parent ... } // constructors, get methods, // set methods ...

Objektr

Ref. auf linken Sohn

r e e e

Ref. auf rechten Sohn

Abbildung 2.2: Struktur eines Knotens Wie in Abb. 2.3 dargestellt, ist ein Baum eine Verzeigerung von Knoten. Jeder BinTreeNode zeigt auf seine S hne und, wie oben schon erw hnt, in manchen Implementationen auch auf seinen Vater. o a

18

KAPITEL 2. BAUME UND PRIORITY QUEUES

Es gibt einen BinTreeNode, hier root genannt, dessen rechter (oder linker) Sohn immer auf die eigentliche Wurzel des Baumes zeigt. Zus tzlich gibt es eine Referenz curr (lies: karr), die auf a einen beliebigen Knoten im Baum zeigt und die auf jeden Knoten umgesetzt werden kann. rootq rr

r j r

Objekt

q

C Objekt

q s q Objekt

% q d d d

q

curr

q

Objekt

q d d dObjekt

q

q d d d

q

q d d d

...

... Abbildung 2.3: Baum, dargestellt als verkettete Struktur class BinTree { BinTreeNode dummy; BinTreeNode curr; ... } Das folgende Programm 2.1 stellt ein Beispiel einer abstrakten Klasse dar, von der bin re B ume a a abgeleitet werden k nnen. Einige Methoden werden im Folgenden genauer erkl rt. o a Programm 2.1 BinTree /** * abstract base class for all sorts of binary trees * * @author N.N. */ abstract class BinTree { /** * class for tree nodes */ protected class BinTreeNode {

// dummy node whose left son is the root // points at the current node

2.1. BAUME public BinTreeNode() { } // default constructor

19

public BinTreeNode(Object obj) { // init constructor } public boolean isLeaf() { } public boolean isRoot() { } public boolean isLeftChild() { } // is node a leaf in tree?

// is node root of tree?

// is node left child // of parent? // get left child

public BinTreeNode getLeftChild() { }

public BinTreeNode getRightChild() { // get right child } public BinTreeNode getParent() { } public String toString() { } } // class BinTreeNode // get parent

// conversion to string

/***

data

******************************************************/

/***

constructors

**********************************************/

// default constructor, initializes empty tree public BinTree() { } /*** get methods ***********************************************/ // is tree empty?

public boolean isEmpty() { }

20

KAPITEL 2. BAUME UND PRIORITY QUEUES // root node of tree // -> what should be returned if tree is empty?? protected BinTreeNode _getRoot() { } // current number of tree nodes public int getSize() { } // height of tree public int getHeight() { }

/***

set methods

***********************************************/

// switch debugging mode public static void setCheck(boolean mode) { }

/***

methods for current node

**********************************/

// reset current node to first node in inorder sequence public void reset() { } // does current node stand at end of inorder sequence? public boolean isAtEnd() { } // reset current node to successor in inorder sequence public void increment() { } // object referenced by current node public Object currentData() { } // ist current node a leaf? public boolean isLeaf() { }

2.1. BAUME /*** conversion methods ****************************************/

21

// convert tree to string // use getClass() somewhere so that class name of "this" shows public String toString() { }

/***

debugging methods

*****************************************/

// check consistency of links in entire tree protected boolean _checkLinks() { } } Es gibt viele Methoden, die man an oder mit B umen durchf hren kann. Dazu geh ren beispielsa u o weise Methoden zum Einf gen und L schen von Knoten, zum Durchlaufen des Baumes (vgl. Abu o schnitt 2.1.3 usw. Wir wollen uns eine m gliche Methode zum Berechnen der H he eines Baumes o o genauer anschauen. Diese benutzt die Gleichung 2.1 zur Berechnung der H he und nutzt die rekursio ve Struktur von B umen. a Programm 2.2 getHeight() int getHeight() { if (isEmpty()){ // empty tree return -1; } else { int lheight = _getRoot().getLeftSon().getHeight(); int rheight = _getRoot().getRightSon().getHeight(); return Math.max(rheight,lheight)+1; } } Implementation im Array B ume k nnen auch mit Hilfe von Arrays implementiert werden. Hierbei handelt es sich zwar nicht a o um eine dynamische Datenstruktur, diese Umsetzung ist allerdings f r manche Programmiersprachen u (z.B. FORTRAN) erforderlich. Die Idee hierbei ist, die Indizes als Zeiger auf die S hne zu nutzen. Das o l sst sich explizit (durch Abspeicherung) oder implizit (durch Berechnung) l sen. Bei der expliziten a o Variante sehen die Knoten so aus: class ArrayBinTreeNode { Object data; int lson;

22 int } rson;

KAPITEL 2. BAUME UND PRIORITY QUEUES

Der Baum wird dann, wie auch in Abbildung 2.4 veranschaulicht, als Array umgesetzt: ArrayBinTreeNode[] tree = new ArrayBinTreeNode[n];0 1 ... i j ... n2 n1

s cObjekt i j

... Abbildung 2.4: Baum als Array

Dazu geh ren nat rlich noch die oben schon dargestellten Zugriffsfunktionen. Die H he wird ebeno u o falls auf die schon erkl rte Weise rekursiv berechnet. a Bei der impliziten Variante werden die beiden S hne nicht im Knoten gespeichert, sondern in geto Methoden berechnet. Die Indizes der S hne des Knoten i ergeben sich bei bin ren B umen immer zu o a a 2i + 1 f r den linken Sohn und 2i + 2 f r den rechten Sohn. u u Der Nachteil an einer Implementation mit Arrays ist leider, dass man bei nicht vollen B umen im a Vergleich zur ublichen Implementation mehr Speicherplatz ben tigt. o

2.1.3

Traversierung von B umen a

Mit Traversierung eines Baumes bezeichnet man den Durchlauf von Knoten zu Knoten, um in jedem Knoten etwas zu tun. In den Knoten sind Daten, ahnlich wie in einer Liste, und um mit diesen arbeiten zu k nnen, m ssen sie nacheinander erreicht werden. Jedoch ist die Reihenfolge des Durchlaufens o u eines Baumes nicht mehr eindeutig wie bei einer Liste. Standardm ig benutzt man die folgenden a drei Traversierungen: WLR: Der Preorder-Durchlauf. Hier wird zuerst die Wurzel betrachtet, dann der linke Teilbaum mit derselben Regel und dann der rechte Teilbaum wieder mit der selben Regel. LWR: Der Inorder-Durchlauf. Hier wird zuerst der linke Teilbaum, dann die Wurzel und dann der rechte Teilbaum besucht, wobei die Teilb ume wieder mit derselben Regel durchlaufen werden. a LRW: Der Post-Durchlauf. Die Wurzel wird erst erreicht, nachdem zuerst der linke und dann der rechte Teilbaum jeweils mit derselben Regel durchlaufen wurden. Die K rzel WLR, LWR und LRW zeigen vereinfacht jeweils die Reihenfolge des Durchlaufens an. u Die Vorsilben Pre-, In- und Post- beziehen sich jeweils auf die Rolle der Wurzel.

2.1. BAUME

23

A B D E C F

Abbildung 2.5: Beispielbaum f r die Traversierung u Beispiel 2.1 Dieses Beispiel zeigt die drei Traversierungsm glichkeiten f r den Baum in Abbilo u dung 2.5. WLR: A, B, D, E, C, F LWR: D, B, E, A, C, F LRW: D, E, B, F, C, A Ist es einfach nur wichtig, unabh ngig von der Reihenfolge alle Knoten zu erreichen, spielt es keine a Rolle, welche Traversierung gew hlt wird. Allerdings gibt es verschiedene Anwendungen, die jeweils a unterschiedliche Reihenfolgen benutzen. Beim Aufrufbaum oder beim Rekursionsbaum beispielsweise, die in Coma I behandelt wurden, werden die Methoden in Postorder Reihenfolge abgearbeitet. Im folgenden Beispiel wird verdeutlicht, welchen Einuss die verschiedenen Reihenfolgen auf arithmetische Ausdr cke haben. u Beispiel 2.2 Der arithmetische Ausdruck ((a + b) (c + d))/e + f /g wird vom Compiler in einen Baum, wie in Abb. 2.6, umgewandelt. In diesem Baum stehen die Identier in den Bl ttern. In den inneren Knoten und der Wurzel stehen Operatoren. Diese verkn pfen jea u weils ihren linken Teilbaum als arithmetischen Ausdruck mit dem Ausdruck ihres rechten Teilbaums. Durchl uft man den Baum in Inorder, ergibt sich der arithmetische Ausdruck in Inx-Notation: a ((a + b) (c + d))/e + f /g Durchl uft man den Baum aber in Postorder, erh lt man den Ausdruck in Postx-Notation beziea a hungsweise umgekehrter polnischer Notation (UPN): ab + cd + e/ f g/+ Dieser wird dann vom Computer, wie in Coma I behandelt, mit Hilfe eines Stacks berechnet. Im Gegensatz zur Inx-Notation ist der Baum aus der Postx-Notation arithmetischer Ausdr cke ohu ne Hilfe von Klammern (re)konstruierbar. Indem man den Ausdruck in Postx-Notation von hinten durchl uft, kann man den Baum uber die Postorder-Reihenfolge von hinten nach vorne (wieder) aufa bauen.

24

KAPITEL 2. BAUME UND PRIORITY QUEUES

+ / + + /

e

f

g

a

b c

d

Abbildung 2.6: Ein arithmetischer Ausdruck als Baum dargestellt Implementation Um einen Baum in den verschiedenen Reihenfolgen zu durchlaufen, kann man sich in den JavaMethoden die rekursive Struktur der B ume n tzlich machen. Die Umsetzung zeigt die folgenden a u Methoden, die sinnvollerweise zur Klasse BinTree geh ren. o Programm 2.3 Traversierung eines Baumes void preOrderTraversal() { if (isEmpty()) { return; } // work on root getLeftSon().preOrderTraversal(); getRightSon().preOrderTraversal(); } void inOrderTraversal(){ if (isEmpty()) { return; } getLeftSon().inOrderTraversal(); // work on root getRightSon().inOrderTraversal(); } void postOrderTraversal() {

2.2. PRIORITY QUEUES if (isEmpty()) { return; } getLeftSon().postOrderTraversal(); getRightSon().postOrderTraversal(); // work on root } }

25

Neben den rekursiven Methoden gibt es auch die M glichkeit den Baum iterativ zu durchlaufen. Exo emplarisch wird hier nur die Inorder Traversierung angesprochen. Die Umsetzung wird in der Ubung behandelt. Zur iterativen Traversierung werden drei Methoden ben tigt: o 1. public void reset() 2. public void increment() 3. public boolean isAtEnd() Die Methode reset() sucht sich den am weitesten links stehenden Knoten des Baumes und setzt den curr-Zeiger auf diesen Knoten. Die Methode increment() setzt den curr-Zeiger auf den Nachfolger, also auf den n chsten Knoten entsprechend der Inorder-Reihenfolge. Die Methode isAtEnd() pr ft, a u ob der Inorder-Durchlauf das Ende erreicht hat. Objekte mit solchen Methoden bezeichnet man als Iterator und die Methoden werden dementsprechend Iteratormethoden genannt.

2.2

Priority Queues

Bei einer Priority Queue handelt es sich um eine Datenstruktur mit folgenden Kennzeichen: Sie hat einen homogenen Komponententyp, wobei jede Komponente einen Schl ssel (Wert) u besitzt. Die folgenden Operationen sind m glich: o 1. Einf gen einer Komponente u 2. Zugriff auf die Komponente mit dem kleinsten Wert 3. Entfernen der Komponente mit dem kleinsten Wert 4. Anderung des Wertes einer Komponente Die Priority Queue wurde schon in Coma I im Zusammenhang mit Heapsort behandelt. Jedoch lag dort die Aufmerksamkeit auf der Komponente mit dem gr ten Wert, nicht auf der mit dem kleinsten o Wert.

26

KAPITEL 2. BAUME UND PRIORITY QUEUES

2.2.1

M gliche Implementationen einer Priority Queue o

a) Als sortiertes Array Wenn die Anzahl n der zu speichernden Elemente bekannt ist, k nnen die Elemente in einem Array, o Abb. 2.7, gespeichert werden, wobei das kleinste Element in der ersten Komponente des Arrays steht und die ubrigen aufw rts sortiert folgen. Damit ist ein sehr schneller Zugriff auf das kleinste Element a gew hrleistet, jedoch dauern die ubrigen Operationen lange, wie in der folgenden Auistung zu sehen a ist. 1. Einf gen: u O(n) (bin re Suche + Verschieben) a 2. Zugriff: O(1) 3. Entfernen: O(n) 4. Wert andern: O(n)0 1 2 3 4 5 6 7

12 18 24 35 44 53 63 72

T

kleinstes Element Abbildung 2.7: Priority Queue als sortiertes Array Eine bessere Variante ist die folgende:

b) Als Heap Wie bei Heapsort wird das Array als Baum mit Heap-Eigenschaft aufgefasst. Die Heapeigenschaft ist dann erf llt, wenn die Wege von der Wurzel zu jedem Blatt jeweils aufsteigend sortiert sind. Zur Heru stellung der Heapeigenschaft wird die Methode heapify() verwendet. Ihre genauere Funktionsweise wurde bereits in Coma I erl utert. a0

12r rr2

1

184

536

d d3

d d5

35

24

63

72

7

44

Abbildung 2.8: Priority Queue als Heap F r die Operationen im Heap ergibt sich dann dieser Aufwand im worst case: u

2.3. LITERATURHINWEISE 1. Einf gen: u 2. Zugriff: 3. Entfernen: 4. Wert andern: O(log n) O(1) O(log n) O(log n) als Blatt in die letzte Arraykomponente einf gen u und nach oben wandern lassen letzte Komp. an die 0-te Stelle tauschen und absinken lassen aufsteigen oder absinken lassen

27

Also sind neben dem sehr schnellen Zugriff auf das kleinste Element auch die anderen Operationen schneller als im sortierten Array. Es gibt aber noch andere Implementationen, die die Operationen noch schneller, allerdings nur amortisiert, schaffen. Dazu geh ren zum Beispiel die Fibonacci Heaps. o

2.3

Literaturhinweise

B ume und Priority Queues werden in jedem Buch uber Datenstrukturen behandelt, vgl. etwa [CLRS01, Knu98, a OW02, SS02].

28

KAPITEL 2. BAUME UND PRIORITY QUEUES

Kapitel 3

Huffman Codes und DatenkompressionDas Ziel der Datenkompression ist es, Daten mit weniger Speicherplatz abzuspeichern. Abh ngig von a den Daten geschieht das verlustfrei oder nicht verlustfrei. Audio-, Video- und Bilddateien werden in der Regel komprimiert, indem Informationen weggelassen werden. Das Prinzip bei MP3-Dateien beispielsweise ist es, all die Informationen wegzulassen, die das menschliche Ohr nicht wahrnehmen kann. Somit stellt der Informationsverlust keinen Qualit tsverlust f r die gespeicherte Musik a u dar. Textdateien m chte man ohne Verlust von Informationen komprimieren. Um zu verstehen, wie o das funktioniert, muss man verstehen, wie Texte abgespeichert werden. Der folgende Abschnitt soll Aufschluss dar ber geben. u

3.1

Codierung

Wir betrachten einen Zeichensatz C (z.B. das Alphabet, ein Java Zeichensatz, alle W rter im Duden) o und ein Zeichen c C. Diese Zeichen werden im Computer mit Codew rtern codiert, und zwar uber o dem Alphabet {0, 1}. Die Gesamtheit der Codew rter f r den Zeichensatz C heit dann Code f r C. o u u Ublicherweise haben die Codew rter aller Zeichen eines Zeichensatzes C die selbe L nge. Ein solcher o a Code heit Blockcode. So sind auch der ASCII Code, bei dem alle Codew rter die L nge 8 haben, o a und der Unicode, bei dem alle Codew rter die L nge 16 haben, Blockcodes. o a Beispiel 3.1 (Blockcode der L nge 3) a Wenn C = {a, b, c, d, e, f , g, } ist, ist a b c d e f g 29 000 001 010 011 100 101 110 111

30

KAPITEL 3. HUFFMAN CODES UND DATENKOMPRESSION

ein zugeh riger Blockcode der L nge 3. o a Es gibt auch Codes, bei denen die L nge der Codew rter der einzelnen Zeichen eines Zeichensatzes a o C unterschiedlich lang ist, die sogenannten variable length codes. Beispiel 3.2 ( variable length code) Wir betrachten wieder C = {a, b, c, d, e, f , g, }. a b c d e f g 0 1 10 11 100 101 110 111

Eine Textdatei ist nun eine Folge von Zeichen c C. Um sie abzuspeichern, muss sie verschl sselt u werden. Die Verschl sselung oder auch Codierung entspricht dem Ersetzen jedes Zeichens durch u sein Codewort. Damit die Datei sp ter wieder lesbar ist, m ssen die Codew rter wieder in die Zeia u o chen des Zeichensatzes umgewandelt werden. Die Entschl sselung bzw. Decodierung entspricht dem u umgekehrten Prozess. Das Codieren/Decodieren einer Nachricht entspricht also einer bijektiven Abbildung. Beispiel 3.3 (Codierung/Decodierung mit Blockcode) Angenommen, die Datei besteht aus diesen Zeichen:

fa gaga gaff fege Wird die Datei mit der Codetabelle von Beispiel 3.1 codiert, ergibt sich diese Bitfolge:

f

a

g

a

g

a

g

a

f

f

f

e

g

e

101 000 111 110 000 110 000 111 110 000 101 101 111 101 100 110 100 Die Decodierung ist hier sehr einfach, da wir wissen, dass drei Bits immer einem Zeichen entsprechen. Es entsteht wieder eindeutig unsere Datei:

101 000 111 110 000 110 000 111 110 000 101 101 111 101 100 110 100f a g a g a g a f f f e g e

3.1. CODIERUNG Beispiel 3.4 (Codierung / Decodierung mit variable length code) Betrachten wir jetzt diese beiden Dateien:

31

Datei 1: abba Datei 2: ag Mit der Codetabelle von Beispiel 3.2 codiert, sehen sie so aus: (abba) 0110 (a g ) Datei 2: 0110

Datei 1:

Doch mit der Decodierung wird es schwer, denn die Dateien sind nicht mehr eindeutig entschl sselbar. u Man sagt, der Code von Beispiel 3.2 ist nicht eindeutig entzifferbar. Ein Code heit also eindeutig entzifferbar, wenn verschiedene Dateien, codiert mit dem selben Code, auch zu verschiedenen Codierungen f hren. Der Grund daf r, dass der Code von Beispiel 3.2 u u nicht eindeutig entzifferbar ist, liegt in der Beschaffenheit der Codew rter. Das Problem besteht daro in, dass es Codew rter gibt, die auch durch Zusammensetzen anderer Codew rter entstehen k nnen. o o o So ist zum Beispiel das Codewort von g, die 110, identisch mit den hintereinander geschriebenen Codew rtern von d und a, die auch 110 ergeben. Es gibt keine M glichkeit zu unterscheiden, ob dort ein o o g oder ein d und ein a steht. Eine L sung f r dieses Problem sind pr xfreie Codes. o u a

3.1.1

Pr xcode a

Ein Code heit Pr xcode1 , wenn kein Codewort Anfangsst ck eines anderen Codewortes ist. Jeder a u Blockcode ist zum Beispiel ein Pr xcode, weil kein Codewort in einem anderen enthalten sein kann. a Aber auch variable length codes k nnen pr xfrei sein. o a Lemma 3.1 Jeder Pr xcode ist eindeutig entzifferbar. a Beweis: Beim Lesen der codierten Datei ist eindeutig klar, wann ein Codewort zu Ende ist. Dann l sst a sich eindeutig sagen, welches Zeichen mit diesem Codewort codiert wurde.

Die Umkehrrichtung von Lemma 3.1 gilt aber nicht. Nicht jeder eindeutig entzifferbare Code muss ein Pr xcode sein. Das zeigt das folgende Beispiel: a1 Der Name ist etwas verwirrend, denn eigentlich m sste der Code pr xfreier Code heien. Aber Pr xcode hat sich in u a a der Literatur durchgesetzt.

32

KAPITEL 3. HUFFMAN CODES UND DATENKOMPRESSION

Beispiel 3.5 Wir betrachten diesen Code: a b c 1 100000 00

Auch wenn dieser Code nicht pr xfrei ist, kann man codierte Dateien wieder decodieren, indem man a die Nullen z hlt. Steht hinter einer Eins eine gerade Anzahl von Nullen, handelt es sich um ein a mit a entsprechend vielen cs dahinter. Steht aber hinter einer Eins eine ungerade Anzahl von Nullen, und zwar mindestens f nf, handelt es sich um ein b mit entsprechend vielen cs dahinter. Daf r ist es aber u u unter Umst nden n tig, sich erst die ganze Datei anzusehen, um zu wissen wie viele Nullen nach den a o Einsen kommen. Es ist also m glich, den folgenden codierten Text zu entschl sseln: o u 1a

00 00c c

1a

00 100000c b

Im weiteren Verlauf werden wir uns nur noch mit Pr xcodes befassen, da vor allem diese in der a Praxis ublich sind. Lemma 3.2 Pr xcodes lassen sich mit bin ren B umen T identizieren, bei denen die Zeichen c C a a a in den Bl ttern stehen und die Wege von der Wurzel bis zum Blatt die Codew rter bilden (eine Kanten a o nach links entspricht der 0, eine Kante nach rechts entspricht der 1). Beweis: =: Sei C ein Zeichensatz mit einem zugeh rigen Pr xcode. Konstruiere einen Baum T , in dem o a pro Codewort der Weg gegangen wird, der durch die 0 bzw. die 1 vorgegeben ist. Bei der 0 gehe nach links, bei der 1 nach rechts. Da der Code pr xfrei ist, endet man immer in einem Blatt. a Es ergibt sich also ein Baum mit den geforderten Eigenschaften. =: Sei C ein Zeichensatz und T ein Baum mit den Zeichen c C in seinen Bl ttern. Betrachte a den Weg von der Wurzel zu den Zeichen c als Codewort f r jedes einzelne Zeichen, wobei u der Weg nach links der 0 und der Weg nach rechts der 1 entspricht. Da die Zeichen in den Bl ttern des Baumes stehen, ist klar, dass kein Weg zu einem Zeichen in einem Weg zu einem a anderen Zeichen enthalten sein kann. Daher kann kein Codewort Anfangsst ck eines anderen u Codewortes sein. Also handelt es sich bei dem Code um einen Pr xcode. a

Beispiel 3.6 Der Baum T in Abb. 3.1 entspricht diesem Pr xcode: a a b c d e 00 01 100 11 101

3.2. DER HUFFMAN ALGORITHMUS0 0 1 1 0 1

33

a

b c

0

1

d

e

Abbildung 3.1: Pr xcode als Baum a Kommen wir nun zur ck zur Frage der Komprimierung. Der ben tigte Speicherplatz f r eine Textdatei u o u entspricht immer der Anzahl der Bits bez glich ihrer Codierung. u Betrachten wir noch einmal Beispiel 3.3 fa gaga gaff fege und codieren es mit dem Code von Beispiel 3.1,f a g a g a g a f f f e g e

101 000 111 110 000 110 000 111 110 000 101 101 111 101 100 110 100 so ben tigen wir 17 3 = 51 Bits. Codiert man aber das Beispiel mit dem Code von Beispiel 3.2, of a g a g a g a f f f e g e

101 0 111 110 0 110 0 111 110 0 101 101 111 101 100 110 100 ben tigt man 43 Bits. o Verschiedene Codierungen nehmen also unterschiedlich viel Speicherplatz in Anspruch. Bei der Komprimierung wird also (dateiabh ngig) ein Code gefunden, der den Speicherplatz reduziert. Dieser Coa de muss nat rlich ein Pr xcode sein, damit die Datei wieder einfach decodierbar ist. u a

3.2

Der Huffman Algorithmus

Textdateien werden standardm ig im Blockcode codiert und gespeichert. Der Huffman Algorithmus a konstruiert einen Pr xcode variabler L nge, so dass die Anzahl der ben tigten Bits kleiner wird. a a o Der Code wird so konstruiert, dass Zeichen, die sehr h ug auftreten, kurze Codew rter bekommen, a o und weniger h uge Zeichen l ngere Codew rter. So wird, abh ngig von der Datei, der ben tigte a a o a o Speicherplatz verringert. Der Huffman Algorithmus ist sogar so gut, das der entstehende Pr xcode a optimal bez glich des ben tigten Speicherplatzes ist. Es sind Speicherplatzeinsparungen von 20% bis u o 90% ublich, je nach Beschaffenheit der Datei.

34

KAPITEL 3. HUFFMAN CODES UND DATENKOMPRESSION

Um den optimalen Pr xcode zu konstruieren, muss die Datei erst einmal gelesen werden, wobei die a H ugkeiten f (c) f r alle Zeichen c C ermittelt werden. Ist die H ugkeit der Zeichen bekannt, a u a und wird die Datei mit dem Pr xcode T (als bin rer Baum aufgefasst) codiert, so ergibt sich der a a ben tigte Speicherplatz der Dateiwie folgt: o B(T ) =

cC

f (c) hT (c)

(3.1)

Dabei gibt hT (c) die H he des Zeichens c im Baum T an, also die Anzahl der Kanten des Baumes o von der Wurzel bis zu dem Blatt, in der das Zeichen c steht, an. Dies entspricht nach Lemma 3.1 der L nge des Codewortes f r das Zeichen c. a u Beispiel 3.7 zeigt, wie der Speicherplatz bei unterschiedlichen Codierungen variieren kann. Zeichen c a b c d e f f (c) in 1000 45 13 12 16 9 5 Code 1 000 001 010 011 100 101 Code 2 0 101 100 111 1101 1100

Code 10 0 0 1 1 0 1 0 1 0 1 0

Code 21 0 0 1 1 0 1

a

a

b c

d

e

f

c

b0 1

d e

fAbbildung 3.2: Die B ume der beiden Codes a Dann ergibt sich der ben tigte Speicherplatz f r Code 1 zu: o u B(T1 ) = (45 + 13 + 12 + 16 + 9 + 5) 1000 3 = 300000 Der ben tigte Speicherplatz f r Code 2 betr gt hingegen: o u a B(T2 ) = (45 1 + 13 3 + 12 3 + 16 3 + 9 4 + 5 4) 1000 = 224000

3.2. DER HUFFMAN ALGORITHMUS

35

Wir wollen wir uns nun Huffman Algorithmus ansehen, der f r jede Textdatei einen optimalen Pr xcode u a ermittelt. Algorithmus 3.1 (Huffman Algorithmus) 1. Fasse jedes Zeichen c C als einelementigen Baum auf und f ge es in eine Priority Queue Q u ein, wobei die H ugkeit f (c) als Schl sselwert dient. a u 2. Solange Q mehr als einen Baum enth lt: a W hle die beiden B ume T1 und T2 mit den kleinsten H ugkeiten (muss nicht eindeutig a a a sein). Entferne sie aus Q. Konstruiere einen neuen Baum aus den T1 , T2 als Teilb ume unter einer neuen Wurzel und a gebe ihm die H ugkeit f (T1 ) + f (T2 ). a F ge diesen Baum in Q ein. u 3. Gebe (den einzig ubrig gebliebenen Baum) T in Q zur ck. Dieser Baum (der so genannte Huffu man Baum oder Huffman Code) ist ein optimaler Pr xcode. a Bei der Codierung einer Datei gem T muss neben der codierten Datei nat rlich der Code z.B. als a u Baum mit abgespeichert werden, denn er ist zur Decodierung notwendig. Der Speicherplatz daf r ist u aber bei gen gend groen Dateien im Vergleich zum eingesparten Speicherplatz so gering, dass er u vernachl ssigt werden kann. a Das folgende Beispiel zeigt, wie der Huffman Algorithmus funktioniert: Beispiel 3.8 (Konstruktion eines Huffman Baumes)

Zeichen c f (c)

a 45

b 13

c 12

d 16

e 9

f 5

Die Zeichen werden alle als einknotige B ume mit f (c) als Schl ssel in die Priority Queue Q eina u gef gt: u

45

13

12

16

9

5

Q:

a

b

c

d

e

f

Die beiden B ume mit den kleinsten Schl sseln werden aus Q entfernt, zu einem neuen Baum zusama u mengef gt und wieder in Q eingef gt: u u

3645

KAPITEL 3. HUFFMAN CODES UND DATENKOMPRESSION13 12 16 14

Q:

a

b

c

d

e

f

Das wird fortgesetzt, bis nur noch ein Baum in Q ist:

45

25

16

14

Q:

a

d

b

c

e

f

45

25

30

Q:

a

b

c

d

e

f

45

55

Q:

a

b

c

d

e

f

3.2. DER HUFFMAN ALGORITHMUS45

37

Q:a

b

c

d

eAm Ende wird der fertige Huffman Baum von Q entfernt:

f

45

a

b

c

d

e

f

Die Laufzeit des Huffman AlgorithmusIst die Priority Queue als Heap implementiert (siehe Abschnitt 2.2.1 auf Seite 26) und hat man n Zeichen, so ergibt sich folgende Laufzeit f r den Huffman Algorithmus: u 1. 2. alle Zeichen einf gen in Q: u n 1 Phasen: - die beiden Kleinsten aus Q entfernen: - neuen Baum bauen: - wieder einf gen in Q: u 3. Baum zur ck geben: u insgesamt: O(3n) n 1 mal: O(2 log n) O(1) O(log n) O(1) O(n log n)

38

KAPITEL 3. HUFFMAN CODES UND DATENKOMPRESSION

Die Optimalit t des Huffman Codes aEin Pr xcode T heit optimal f r einen Zeichensatz C und H ugkeiten f (c), c C, wenn a u a B(T ) B(T ), f r jeden anderen Pr xcode T (zu C und denselben H ugkeiten f (c)). u a a Lemma 3.3 Sei C eine Menge von Zeichen mit den H ugkeiten f (c). Seien x, y die Zeichen mit den a niedrigsten H ugkeiten. Dann gibt es einen optimalen Pr xcode T , in dem x und y die gr te H he a a o o und gemeinsamen Vater haben. Die beiden Codew rter f r x und y haben dann dieselbe L nge und o u a unterscheiden sich nur im letzten Bit. Beweis: Die Idee des Beweises ist es, einen Baum T zu betrachten, der einen optimalen Pr xcode repr sentiert a a und ihn gegebenenfalls so zu modizieren, dass er optimal bleibt und die beiden Zeichen x und y in den Bl ttern mit der gr ten H he stehen und denselben Vater haben. Ist das m glich, sind die beiden a o o o gew nschten Eigenschaften im optimalen Pr xcode erf llt. Abbildung 3.3 skizziert diese Vorgehensu a u weise. T s rr rs s d x s ds d y s ds a b T s rr rs s d a s ds d y s ds x bE

(3.2)

E

T s rr rs s d a s ds d b s ds x y

Abbildung 3.3: Modizieren des Baumes T f r den Beweis von Lemma 3.3 u Zun chst uberlegen wir uns, dass es einen optimalen Pr xcode gibt. Dies folgt daraus, dass der Wert a a B(T ) immer ganzzahlig und positiv ist. In dieser Menge von Zahlen existiert ein kleinster Wert B(T ), und der zugeh rige Baum T ist ein optimaler Pr xcode. o a Sei also nun T ein optimaler Pr xcode. Sei a ein Blatt in T mit gr ter H he hT (a). Dann hat a a o o aufgrund der Optimalit t von T einen Bruder b, denn h tte a keinen Bruder, k nnte man a einen a a o Level h her h ngen und h tte den Wert B(T ) verbessert, was im Widerspruch zur Optimalit t von o a a a T st nde. Betrachte nun x und y, die beiden Zeichen mit den geringsten H ugkeiten. Vertauscht man u a a und x im Baum T , so entsteht der Baum T . F r den Speicherplatz der beiden B ume gilt dann: u a B(T ) B(T ) = =cC

f (c) hT (c) f (c) hT (c)cC

f (x) hT (x) + f (a) hT (a) f (x) hT (x) f (a) hT (a)=hT (a) =hT (x)

= ( f (a) f (x)) (hT (a) hT (x))0 0

0 = B(T ) B(T )

3.2. DER HUFFMAN ALGORITHMUS

39

Da T aber optimal ist, kann es keinen Pr xcode geben, der weniger Speicherplatz ben tigt. Also gilt: a o B(T ) = B(T ) Der Pr xcode T ist also auch optimal. a Entsteht T , indem man in T b und y vertauscht, l sst sich auf analoge Weise zeigen, dass auch T a optimal ist. T erf llt dann die Aussagen des Lemmas. u

Lemma 3.4 (Prinzip der optimalen Substruktur) Sei C ein Zeichensatz mit den Zeichen c C und den H ugkeiten f (c), und seien x und y die beiden Zeichen mit den geringsten H ugkeiten. Sei T a a ein Pr xcode f r C und f (c), in dem x und y einen gemeinsamen Vater z haben. Sei T der Baum, a u der aus T entsteht, indem x und y wegfallen, statt dessen aber z als neues Zeichen mit der H ugkeit a f (z) = f (x) + f (y) hinzukommt. T ist dann ein Pr xcode f r C := C \ {x, y} {z}. a u Unter diesen Voraussetzungen gilt: T ist optimal f r C u T ist optimal f r C u

Beweis: Da wir nur die R ckrichtung ben tigen, wollen wir nur zeigen: u o T ist optimal f r C u F r T und T gilt: u B(T ) B(T ) = f (x)hT (x) + f (y)hT (y) f (z) hT (z)=:

=

T ist optimal f r C u

(hT (x) = hT (y) = + 1, da x und y S hne von z) o = f (x)( + 1) + f (y)( + 1) ( f (x) + f (y)) = ( f (x) + f (y))( + 1) ( f (x) + f (y)) = ( f (x) + f (y))( + 1 ) = B(T ) B(T ) = f (x) + f (y) (3.3) = B(T ) = B(T ) + f (x) + f (y)

Sei T optimal, T aber nicht. Betrachte den optimalen Baum T f r C, d.h. B(T ) < B(T ). Nach u Lemma 3.3, darf angenommen werden, dass x und y an der tiefsten Stelle im Baum T stehen und einen gemeinsamen Vater w haben. Betrachte nun den Baum T , der entsteht, wenn x und y im Baum T wegfallen und w als neues Zeichen mit der H ugkeit f (w) = f (x) + f (y) hinzukommt. Dann a ergibt sich nach Gleichung 3.3 f r den Speicherbedarf: u B(T ) = B(T ) + f (x) + f (y)

40

KAPITEL 3. HUFFMAN CODES UND DATENKOMPRESSION

Da T optimal f r C ist und T ein Pr xcode f r C und dieselben H ugkeiten ist, folgt B(T ) u a u a B(T ). Damit gilt: B(T ) < B(T ) = B(T ) + f (x) + f (y) B(T ) + f (x) + f (y) = B(T ) Das ergibt einen Widerspruch. Also muss auch T optimal sein.3.3

Satz 3.1 (Optimalit t) Der Huffman Code T ist optimal unter allen Pr xcodes, das heit: a a B(T ) := f r alle Pr xcodes T . u a Beweis: (Induktion nach der Anzahl der Zeichen n = |C|) Induktionsanfang: F r n = 2 liefert der Algorithmus eine Codierung, bei denen die Codew rter nur u o aus einer 0 bzw. 1 bestehen. Das entspricht dem optimalen Speicherplatz. Induktionsschritt von n 1 auf n: Sei n = |C|. Der Huffman Algorithmus ermittelt die Zeichen x und y mit den kleinsten H ugkeiten und ersetzt sie durch einen neuen Baum, mit dem Zeichen z a in der Wurzel und den beiden S hnen x und y, wobei f (z) = f (x) + f (y) ist. Danach wird mit dem o Zeichensatz C = C\{x, y} {z} weitergearbeitet. Nach Induktionsvoraussetzung liefert der Huffman Algorithmus einen optimalen Baum T f r C . Ersetzt man z wieder entsprechend Lemma 3.4, so u erh lt man einen optimalen Pr xcode T f r C. Da der Huffman Algorithmus gerade auf diese Weise a a u den Baum T konstruiert, ist der Huffman Code optimal unter allen Pr xcodes. a

cC

f (c) hT (c) B(T

)

Bemerkungen zu Huffman CodesHier soll nochmal auf besondere Eigenschaften des Huffman Algorithmus und auf Alternativen dazu hingewiesen werden. Der Huffman Algorithmus entspricht einer zeichenweisen Codierung. Es wird jedem Zeichen c C ein eigenes Codewort zugewiesen. Wie wir sp ter noch sehen werden, gibt es auch Algorithmen, die a Codew rter f r Teilstrings konstruieren. Auch beim Huffman Algorithmus w re das m glich, indem o u a o man Strings der L nge k als Zeichen betrachtet. a Beim Huffman Code handelt es sich um einen statischen Code. Das bedeutet, dass die ganze Nachricht vorab gelesen und analysiert wird, um die H ugkeiten der einzelnen Zeichen zu ermitteln und a

3.3. WEITERE DATENKOMPRESSIONSVERFAHREN

41

dementsprechend feste Codew rter zu konstruieren. Danach muss die Datei noch einmal gelesen wero den, um sie mit den entstandenen Codew rtern zu codieren. Das Gegenteil hierzu w ren die dynamio a schen bzw. adaptiven Codes. Bei diesen Codierungen werden die Codew rter w hrend des Lesens des o a Textles erstellt und im Laufe des Lesens ge ndert, wenn es mehr Informationen uber die Nachricht a gibt. Die Datei wird w hrend des Lesens codiert, und zwar mit sich andernden Codew rtern. Diese a o Vorgehensweise erspart ein zweimaliges Lesen. Das dynamische Codieren wird auch Komprimierung on the y genannt. Die Datenkompression mittels des Huffman Algorithmus ist im Gegensatz zur Kompression von Audio-, Video- und Graphikdateien verlustfrei. Die urspr ngliche Nachricht kann also ohne Verlust u von Informationen wieder rekonstruiert werden.

3.33.3.1

Weitere DatenkompressionsverfahrenDer adaptive Huffmancode

Im Gegensatz zum statischen Huffmancode, wird bei diesem Verfahren ein dynamischer Code erstellt. Im Laufe des Lesens werden zu jedem Zeitpunkt, abh ngig von der schon gelesenen Nacha richt, die wahrscheinlichen H ugkeiten abgesch tzt und auf dieser Grundlage die Codew rter era a o stellt. Der Pr xcode wird also immer so ver ndert, dass er f r die aktuellen Absch tzungen optimal a a u a ist. Beim Verschl sseln wird die Beschaffenheit der Quelldatei gelernt. Beim Entschl sseln muss u u der Huffman Baum kontinuierlich aktualisiert werden, damit die Zeichen wieder mit den richtigen Codew rtern ubersetzt werden k nnen. Wie oben schon beschrieben liegt der Vorteil darin, dass die o o Quelldatei nur einmal gelesen werden muss. Abh ngig von der Datei, kann diese Vorgehensweise a bessere, aber auch schlechtere Leistung bringen. Der Algorithmus bildet die Basis f r den Unixbefehl u compact, der in der Regel eine Kompressionsrate von 30-40% erbringt.

3.3.2

Der run length code

Dieses Verfahren wird zum Komprimieren von Bildern verwendet. Der Computer speichert Bilder, indem er sie in viele Bildpunkte (Pixel) aufteilt und sich die genaue Farbe eines Pixels merkt. Die Pixel werden dann in einer bestimmten Reihenfolge (z.B. zeilenweise) abgespeichert. Um das Bild nun zu komprimieren, werden nicht alle Pixel gespeichert, sondern es wird ausgenutzt, dass oft Wiederholung eintritt (z.B. schwarze Fl che). Daher werden nur Pixel mit der Vielfachheit ihres wiederholten a Auftretens gespeichert. Damit wird Speicherplatz eingespart.

3.3.3

Der Lempel-Ziv Code

Dieser Algorithmus wurde 1977 entwickelt. Er arbeitet verlustfrei und adaptiv. Es ist leider nicht m glich f r ihn eine Optimalit tsaussage zu treffen, jedoch ist er empirisch gut. Typisch sind Leiso u a tungen, die in der Gr enordnung von 50 60% liegen. Da er in zip, compress und gzip genutzt wird, o soll er hier genauer erkl rt werden. a

42

KAPITEL 3. HUFFMAN CODES UND DATENKOMPRESSION

Der Lempel-Ziv Algorithmus erstellt sog. Ketten, die Strings wachsender L nge entsprechen. In a jedem Schritt des Algorithmus wird von der Rest-Nachricht, die noch nicht codiert wurde, der l ngste a Pr xcode ermittelt, der einer bereits denierten Kette entspricht. Diese wird dann mit dem danach a kommenden Zeichen c als String c in eine Tabelle, dem sog. W rterbuch, eingetragen und sie wird o mit ic codiert, wobei i dem Codewort von entspricht. Die Kette ic erh lt dann ein eigenes Codewort. a Die Codew rter haben dieselbe vordenierte L nge, die dann die Gr e der Tabelle und die L nge o a o a der l ngsten Kette bestimmt. a Um den Lempel-Ziv Algorithmus besser zu verstehen, folgen hier zur Veranschaulichung zwei Beispiele. Beispiel 3.9 Gegeben sei die Nachricht: aa bbb cccc ddddd eeeeee fffffffgggggggg und der dazugeh rige Blockcode: o Zeichen a b c d e f g Codewort 000 001 010 011 100 101 110 111

Daraus ergibt sich dann mit dem beschriebenen Verfahren der Lempel-Ziv Code: String Kette Codewortnummer String Kette Codewortnummer leer 0 0 eee 13e 14 a 0a 1 f 5f 15 a 1 2 f 0f 16 b 0b 3 ff 16f 17 bb 3b 4 fff 17f 18 0 5 g 0g 19 c 0c 6 cc 6c 7 c 6 8 ggg 20g 21 d 0d 9 dd 9d 10 dd 10 11 e 0e 12 ee 12e 13

gg 19g 20

Die komprimierte Nachricht sieht dann so aus: (0a)(1 )(0b)(3b)(0 )(0c)(6c)(6 )(0d)(9d)(10 )(0e)(12e)(13e)(5f)(0f)(16f)(17f)(0g)(19g)(20g)(20) Um 21 Codewortnummern abzuspeichern, ben tigt man log 21 = 5 Bit. F r die Zeichen an sich o u werden zum Abspeichern die urspr nglichen 3 Bit ben tigt. Dann folgt f r den Speicherplatz: u o u 21 Zeichen + 1 Zeichen a a 5 Bit 5 Bit + 3 Bit = = 168 Bit 5 Bit 173 Bit

3.4. ABSCHLIESSENDE BEMERKUNGEN

43

Beispiel 3.10 Das nun folgende Beispiel benutzt den uns bereits bekannten ASCII Code, der eine L nge von 8 Bit hat: a schers fritze scht frische sche frische sche scht schers fritze Der Lempel-Ziv Code sieht dann wie folgt aus String Kette Codewortnummer String Kette Codewortnummer String Kette Codewortnummer leer 0 0 sc 3c 14 isch 23h 27 f 0f 1 ht 5t 15 t 0t 28 i 0i 2 0 16 26i 29 s 0s 3 fri 9i 17 c 0c 4 sch 14h 18 h 0h 5 e f 12f 19 s f 8f 31 e 0e 6 is 2s 20 ri 7i 32 r 0r 7 s 3 8 ch 4h 21 tz 28z 33 fr 1r 9 e fr 19r 22 it 2t 10 isc 20c 23 z 0z 11 e 19i 24 e 6 12 1i 13 f 16f 26

sche 18e 25

scher 25r 30

und codiert die Nachricht folgendermaen: (0f)(0i)(0s)(0c)(0h)(0e)(0r)(3 )(1r)(2t)(0z)(6 )(1i)(3c)(5t)(0 )(9i)(14h)(12f) (2s)(4h)(19r)(20c)(19i)(18e)(16f)(23h)(0t)(26i)(25r)(8f)(7i)(28z)(6) Zum Abspeichern von 33 Codew rtern werden log 33 = 6 Bit und zum Abspeichern eines Zeichens o mit ASCII Code werden 8 Bit ben tigt. Das heit f r den Speicherplatzbedarf: o u 33 (6Bit + 8Bit) + 6Bit = 468Bit

3.4

Abschlieende Bemerkungen

Abschlieend soll darauf hingewiesen werden, dass es eine von den H ugkeiten abh ngige untere a a Schranke f r Datenkompression, die Entropie, gibt. Um diese zu erkl ren ben tigen wir die folgenden u a o beiden Begriffe. Zum Einen gibt es den Begriff der normierten H ugikeit P(c), die die H ugkeit eia a nes Zeichens c in Abh ngigkeit von der Summe der H ugkeiten aller Zeichen der Quelldatei angibt: a a P(c) = f (c) f (u) (3.4)

uC

Wird der Speicherplatz B(T ), der die Gesamtheit der Wortl ngen angibt, normiert, ergibt sich die a mittlere Wortl nge: a A(T ) = P(c) h(c) (3.5)cC

Nun k nnen wir die Entropie H(C) einer Quelldatei C denieren: o H(C) =cC

[P(c) ( log P(c))]

(3.6)

44

KAPITEL 3. HUFFMAN CODES UND DATENKOMPRESSION

Der Satz von Shannon sagt nun, dass die mittlere Wortl nge jeder verlustfreien Komprimierung uber a der Entropie liegt: A(T ) H(C) f r jede Codierung T von C u (3.7) Um nun quantitativ sagen zu k nnen wie gut ein Kompressionsverfahren ist, gibt es den Begriff der o Redundanz . Die Redundanz kann als Differenz zwischen der mittleren Wortl nge der codierten Datei a und der Entropie der Quelldatei deniert werden: R(T ) = A(T ) H(C) f r eine Codierung T von C u (3.8)

Beispiel 3.11 In der gegebenen Quelldatei treten die folgenden Zeichen mit den angegebenen normierten H ugkeiten auf: a cC P(c) a 0, 45 b 0, 13 c 0, 12 d 0, 16 e 0, 09 f 0, 05

a Dann folgt mit Gleichung 3.6, dass die Entropie H(c) = 2, 2199 betr gt. Komprimiert man die Datei mit dem Huffman Code, ergibt sich mit Gleichung 3.5 eine mittlere Wortl nge von A(T ) = 2, 24. Die a Redundanz betr gt dann nach Gleichung 3.8 R(T ) = 0.0201. Die Redundanz ist also beim Huffman a Code sehr gering. Der Huffman Code ist asymptotisch optimal unter allen Codierungsverfahren. Betrachtet man n mlich a den verallgemeinerten Huffman Block Code, der Teilstrings fester L nge k als einzelne Zeichen bea handelt, gilt folgender Satz: Satz 3.2 Zu jedem > 0 gibt es ein k, so dass f r den Huffman Block Code Tk mit den Teilstrings der u L nge k gilt: a A(Tk ) H(C) +

3.5

Literaturhinweise

Der Huffman Algorithmus wird ausf hrlich [CLRS01] behandelt. Eine ausgezeichnete Ubersicht und ach tieferu gehende Informationen uber Kompressionsverfahren ndet man unter http://www.data-compression.com/

Kapitel 4

Suchb ume aDie Hintergrundanwendung f r Suchb ume ist das Verwalten von dynamischen Daten. Wir m chten u a o die Operationen Einf gen u L schen o Suchen schnell ausf hren. Suchb ume haben viele Anwendungen, zum Beispiel in Datenbanken oder Dateiu a systemen auf Festplatten. Wir kennen daf r bisher nur Listen; der Hauptnachteil an Listen ist der rein u sequentielle Zugriff, der zu einem Worst Case Aufwand von O(n) f r das Suchen f hrt. Wir beschleuu u nigen die Suche durch die Verwendung von Suchb umen und erhalten dabei O(log n) statt O(n) als a Aufwand. Was ist also ein Suchbaum? Ein Suchbaum hat in jedem Knoten einen Datensatz und die Suchschl ssel sind aufsteigend sortiert bez glich der Inorder-Traversierung. u u7

5

8

2

6

10

9

45

46 Aquivalent dazu ist folgende Denition: F r jeden Knoten v gilt: u

KAPITEL 4. SUCHBAUME

1. Alle Knoten im linken Teilbaum zu v haben einen kleineren Schl ssel als v u 2. Alle Knoten im rechten Teilbaum zu v haben einen gr eren Schl ssel als v o u

4.14.1.1

Basisoperationen in Suchb umen a Suchen nach Schlussel k

1. Beginne in der Wurzel w: falls der Schl ssel in w den Wert k hat, gebe w zur ck u u 2. sonst falls der Schl ssel in w > k: suche im linken Teilbaum weiter u 3. sonst suche im rechten Teilbaum weiter Der Aufwand, gemessen in der Anzahl der Vergleiche, entspricht der H he des zu suchenden Knoten o plus eins. Der Worst-Case-Aufwand ist daher h(T ) + 1 = O(h(T )).

4.1.2

Einfugen eines Knoten

Das nat rlichen Einf gen funktioniert folgendermaen: u u 1. Nach dem neuen Schl ssel suchen, bis man in einem Blatt angelangt ist u 2. Dann gem Schl sselwert des Blattes den neuen Schl ssel links oder rechts an das Blatt a u u anh ngen a Als Beispiel f gen wir die Sequenz 5,7,8,3,2,12,0,6,9 in einen Suchbaum ein und stellen einige Teilu schritte in Abbildung 4.1 dar. Der Aufwand zum Einf gen in den Suchbaum T entspricht der H he des Blattes, an das angeh ngt u o a wird, plus eins. Der Worst Case hat also den Aufwand h(T ) + 1 = O(h(T )). Ein Problem beim nat rlichen Einf gen besteht darin, dass der Baum zu einer Liste entarten kann und u u Suchen und Einf gen dann einen Worst-Case von O(n) haben, siehe Abblidung 4.2. u

4.1.3

L schen eines Knoten o

1. Suche den zu l schenden Knoten v. o 2. Falls v Blatt ist, so l sche v. o 3. Falls v nur einen Sohn w hat, so h nge w an den Vater von v und l sche v. a o

4.1. BASISOPERATIONEN IN SUCHBAUMEN

47

5

5 7 2 0 3

5 7 6 8 12 9

Abbildung 4.1: Nat rliches Einf gen der Sequenz 5,7,8,3,2,12,0,6,9 in einen Suchbaum. u u

1 2 3 4

Abbildung 4.2: Durch nat rliches Einf gen zu einer Liste entarteter Suchbaum. u u 4. Falls v zwei S hne hat: o 4.1 Suche den Knoten w im linken Teilbaum von v, der am weitesten rechts steht (dieser ist Vorg nger von v bzgl. Inorder). a 4.2 Tausche die Daten von v und w. 4.3 L sche w (w ist Blatt oder hat nur einen Sohn und kann daher mit Schritt 1 oder 2 gel scht o o werden). Statt den am weitesten rechts stehenden Knoten im linken Teilbaum zu suchen, kann man nat rlich u auch den am weitesten links stehenden Knoten im rechten Teilbaum suchen und analog verfahren. Beispiel 4.1 Es soll die 7 aus dem Suchbaum links in Abbildung 4.3 gel scht werden. Zuerst wird o der am weitesten rechts stehende Knoten im linken Teilbaum der 7 gesucht und liefert die 6 (Mitte). Daraufhin werden die 6 und die 7 vertauscht und die 7 gel scht (rechts). o Wie beim Suchen und Einf gen betr gt der Aufwand im Worst Case h(T ) + 1 = O(h(T )). u a Satz 4.1 Der Aufwand f r das Suchen, das nat rliche L schen und das nat rliche Einf gen erfordert u u o u u im Worst Case O(h(T )) Vergleiche.

487 5 4 2 6 8 9 10 12

KAPITEL 4. SUCHBAUME

7 5 4 2 1 3 6 8 9 10 12 2 1 3 4 5 7

6 10 8 9 12

1

3

Abbildung 4.3: L schen des Knoten 7. o

Im Extremfall kann h(T ) gleich n 1 sein, der Aufwand dann also O(n). Deswegen werden wir die Operationen so verbessern, dass h(T ) klein bleibt. Die daf r entscheidenden Operationen sind die u Rotationen, mit denen die Gestalt eines Baumes ver ndert werden kann um die H he klein zu halten. a o Wir unterscheiden zwischen Links- und Rechtsrotation, die Linksrotation RotLinks(v, w) um die Knoten v und w, und die dazu symmetrische Rechtsrotation RotRechts(v, w). Beide sind in ist in Abbildung 4.4 dargestellt. Man beachten, dass das Resultat in beiden F llen wieder ein Suchbaum ist, die a Teilb ume T1 , T2 , T3 also korrekt umgeh ngt werden. a a

v w T1 T2 v w T3 T1 T2 RotRechts(v,w) T1 T3 T1 RotLinks(v,w) v

w

T3 T2 w v

T2

T3

Abbildung 4.4: Linksrotation RotLinks(v,w)

4.1. BASISOPERATIONEN IN SUCHBAUMEN

49

Linksrotation und Rechtsrotation sind in O(1) durchf hrbar, da nur konstant viele Referenzen ge ndert u a werden m ssen. Eine Java-Methode f r die Linksrotation k nnte in etwa wie folgt aussehen: u u o TreeNode rotateLeft (TreeNode v) { TreeNode aux = v.rson; v.rson = aux.lson; aux.lson = v; v = aux; return v; } Diese Anweisungen werden genauer in Abbildung 4.5 erl utert. Die erste Zeile a TreeNode aux = v.rson; setzt die Hilfsreferenz aux auf den rechten Sohn des zu rotierenden Knotens v. Die Zeile v.rson = aux.lson; setzt den rechten Sohn f r den zu rotierenden Knoten v neu. Danach setzt die Zeile u aux.lson = v; den linken Sohn des von aux referenzierten Knotens auf den zu rotierenden Knoten v. Dann wird durch v = aux; v neu gesetzt und schlielich durch return v; v zur ckgegeben. u Der folgende Satz sagt, dass Rotationen ausreichen, um Suchb ume beliebig zu andern und daher a insbesondere ausreichen, um h(T ) klein zu halten. Satz 4.2 Seien T1 , T2 Suchb ume zur selben Schl sselmenge. Dann gibt es eine endliche Folge von a u Rechts- und Linksrotationen, die T1 in T2 transformiert. Beweis: Wir beweisen den Satz durch vollst ndige Induktion nach der Anzahl der Schl ssel. Aba u bildung 4.6 illustriert das Prinzip. F r n = 1 ist nichts zu zeigen, denn zwei B ume mit nur einem u a Schl ssel und der gleichen Schl sselmenge sind gleich. u u

50

KAPITEL 4. SUCHBAUME

v w T1 T2 T3

aux

v w T1 T2 T3

aux

aux w v T3 T1 T2 T1 T2 T3 v w

aux

Abbildung 4.5: Umh ngen der Referenzen bei der Linksrotation RotLinks(v, w). a

F r n = 2 gibt es nur zwei verschiedene Suchb ume zur gleichen Schl sselmenge, die durch genau u a u berf hrt werden k nnen. eine Links- beziehungsweise Rechtsrotation ineinander u u o n n + 1 : Sei T1 der Ausgangsbaum, w(T1 ) seine Wurzel und T1 der linke und T1r der rechte Teilbaum von T1 . Sei T2 der Baum, in den T1 durch Rotationen umgewandelt werden soll. Das Wurzel w(T2 ) von T2 ist, da die beiden B ume die gleiche Schl sselmenge haben, auch in T1 enta u r von T . Auf die Teilb ume von T trifft die Induktionshalten, etwa o.B.d.A. im rechten Teilbaum T1 a 1 1 voraussetzung zu, und wir k nnen daher T1 mit Rotationen so transformieren, dass der transformierte o Baum T1 eine linke Liste wird (siehe Baum links unten in Abbildung 4.6). Entsprechend k nnen o r mit Rotationen so transformieren, dass der transformierte Baum T r eine rechte Liste wird. 1 wir T1 O.b.d.A. war w(T2 ) im rechten Teilbaum von T1 enthalten. Wegen der speziellen Listengestalt der o B ume T1 und T2r k nnen wir durch eine Folge von Linksrotationen um die Wurzel des ganzen Baua mes daf r sorgen, dass w(T2 ) die Wurzel wird (siehe Baum rechts unten in Abbildung 4.6). u Wegen der Suchbaumeigenschaft haben die Teilb ume T und T r des so entstehenden BaumesT mit a Wurzel w(T2 ) dieselben Schl ssel wie T2 und T2r . Nach Induktionsvoraussetzung k nnen wir daher u o r in T r uberf hren. mit Rotationen T in T2 und T u 2 Die Zusammensetzung dieser Folgen von Rotationen (zun chst Teilb ume von T1 zu Listen, dann a a r ) liefert dann die gew nschte Folge von w(T2 ) an die Wurzel, dann Teilb ume von T zu T2 und T2 a u Rotationen, die T1 in T2 uberf hrt. u

4.2. LITERATURHINWEISE5 6

51

3

7

4

7

1

4

6

1

5

3

5

6

4

6

5

7

3

7

4

1

3

1

Abbildung 4.6: Illustration des Beweises von Satz 4.2.

Rotationen reichen also aus, um Suchb ume zu balancieren. Wir denieren daher: a Eine Klasse T von Suchb umen heit balanciert zur H he f (n), wenn gilt: a o 1. Jede Schl sselmenge mit n Knoten (n = 1, 2, . . . ,) kann durch einen Suchbaum T T dargestellt u werden und es gilt h(T ) f (n). 2. Basisoperationen k nnen f r T T in O(h(T )) durchgef hrt werden. o u u 3. Die Klasse T ist abgeschlossen gegen ber Basisoperationen, d.h. Einf gen und L schen in u u o T T f hren wieder zu einem Baum in T. u Gesucht ist eine Klasse von Suchb umen mit f (n) O(log n). Es gibt verschiedene solche Klassen, a die historische alteste ist die der sogenannten AVL-B ume. F r sie ist f (n) 1.4405 log(n + 2) a u 0.3277. Wir werden jedoch nur zeigen, dass f (n) 2 log n gilt.

4.2

Literaturhinweise

Suchb ume werden in jedem Buch uber Datenstrukturen behandelt, vgl. etwa [CLRS01, Knu98, OW02, SS02]. a

52

KAPITEL 4. SUCHBAUME

Kapitel 5

AVL-B ume a Die historisch alteste Klasse balancierter Suchb ume zur H he f (n) O(log n), die Klasse der AVLa o B ume, wurde 1962 von Adelson-Velski und Landis aus der UdSSR deniert. Die grunds tzliche Idee a a hierbei ist, das Entarten der Suchb ume zu einer Liste durch eine Forderung an die H hendifferenz a o der beiden Teilb ume eines jeden Knotens zu verhindern. Mit dieser Forderung wird erreicht, dass die a H he von f (n) = 1, 44 . . . log n nicht uberschritten wird. o

5.1

Grunds tzliche Eigenschaften a

Um AVL-B ume denieren zu k nnen, ben tigen wir zun chst die Denition der Balance: a o o a Sei T ein bin rer Suchbaum und sei v V ein Knoten. Seien T (v) und Tr (v) die (evtl. leeren Teilb ume) a a von v. Dann heit (v) := h(Tr (v)) h(T (v)) die Balance des Knoten v.v T (v)h(T (v)) (v) h(Tr (v))

mit h(0) = 1 /

(5.1)

Tr (v)

Abbildung 5.1: Balance als H hendifferenz o Wie in Abb. 5.1 zu sehen, beschreibt sie die H hendifferenz zwischen dem rechten und linken Teilo baum des Knoten v. Damit k nnen wir AVL-B ume denieren: o a 53

54

KAPITEL 5. AVL-BAUME

Ein bin rer Suchbaum T heit AVL-Baum genau dann, wenn f r alle v V gilt: a u | (v)| 1. (5.2)

Beispiel 5.1 Abbildung 5.2 zeigt die beiden B ume T und T . Die Zahlen an den Knoten geben ihre a Balance an. Bei T handelt es sich um einen AVL-Baum. T erf llt die AVL-Eigenschaft nicht, da die u Balance der Wurzel 2 betr gt. aT1

T

2

1

1

1

0

0

0 1

0

1

0

0

Abbildung 5.2: Der AVL-Baum T und der Suchbaum T

Aus der Denition von AVL-B umen folgt, dass auch jeder Teilbaum eines AVL-Baums wieder ein a AVL-Baum ist. Wir zeigen nun, dass die Balance-Bedingung daf r sorgt, dass AVL-B ume nicht allzu u a hoch werden. Satz 5.1 Sei T ein AVL-Baum mit n Knoten (n 2). Dann gilt f r seine H he h(T ): u o h(T ) 2 log n (5.3)

Beweis: Um diese Absch tzung zu beweisen, betrachten wir extremale AVL-B ume, die zu vorgegea a bener H he h eine minimale Knotenzahl aufweisen. Sei deshalb o n(h) := min{n | T ist AVL-Baum mit n Knoten und h(T ) = h} (5.4)

Abbildung 5.3 zeigt, wie solche extremalen AVL-B ume aufgebaut sind. Bez glich der Struktur eines a u extremalen AVL-Baumes T , lassen sich daher folgende Eigenschaften vermuten: 1. Die H hendifferenz der Teilb ume eines extremalen AVL-Baumns ist 1 oder 1 (auer der o a Baum hat nur einen Knoten). 2. Die Teilb ume T und Tr eines extremalen AVL-Baums der H he h 1 sind selber wieder a o extremale AVL-B ume zu den H hen h 1 und h 2 bzw. h 2 und h 1. a o Wir wollen nun diese Vermutungen beweisen:

5.1. GRUNDSATZLICHE EIGENSCHAFTENh 0 1 2 ... 3 ... ... ...

55

Abbildung 5.3: Extremale AVL-B ume. a zu 1.: Angenommen, T sei ein extremaler AVL-Baum mit einer H hendifferenz 0. Nimmt man nun, o a wie in Abb. 5.4, im linken Teilbaum alle Bl tter weg, ergibt sich wieder ein AVL-Baum, da das L schen der Bl tter die Balancebedingung | (u)| 1 f r alle u V nicht verletzt. Dieser o a u AVL-Baum hat dieselbe H he, aber weniger Knoten als T . Damit ist T nicht mehr extremal. o Das ergibt einen Widerspruch. zu 2.: Da nach Denition von AVL-B umen klar ist, dass alle Teilb ume von AVL-B umen wieder a a a AVL-B ume sind, bleibt zu zeigen, dass seine Teilb ume extremal zu den H hen h 1 und a a o h 2 sind. Angenommen, T sei extremal, aber ein Teilbaum, o.B.d.A. T , nicht. Dann gibt es einen AVL-Baum T mit weniger Knoten als T , aber derselben H he. Ersetze nun T durch T o in Baum T . Damit ergibt sich ein neuer Baum T mit weniger Knoten, aber derselben H he. o o a o Da T und Tr beides AVL-B