Unversität Koblenz-Landau Abteilung Koblenz Fachbereich Informatik Dipl.-Inf. Jörg Müller Proseminar WS 2000/2001 Algorithmen Kompression: Lempel/Ziv-Algorithmen 9. Januar 2001 Tobias Koch <tkoch@uni-koblenz.de> http://www.uni-koblenz.de/ tkoch/
INHALTSVERZEICHNIS i Inhaltsverzeichnis 1 Familie der Lempel/Ziv-Algorithmen 1 1.1 Ursprung................................ 1 1.2 Gemeinsamkeiten............................ 1 2 LZ77 2 2.1 Der Algorithmus............................ 2 2.2 Nachteile von LZ77........................... 4 3 LZSS 5 3.1 Unterschiede zu LZ77.......................... 5 3.2 Der Algorithmus............................ 5 3.3 Nachteile von LZSS........................... 6 4 LZ78 7 4.1 Der Algorithmus............................ 7 4.2 Nachteile von LZ78........................... 10 5 LZW 11 5.1 Unterschiede zu LZ78.......................... 11 5.2 Der Algorithmus............................ 11 6 Burrows-Wheeler-Transformation 13 Literaturverzeichnis 16
1 FAMILIE DER LEMPEL/ZIV-ALGORITHMEN 1 1 Familie der Lempel/Ziv-Algorithmen Diese Ausarbeitung entstand während des Wintersemesters 2000/2001 im Rahmen eines Proseminars im Studiengang Informatik an der Universität Koblenz-Landau. Das Thema der Arbeit ist eine Vorstellung der Lempel/Ziv-Algorithmen. 1.1 Ursprung Ihren Anfang hat die moderne Komprimierung im Jahre 1977. Abraham Lempel und Jacob Ziv veröffentlichten in einem Magazin des IEEE (Institute of Electrical and Electronics Engineers) einen ersten Algorithmus (LZ77 ) zu diesem Thema. Dieser war einfach konzeptioniert und hätte schon vor 1977 implementiert werden können. Allerdings hatten die bisherigen Bemühungen in die Verbesserung der Huffman-Codierung einer Entwicklung in diese Richtung entgegengewirkt. LZ77 bildet eine Grundlage für eine ganze Familie von Algorithmen: die Lempel/Ziv- Algorithmen (im Folgenden LZ-Algorithmen genannt). Diese entstanden durch Verbesserungen des ursprünglichen Algorithmus und durch Anpassung an spezielle Anforderungen (Kompression von Text, Bildern etc.). 1.2 Gemeinsamkeiten Aufgrund des oben genannten gemeinsamen Ursprungs dieser Algorithmen besitzen sie alle die folgenden Eigenschaften: verlustfreie Kompression alle Informationen, die bei der Dekompression benötigt werden, werden während des Dekompressionsvorgangs gewonnen Eine weitere Gemeinsamkeit liegt in ihrem Funktionsprinzip. Um dieses zu verstehen, muss erwähnt werden, dass jede Eingabe einen gewissen Grad an Redundanz aufweist. Das heisst, es existieren sich wiederholende Zeichenketten innerhalb der Eingabe. Diese Redundanz ist natürlich von der Art der Eingabedaten abhängig. In Texten treten bestimmte Wörter immer wieder auf, in Bilddaten ergeben sich oft Ketten aus gleichen aufeinanderfolgenden Zeichen. Daher wurden für die verschiedene Anwendungsgebiete spezielle Algorithmen entwickelt. Die LZ-Algorithmen ersetzen ein erneutes Auftreten einer Zeichenkette durch eine Referenz auf ihr erstmaliges Auftreten. Sie werden auch substituierende Kompressoren genannt. Man kann die beiden folgenden Gruppen unterscheiden:
2 LZ77 2 1. Die zu komprimierende Zeichenfolge wird in dem bereits abgearbeiteten Teil gesucht. Bei Erfolg wird statt der Zeichenfolge selbst ein Zeiger auf das vorige Auftreten gespeichert. Die bereits bearbeitete Datenmenge stellt also das Wörterbuch für die weitere Kompression dar. Alle auf LZ77 basierenden Algorithmen gehören zu dieser Gruppe. 2. Die Algorithmen der zweiten Gruppen erzeugen ein Wörterbuch aus Folgen, die in den Ursprungsdaten vorliegen. Wird eine Zeichenfolge nochmals gefunden, so wird nur deren Index abgespeichert. Der Vorteil ist, dass das Wörterbuch während der Dekompression wieder erzeugt werden kann. Hierzu zählen alle Ableitungen von LZ78. Innerhalb dieser Ausarbeitung werden nun vier Algorithmen dieser Familie näher erläutert: 2 LZ77 LZ77 ist der erste von Lempel und Ziv entwickelte Algorithmus. 2.1 Der Algorithmus Die Hauptdatenstruktur in LZ77 ist ein zweigeteiltes Datenfenster. Den ersten Teil bildet ein Block, der bereits abgearbeite Daten enthält. Ein Puffer stellt den zweiten Teil dar. In ihm befinden sich eingelesene Daten, die noch codiert werden müssen....abaabcbcbcbcaabbcabcbabbcbbbabcaaaabcbbcc... Fensterkörper Puffer Abbildung 1: Datenfenster über Eingabedaten Das Datenfenster erstreckt sich nicht über die gesamten bisher bearbeiteten Daten, da es zu viel Rechenzeit in Anspruch nehmen würde, die kompletten Daten nach Übereinstimmungen zu durchsuchen. Allgemein hat es eine Grösse von ca. tausend Zeichen. Somit liegen nur die letzten n Zeichen als Wörterbuch vor. Der Puffer ist mit zehn bis hundert Zeichen wesentlich kleiner. Das Datenfenster bewegt sich (in Pfeilrichtung) über die Eingabedaten.
2 LZ77 3 Natürlichsprachlich läßt sich die Arbeitsweise des Algorithmus folgendermaßen formulieren: 1. Bewege den Puffer auf den Anfang (Position 0) der Eingabedaten. Der Fensterkörper befindet sich also zu Beginn nicht über der Eingabe. 2. Suche im Fensterkörper die längste Übereinstimmung mit der Zeichenkette im Puffer. 3. Gib den Zeiger (Position, Länge) auf die gefundene Zeichenkette im Fensterkörper und das erste nicht übereinstimmende Zeichen im Puffer aus. 4. Ist das Ende der Eingabedaten noch nicht erreicht, so bewege das Datenfenster um die Länge der gefundenen Zeichenkette+1 weiter. Fahre mit Schritt 2 fort. Betrachten wir dies anhand eines Beispiels: Ausgabe AABCCBBABAACCCBCABACBBCAABCCBA AABCCBBABAACCCBCABACBBCAABCCBA AABCCBBABAACCCBCABACBBCAABCCBA AABCCBBABAACCCBCABACBBCAABCCBA AABCCBBABAACCCBCABACBBCAABCCBA AABCCBBABAACCCBCABACBBCAABCCBA (0,1,C) (1,3,C) (1,3,C) (4,1,B) (3,2,A) (6,2,C) Abbildung 2: Beispiel LZ77 Im ersten Schritt ist die längste gefundene Übereinstimmung nur das Zeichen A an Position 0 im Fensterkörper. Es wird also die Position 0, die Länge 1 und das nachfolgende Zeichen C ausgegeben. Nachdem das Textfenster weiterbewegt wurde, wird die nächste Übereinstimmung gesucht. Es wird die Zeichenkette CCB gefunden. Diese befindet sich an Position 1, besitzt die Länge 3 und auf sie folgt das Zeichen C. Der Algorithmus wird nun fortgeführt, nachdem das Textfenster wieder weiter über die Eingabedaten bewegt wurde. Die Ausgabe des Algorithmus ist also ein Tripel aus dem Index der gefundenen Zeichenkette, ihrer Länge und dem ersten nicht übereinstimmenden Zeichen. Dieses
2 LZ77 4 muss übergeben werden, da die Möglichkeit besteht, dass keine Übereinstimmung gefunden wird. Der leere Zeiger (0,0), der eine leere Zeichenkette repräsentiert, da die Länge 0 ist, reicht dann nicht aus. Der Index bezieht sich auf die Postion im aktuellen Datenfenster, nicht auf die Position in der kompletten Eingabe. Daher muss dem Dekompressionsalgorithmus auch die Grösse des benutzten Datenfensters mitgeteilt werden. Die Dekompression ist noch einfacher. Ein Tupel wird eingelesen, die dadurch codierte Phrase und das darauffolgende Zeichen werden ausgegeben. Jetzt wird das Textfenster weitergeschoben und der Vorgang wird wiederholt. Dies benötigt weniger Rechenzeit, da keine Vergleichsoperationen durchgeführt werden müssen. 2.2 Nachteile von LZ77 Hier die Nachteile von LZ77 im Überblick: 1. Der Kompressionsgrad ist von der Größe des Datenfensters abhängig. Je größer das Datenfenster allerdings gewählt wird, desto mehr Vergleichsoperationen sind notwendig. 2. Wird keine Übereinstimmung gefunden, so wird mehr Speicherplatz zur Abspeicherung des Tupels benötigt als für das ursprüngliche Zeichen. Was sind nun die Ursachen für diese Probleme: 1. Bei der Codierung erfolgt eine Stringvergleichsoperation jeder Position im Datenfenster mit dem Pufferinhalt. Durch Vergrößerung des Fensters steht ein größeres Wörterbuch zur Verfügung, wodurch wiederum der Grad der Kompression erhöht wird. Nun sind aber mehr Vergleichsoperationen notwendig, was eine höhere Rechenzeit zur Folge hat. 2. Nehmen wir an, der Algorithmus benutzt ein 4096 Bytes großen Fensterkörper und einen sechzehn Byte großen Puffer. So werden zur Codierung zwölf Bit (2 12 = 4096) für die Position und vier Bit (2 4 = 16) für die Länge benötigt. Weitere 8 Bit entfallen auf die Codierung des nachfolgenden Zeichens. Dies bedeutet pro Tupel werden 24 Bit genutzt. Der beste Fall ergibt sich, wenn n 1 Zeichen des Pufferinhalts (n := Länge des Puffers) im Datenfenster gefunden werden. In o.a. Fall werden dann sechzehn Bytes (128 Bit) auf 24 Bit abgebildet. Wird nun aber keine Übereinstimmung gefunden, so stellt dies den schlechtesten Fall (worst case) dar. Ein Zeichen (8 Bit) muss nun durch den leeren Zeiger (0,0) und das Zeichen selbst, also insgesamt 24 Bit, abgebildet werden. Es entsteht ein Overhead von 16 Bit.
3 LZSS 5 3 LZSS LZSS wurde im Jahr 1982 von Storer und Szymanski als Weiterentwicklung von LZ77 veröffentlicht. Es wurde versucht, Ausführungsgeschwindigkeits- und Effektivitätsprobleme von LZ77 zu verbessern. 3.1 Unterschiede zu LZ77 In LZSS existiert ebenso ein Datenfenster, welches sich in zwei Teile unterteilt. Das Datenfenster von LZ77 verwaltet den bereits abgearbeiteten Teil als fortlaufenden Text ohne weitere Organisation. Bei LZSS wurde dieser Teil um einen binären Suchbaum erweitert. Beim Bearbeiten der Daten wird bereits codierter Text in diese Baumstruktur eingefügt. Dadurch ist der Aufwand der Stringvergleichsoperationen nicht mehr proportional zum Produkt aus Fenstergröße und Phrasenlänge, sondern zum Logarithmus (mit Basis 2) dieses Produktes. Die zweite Veränderung wurde an der Ausgabe des Algorithmus vorgenommen. LZ77 muss bei keiner Übereinstimmung dennoch das komplette Tupel aus leerem Zeiger und dem Zeichen selbst abspeichern. LZSS unterscheidet nun aber: Es wird entweder ein Zeiger auf ein voheriges Vorkommen oder das Zeichen selbst abgespeichert. Dazu wird das Tupel um ein Bit erweitert, in welchem festgelegt wird, ob es sich um ein Tupel (Index, Länge) oder um ein einzelnes Zeichen handelt. Dies reduziert den Overhead auf ein Bit pro Zeichen. Eine Abspeicherung als Zeiger ist also erst vorteilhaft, wenn die Länge des Zeigers kleiner ist als die Länge der Übereinstimmung. 3.2 Der Algorithmus Aufgrund des Umfangs wird auf die Darstellung von Quellcode verzichtet und der Algorithmus natürlichsprachlich formuliert: 1. Bewege den Puffer auf den Anfang (Position 0) der Eingabedaten. 2. Suche im Fensterkörper mit Hilfe des Baumes die längste Übereinstimmung mit der Zeichenkette im Puffer. 3. Ist die Länge der Übereinstimmung der Länge des Zeigers? TRUE: Speichere das Tupel (Position, Länge der Übereinstimmung) und bewege das Textfenster um die Länge der Übereinstimmung weiter. FALSE: Speichere das erste Zeichen des Puffers und bewege das Textfenster um eine Position weiter.
3 LZSS 6 4. Ist das Ende der Eingabedaten noch nicht erreicht, so fahre mit Schritt 2 fort. Die Dekompression läuft analog zu LZ77 ab. Nur wird zunächst noch ausgewertet, ob es sich um einen Zeiger oder um ein einzelnes Zeichen handelt. 3.3 Nachteile von LZSS Bei bestimmten Zeichenfolgen ergibt sich ein Nachteil, wenn sie in den binären Baum eingebunden werden. Nehmen wir an, der Algorithmus erhält die Zeichenfolge ABCDEFGHIJKLMNOP als Eingabe, so ergibt sich der Baum aus Abbildung 3. ABCDEFG BCDEFGH CDEFGHI DEFGHIJ EFGHIJK FGHIJKL GHIJKLM... Abbildung 3: Ungünstige Baumstruktur in LZSS Der Baum wird zu einer verknüpften Liste, da kein Zeichen mehrfach vorkommt und diese bereits ansteigend angeordnet sind. In einer Liste, die dieser Baum nun darstellt, gestaltet sich die Suche nach einem String sehr ungünstig. Ein weiterer Nachteil ist der Komprimierungsgrad am Anfang der Eingabe. Hier ist dieser sehr gering, da noch keinerlei Daten im Datenfenster vorliegen. Als Verbesserung füllt man daher das Datenfenster zu Beginn mit 256 Strings. Diese enthalten 16 aufeinanderfolgende Sequenzen aller Symbole des Alphabets.
4 LZ78 7 4 LZ78 LZ77 und darauf aufbauende Algorithmen weisen einen großen Mangel auf: das Datenfenster, in dem nach vorhandenen Strings gesucht wird, beinhaltet immer nur die letzten n Zeichen. Das heisst, Zeichenfolgen, die jetzt ausserhalb des Datenfensters liegen und nochmals auftreten, können nicht mehr durch einen Zeiger codiert werden. Ebenso kann ein String, der gefunden wird, maximal die Länge des Puffers 1 besitzen, obwohl möglicherweise eine längere Übereinstimmung gefunden worden wäre. Eine Vergrösserung des Datenfensters hätte aber zur Folge, dass auch die Größe des zu speichernden Tupels zunimmt, da sowohl Index als auch Länge der gefundenen Zeichenkette größere Werte annehmen können. Ausserdem entsteht eine viel höhere Rechenzeit, da diese direkt proportional zur Länge des Fensters und des Puffers ist. Ziv und Lempel entwickelten aufgrund dieser Probleme 1978 einen weiteren Algorithmus: LZ78. 4.1 Der Algorithmus In LZ78 gibt es kein Datenfenster. Es wird eine potentiell unbegrenzte Tabelle/Liste aller vorher eingelesen Phrasen gebildet. Die Ausgabe besteht aus dem Code für eine Phrase und einem Zeichen, das auf die Phrase folgt. Zu Beginn ist sowohl beim Kompressions- als auch beim Dekompressionsvorgang die Tabelle fast leer. Sie enthält nur einen leeren String, den Null-String. Der Algorithmus soll wieder natürlichsprachlich dargestellt werden: 1. Zu Beginn sind Präfix und Wörterbuch leer. 2. Z := nächstes eingelesenes Zeichen 3. Ist Präfix Z im Wörterbuch zu finden? TRUE: Präfix = Präfix Z FALSE: Tue: (a) Gib den Wörterbuchcode von Präfix und das Zeichen Z aus. (b) Füge Präfix Z dem Wörterbuch hinzu. (c) Leere Präfix. 4. Ist das Ende der Eingabe erreicht?
4 LZ78 8 FALSE: Fahre mit Schritt 2 fort. TRUE: Sollte Präfix nicht leer sein, so gib den Wörterbuchcode von Präfix aus. Das Beispiel aus Tabelle 1 soll dies nun veranschaulichen: Eingabetext: DAD DADA DADDY DADO... Code Ausgabezeichen Codierter String 0 D D 0 A A 1 D 1 A DA 4 DA 4 D DAD 1 Y DY 0 6 O DADO Tabelle 1: Beispiel LZ78 Zu Beginn steht nur der leere String an Position 0 im Wörterbuch. D wird eingelesen, aber es steht noch nicht im Wörterbuch. Also wird Code 0 (Position des leeren Strings) und D selbst ausgegeben. Gleichzeitig wird D an Position 1 des Wörterbuchs hinzugefügt. Gleichermaßen vollzieht es sich mit A. Dann wird ein D eingelesen. Dieses wird an Position 1 gefunden. Das nächste Zeichen wird eingelesen. D findet sich nicht im Wörterbuch. Die Position von D (1) und werden ausgegeben. D wird im Wörterbuch gespeichert. Das Wörterbuch hat nach dem Durchlauf des Beispiels den in Tabelle 2 angegebenen Inhalt. Bei der Größe der Phrasentabelle muss darauf geachten werden, dass folgende Größen möglichst wenig zunehmen: 1. die Anzahl der Bits, die im Ausgabe-Tupel verwendet werden 2. die Rechenzeit zur Verwaltung der Tabelle Bei der Dekompression muss das Wörterbuch analog zum Kompressionsvorgang aufgebaut werden. Nach der Dekompression ist das entstandene Wörterbuch identisch zu jenem das bei der Kompression erzeugt wurde. Der Dekompressionalgorithmus in Einzelschritten: 1. Zu Beginn ist das Wörterbuch leer
4 LZ78 9 Positon Phrase 0 1 D 2 A 3 D 4 DA 5 DA 6 DAD 7 DY 8 9 DADO Tabelle 2: Wörterbuchinhalt nach Ausführung des Beispiels 2. C := der nächste eingelesene Phrasenindex aus dem Code-Tupel 3. Z := das darauffolgende Zeichen aus dem Code-Tupel 4. Gib die durch C dargestellte Phrase aus dem Wörterbuch und Z aus 5. Trage C Z ins Wörterbuch ein 6. Ist das Ende der Eingabedaten noch nicht erreicht, fahre mit Schritt 2 fort Führen wir diesen Algorithmus mit der oben komprimierten Zeichenfolge durch: Eingelesenes Tupel Codierte Phrase Zeichen Wörterbucheintrag (0, D ) D D (0, A ) A A (1, ) D D (1, A ) D A DA (4, ) DA DA (4, D ) DA D DAD (1, Y ) D Y DY (0, ) (6, O ) DAD O DADO Tabelle 3: Beispiel Dekompression mit LZ78
4 LZ78 10 4.2 Nachteile von LZ78 Die Verwaltung der Tabelle in LZ78 stellt eine Schwierigkeit dar. Wählt man für den Phrasenindex eine Größe von 16 Bit, so können maximal 65536 (2 16 ) Phrasen einschließlich des Null-Strings aufgenommen werden. Auch in LZ78 werden die Phrasen in einem Baum - allerdings kein binärer Baum - verwaltet. Der Null-String bildet hierbei mit Index 0 die Wurzel. Wird ein neues Zeichen dem Null-String hinzugefügt, so wird es zu einem neuen Zweig des Baumes und erhält eine neue Knotennummer. Der Baum des verwendeten Beispiels hat die in Abbildung 4 ersichtliche Struktur. (8, ) (2, A ) (1, D ) 0 (3, ) (4, A ) (7, Y ) (5, ) (6, D ) (9, O ) Abbildung 4: LZ78-Tabellenbaum Das größte Problem der Baumstrukturen in LZ78 besteht in der potentiell großen Anzahl von Zweigen. Legt man z.b. ein 8-Bit-Alphabet zugrunde, so kann jeder Knoten bis zu 256 Zweige besitzen. Um dies mit möglichst wenig Speicherverbrauch zu bewerkstelligen, werden die Zweige eines jeden Knoten in einer Indexliste verwaltet. Diese ist nicht länger als die tatsächliche Anzahl der Verzweigungen. Was sollte nun aber geschehen, wenn die Tabelle voll ist, also z.b. 65535 Phrasen eingefügt worden sind? Die einfachste Möglichkeit wäre, keine weiteren Phrasen in die Tabelle aufzunehmen. Dies ist aber in der Praxis unvorteilhaft, da es sehr große Unterschiede in der Natur der eingelesenen Zeichen gibt. So ist beispielsweise der Codebereich eines compilierten Programms anders aufgebaut als sein Datenbereich. Dieses Problem wurde z.b. dadurch gelöst, dass der Grad der Kompression ständig überwacht wird. Wird der Komprimierungsgrad zu schlecht, wird die Tabelle gelöscht und eine neue Tabelle aufgebaut. Das Unix-Programm compress nutzt diese Lösung.
5 LZW 11 Ein weiterer Nachteil von LZ78 gegenüber LZ77 ist, dass auch das Dekompressionsprogramm diesen Baum aufbauen muss. Bei LZ77 handelte es sich lediglich um einen Zeiger auf die vorherige Position im Datenfenster. 5 LZW LZW ist eine von Terry Welch im Jahr 1984 veröffentlichte Weiterentwicklung von LZ78. LZW liegt vielen heute benutzten Packprogramm wie compress oder zip zugrunde. Ebenso wird der Algorithmus im Grafikformat gif verwendet. 5.1 Unterschiede zu LZ78 Der Vorteil von LZW gegenüber LZ78 ist, dass niemals Einzelzeichen kodiert werden müssen. Dies geschieht dadurch, dass zunächst für jedes in der Eingabe vorkommende Zeichen ein Wörterbucheintrag vorgenommen wird. Daraus ergeben sich folgende Konsequenzen: Es muss nur noch nach Strings aus mindestens zwei Zeichen gesucht werden. Das erste Zeichen der nächsten zu suchenden Zeichenkette ist das letzte der zuvor abgearbeiteten Zeichenkette. Dies ist notwendig, damit der Dekodierungsalgorithmus das Wörterbuch korrekt aufbauen kann. 5.2 Der Algorithmus De-/Kompression werden analog zu LZ78 durchgeführt. Der Kompressionsvorgang kann folgendermaßen beschrieben werden: 1. Zu Beginn enthält das Wörterbuch alle Einzelzeichen der Eingabe. Der Präfix ist leer. 2. C := das nächste Zeichen der Eingabe 3. Ist Präfix C im Wörterbuch vorhanden? TRUE: Präfix := Präfix C FALSE: Tue: (a) Gib den Wörterbuchindex von Präfix aus. (b) Füge Präfix C dem Wörterbuch hinzu. (c) Präfix := C 4. Ist das Ende der Eingabe erreicht?
5 LZW 12 FALSE: Fahre mit Schritt 2 fort. TRUE: Gib den Wörterbuchindex von Präfix aus. Hierzu wieder ein Beispiel: Das Wörterbuch enthält an den Stellen 0-255 bereits alle möglichen Einzelzeichen eines 8-Bit-Alphabets! Eingabetext: DAD DADA DADDY DADO... Zeicheneingabe Ausgegebener Code Wörterbucheintrag DA < D > 256= DA AD < A > 257= AD D < D > 258= D DAD 256 259= DAD DA 256 260= DA D < > 261= D DADD 259 262= DADD DY < D > 263= Y Y 263 264= Y DA 261 265= DA ADO 257 266= ADO O < O > Tabelle 4: Beispiel LZW < D > repräsentiert den Wörterbuchindex des Zeichen D. Bei der Dekodierung ergibt sich die Problematik, dass kein Zeichen mehr im LZW- Code angegeben wird. Das Dekodierungsprogramm muss aber dennoch das Wörterbuch korrekt aufbauen können. Dies wird dadurch erreicht, dass das erste Zeichen eines neuen Wörterbucheintrags identisch sein muss mit dem letzten Zeichen des vorherigen Eintrags. Der Dekodierer ist dadurch aber einen Schritt langsamer als der Kodierer, da er ja zunächst das erste Zeichen des nächsten Codes ermitteln muss. Tritt in den Eingabedaten nun aber die Zeichenfolge ZwZ auf, wobei Z ein beliebiges Zeichen und w eine beliebige Zeichenfolge seien, so wird ein Code eingelesen, der noch nicht im Wörterbuch existiert. Da aber die Zeichenkette Zw schon eingetragen sein muss, ist es nur notwendig, die letzte Zeichenkette konkateniert mit ihrem ersten Zeichen - also ZwZ - auszugeben. In natürlichsprachlicher Formulierung läßt sich der Algorithmus zur Dekompression folgendermaßen ausdrücken:
6 BURROWS-WHEELER-TRANSFORMATION 13 1. Zu Beginn enthält das Wörterbuch alle Einzelzeichen der Eingabe. 2. C 1 := erster Code aus der Eingabe. 3. Gib den Wörterbucheintrag von C 1 aus. 4. C 2 = C 1 5. C 1 := nächster Code aus der Eingabe. 6. Ist die Phrase von C 1 im Wörterbuch enthalten? TRUE: Tue: (a) Gib die Phrase von C 1 aus. (b) Präfix := Phrase von C 2. (c) Z := erstes Zeichen von Präfix. (d) Füge Präfix Z dem Wörterbuch hinzu. FALSE: Tue: (a) Präfix := Phrase von C 2. (b) Z := erstes Zeichen von Präfix. (c) Gib Präfix Z aus und füge es dem Wörterbuch hinzu. 7. Ist das Ende der Eingabe erreicht? FALSE: Fahre mit Schritt 4 fort. TRUE: Gib den Wörterbuchindex von Präfix aus. 6 Burrows-Wheeler-Transformation Ein Nachteil der LZW- und auch anderer Kompressionsalgorithmen ist, dass die Kompressionsrate fällt, sobald sich die Eigenschaften der Eingabedaten ändert. Mit der Burrows-Wheeler-Tranfsformation (BWT) besteht nun die Möglichkeit beliebig lange Kontexte für das Vohersagen des nächsten Zeichens zu verwenden. BWT wurde erstmals im Mai 1994 von Michael Burrows und David Wheeler veröffentlicht. BWT betrachtet die Eingabe nicht als Eingabestrom, sondern blockweise. Der Block wird in einer quadratischen Matrix dargestellt (siehe Tabelle 5). Diese wird nun alphabetisch sortiert (siehe Tabelle 6), so dass die erste Spalte
6 BURROWS-WHEELER-TRANSFORMATION 14 H A L L O H A L L 0 A L L O H A L L 0 H L L O H A L L 0 H A L O H A L L O H A L O H A L L O H A L L H A L L O H A L L O A L L O H A L L O H L L O H A L L O H A L O H A L L O H A L O H A L L O H A L L Tabelle 5: Beispiel einer quadratischen Matrix von HALLOHALLO A L L O H A L L 0 H A L L O H A L L 0 H H A L L O H A L L 0 H A L L O H A L L 0 L L O H A L L 0 H A L L O H A L L 0 H A L O H A L L O H A L L O H A L L O H A L O H A L L O H A L L O H A L L O H A L L Tabelle 6: Sortierte quadratische Matrix von HALLOHALLO
6 BURROWS-WHEELER-TRANSFORMATION 15 nun in alphabetischer Reihenfolge vorliegt und die letzte Spalte jeweils den Buchstaben, der dem String in der gleichen Zeile vorangeht, enthält. Geht man nun davon aus, dass jeder String in den einzelnen Zeilen der Matrix einen Kontext zu dem Buchstaben in der letzten Spalte bildet, so fällt Folgendes auf: Aufgrund der alphabetischen Sortierung stehen ähnliche Kontexte beieinander. Einem ähnlichen Kontext geht aber auch meist ein gleicher Buchstabe voraus. Zum Beispiel steht vor der Zeichenfolge hat im Englischen meist ein t, ein w oder ein Leerzeichen. Diese vorangehenden Buchstaben befinden sich nun aber - wie schon angemerkt - in der letzten Spalte, wodurch sich ergibt, dass dort meist gleiche Buchstaben aufeinanderfolgen. Die Ausgabe des Algorithmus besteht aus der letzten Spalte und der Position des Ausgangsstrings in der Matrix. Ohne dies zu zeigen, sei nun angebenen, dass sich hieraus wieder der ursprüngliche String herstellen lässt. In dieser Ausgabe befinden sich also lange Folgen gleicher Zeichen, welche kurz durch andere Zeichen unterbrochen werden. Eine solche Folge lässt sich nun z.b. mit einem der eingeführten LZ-Algorithmen optimal komprimieren. Mit BWT hat sich gezeigt, dass es auch auf Gebieten, in denen lange Zeit keine neuen Entdeckungen hervorgebracht wurden, immer wieder neue und einfache Lösungsansätze gibt.
LITERATUR 16 Literatur [1] Mark Nelson. Datenkomprimierung: effiziente Algorithmen in C. Heise Verlag, 1993 [2] Data compression reference center. http://www.rasip.fer.hr/research/compress/ [3] Michael Tamm. Packen wie noch nie - Datenkompression mit dem BWT- Algorithmus. c t - Magazin für Computertechnik, Ausgabe 16/2000, S.194-201. Heise Verlag