Komprimierung von Texten mit Byte-Huffman-Kodierungsverfahren für ...

Definition der Aufgabenstellung . ...... dabei die regulären Ausdrücke definiert sind, kann man damit eine ...... doppelte Kosten der Lösung definiert werden.
946KB Größe 10 Downloads 82 Ansichten
Fachbereich Informatik

Diplomarbeit

Komprimierung von Texten mit Byte-HuffmanKodierungsverfahren für effektive Suche

ausgeführt in der Firma

MedicDAT GmbH

unter Anleitung von

Prof. Jürgen Sauer und

Dipl. math. Alfred Fuchs

durch Diplomandin

Marita Kröcker

Grunewaldstraße 6 93053 Regensburg

Diplomarbeit

Inhaltsverzeichnis

Inhaltsverzeichnis: VORWORT................................................................................................................. 6 1

EINLEITUNG....................................................................................................... 7

1.1

Motivation und Ziel...................................................................................................... 7

1.2

Definition der Aufgabenstellung ................................................................................. 8

1.3

Gliederung der Diplomarbeit...................................................................................... 8

2

LEXIKALISCHE ANALYSE .............................................................................. 10

2.1

Kurze Einleitung in die Automatentheorie.............................................................. 10

2.1.1

Was sind endliche Automaten (FSM)? ................................................................ 10

2.1.2

Was ist ein nicht deterministischer Automat (NFA)? .......................................... 11

2.1.3

Was ist ein deterministischer Automat (DFA)? ................................................... 12

2.2

JLex ............................................................................................................................. 14

2.2.1

Die JLex-Programmstruktur................................................................................. 15

2.2.1.1

Benutzercodeteil............................................................................................... 15

2.2.1.2

Definitionsteil................................................................................................... 15

2.2.1.2.1 Interner Code der lexikalischen Klasse ...................................................... 16 2.2.1.2.2

Zustandsdefinitionen .................................................................................. 16

2.2.1.2.3

Makrodefinitionen...................................................................................... 16

2.2.1.2.4

Lexikalisch-analytische Komponentenüberschriften ................................. 17

2.2.1.2.5

Zeilenvorschub und Betriebssystemkompatibilität .................................... 18

2.2.1.2.6

Zeichencode ............................................................................................... 18

2.2.1.3 2.2.2

Regelteil ........................................................................................................... 18 Reguläre Ausdrücke ............................................................................................. 19

2.2.2.1

Metazeichen ..................................................................................................... 20

2.2.2.2

Zeichenklassen ................................................................................................. 20

2.2.2.3

Wiederholungsoperatoren ................................................................................ 21

2.2.2.4

Kontextoperatoren............................................................................................ 21

2.2.2.5

Komplexe Ausdrücke....................................................................................... 22

2.2.2.6

Operatoren-Prioritäten...................................................................................... 22 -2-

Diplomarbeit 2.2.2.7 2.2.3 2.3 3

Inhaltsverzeichnis Maskierung von Metazeichen .......................................................................... 22

Lexikalische Werte............................................................................................... 23

Anwendungsgebiete.................................................................................................... 24 ANALYSE VON ALGORITHMEN ..................................................................... 25

3.1

Die Richtlinien ............................................................................................................ 25

3.2

Klassifikation von Algorithmen ................................................................................ 26

3.3

Komplexität der Berechung ...................................................................................... 27

3.4

Analyse des durchschnittlichen Falles...................................................................... 27

3.5

Grundlegende rekurrente Beziehung ....................................................................... 28

4

DATENKOMPRIMIERUNG............................................................................... 31

4.1

Komprimierungsverfahren ....................................................................................... 31

4.1.1

Komprimierungsmodelle...................................................................................... 31

4.1.1.1

Statische Modelle ............................................................................................. 34

4.1.1.2

Semi-statische Modelle .................................................................................... 34

4.1.1.3

Adaptive Modelle............................................................................................. 35

4.1.2 4.1.2.1

Statische Methoden .............................................................................................. 35 Huffman-Kodierung ......................................................................................... 35

4.1.2.1.1

Statische Huffman-Kodierung.................................................................... 36

4.1.2.1.2

Adaptive Huffman-Kodierung ................................................................... 40

4.1.2.1.3

Wortbasierte Komprimierung .................................................................... 42

4.1.2.2

Arithmetische Kodierung ................................................................................. 43

4.1.2.2.1 4.1.3

5

PPM – Prediction by Partial Matching....................................................... 45

Wörterbuch-Methoden ......................................................................................... 45

4.1.3.1

LZ 77 ................................................................................................................ 46

4.1.3.2

LZ 78 ................................................................................................................ 47

4.1.3.3

LZW ................................................................................................................. 48

DAS ANGEWENDETE KOMPRIMIERUNGSVERFAHREN............................. 50

-3-

Diplomarbeit 5.1

Inhaltsverzeichnis

Die wortbasierte Byte-Huffman-Kodierung ............................................................ 50

5.1.1

Wieso diese Komprimierungstechnik .................................................................. 51

5.1.2

Byteorientierter Huffman-Code ........................................................................... 51

6

5.1.2.1

Spaceless Wortmodell ...................................................................................... 52

5.1.2.2

Kanonische Huffmancode ................................................................................ 53

5.1.2.3

Die Kodierung .................................................................................................. 56

5.1.2.4

Optimierung des Baumes ................................................................................. 57

5.1.2.5

Die Dekodierung .............................................................................................. 58

SUCHVERFAHREN .......................................................................................... 60

6.1

Elementare Suchmethoden........................................................................................ 60

6.1.1

Sequentielle Suche ............................................................................................... 61

6.1.2

Binäre Suche ........................................................................................................ 62

6.2

Suche in den mit tagged Huffman kodierten Texten............................................... 63

6.2.1

Erste Phase: Vorverarbeitung............................................................................... 64

6.2.2

Zweite Phase: Suche............................................................................................. 65

6.3

Suche in den mit plain Huffman kodierten Texten ................................................. 67

6.3.1 6.3.1.1

Der grundlegende Algorithmus ........................................................................ 67

6.3.1.2

Erweiterung des grundlegendes Algorithmus .................................................. 70

6.3.1.2.1

Art der Phrasentypen.................................................................................. 70

6.3.1.2.2

Das Algorithmusschema ............................................................................ 71

6.3.2 7

Automatenbasierter Algorithmus: plain filterless ................................................ 67

Filterungsalgorithmus: plain filter ....................................................................... 73

IMPLEMENTIERUNG ....................................................................................... 75

7.1

Die eingesetzte Programmiersprache ....................................................................... 75

7.2

Grundlagen zu XML.................................................................................................. 76

7.2.1

DOM..................................................................................................................... 78

7.2.2

SAX...................................................................................................................... 79

7.2.3

Zusammenfassung und Abwägung ...................................................................... 81

7.3

Die grundlegenden Komponenten ............................................................................ 81 -4-

Diplomarbeit 7.3.1

Inhaltsverzeichnis

Implementierung des Komprimierungsalgorithmus............................................. 82

7.3.1.1

Package-Struktur .............................................................................................. 82

7.3.1.2

Weiteren Komponente...................................................................................... 83

7.3.1.2.1

Klassen und Klassendiagramme................................................................. 83

7.3.1.2.2

Implementierungskonzept Kodierung-Dekodierung.................................. 92

7.3.1.2.3

Implementierungskonzept Transformer-Parser.......................................... 93

7.3.2 7.3.2.1

Implementierung der graphischen Oberfläche ..................................................... 99 Graphikprogrammierung.................................................................................. 99

7.3.2.1.1 7.3.2.2

Graphische Oberfläche................................................................................... 102

7.3.2.2.1 8

Die Swing Bibliothek ............................................................................... 101 Graphische Aufbau des byteorientierten Huffman-Baumes .................... 103

RESÜMEE UND AUSBLICK ...........................................................................106

8.1

Zusammenfassung.................................................................................................... 106

8.2

Ausblick..................................................................................................................... 107

9

ANHANG A: LITERATURVERZEICHNIS: ......................................................108

10

ANHANG B: ABBILDUNGSVERZEICHNIS: ...............................................111

11

ANHANG C: TABELLENVERZEICHNIS: ....................................................113

12

ANHANG D: GLOSSAR...............................................................................114

ERKLÄRUNG..........................................................................................................117

-5-

Diplomarbeit

Vorwort

Vorwort Das vorliegende Dokument wurde im Rahmen der Diplomarbeit als Diplom-Informatikerin in der Fachrichtung Wirtschaft an der Fachhochschule in Regensburg erstellt. Mein besonderer Dank für die wissenschaftliche Unterstützung und Betreuung während der Durchführung gilt Dipl.-Ing., Dipl.-Wirt.-Ing. Jürgen Sauer von der Fachhochschule, der mir bei meinen Fragen helfend zur Seite stand. Für die Hilfe bei der Ausarbeitung und Umsetzung der Diplomarbeit bedanke ich mich bei den Mitarbeitern der Firma MedicDAT in Regensburg, insbesondere aber bei Dipl.-Math. Alfred Fuchs, der mich als technischer Betreuer unterstützt hat. Ferner möchte ich mich beim Firmenleiter Dr. med. Michael Reng bedanken, mit dessen Hilfe diese Diplomarbeit überhaupt erst möglich war. Außerdem danke ich Dr. phil. Hildegard Fuchs für ihre Hilfe bei der Korrektur und allen anderen, die mich während der Verwirklichung dieser Arbeit unterstützt haben.

-6-

Diplomarbeit

Einleitung

1 Einleitung Im Rahmen dieser Diplomarbeit wurde eine Komprimierungstechnik untersucht und implementiert, die eine schnelle Wildcard-Suche auf den damit komprimierten Texten ermöglicht. In folgenden Kapiteln werden einige Themen, die die Idee dieses Projekts beleuchten, genauer erläutert.

1.1 Motivation und Ziel Durch die rasante technische Entwicklung hat die Menge der für den Menschen verfügbaren Informationen stark zugenommen. Unerschöpfliche Informationsbibliotheken stehen heutzutage zur Verfügung. Um die benötigten Informationen aus diesen Riesenmengen an Daten herauszufiltern, ist es unumgänglich, Hilfsmittel zu erschaffen. In zahlreichen Datenverarbeitungssystemen werden verschiedene Techniken dafür angewendet. Eines davon ist das Suchverfahren. Man sucht nach Dateien in einem Filesystem, nach Records in einer Datenbank, nach URLs zu einem bestimmten Thema im Internet usw. Je schneller es geht, desto schneller hat man das erwünschte Ergebnis. Der in dieser Arbeit untersuchte Komprimierungsalgorithmus dient als Basis für eine Volltextsuche direkt auf den komprimierten Textbeständen, ohne vorherige Dekomprimierung.

-7-

Diplomarbeit

Einleitung

1.2 Definition der Aufgabenstellung

Diese Diplomarbeit befasst sich o an erster Stelle mit der Untersuchung wesentlicher Komprimierungsverfahren für Texte, insbesondere der byteorientierten Huffmankodierung. o An zweiter Stelle steht die Erstellung des Konzepts zur Komprimierung von Texten nach dem byteorientierten Huffman-Kodierungsverfahren (als Grundlage dient die Beschreibung von Edleno Silva de Moura, Gonzalo Navarro, Nivio Ziviani, Ricardo Baeza-Yates). o Bei der Implementierung des byteorientierten Huffman-Kodierungsverfahrens wird die Programmiersprache Java eingesetzt (unter Windows 2000). o Diese Implementierung wird am PC getestet. o Für spätere Lernprojekte wird eine graphische Oberfläche erstellt, die den byteorientierten Huffman-Baum anzeigt. o Zudem werden zwei Methoden zur Volltextsuche auf Byte-Huffman kodierten Texten erläutert.

1.3 Gliederung der Diplomarbeit Der Aufbau dieses Dokumentes und die Reihenfolge der fachbezogenen Kapitel entspricht der Vorgehensweise bei der Realisierung der Diplomarbeit. Zu Beginn galt es, die notwendigen theoretischen Grundlagen auf dem Gebiet der Datenkomprimierung zu erarbeiten. Wer mit Daten arbeitet, muss sie auch analysieren können. Das Kapitel 2 erläutert kurz die Arbeitsweise des lexikalischen Analysegenerators JLex. In Kapitel 3 wird eine Zusammenfassung gegeben, wie Algorithmen analysiert werden. Kapitel 4 enthält die Beschreibung einiger

Komprimierungsalgorithmen,

unter

denen

auch

der

klassische

Huffman-

Kodierungsverfahren erläutert wird. Dieser Algorithmus wird als Basis für den in dieser Arbeit entwickelten Komprimierungsalgorithmus verwendet, auf den es im Kapitel 5 näher ein-8-

Diplomarbeit

Einleitung

gegangen wird. In Kapitel 6 werden zwei bekannte und zwei für diese Arbeit spezifische Suchalgorithmen beschrieben. Kapitel 7 befasst sich mit den Implementierungen, die im Laufe dieser Diplomarbeit erstellt wurden. Im ersten Teil dieses Kapitels wird die Implementierung des Algorithmus beschrieben. Im zweiten die Implementierung der graphischen Oberfläche. Im letzten Kapitel 8 dieser Diplomarbeit werden die Ergebnisse des Projekts zusammengefasst und einen Ausblick auf mögliche Erweiterungen und Anwendungsmöglichkeiten des Algorithmus gegeben.

-9-

Diplomarbeit

Lexikalische Analyse

2 Lexikalische Analyse Die lexikalische Analyse ist dafür verantwortlich, einen Eingabestrom von Zeichen in einzelne Tokens aufzuteilen, die oft als Input für einen Parser dienen, der für die semantische Analyse zuständig ist. Viele lexikalische Analysatoren werden direkt ausprogrammiert (wie im Code die Klasse TreeDataParser.Tokenizer) oder arbeiten auf der Basis eines deterministischen Automa-

ten, der über eine spezielle Sprache mit regulären Ausdrücken programmiert wird (im Code die Klasse SpacelessWordModelLex). Ein solcher Automat wird durch den Eingabestrom gesteuert und ist ein Modell für ein Programm. Anhand des ersten Zeichens (oder mehreren) kann er eindeutig erkennen, was vorliegt. Die meisten Programmiersprachen definieren z.B. ein Wort so, dass das erste Zeichen ein Buchstabe sein muss. So weiß der Automat bei Eintreten einer Ziffer, dass es sich um eine Zahl handelt und bei Erscheinen eines Buchstabens, dass es sich um ein Wort handelt.

2.1 Kurze Einleitung in die Automatentheorie

2.1.1 Was sind endliche Automaten (FSM)? Eine FSM (Finite State Machine, endlicher Automat) ist ein formales Modell, durch das sich viele Probleme der Informatik beschreiben lassen. Ein Automat beschreibt ein System, das sich aus einer endlichen Anzahl von internen Konfigurationen so genannten Zuständen zusammensetzt. Der Zustand des Systems umfasst die Informationen, die sich aus den bisherigen Eingaben ergeben haben. Diese Informationen werden benötigt, um die Reaktion des Systems auf die noch folgenden Eingaben zu bestimmen. Einer dieser Zustände wird als Startzustand definiert. Das System kann nun unter bestimmten Bedingungen den Zustand wechseln. So einen Zustandsübergang nennt man im Automatenmodell Transition. Graphisch stellt man Zustände durch Kreise und Transitionen durch Pfeile – beschriftet mit der Übergangsbedingung – dar. - 10 -

Diplomarbeit

Lexikalische Analyse

Formal besteht ein Automat (A) aus einer endlichen Menge von Zuständen (X), einem Eingabealphabet (Z), einer Übergangsfunktion (f), einem Startzustand (z0) und einer Menge von Endzuständen (Zf): A = (X, Z, f, z0, Zf)

Abb. 1: Endlicher Automat

Grundsätzlich gibt es zwei verschiedene Arten von Automaten: deterministische und nicht deterministische [vgl. FRI01].

2.1.2 Was ist ein nicht deterministischer Automat (NFA)? Bei nicht deterministischen Automaten (Non-deterministic Finite Automaton, NFA) kann es mehrere Übergangsmöglichkeiten geben, von denen der Automat die Richtige durch Ausprobieren finden muss. Um (a | b ) * abb zu erkennen, ist folgender Automat notwendig:

- 11 -

Diplomarbeit

Lexikalische Analyse

Abb. 2: Der nicht deterministischer Automat

Ein NFA akzeptiert einen Eingabestring nur, wenn es einen Weg vom Start- zum Endzustand gibt. Aus dieser Abbildung ergibt sich folgende Tabelle in Mengenschreibweise [vgl. FRI01]: Transition Zustand

a

b

Z1

{1,2}

1

Z2

3

Z3

4

Tab. 1: Übergangstabelle des NFA

2.1.3 Was ist ein deterministischer Automat (DFA)? Bei deterministischen Automaten (Deterministic Finite Automaton, DFA) sind die Übergänge immer eindeutig definiert. Das Eingabealphabet enthält keinen Leerstring und für jeden Zustand und jedes Eingabesymbol gibt es höchstens eine Übergangsfunktion, die den Zustand verlässt. Der Übergangsgraph für den String (a | b ) * abb sieht folgendermaßen aus:

- 12 -

Diplomarbeit

Lexikalische Analyse

Abb. 3: Der deterministische Automat

Die Tabelleneinträge der Übergangstabelle entsprechen im Einzelnen einem einzigen Zustand (keine Menge) [vgl. FRI01] Transition Zustand

a

b

Z1

2

1

Z2

2

3

Z3

2

4

Z4

2

1

Tab. 2: Übergangstabelle des DFA

Da der deterministische Automat ein Spezialfall des nicht deterministischen ist, werden sie öfters verglichen. Ein deterministischer Automat braucht meist mehr Speicher als ein nicht deterministischer. Seine Implementierung ist dafür einfacher und meistens ist ein DFA auch schneller. Ein DFA akzeptiert eine Zeichenkette, falls ein eindeutiger Pfad durch Transitionsgraphen existiert, der in einem Anfangszustand beginnt und einem Endzustand so endet, dass die Sequenzen der durchlaufenden Kanten genau diese Zeichenkette ergeben. - 13 -

Diplomarbeit

Lexikalische Analyse

2.2 JLex Lex ist der bekannte Generator für lexikalische Analysatoren. Er erzeugt aus einer Spezifikationsdatei C-Sourcecode für den entsprechenden lexikalischen Analysator. JLex ist eine Java-Version von Lex. Man kann ihn auch als ein Scanner für lexikalische Analyse bezeichnen, der Java-Programme aus den lexikalischen Spezifikationen erstellt. Er durchsucht einen Eingabetext nach bestimmten Regeln, die vom Benutzer festgestellt werden, und löst gewisse Aktivitäten aus.

Abb. 4: Aufbau der lexikalischen Analyse mit JLex

Genau wie Lex ist JLex ein Tool, das aus einer lexikalischen Spezifikation ein Programm in einer „Wirtssprache“ - in diesem Fall ist die Sprache immer Java - erzeugt. Wie Lex-Code kann auch JLex-Code am ehesten mit einer Tabelle verglichen werden, in der jedem Textmuster ein entsprechendes Codefragment zugeordnet wird. Dieses Codefragment wird ausgeführt, wenn der zugehörige Ausdruck im Eingabetext erkannt wurde. JLex erstellt einen deterministischen, endlichen Automaten in Java. Der wiederum erkennt die Ausdrücke und übernimmt ihre Verarbeitung [vgl. FRI01].

- 14 -

Diplomarbeit

Lexikalische Analyse

2.2.1 Die JLex-Programmstruktur Ein JLex Quellprogramm besteht aus drei Teilen:

Abb. 5: JLex-Programmstruktur

Die drei JLex-Programmteile werden durch doppelte Prozentzeichen %% in Zeile 2 und 4 voneinander getrennt. Wenn das Programm lediglich aus dem Definitionsteil besteht, muss dennoch die einleitende %%-Folge gesetzt werden.

2.2.1.1 Benutzercodeteil Der Benutzercode kommt vor dem ersten Prozentzeichen (%%) vor. Er enthält Daten, die direkt der lexikalischen Ausgabedatei (foo.lex.java) hinzugefügt werden. Meistens sind das import- und package-Anweisungen und Klassendefinitionen [vgl. BER00].

2.2.1.2 Definitionsteil Der Definitionsteil befindet sich zwischen den zwei doppelten Prozentzeichen %% ... %% Jede JLex Anweisung beginnt immer am Zeilenanfang. Diese Anweisungen können Definitionen von Java-Variablen, Konstanten und Methoden enthalten, außerdem noch die Makrodefinitionen und die Zustandsnamen. - 15 -

Diplomarbeit

Lexikalische Analyse

2.2.1.2.1 Interner Code der lexikalischen Klasse Die %{...%} Anweisungen erlauben dem User Java-Programme zu schreiben. Diese Programme werden direkt in die lexikalische Klasse (Yylex) kopiert %{ < java Code > %}

2.2.1.2.2 Zustandsdefinitionen Die lexikalischen Zustände sind optional. Wenn sie deklariert werden, dann folgenderweise %state state[0][,state[1], state[2],...]. YYINITIAL ist der Standardzustand vom JLex. Der Scanner beginnt seine Analyse immer in diesem Zustand.

2.2.1.2.3 Makrodefinitionen Die Makros stellen reguläre Definitionen dar. Mit regulären Definitionen wird einzelnen regulären Ausdrücken ein Name zugeordnet, der dann wiederum im Regelteil verwendet wird [vgl. BER00]. Beispiel: Java-Anweisungen %{ int count = 0; boolean flag = true; void das_ist_ein_test() { System.out.println(„Wir testen den JLex!“); } %}

- 16 -

Diplomarbeit

Lexikalische Analyse

Reguläre Ausdrücke Der Bezeichner letter erlaubt nur die Wörter, die mit einem Großbuchstaben beginnen. Mit digit wird eine einzige Ziffer aus dem Bereich von 0 bis 9 erlaubt (das Bindestrich bedeutet einen Bereich). letter [A-Z][a-z]+ digit [0-9]

2.2.1.2.4 Lexikalisch-analytische Komponentenüberschriften Mit folgenden Anweisungen können der Name der lexikalischen Klasse (Yylex), der lexikalischen Zerlegungsfunktion (yylex()) und die lexikalischen Rückgabewerte geändert werden. Die %class Variable erlaubt den Namen der Klasse Yylex zu ändern: %class Die %function Variable ändert den Namen der yylex() Funktion: %function Um den Rückgabewert Yytoken zu ändern, steht die %type Variable zur Verfügung: %type

- 17 -

Diplomarbeit

Lexikalische Analyse

2.2.1.2.5 Zeilenvorschub und Betriebssystemkompatibilität Im Betriebssystem UNIX wird der Zeilenvorschub „\n“ repräsentiert. Bei den DOS basierten Betriebsystemen entspricht dem Zeilenvorschub eine Zwei-Zeichen-Sequenz „\r\n“. Mit Hilfe der %notunix Variabel ist es möglich zwischen „\r“ und „\n“ zu unterscheiden: %notunix Die Bestimmung der richtigen Zeichensequenz für den Zeilenvorschub ist wichtig, um die Java Plattform Unabhängigkeit zu garantieren.

2.2.1.2.6 Zeichencode Standardmäßig unterstütz JLex das Zeichenalphabet zwischen 0 und 127. Wenn der lexikalische Generator einen diesen Bereich überschreitenden Zeichencode bekommt, dann wird die Analyse fehlschlagen. Die Variable %full unterstützt einen 8-Bit-Zeichencode. Hiermit wird der JLex einen lexikalischen Generator erzeugen, der ein Zeichenalphabet im Bereich zwischen 0 und 255 nutzen kann. Wenn aber ein komplettes 16-Bit Unicode benötigt wird, soll die %unicode Variable deklariert werden. In dem Fall wird ein Zeichenalphabet in Bereich zwischen 0 und 2^16-1 verwendet.

2.2.1.3 Regelteil Der dritte Teil des lexikalischen Analyseprogramms besteht aus mehreren Regeln zum Zerlegen des Eingabedatenstroms in mehrere Tokens. Diese Regeln bewerten reguläre Ausdrücke, um sie mit den entsprechenden Aktionen in Javacode zu verwenden und beinhalten optionale Listen von Zuständen, reguläre Ausdrücke und zu diesen Ausdrücken passende Aktionen: [] {action} Zustandsliste - 18 -

Diplomarbeit

Lexikalische Analyse

Die Zustände überprüfen die regulären Ausdrücke und bestimmen, wann sie auftreten können. Z. B. wenn yylex() mit dem Zustand Z aufgerufen wird, wird der Lexer versuchen, nur die Ausdrücke auszuführen, die in ihrer Liste den Zustand Z enthalten. Reguläre Ausdrücke dienen dazu, Sprachen, d.h. Mengen von Zeichenreihen, zu beschreiben. Je nachdem, wie dabei die regulären Ausdrücke definiert sind, kann man damit eine bestimmte Sprache beschreiben oder auch nicht. Die passende Aktion Die mit den lexikalischen Regeln verbundenen Aktionen bestehen aus dem Javacode und werden innerhalb geschweifter Klammern eingeschlossen. {aktion}

2.2.2 Reguläre Ausdrücke Eine nicht leere Menge von Zeichenfolgen (Textmuster) wird von regulären Ausdrücken beschrieben. Bei der lexikalischen Analyse mit JLex wird zu einer Zeichenfolge geprüft, ob sie einem vorgegebenen Textmuster passt. Wenn dafür mehrere Alternativen bestehen, dann wird immer die längste Mögliche verwendet [vgl. KO00]. Ein regulärer Ausdruck besteht aus •

„terminalen“ Textzeichen, die in den Textmustern direkt auftreten dürfen

• Metazeichen, mit denen komplette Textmuster beschrieben werden können

- 19 -

Diplomarbeit

Lexikalische Analyse

2.2.2.1 Metazeichen Die Metazeichen haben innerhalb der regulären Ausdrücke eine spezielle Bedeutung: ?,*,+,|,(,),^,$,/,;,.,=,,[,],{,},„,\

2.2.2.2 Zeichenklassen Die folgenden Operatoren stellen reguläre Ausdrücke dar, die jeweils eine Klasse von Zeichen beschreiben. .

Der Punkt ist ein Platzhalter für ein beliebiges terminales Zeichen (außer NewLine - \n). Bsp.: x.y trifft auf alle Zeichenfolgen zu, die mit x anfangen und mit y aufhören und dazwischen ein beliebiges Zeichen haben x+y, x-y, x*y, xay, ...

[]

Eckige Klammern sind Platzhalter für genau ein beliebiges Zeichen aus der zwischen ihnen angegebenen Menge. Bsp.:

[abc]

steht für ‚a’, ‚b’ oder ‚c’

[a − z ]

steht für alle Buchstaben von ‚a’ bis ‚z’

[a − zAE ] steht für alle Kleinbuchstaben oder einen der Buchstaben ‚A’ oder ‚E’ [0 − 9]

steht für alle Ziffern von 0 bis 9

Tab. 3: Zeichenklassen

- 20 -

Diplomarbeit

Lexikalische Analyse

2.2.2.3 Wiederholungsoperatoren Es sei ß ein regulärer Ausdruck. Die folgenden Operatoren machen Angaben über das mehrfache Vorkommen des regulären Ausdrucks ß*

0-maliges oder mehrfaches Vorkommen Bsp.: a*bc trifft auf bc, abc, aabc, aaabc usw.

ß?

0-maliges oder 1-maliges Vorkommen Bsp.: a?bc trifft auf bc, abc

ß+

1-maliges oder mehrfaches Vorkommen Bsp.: a*bc trifft auf abc, aabc, aaabc, aaaabc usw.

Tab. 4: Wiederholungsoperatoren

2.2.2.4 Kontextoperatoren Zeichenmanipulationssprachen erlauben meist die Angaben, in welchen Kontext der gesuchte Ausdruck vorkommen soll. Diese Möglichkeit besteht in beschränkten Maße auch für reguläre Ausdrücke. Hier kann spezifiziert werden, ob das gesuchte Textmuster am Anfang oder am Ende einer Zeile vorkommen soll. Der so gebildete Ausdruck ist wieder regulär. ^ß

Vorkommen des Ausdrucks ß wird nur am Zeilenanfang erlaubt Bsp.: ^abc steht für die Zeichenfolge abc, wenn sie am Anfang der Zeile vorkommt



Vorkommen des Ausdrucks ß wird nur am Ende der Zeile erlaubt Bsp.: $abc steht für die Zeichenfolge abc, wenn sie am Ende der Zeile vorkommt

Tab. 5: Kontextoperatoren

- 21 -

Diplomarbeit

Lexikalische Analyse

2.2.2.5 Komplexe Ausdrücke Aus regulären Ausdrücken a und ß werden komplexe Ausdrücke gebildet wie folgt: aß

Die Verkettung ist ein regulärer Ausdruck, der Textmuster beschreibt, die durch Aneinanderreihung von Textmustern entstehen, die zu a und ß passen. Bsp.: [A − Z ][a − z ] + beschreibt die Syntax von den Namen, die mit einem Großbuchstaben beginnen.

a|ß

Dieser regulärer Ausdruck steht für a oder ß Bsp.: abc|klm trifft auf abc oder klm zu.

Tab. 6: Komplexe Ausdrücke

2.2.2.6 Operatoren-Prioritäten Die Reichweite der Operatoren ist auf die unmittelbare linke oder rechte Umgebung beschränkt. Die Operatorfolge ‚[...]’, ‚?’, ‚+’, ‚*’, Verkettung oder ‚|’ist nach fallenden Prioritäten geordnet. Operatoren gleicher Priorität werden von links nach rechts bearbeitet. Abweichungen von der durch diese Regeln gegebenen Verarbeitungsreihenfolge können durch Klammerung mit runden Klammern erreichet werden. (ab|cd+)?(ef)+ Steht z.B. für die folgenden Zeichenketten der Form: ‚abef’, ‚efefef’, ‚cdef’, ‚cdddef’ usw. Tab. 7: Prioritäten der Operatoren

2.2.2.7 Maskierung von Metazeichen Wenn das Zeichen ‚\’ vor einem Metazeichen steht, dann wird dieses als Terminalzeichen behandelt.

- 22 -

Diplomarbeit

Lexikalische Analyse

^\.

steht für einen Punkt am Zeilenanfang

^.

steht für ein beliebiges Zeichen am Zeilenanfang

Tab. 8: Operatoren der Maskierung

Wenn das ‚\’-Zeichen von einem Terminalzeichen steht, so hat dies keinen Effekt. ‘a’ und ‘\a’ sind also äquivalent.

2.2.3 Lexikalische Werte Die folgenden Werte sind im Regelteil innerhalb der Aktionen verfügbar. •

java.lang.String.yytext() gibt den zu dem regulären Ausdruck passenden String aus der Eingabedatei zurück



int yychar gibt das erste Zeichen des passenden Strings aus der Eingabedatei zurück



int yyline gibt die Startposition des passenden Strings in der Eingabedatei zurück



yybegin(state) bekommt den Zustandsnamen als Parameter und erzielt den Übergang zu diesem Zustand

- 23 -

Diplomarbeit

Lexikalische Analyse

2.3 Anwendungsgebiete Das Anwendungsgebiet der lexikalischen Analyse ist sehr umfangreich. In der folgenden Liste werden ein Paar Beispiele aufgeführt: •

Analyse von Mustern



Konvertierung



Erstellen von Listen



Realisierung von Sprachen (z.B. SQL)



Rechner



Zeichnen von Grafen

- 24 -

Diplomarbeit

Analyse von Algorithmen

3 Analyse von Algorithmen Das Concise Oxford Dictionary definiert einen Algorithmus als „Verfahren oder Regeln für (speziell maschinelle) Berechnung“. Die Ausführung eines Algorithmus darf weder subjektive Entscheidungen beinhalten, noch unsere Intuition und Kreativität fordern. Wenn man über Algorithmen spricht, denkt man meistens an Computer. Nichtsdestoweniger können andere systematische Methoden zur Lösung von Aufgaben eingeschlossen werden. So sind zum Beispiel die Methoden der Multiplikation und Division ganzer Zahlen, die man in der Schule lernt, ebenfalls Algorithmen. Der berühmtste Algorithmus der Geschichte stammt aus der Zeit der alten Griechen: Euklids Algorithmus zur Berechnung des größten gemeinsamen Teilers zweier ganzer Zahlen. Es ist sogar möglich, bestimmte Kochrezepte als Algorithmus aufzufassen, vorausgesetzt, sie enthalten keine Anweisungen wie „nach Geschmack salzen“. „Für die meisten Probleme stehen viele unterschiedliche Algorithmen zur Verfügung. Wie soll man die beste Implementierung auswählen? Dies ist gegenwärtig ein gut erforschtes Gebiet der Informatik. Man wird oft Gelegenheit haben, sich auf Forschungsergebnisse zu berufen, welche die Leistungsfähigkeit grundlegender Algorithmen beschreiben. Der Vergleich von Algorithmen kann jedoch sehr kompliziert sein, weshalb gewisse allgemeine Richtlinien von Nutzen sind.“ [SED91; 93]

3.1 Die Richtlinien „Der erste Schritt der Analyse eines Algorithmus besteht darin, die Daten zu charakterisieren, die als Eingabedaten für den Algorithmus verwendet werden sollen, und zu entscheiden, welcher Typ einer Analyse geeignet ist.“ [SED91; 94] „Der zweite Schritt bei der Analyse eines Algorithmus ist die Bestimmung der abstrakten Operationen, auf denen der Algorithmus beruht, um die Analyse von der Implementierung zu trennen.“ [SED91; 94] Als dritten Schritt werden die mathematische Analyse durchgeführt, um für jede der grundlegenden Größen die Werte für den durchschnittlichen Fall und für den ungünstigsten Fall zu ermitteln. [vgl. SED91] - 25 -

Diplomarbeit

Analyse von Algorithmen

3.2 Klassifikation von Algorithmen Die meisten Algorithmen besitzen einen Hauptparameter N (meist ist es die Anzahl der zu verarbeitenden Datenelemente), der die Laufzeit am stärksten beeinflusst. Der Parameter N könnte ein beliebiger Parameter sein, z.B. der Grad eines Polynoms, die Größe einer zu durchsuchenden Datei, die Anzahl der Knoten in einem Baum usw. Bei praktisch allen Algorithmen ist die Laufzeit zu einer der folgenden Funktionen proportional [vgl. SED91]: 1 „Die meisten Anweisungen in der Mehrzahl der Programme werden einmal oder höchstens einige Male ausgeführt. Falls alle Anweisungen eines Programms diese Eigenschaft haben, sagt man, dass seine Laufzeit konstant ist. Offensichtlich ist das die Situation, die bei der Entwicklung von Algorithmen angestrebt werden sollte.“ [SED91; 96] log( N ) „Wenn die Laufzeit eines Programms logarithmisch ist, wird das Programm

mit wachsendem N nur allmählich langsamer. Diese Laufzeit tritt gewöhnlich bei Programmen auf, die ein umfangreiches Problem lösen, indem sie es in ein kleineres Problem umwandeln, wobei sie den Umfang auf einen gewissen konstanten Anzahl verringern.“ [SED91; 96] N „Wenn N eine Million ist, dann ist die Laufzeit genau so groß. Jedes Mal, wenn N sich verdoppelt, trifft das auch für die Laufzeit zu. Dies ist die optimale Situation für einen Algorithmus, der N Eingabedaten verarbeiten muss (oder N Ausgabewerte erzeugen muss).“ [SED91; 96] N log(N )

„Diese Laufzeit tritt bei Algorithmen auf, die ein Problem lösen, indem

sie es in kleinere Teilprobleme aufteilen, diese unabhängig voneinander lösen und dann die Lösungen kombinieren. Wenn sich N verdoppelt, wird die Laufzeit mehr als doppelt so groß (aber nicht wesentlich mehr).“ [SED91; 96]

N2

„Wenn die Laufzeit des Algorithmus quadratisch ist, lässt er sich praktisch nur

für Probleme mit relativ kleinem N anwenden. Quadratische Laufzeiten sind typisch für Algorithmen, die alle paarweisen Kombinationen von Datenelementen verarbeiten (eventuell in einer doppelt verschachtelten Schleife).“ [SED91; 96]

- 26 -

Diplomarbeit

N3

Analyse von Algorithmen

„Analog gilt, dass ein Algorithmus, der Tripel von Datenelementen verarbeitet

(zum Beispiel in einer dreifach verschachtelten Schleife), eine kubische Laufzeit hat und praktisch nur für kleine Probleme verwendbar ist. Wenn N ein Hundert beträgt, ist die Laufzeit eine Million. Wenn sich N verdoppelt, erhöht sich die Laufzeit auf das Achtfache.“ [SED91; 97] 2N

„Bei wenigen Algorithmen mit exponentieller Laufzeit kann man erwarten, dass sie für praktische Zwecke geeignet sind, obwohl solche Algorithmen in natürlicher Weise als „brute force“ Lösungen von Problemen auftreten.“ [SED91; 97].

3.3 Komplexität der Berechung Eine Funktion g ( N ) wird O( f ( N )) genannt, falls es Konstanten c 0 und N 0 gibt, so dass für alle N > N 0 , g ( N ) < c0 f (N ) ist.

„Die O-Schreibweise erweist sich als äußerst nützlich, da sie Analytikern hilft, Algorithmen nach ihrer Leistungsfähigkeit zu klassifizieren, und Entwickler von Algorithmen bei der Suche nach den „besten“ Algorithmen unterstützt. Das Ziel der Untersuchung der Berechnungskomplexität eines Algorithmus besteht darin zu zeigen, dass seine Laufzeit O( f ( N )) für eine gewisse Funktion f ist, und dass kein Algorithmus mit einer Laufzeit O( g ( N )) mit einer „kleineren“ Funktion g(N) (einer Funktion mit lim n→∞

g (N ) = 0 ) existiert.“ [SED91; 99] f (N )

3.4 Analyse des durchschnittlichen Falles „Ein anderer Zugang zur Untersuchung der Leistungsfähigkeit eines Algorithmus ist die Betrachtung des durchschnittlichen Falles. In der einfachsten Situation könnte man die Eingabedaten für den Algorithmus exakt charakterisieren: Zum Beispiel könnte ein Sortieralgorithmus mit einem Feld aus N zufälligen ganzen Zahlen operieren, oder ein geometrischer Algorithmus könnte eine Menge von N zufälligen Punkten in der Ebene mit Koordinaten zwischen 0 - 27 -

Diplomarbeit

Analyse von Algorithmen

und 1 verarbeiten. Dann berechnet man die durchschnittliche Anzahl der Ausführungen jeder Anweisung, und die durchschnittliche Laufzeit des Programms, indem man jede Häufigkeit einer Anweisung mit der für diese Anweisung benötigen Zeit multipliziert und die Produkte addiert. Bei dieser Vorgehensweise treten jedoch wenigstens drei Schwierigkeiten auf, die jetzt nacheinander betrachtet werden.“ [SED91; 101] „Erstens kann es bei manchen Computern sehr kompliziert sein, den exakten Zeitaufwand zu bestimmen, der für jede Anweisung erforderlich ist. Noch schlimmer ist, dass dieser Zeitaufwand Veränderungen unterliegt. Und auch, dass ein großer Teil der Analyse, die für einen Computer vorgenommen wurde, möglicherweise für die Laufzeit des gleichen Algorithmus auf einem anderen Computer überhaupt nicht brauchbar ist. Dies ist genau die Art von Problemen, die durch Untersuchungen zur Berechnungskomplexität vermieden werden soll.“ [SED91; 101] „Zweitens ist die Analyse des durchschnittlichen Falls selbst oft eine schwierige mathematische Aufgabe, die komplizierte und detaillierte Überlegungen erfordert.“ [SED91; 101] „Drittens, und dies ist die größte Schwierigkeit, charakterisiert das Modell der Eingabedaten bei der Analyse des durchschnittlichen Falls möglicherweise nicht genau die in der Praxis auftretenden Eingabedaten, oder es gibt eventuell gar kein natürliches Modell der Eingabedaten.“ [SED91; 101 f.]

3.5 Grundlegende rekurrente Beziehung Sehr viele Algorithmen beruhen auf dem Prinzip der rekursiven Zerlegung eines umfangreichen Problems in kleinere Probleme, wobei die Lösungen der Teilprobleme benutzt werden, um das ursprüngliche Problem zu lösen. Die Laufzeit eines solchen Algorithmus wird durch die Größe und Anzahl der Teilprobleme sowie den Aufwand bei der Zerlegung bestimmt. Aus einem rekursiven Programms selbst ergibt sich zwangsläufig, dass seine Laufzeit für Eingabedaten vom Umfang N von seiner Laufzeit für Eingabedaten von geringerem Umfang abhängt. Dies kommt in einer mathematischen Formel zum Ausdruck, die rekurrente Relation genannt wird. Im folgende werden solche Formeln, welche die Leistungsfähigkeit des entsprechenden Algorithmus exakt beschreiben, dargestellt [vgl. SED91]. - 28 -

Diplomarbeit

Analyse von Algorithmen

Formel 1. Die folgende rekurrente Beziehung entsteht bei einem rekursiven Programm, bei dem die Eingabedaten eine Schleife durchlaufen, wobei jedes Mal ein Element aus diesen entfernt wird:

C N = C N −1 + N für N >= 2 , mit C1 = 1

N2 Lösung: C N beträgt ungefähr 2 Formel 2. Die folgende rekurrente Beziehung entsteht bei einem rekursiven Programm, das die Menge der Eingabedaten in einem Schritt halbiert:

CN = C N + 1 2

für N >= 2 , mit C1 = 0 Lösung: C N beträgt ungefähr ln N

Formel 3. Die folgende rekurrente Beziehung entsteht bei einem rekursiven Programm, das die Eingabedaten halbiert, jedoch dazu jedes Element der Eingabedaten betrachten muss: CN = C N + N 2

für N >= 2 , mit C1 = 0 Lösung: C N beträgt ungefähr 2 N

- 29 -

Diplomarbeit

Analyse von Algorithmen

Formel 4. Die folgende rekurrente Beziehung entsteht bei einem rekursiven Programm, das die Eingabedaten der Reihe nach durchläuft, bevor, während oder nachdem, diese in zwei Hälften geteilt worden sind: C N = 2C N + N 2

für N >= 2 , mit C1 = 0 Lösung: C N beträgt ungefähr N ln N

Formel 5. Die folgende rekurrente Beziehung entsteht bei einem rekursiven Programm, welches die Eingabedaten in einem Schritt in zwei Hälften aufspaltet: C N = 2C N + 1 2

für N >= 2 , mit C1 = 0 Lösung: C N beträgt ungefähr 2 N

- 30 -

Diplomarbeit

Datenkomprimierung

4 Datenkomprimierung Techniken zur Komprimierung (Verdichtung) von Dateien finden sich unter Anderem im Bereich der Datenarchivierung, der Telekommunikation und der Unterhaltungselektronik. Ihr Ziel ist je nach Anwendung sowohl Speicherplatzeffizienz als auch Schnelligkeit der Transformation. Auch bei den heutigen Übertragungsraten ist der Austausch großer Datenpakete über Rechnernetze ohne den Einsatz leistungsfähiger Komprimierungswerkzeuge nicht denkbar. Durch die riesigen Datenmengen, die heute über das Internet versandt werden, sind sie sogar aktueller denn je. Von zentralem Interesse sind dabei Bild-, Audio- und Videokompressionen. Grundlage vieler dieser fortgeschrittenen Komprimierungstechniken ist die Textkomprimierung. Die wichtigsten Algorithmen dazu sollen in diesem Abschnitt vorgestellt werden.

4.1

Komprimierungsverfahren

Datenkomprimierung ist die Reduktion der Redundanz eines Eingabestroms von Daten. Technisch gesehen stellt die Redundanzreduktion das Ergebnis von Modellierung und Kodierung dar. Bei den Komprimierungsverfahren unterscheidet man zwischen den statischen und wörterbuchbasierten Modellierungsansätzen (Methoden). Auf verlustbehaftete Komprimierungsverfahren wird in dieser Arbeit nicht näher eingegangen, da sie bei der Textkomprimierung verständlicherweise kaum eine Rolle spielen. In diesem Abschnitt werden einige typische Vertreter dieser verschiedenen Klassen vorgestellt.

4.1.1 Komprimierungsmodelle Im Allgemeinen führen Komprimierungssysteme drei fundamentale Operationen durch: •

Modellierung



Statistische Erfassung von Daten - 31 -

Diplomarbeit



Datenkomprimierung

Kodierung

Die erste Operation, die Modellierung, ist nichts anderes als herauszufinden bzw. Annahmen darüber zu machen, wie die zu komprimierenden Daten strukturiert sind. Ein sehr einfaches Textmodell ist z. B. die Annahme, dass es keine Korrelation zwischen den benachbarten Symbolen gibt. So ein Modell wird als ein zero-order character-based model bezeichnet. Die zweite wichtige Operation ist die statistische Erfassung von Daten (Wahrscheinlichkeitsschätzung). Dabei wird jedem möglichen „nächsten“ Symbol des zu komprimierenden Eingabestroms eine Wahrscheinlichkeit zugewiesen, wobei ein bestimmtes Datenmodell zugrunde gelegt wird. Ein sehr einfaches Beispiel wäre anzunehmen, dass alle „nächsten“ Textsymbole die gleiche Wahrscheinlichkeit besitzen. Eine derart vereinfachende Annahme führt in der Regel zu keiner besonders guten Komprimierung. Die dritte der drei oben genannten Operationen ist die Kodierung. Ist z. B. die Wahrscheinlichkeit von den Textsymbolen in einem definierten Quellalphabet gegeben und ein Symbol stammt aus diesem Alphabet, dann schickt die Kodierungseinrichtung den Identifier des Symbols zur wartenden Dekodierseinrichtung. Die Kodierungseinrichtung muss ein spezielles Alphabet (normalerweise, aber nicht immer, Binärcode) benutzen und die Alphabetkapazität so effizient wie möglich nützen. Bei einer sehr einfachen Kodierungsweise wird die Zahl 1 mit „0“, die Zahl 2 mit „10“ und die Zahl 3 mit „110“ usw. kodiert. Eine solche Kodierungsmethode macht jedoch keinen Gebrauch von den Wahrscheinlichkeiten, die von der zweiten (statistischen) Komponente geschätzt wurden. Eine Einbeziehung dieser Wahrscheinlichkeiten ergibt jedoch unter der Voraussetzung, dass die Schätzung zuverlässig war, eine bessere Komprimierung. Die Abb. 6 zeigt die Beziehung zwischen den drei oben genannten Komponenten.

- 32 -

Diplomarbeit

Datenkomprimierung

Abb. 6: Modellierung, Statistik, (De-) Kodierungseinrichtung

Mit Hilfe von verschiedenen Komprimierungsmodellen - man unterscheidet zwischen •

statischen



semi-statischen



und adaptiven

Modellen - werden die Wahrscheinlichkeiten des nächsten zu kodierenden Symbols bestimmt. Die Wahrscheinlichkeit P(S) eines Symbols S wird bei statischen Komprimierungen durch den Quotient seiner Häufigkeit H(S) und der gesamten Anzahl von Symbolen im Text (Len) bestimmt: P(S ) = H (S ) / Len

Sei P(S) die Wahrscheinlichkeit dieses Symbols, dann beträgt sein Informationsgehalt I(S): I (S ) = log(1 / P(S )) = − log(P(S ))

- 33 -

Diplomarbeit

Datenkomprimierung

Kennt man die Wahrscheinlichkeiten und den Informationsgehalte aller Symbole, so ist es möglich, den durchschnittlichen Informationsgehalt des Textes (die Entropie) auszurechnen: E = ∑ P(S ) * I (S ) = ∑ − P(S ) * log(P(S )) s

s

4.1.1.1 Statische Modelle

Bei den statischen Modellen wird für jeden Eingabetext eine durchschnittliche Wahrscheinlichkeitsverteilung der Symbole vorausgesetzt. Nachteil: schlechtes Komprimierungsverhältnis, wenn die Daten von der Initialisierung abweichen.

4.1.1.2 Semi-statische Modelle

Bei den semi-statischen Modellen wird die Wahrscheinlichkeitsverteilung in zwei Phasen bestimmt: •

In der ersten Phase wird die Häufigkeit jedes Symbols im Text bestimmt.



In der zweiten Phase wird die eigentliche Komprimierung durchgeführt, bei der jedem Symbol je nach Häufigkeit eine Bitsequenz zugewiesen wird.

Nachteil: •

Langsamer als statisches Modell, da bedingt durch die zwei Phasen jedes Symbol auch zweimal bearbeitet werden muss.



Mehr Speicherplatz als beim statischen Modell, da die Wahrscheinlichkeitsverteilung immer mitgespeichert werden muss.

- 34 -

Diplomarbeit

Datenkomprimierung

4.1.1.3 Adaptive Modelle

Ein wesentlicher Unterschied der adaptiven zu den statischen Modellen ist, dass keine Häufigkeitstabellen mehr existieren. Das adaptive Modell lernt seine Wahrscheinlichkeitsverteilung während der Kodierung; somit kann der Text in einem Schritt abgearbeitet werden. Nachteil: Die Dekodierung muss immer am Anfang der Datei beginnen (d.h. weniger geeignet für Volltextsuche; Fehler am Anfang der Datei machen eine Dekomprimierung unmöglich).

4.1.2 Statische Methoden Bei statischen Methoden müssen zuerst die Häufigkeiten für die auftretenden Symbole ermittelt werden. Anhand dieser Häufigkeiten wird ein Ausgabecode bestimmt, in dem ein Symbol, das sehr häufig im Eingabestrom vorkommt, mit einer kurzen Codesequenz dargestellt wird. Ein Symbol, das sehr selten auftritt, bekommt dagegen einen längeren Code. Um ein späteres Dekodieren möglich zu machen, muss der Code allerdings eindeutig sein. Präfixcodes - kein Codewort ist Präfix eines anderen - sind ein Beispiel für solche eindeutige Codes.

4.1.2.1 Huffman-Kodierung

Für die Huffman-Kodierung stehen zwei unterschiedliche Verfahren zur Verfügung: ein Statisches und ein Adaptives. Diese Kodierungsverfahren liefern zu einem Datenblock einen Suchbaum, aus dem ein optimaler Code mit variabler Länge abgeleitet werden kann. Im Unterschied zu den meisten Bäumen erfolgt der Aufbau des Huffman-Baumes von unten nach oben. Um dies verständlicher zu machen, wird das an einem konkreten Beispiel demonstriert. Die Zeichenfolge MISSISIPI wird als Datenblock zur Konstruierung des einfachen HuffmanBaumes verwendet. Die Buchstaben sind die einzelnen Symbole.

- 35 -

Diplomarbeit

Datenkomprimierung

4.1.2.1.1 Statische Huffman-Kodierung

Beim statischen Verfahren werden im ersten Schritt die Häufigkeiten für die auftretenden Symbole ermittelt und in der Häufigkeitsstabelle abgelegt M

I

S

P

1

4

3

1

Tab. 9: Schritt 1 - Häufigkeitstabelle erstellen

In den nächsten Schritten wird der Huffman-Baum aufgebaut und mit dessen Hilfe der zugrunde liegende Text komprimiert. Zum Aufbau des Baumes wird zuerst aus jedem Symbol ein mit der Häufigkeit des Symbols gewichteter Blattknoten gebildet, der auch als ein Teilbaum gesehen werden kann.

Abb. 7: Schritt 2 - Knoten erstellen

Nun werden aus der Menge der Teilbäume zwei Elemente mit minimalem Gewicht (Häufigkeit) entnommen. Aus diesen beiden Teilbäumen wird ein binärer Baum gebildet, dessen Wurzelknoten mit der Summe der beiden Teilbäumen gewichtet wird (siehe Abb. 8): H (a1) = H (M ) + H (P )

- 36 -

Diplomarbeit

Datenkomprimierung

Abb. 8: Schritt 3 - Huffman-Baum erstellen

Dieses Verfahren wird so lange wiederholt, bis man einen einzigen Baum erhält. H (a 2 ) = H (a1) + H (S )

Abb. 9: Schritt 4 - Huffman-Baum erstellen

H (a3) = H (a 2 ) + H (I )

- 37 -

Diplomarbeit

Datenkomprimierung

Abb. 10: Schritt 5 - Huffman-Baum erstellen

Die Kanten des Huffman-Baumes werden mit 0 (für die Linke) und 1 (für die Rechte) markiert.

Abb. 11: Schritt 6 - Huffman-Baum erstellen

- 38 -

Diplomarbeit

Datenkomprimierung

Jetzt können aus dem endgültigen Suchbaum die Codes für die jeweiligen Symbole (in dem Beispiel die Buchstaben M, I, S, P) abgelesen werden. M

I

S

P

000

1

01

001

Tab. 10: Kodierungstabelle des statischen Huffman-Algorithmus

Die Zeichenfolge MISSISIPI wird wie folgt kodiert:

0001010110110011

Eigenschaften dieses Verfahrens: Eigenschaft 1: Die Länge der kodierten Zeichenfolge ist gleich der gewichteten äußeren Pfadlänge (L) des Huffman-Baumes L=

∑ [H (a )]* [T (a )]

a∈{alleBlätter }

H(a) := zugehörige Häufigkeit des Blattes T(a) := seine Pfadlänge von der Wurzel Im obenstehenden Beispiel besteht die kodierte Länge der Zeichenfolge MISSISIPI aus 16 Bits. Die Überprüfung mit der Eigenschaft 1 führt zu demselben Ergebnis: L = 1*3+1*3+3*2+4*1 = 16 Eigenschaft 2: Kein Baum mit den gleichen Häufigkeiten der äußeren Knoten hat eine kleinere gewichtete äußere Pfadlänge als der Huffman-Baum.

Verwirft man im obigen Verfahren die Bedingung, immer die Knoten mit den kleinsten Häufigkeiten auszuwählen, und ersetzt sie durch eine andere Regel, kann ein beliebiger Baum konstruiert werden. Es lässt sich mittels Induktion beweisen, dass keine andere Strategie zu - 39 -

Diplomarbeit

Datenkomprimierung

einem besseren Ergebnis führen kann als die, bei der immer die beiden kleinsten Häufigkeiten ausgewählt werden [vgl. SED91].

4.1.2.1.2 Adaptive Huffman-Kodierung

Das Konzept der adaptiven Huffman-Kodierung ist einfach: Auf der Encoder-Seite: Initialisiere das Model Wiederhole für jedes Zeichen { Kodiere das Zeichen Aktualisiere das Model }

Auf der Decoder-Seite: Initialisiere das Model Wiederhole für jedes Zeichen { Dekodiere das Zeichen Aktualisiere das Model }

So lange beide Seiten die selbe Initialisierung verwenden und das Model auf die gleiche Art und Weise aktualisieren, besitzen Encoder und Decoder den gleichen Huffman-Baum. Eine Methode, den Baum zu erstellen, ist, den Huffman-Baum mit einem einzigen Knoten zu initialisieren, der hier als Not-Yet-Transmitted (NYT) bezeichnet wird. Immer wenn ein bisher unbekanntes Zeichen gelesen wird, wird NYT gefolgt von dem Zeichen dem Decoder gesendet. So wird dem Decoder die Möglichkeit gegeben, zwischen Code und einem neuen Zeichen zu unterscheiden. Er kann dann einen neuen Knoten in seinem Baum anlegen. Eine andere Methode wäre, den Baum mit allen möglichen Zeichen zu initialisieren. Zur Aktualisierung des Huffman-Baumes werden keine Häufigkeitstabellen benötigt. Der Baum muss aber nach jedem eingelesenen Symbol aktualisiert werden. Um den Code während der Kodierung zu ändern, nimmt man zuerst an, dass alle Symbole eine HuffmanKodierung besitzen. Man weist den Symbolen eine bestimmte Häufigkeit zu und konstruiert - 40 -

Diplomarbeit

Datenkomprimierung

den Huffman-Code. Dann liest man das nächste Symbol ein. Wenn es schon vorhanden ist, inkrementiert man seine Häufigkeit um eins und passt den Code derart an, dass er einen neuen Code zu den neuen Häufigkeiten darstellt [vgl. KOL00].

Abb. 12: Schritt 1 - adaptiven Huffman-Baum erstellen

Abb. 13: Schritt 2 - adaptiven Huffman-Baum erstellen

- 41 -

Diplomarbeit

Datenkomprimierung

Abb. 14: Schritt 3 - adaptiven Huffman-Baum erstellen

Dieses Verfahren besitzt den Vorteil, dass es sich dem Datenmaterial besser anpassen kann, z.B. wenn dieser Text und Binärcode gemischt enthält. Darüber hinaus muss die Kodierungsvorschrift nicht mitgeliefert werden, da der Decoder diese während der Decodierung, wie oben beschrieben, nachvollziehen kann [vgl. KOP00].

4.1.2.1.3 Wortbasierte Komprimierung

Das wortbasiertes Komprimierungsmodell liest statt der Zeichen eines ASCII-Zeichensatzes ganze Wörter als eigenständige Symbole ein und kodiert sie dementsprechend. Eine solche Methode vollzieht zunächst einmal ein Parsing des Textes, wodurch der Text in •

Wörter und



Nicht-Wörter

zerlegt wird. Als Wort kann man eine ununterbrochen Folge von •

Buchstaben

- 42 -

Diplomarbeit



Ziffern



Interpunktionszeichen (Apostroph, Trennzeichen, ...)

Datenkomprimierung

definieren. Zu den Nicht-Wörtern gehören •

Leerzeichen



Trennzeichen und



auch die restlichen Interpunktionszeichen.

Das enorm umfangreiche Alphabet aus Wort-Symbolen niedriger Wahrscheinlichkeit wird mit einem semi-statischen Modell in Kombination mit einem kanonischen Huffman-Code verwaltet. Der kanonische Huffman-Code ermöglicht eine effiziente Verwaltung des Modells. Die Komprimierungsrate dieses Komprimierungsmodells beträgt im Durchschnitt 45%. Bei wortbasierenden (reinen) Textdokumenten erzielt man etwas bessere Resultate. Durch Speicherung des umfangreichen Alphabets wird insbesondere bei kurzen Dokumenten einen Overhead erzeugt. Für umfangreichere Dokumente fällt er zunehmend geringer aus. Wegen des semi-statischen Modells arbeiten die wortbasierte Methoden schneller als einige andere (wie z. b. das PPM-Verfahren). Das liegt im Wesentlichen daran, dass durch Kodierung von Wörtern mehrere Zeichen gleichzeitig bearbeitet werden können.

4.1.2.2 Arithmetische Kodierung

Die Arithmetische Kodierung basiert genau wie die Huffman-Methode auf einem statistischen Modell, geht aber einem völlig anderen Ansatz nach, in dem einem Symbol kein explizites Codewort zugeordnet, sondern aus den Wahrscheinlichkeiten (Häufigkeiten) der Symbole sukzessiv ein Kodierungsintervall berechnet wird. Die Arbeitsweise dieses Algorithmus ist nicht kompliziert. Man muss sich vorstellen, dass die Häufigkeiten der Symbole eines Datenstromes in einem Teilintervall [0, 1) aufgefasst werden. In der ersten Phase werden die jeweiligen Häufigkeiten ermittelt. Und in der zweiten wird das die Datei kodierende Intervall bestimmt, das gemäß der Auftrittswahrscheinlichkeit und der Reihenfolge der Symbole im Text rekursiv aufgeteilt wird. Letztendlich wird aus dem Inter- 43 -

Diplomarbeit

Datenkomprimierung

vall ein Binärcode gebildet, aus dem der Decoder den ursprünglichen Text rekonstruieren kann [vgl. KOP00]. Beispiel: A 0

0,5

B

C

D

0,75

0,9

1

Tab. 11: Festelegung der Intervalle

Wenn das Alphabet und die Häufigkeiten der Symbole folgende Werte haben, dann erhält man für die Zeichenfolgen AB und ABD folgende Teilintervalle

Abb. 15: Bestimmung der Teilintervalle

Symbol

Teilintervall

Binärbruch aus dem Intervall

Codewort

AB

[ 0.25, 0.375 )

2^-2 = 0.25 = 0.01b

01

ABD

[ 0.3625, 0.375 )

2^-2+2^-4+2^-5+2^-6 0.359375 = 0.010111b

= 010111

Tab. 12: Kodierungstabelle der arithmetischen Kodierung

Beim Dekodieren wird zuerst ein Subintervall eingelesen. Das Intervall, in dem die Komprimierung liegt, ist ein Subintervall jenes Intervalls, das während der Komprimierung erzeugt

- 44 -

Diplomarbeit

Datenkomprimierung

wurde. Die Dekodierung wird ausgegeben. Mit dem ausgewählten Subintervall wird analog weiter verfahren, bis der komplette Text dekodiert ist.

4.1.2.2.1 PPM – Prediction by Partial Matching

Das PPM-Verfahren stellt eine Sonderform der Arithmetischen Kodierung dar und arbeitet mit einem adaptiven, kontextbezogenen Modell. Die Abkürzung PPM steht im Deutschen für „Voraussage durch teilweise Übereinstimmung“. Dabei wird das nächste einzulesende Zeichen vom Encoder vor dessen Kodierung vorhergesagt. Beim PPM geschieht dies, indem die Wahrscheinlichkeitsverteilung für die Arithmetische Kodierung für jedes Zeichen auf den aktuellen Kontext angepasst wird. Problematisch bei PPM sind die Geschwindigkeit und der Speicherbedarf. Die Datenstrukturen zur Speicherung der adaptiven Modelle für die höheren Ordnungen sind äußerst komplex und benötigen viel Speicher. Da in diesen Mengen öfters gesucht werden muss, leidet auch die Geschwindigkeit beträchtlich [vgl. MUE97].

4.1.3 Wörterbuch-Methoden Die Idee aller Wörterbuch-Methoden (dictionary) ist Folgende: im Komprimierprogramm ist bereits ein Wörterbuch eingebaut, beispielweise der komplette deutsche Duden. Im zu komprimierendem Text muss dann bei jedem Wort nur noch Seite, Zeile und Spalte im Duden angegeben werden [KOL00]. In Hinblick auf die Leistungsfähigkeit solcher Komprimierungsverfahren spielt das Komprimierungsmodell eine bedeutende Rolle. Als Basis für die Wörterbuch-Methoden dient das adaptive Modell. Ein solches adaptives Modell referenziert ein Wort durch einen Zeiger auf seine Stelle im zu komprimierenden Text. Auf diese Weise wird das Wörterbuch implizit übermittelt, was gegenüber anderen Komprimierungsmodellen einen wesentlichen Vorteil darstellt.

- 45 -

Diplomarbeit

Datenkomprimierung

4.1.3.1 LZ 77

Dieses Verfahren wurde 1977 vom Jacob Ziv und Abraham Lempel erstellt. Dieser Algorithmus erstellt ein dynamisches Wörterbuch, indem er gleiche Zeichenfolgen nur einmal abspeichert. Der LZ 77-Algorithmus wird auch Fenster-Algorithmus (sliding window) genannt, da man den zu komprimierenden Text durch ein Fenster bearbeitet, indem man es von links nach rechts durch den Text verschiebt. Dieses Fenster ist in zwei Teilen aufgeteilt: Vorschaupuffer und Suchpuffer. Im hinteren Teil des Fensters befindet sich der Suchpuffer. Er enthält die bereits verarbeiteten Daten und dient als Wörterbuch für die Daten, die sich im Vorschaupuffer (look-ahead) befinden. Wie der Name schon sagt, liegt der Vorschaupuffer im vorderen Teil des Fensters [vgl. KOL00]. LZ 77-Algorithmus: 1. Es ist ein Text {x1,..., xn} mit x ∈ ∑

gegeben

2. Die Daten werden jeweils in 3-er Tupeln gespeichert, die folgendermaßen belegt sind: o Offset des nächsten Zeichens im Suchpuffer (Rückwärtsreferenz auf die Stelle

des Zeichens) o Anzahl der noch folgenden Zeichen o das Zeichen selbst

3. Am Anfang ist der Suchpuffer leer, der Vorschaupuffer hat eine konstante Größe 4. Setzte den Cursor an den Anfang des Textes 5. Solange das Textende noch nicht erreicht ist o Nimm das erste Zeichen im Vorschaupuffer und prüfe, ob es bereits vorkommt

Wenn nein, dann generiere den Code (0, 0, ‚Zeichen’) Wenn ja, dann prüfe, so lange bis keine weitere Übereinstimmung gefunden wird, ob vielleicht auch das nächste Zeichen im Suchpuffer mit - 46 -

Diplomarbeit

Datenkomprimierung

dem nächsten Zeichen im Vorschaupuffer übereinstimmt. Dann generiere den Code (Offset des nächsten Zeichens im Suchpuffer, Anzahl der übereinstimmenden Zeichen –1, ‚Zeichen’) o Verschiebe das Fenster um 1 nach recht

Tabelle 13 [vgl. KOL00] verdeutlicht den Ablauf des LZ77-Algorithmuses.

Tab. 13: LZ 77

Beim Dekodieren wird zuerst ein Fenster erstellt, das dieselbe Größe besitzt, wie das Fenster vom Encoder. Dann wird ein Tripel (Offset, Länge, Zeichen) eingelesen und nach Übereinstimmung im kodierten Text gesucht. Wird ein solches gefunden, gibt Decoder das Zeichen und seine Länge zurück und fügt es dem Puffer hinzu.

4.1.3.2 LZ 78

LZ 78-Methode unterscheidet sich von der LZ 77 im Wesentlichen durch eine Beschränkung der referenzierbaren Phrasen, die nicht mehr beliebig einem Textfenster entstammen können. Im LZ 78 gibt es nur noch ein Wörterbuch anstatt des fortlaufendem Suchpuffers.

- 47 -

Diplomarbeit

Datenkomprimierung

Das Wörterbuch ist im Form einer Matrix aufgebaut und enthält für jede Zeichenfolge 3 Einträge: •

Index



Zeichen und



Folgeelement

Jeder komprimierte Code hat dann das Format (ind, char), wobei ind dem Index zum Wörterbuch und char dem Code dieses Zeichens entspricht [KOL00].

Tab. 14: LZ 78

Diese Tabelle [vgl. KOL00] zeigt, wie der Code mit Hilfe des LZ78-Algorithmuses erstellt wird.

4.1.3.3 LZW

Der LZW-Algorithmus ist eine in der Praxis häufig verwendete Variante des LZ78Verfahrens. Die bekanntesten Beispiele dafür sind das Packprogramm gzip (meistens unter UNIX) oder das Dateiformat .gif [vgl. KOL00]. Der Wörterbuch-Algorithmus erstellt zuerst ein Wörterbuch, das mit den Buchstaben des Alphabets initialisiert wird. Der Text wird Schritt für Schritt abgearbeitet und anschließend suk- 48 -

Diplomarbeit

Datenkomprimierung

zessive kodiert, indem man die bereits gefundenen Wörter durch Zeiger auf ihre Kopie im Wörterbuch kodiert und die neuen Einträge ins Wörterbuch hinzufügt und ebenfalls für die Kodierung verwendet.

Tab. 15: LZW

In der oben stehenden Tabelle [vgl. KOL00] wird gezeigt, wie eine Zeichenkette abababcabac mit dem LZW-Algorithmus kodiert wird.

- 49 -

Diplomarbeit

Das angewendete Komprimierungsverfahren

5 Das angewendete Komprimierungsverfahren In diesem Kapitel wird der wortbasierte Byte-Huffman-Algorithmus vorgestellt. Die theoretische Grundlage bildet die Arbeit von de Moura et. al. [MNZB00], in der ein kurzer Überblick über diesen Algorithmus gegeben wird. Nach intensiver Recherche wurden viele Artikeln gefunden, in deren der Algorithmus selbst erwähnt wurde. Es stellte sich aber bei genauerem Hinsehen jedes Mal heraus, dass einfach nur auf den Originalartikel verwiesen, oder einfach das Original reproduziert wurde, ohne wirklich auf die Grundlagen einzugehen. Nur oberflächlich erläutert werden bei de Moura et. al. die schwierigsten Aspekte, wie einen optimalen Mehrwegebaum zu erstellen, den Mehrwegebaum kanonisch zu machen und die genauere Definition des spaceless-word-models. Deswegen hat die Lösung dieser konkreten Probleme sehr viel Zeit in Anspruch genommen.

5.1 Die wortbasierte Byte-Huffman-Kodierung Bei der wortbasierten Byte-Huffman-Kodierung sind die Eingabesymbole ganze Wörter, statt einzelner Zeichen, und jedes Codewort wird durch eine Sequenz von ganzen Bytes dargestellt. Die Ordnung des Huffman-Baumes ist üblicherweise 128 (tagged Huffman Code) oder 256 (plain Huffman Code), nicht mehr 2 wie beim klassischen Huffman-Algorithmus. Beim plain Huffman Code werden alle 8 Bits eines jeden Bytes zur Codierung genutzt. Beim tagged Huffman Code werden von jedem Byte nur die 7 niederwertigen Bits zur Codierung, das höchstwertige Bit aber als Signalbit verwendet. In jedem Codewort ist das höchste Bit des ersten Bytes auf 1, das höchste Bit der restlichen Bytes auf 0 gesetzt. Die etwas geringere Kompressionsrate wird dadurch wettgemacht, dass die Dekomprimierung an jeder Codewortgrenze einer so komprimierten Datei starten kann. Dies ist besonders nützlich bei der direkten Suche im komprimierten Text, denn eine gefundene Stelle kann effizient extrahiert werden, ohne die ganze Datei vorher dekomprimieren zu müssen.

- 50 -

Diplomarbeit

Das angewendete Komprimierungsverfahren

5.1.1 Wieso diese Komprimierungstechnik In der natürlichen Sprache haben Wörter eine große Anzahl an Bedeutungen. Abhängig davon in welchem Zusammenhang ein Wort benutzt wird, kann man es anders interpretieren. Das wortbasierte Komprimierungsmodell kann eine bessere Komprimierungsrate als die Zeichenbasierte erzielen. Ein weiterer Vorteil des wortbasierten Modells liegt darin, dass die Wörter die wichtigsten Bausteine in Information Retrieval Systemen darstellen. Und da die IRSysteme immer mehr an Bedeutung gewinnen, kann diese Komprimierungstechnik öfters eingesetzt werden. In der Praxis ist die Byteverarbeitung viel schneller als die Bitverarbeitung, da das Shiften von Bits und Maskierungsoperationen nicht mehr nötig sind. Darüber hinaus sind die Komprimierung und die Dekomprimierung sehr schnell und die Komprimierung kann eine bessere Komprimierungsrate erzielen als die der Lempel-Ziv-Familie [MNZB00].

5.1.2 Byteorientierter Huffman-Code In Kapitel 4.1.2.1 wurde der klassische Huffman-Algorithmus vorgestellt, bei dem jedes Symbol des Eingabestroms eine Bitsequenz zugewiesen bekommt. Im Gegensatz zum Original wird bei dem in dieser Arbeit verwendeten Huffman-Algorithmus jedem Symbol eine Sequenz von ganzen Bytes zugewiesen. Der Huffman-Baum stellte einen Mehrwegebaum der Ordnung 256 anstatt von 2 dar. Zusätzlich muss Folgendes berücksichtigt werden: •

Als Symbole werden hier alle Text-Wörter und –Trennzeichen bezeichnet.



Diese Symbole werden mit Hilfe eines spaceless Wortmodells kodiert.



Der Huffman-Baum muss kanonisch sein.



Die Symboltabelle (Tabelle, die alle Wörter und Trennzeichen enthält) muss immer zusammen mit der kodierten Datei übertragen werden.

- 51 -

Diplomarbeit

Das angewendete Komprimierungsverfahren

5.1.2.1 Spaceless Wortmodell

In vorherigen Kapiteln kam die Sprache darauf, dass bei der Huffman-Kodierung semistatische Modelle angewendet werden, d. h. die Kodierung erfolgt in zwei Phasen. In der ersten werden die Häufigkeiten für die auftretenden Symbole ermittelt und in der Zweiten wird kodiert. Diese Vorgehensweise bildet auch die Grundlage der wortbasierten Komprimierung. Das Parsen von Texten erfolgt laut de Moura et. al. [MNZB00] mit Hilfe eines spaceless Wortmodells, deren Symbole sowohl aus alphanumerischen als auch nicht-alphanumerischen Zeichen bestehen. Bei den spaceless Wortmodellen werden alle Texte in Wörter und Nicht-Wörter zerlegt, mit der Annahme, dass standardmäßig nach jedem Wort ein einzelnes Leerzeichen vorkommen darf. Als Nicht-Wort wird eine Zeichenkette bezeichnet, die zwischen zwei aufeinanderfolgenden Wörtern auftritt und nicht nur aus einem Leerzeichen besteht. Nicht-Wörter werden genauso wie normale Wörter kodiert. Zum Beispiel wird die Zeichenkette, in der das Zeichen # dem Leerzeichen entspricht, „sum,#sum,#sum,#Bienchen#sum#herum.“ wie folgt übertragen „1, 2, 1, 2, 1, 2, 3, 1, 4, 5“ mit sum

,#

Bienchen

herum

.

1

2

3

4

5

Symbole mit den Nummern 2 und 5 präsentieren zwei Nicht-Wörter dieser Zeichenkette. Der Decoder kann verlustfrei diese Zeichenkette rekonstruieren, in dem er ein Leerezeichen zwischen zwei Wörtern (aber nicht zwischen Wörtern und Nicht-Wörtern) hinzufügt.

- 52 -

Diplomarbeit

Das angewendete Komprimierungsverfahren

5.1.2.2 Kanonische Huffmancode

Der Huffmancode ist ein binärer Präfixcode, der jedem Symbol ein festes Codewort variabler Länge zuweist. Da man die Zusammenfassung bei der Konstruktion der Codestruktur sowie die Zuordnungen der Zeichen aus dem Codealphabet {0,1} gemäß den Regeln beliebig treffen kann, ist er nicht eindeutig. Es ist also möglich, dass Encoder und Decoder bei verschiedener Implementierung unterschiedliche Symboltabellen berechnen, obwohl ihnen die selben Symbole mit zugehörigen Häufigkeiten zur Verfügung stehen. Zur korrekten Dekodierung müsste also die vollständige Symboltabelle übermittelt werden. Da der kanonische Baum eindeutig ist, lassen sich stattdessen die Codewörter über die Position der ihnen zugeordneten Symbole in der Liste direkt berechnen [MUE97]. Es genügt also, nur die Liste der Symbole zusammen mit wenigen Zusatzinformationen (je Knoten einmal die Anzahl der Symbole und die Länge des Codewortes, siehe auch Kapitel 7.3.1.2.3) zu übertragen. Somit ergibt sich als ein Vorteil ein geringerer Speicherbedarf beim Ablegen der Symboltabelle, denn im Gegensatz zum gewöhnlichen Huffmancode muss weder die komplette Liste der Codewörter gespeichert noch ein expliziter Huffman-Baum angelegt werden Um einen eindeutigen Huffmancode zu erhalten, muss man bei der Konstruktion des Codes zusätzliche Regeln beachten. Eine spezielle Variante davon, die mit zwei zusätzlichen Kriterien gebildet wird, stellt der kanonische Huffmancode dar. Bei der Konstruktionen des kanonischen Huffmancodes setzt man voraus, •

dass erstens die Codelänge jedes Zeichens, die mit dem Huffman-Algorithmus spezifiziert wird, schon bekannt ist,



dass zweitens, um die Eindeutigkeit zu garantieren, die Codewörter selbst nach Länge sortiert werden. Die sortierten Codewörter müssen auch lexikographisch geordnet werden.

Anhand der folgenden Abbildungen lässt sich der Unterschied zwischen dem klassischen und kanonischen Byte-Huffman-Baum sehr deutlich zeigen.

- 53 -

Diplomarbeit

Das angewendete Komprimierungsverfahren

Abb. 16: Byte-Huffman-Baum

In einem links-kanonischen Baum darf die Länge des linken Subbaumes nie kleiner als die des rechten sein, beziehungsweise umgekehrt (rechts-kanonisch). Da die Länge des Subbaumes mit dem Vaterknoten 3 auf der ersten Ebene länger als des mit dem Vaterknoten 2 ist, wird er ganz nach links geschoben.

Abb. 17: Kanonisierung - erster Schritt

- 54 -

Diplomarbeit

Das angewendete Komprimierungsverfahren

Im nächsten Schritt wird der Subbaum des Vaterknotens i zum Knoten 1 geschoben.

Abb. 18: Kanonisierung - zweiter Schritt

Es wird so lange auf jeder Ebene fortgesetzt, bis der aufgestellte Baum den Kriterien eines kanonischen Baumes entspricht.

Abb. 19: Kanonischer Baum

- 55 -

Diplomarbeit

Das angewendete Komprimierungsverfahren

Aus diesem Verfahren ist es sofort ersichtlich, dass durch diese Kanonisierung des Baumes der Code optimal bleibt.

5.1.2.3 Die Kodierung

Wie bei der originalen Methode erfolgt die Kodierung in zwei Phasen: •

Im ersten Schritt wird die Häufigkeit der im Eingabestrom vorkommenden Worte ermittelt



Im zweiten Schritt wird die Kodierung aufgebaut. Dafür wird zuerst der HuffmanBaum erstellt und abschließend jedem Wort das Codewort (Sequenz von Bytes) zugewiesen.



Beim Aufbau des byteorientierten Huffman-Baumes geht man fast so vor wie beim Original: o Für jedes Symbol wird ein Knoten erstellt, der das Symbol selbst und seine

Häufigkeit beinhaltet. o Beim klassischen Huffman-Algorithmus wird nach diesem Schritt der Baum

aufgebaut. Beim byteorientierten Huffman-Algorithmus muss zuerst der Optimierungsprozess wie unten beschrieben durchgeführt (Kap. 5.1.2.4) und anschließend der Baum aufgebaut werden. •

Aus den 256 Knoten mit der kleinsten Häufigkeit wird ein Subbaum gebildet. Sein Vaterknoten stellt einen neuen Knoten dar und wird mit der Summe von Häufigkeiten seiner Kinder gewichtet.



Dieser Prozess wird so lange fortgesetzt, bis man einen kompletten byteorientierten Huffman-Mehrwegebaum erhält.

- 56 -

Diplomarbeit

Das angewendete Komprimierungsverfahren

5.1.2.4 Optimierung des Baumes

Bei der Konstruktion eines Mehrwegebaumes und somit des byteorientierten HuffmanBaumes kann es passieren, dass auf der zweiten Ebene des Baumes leeren Knoten vorkommen. Der Code ist somit nicht optimal. Die folgende Abbildung zeigt einen nicht optimalen Baum, der mit dem Huffman-Algorithmus erstellt wurde.

Abb. 20: Ein nicht optimaler Huffman-Baum

Um den Optimierungsprozess zu verdeutlichen, wird ein kleines Beispiel aufgeführt. In diesem Beispiel besteht der Eingabestrom aus 512 verschiedenen Symbolen mit derselben Häufigkeit. Der Wurzelknoten hat 254 leere Kinderknoten, die durch Knoten aus der dritten Ebene ersetzt werden. Somit wird ein optimaler Baum gebildet.

Abb. 21: Ein optimaler Huffman-Baum

- 57 -

Diplomarbeit

Das angewendete Komprimierungsverfahren

Wie lässt sich nun erreichen, dass die leeren Knoten sich immer auf einer untersten Ebene befinden? Betrachten wir dazu den Baumerstellungsprozess genauer: Seien n0 Symbole, also Knoten, gegeben. Beim ersten Schritt werden 256 Knoten zu einem zusammengefasst, es bleiben also n1 := n0 – 256 + 1 = n0 –255 Knoten übrig. Es gilt: ni+1 = ni –255 für ni >= 255. Die Anzahl der Knoten auf der 2-ten Ebene ist somit der kleinste positive Repräsentant von n0 mod 255. Im Beispiel von oben: 512 mod 255 = 2; somit ist die Anzahl der leeren Knoten auf der 2ten Ebene 256 – 2 = 254. Damit im letzten Schritt 256 Knoten übrig bleiben, was notwendig ist, um einen optimalen Baum zu erhalten, können entweder passend „Dummy“-Knoten mit minimalem Gewicht hinzugefügt werden, die dann nach der Konstruktion des Baumes wieder entfernt werden, oder es werden beim ersten Schritt nicht 256 Knoten zusammengefasst, sondern weniger. Sei x die Anzahl der Knoten, die beim ersten Schritt zusammengefasst werden müssen (zu einem Knoten, also +1), um einen optimalen Baum zu erhalten, dann lautet die Bedingung: 256 mod 255 = (n0 − x + 1) mod 255 ⇔ 1mod255 = n 0 mod 255 − x mod 255 + 1 mod 255 ⇔ x mod 255 = n0 mod 255 wobei für x der kleinste positive Vertreter der Restklasse zu wählen ist. Im obigen Beispiel müssten also im ersten Schritt nur 512 mod 255 = 2 mod 255, also 2 (als kleinster positiver Repräsentant der Restklasse) Knoten mit kleinsten Gewichten zu einem Unterbaum zusammengefasst werden [vgl. MNZB98].

5.1.2.5 Die Dekodierung

Um eine mit dem byteorientierten Huffmanalgorithmus kodierte Datei dekodieren zu können, ist die Zuordnung: Code → Symbol nötig. Als eine effiziente Möglichkeit erweist sich den ursprünglichen Baum wieder aufzubauen. Beginnend von der Wurzel wird der Baum durch- 58 -

Diplomarbeit

Das angewendete Komprimierungsverfahren

laufen. Wenn ein Symbol gefunden, d.h. ein Blatt des Baumes erreicht wurde, wird es dementsprechend ausgegeben. Der Dekodierungsprozess wird weiter von der Wurzel aus fortgesetzt, so lange bis der gesamte Text dekodiert ist. Es ist zu beachten, dass zwischen zwei Worten ein Leerzeichen eingefügt werden muss, zwischen einem Wort und einem Trennwort bzw. zwischen zwei Trennwörtern jedoch keines (siehe Spaceless Wortmodell). Es muss also auch beim Decodieren zwischen Wörtern und Trennwörtern unterschieden werden.

- 59 -

Diplomarbeit

Suchverfahren

6 Suchverfahren Eine grundlegende Operation, die Bestandteil sehr vieler Berechnungsaufgaben ist, ist das Suchen: Das Wiederauffinden eines bestimmten Elements oder bestimmter Informationsteile aus einer großen Menge früher gespeicherter Informationen. Normalerweise stellt man sich die Informationen so vor, als wären sie in Datensätze zerlegt. Dabei hat jeder Datensatz einen Schlüssel zur Verwendung beim Suchen. In einem Wörterbuch der deutschen Sprache sind z.B. die „Schlüssel“ die Wörter und die „Datensätze“ die zu den Wörtern gehörenden Einträge, welche die Definition, Aussprache und sonstigen Informationen enthalten. Der Zweck des Suchens besteht gewöhnlich darin, den Zugriff auf die Information im Datensatz (nicht nur auf die Schlüssel) für die Verarbeitung zu ermöglichen [SED91]. In den früheren Kapiteln wurde des Öfteren erwähnt, dass die byteorientierte Kodierung eine schnelle und flexible Suche nach Wörtern und Phrasen erlaubt, indem man direkt im komprimierten Text sucht. In diesem Kapitel werden zuerst elementare Suchverfahren beschrieben und anschließend zwei Suchverfahren vorgestellt, mit deren Hilfe die Suche in komprimierten Texten durchgeführt werden kann. Die Ausführungen ab dem Kapitel 6.2 basieren ausschließlich auf der Theorie von [MNZB00]. Im Kapitel 6.2 wird ein Suchalgorithmus beschrieben, den man an mit tagged Huffman kodierten Texten anwendet, und im nachfolgenden ein Algorithmus, mit dem man in den Texten suchen kann, die mit in diesem Dokument beschriebenem Huffmanalgorithmus (plain) kodiert wurden.

6.1 Elementare Suchmethoden Die elementaren Suchverfahren, die in diesem Kapitel betrachtet werden, sind durch folgende Merkmale charakterisiert: •

Die Items der Basismenge sind durch einen Schlüssel eindeutig charakterisiert. Es gibt bei einem Suchprozess keine Mehrfachtreffer



Die Schlüsselmenge besitzt eine lineare Ordnung - 60 -

Diplomarbeit

Suchverfahren



Als Operationen sind nur Schlüsselvergleiche erlaubt



Das Ergebnis der Suche ist entweder das gefundene Item oder eine Fehlanzeige

6.1.1 Sequentielle Suche Die einfachste Methode des Suchens besteht darin, die Datensätze einfach in einem Feld zu speichern. int A[] = new int[N+1];

Wenn ein neuer Datensatz eingefügt werden soll, setzt man ihn an das Ende des Feldes. Wenn eine Suche ausgeführt werden soll, durchsucht man das Feld sequentiell. Das folgende Programm zeigt eine Implementierung der zugrundelegenden Funktion unter Verwendung dieser einfachen Organisation und veranschaulicht einige der Vereinbarungen, die bei der Implementierung von Suchmethoden verwenden werden können. public int seqSearch(int A[], int n, int item){ int i = 0;

// Laufvariable

A[0] = item; i = n +1;

// Stopper-Element besetzten

while(item != A[--i]); // Suchschleife return (i);

// Index des Item oder 0 zurückliefern

}

Eigenschaft 1: Die sequentielle Suche (Implementierung mit einem Feld) benötigt (immer) N+1 Vergleiche für eine erfolglose Suche und (durchschnittlich) ungefähr

N Vergleiche für eine 2

erfolgreiche Suche.

Um bei einer erfolglosen Suche festzustellen, dass ein Datensatz mit irgendeinem bestimmten Schlüssel nicht vorhanden ist, muss jeder Datensatz betrachtet werden. Bei einer erfolgreichen

- 61 -

Diplomarbeit

Suchverfahren

Suche mit der Annahme, dass jeder Datensatz mit der gleichen Wahrscheinlichkeit gesucht wird, beträgt die durchschnittliche Anzahl der Vergleiche 1 (1 + 2 + ... + N ) = N + 1 2 N was genau der Hälfte der Kosten einer erfolglosen Suche entspricht. [vgl. SED91] Eigenschaft 2: Die sequentielle Suche (Implementierung mit einer sortierten Liste) benötigt (durchschnittlich) sowohl für eine erfolgreiche als auch für eine erfolglose Suche ungefähr

N 2

Vergleiche. „Für eine erfolgreiche Suche ist die Situation die gleiche wie zuvor. Für eine erfolglose Suche gilt, wenn man annimmt, dass die Suche mit gleicher Wahrscheinlichkeit beim Endknoten oder bei jedem der Elementen in der Liste abgebrochen wird (was für eine Reihe von „zufälligen“ Suchmodellen der Fall ist), dass die durchschnittliche Anzahl der Vergleiche 1 (1 + 2 + ... + N + (N + 1)) = N + 2 N +1 2 beträgt, also etwas mehr als die Hälfte der Kosten, die ohne Sortieren entsteht.“ [SED91; 236]

6.1.2 Binäre Suche Wenn die Menge der Datensätze groß ist, kann die Gesamtdauer der Suche beträchtlich verringert werden, indem eine Suchprozedur verwendet wird. Diese Suchprozedur beruht auf der Anwendung der Divide et Impera-Strategie, die auf der selben Idee wie das QuicksortVerfahren basiert. Zuerst wird die Menge der Datensätze in zwei Hälften geteilt. Danach bestimmt, welchem der zwei Teile der gesuchte Schlüssel angehört. Ein sinnvoller Weg diese Mengen zu zerlegen besteht darin, die Datensätze sortiert zu lassen und dann Feld-Indizes zu verwenden, um den Teil des Feldes zu begrenzen. Um festzustellen, ob das gesuchte Element (item) vorhanden - 62 -

Diplomarbeit

Suchverfahren

ist, soll es zuerst mit dem Element auf der mittleren Position verglichen werden. Wenn es kleiner ist, dann muss es sich in der ersten Hälfe befinden; wenn es größer ist, dann befindet sich das gesuchte Element in der zweiten Hälfte. public int binSearch(int A[], int left, int right, int item){ int i = 0, j; while(right > left){

//solange noch mind. 1 Item existiert

i = (left + right) / 2; // Feld teilen if(item < A[i]) right = i - 1; else left = i + 1; if(item == A[i]) return (i); else j = 0;

// links weiter suchen // rechts weiter suchen // bei Erfolg Index zurückliefern

}

6.2 Suche in den mit tagged Huffman kodierten Texten Dieser Algorithmus sucht in Texten, die mit Hilfe des tagged Huffman kodiert werden. Zur Erinnerung muss man erwähnen, dass die tagged Huffmankomprimierung mit dem ersten Bit eines jeden Byte den Anfang jedes Codewortes in komprimierten Texten markiert. Der klassische Huffmancode ist präfixfrei. Das bedeutet, dass es kein Wort gibt, welches das Präfix von einem Anderen bildet. Diese Eigenschaft ist sehr hilfreich bei der Dekomprimierung. Bei der direkten Suche reicht sie aufgrund von möglichen falschen Übereinstimmungen nicht aus. Damit falsche Übereinstimmungen vermieden werden, darf kein einziges Codewortpräfix das Suffix von einem Anderen bilden. Bei der tagged Huffmankodierung wird diese Bedingung erfüllt, indem das höchste Bit jedes ersten Bytes auf 1 gesetzt wird. Die Eigenschaft, die komprimierten Phrasenmuster byteweise mit den kodierten Worten im Text zu vergleichen, erlaubt direkte Suche in komprimierten Texten.

- 63 -

Diplomarbeit

Suchverfahren

Die Suche nach verschiedenen Mustern im komprimierten Text passiert in zwei Phasen. In der ersten Phase wird das Muster genau so wie der vorliegende Text kodiert. In der zweiten Phase wird das kodierte Muster gesucht. Bei einer genauen Suche generiert die erste Phase ein eindeutiges Muster, welches mit beliebigen konventionellen Suchalgorithmen gesucht werden kann. Bei einer annähernden oder erweiterten Suche erzeugt die erste Phase alle gefundenen Varianten des komprimierten Musters. In diesem Fall wird ein Multipattern-Algorithmus verwendet, um im Text zu suchen.

6.2.1 Erste Phase: Vorverarbeitung Bei einer genauen Suche ähnelt die Musterkodierung der Kodierungsphase des Huffmanalgorithmus. Man sucht nach jedem Musterelement im Huffmanwortschatz (Symboltabelle) und generiert ein komprimiertes Codewort dafür. Wenn in dem Muster ein Element existiert, das aber im Huffmanwortschatz nicht vorhanden ist, gibt es kein Vorkommen dieses Musters im Text. Bei einer ungenauen Suche generiert die erste Phase komprimierte Codeworte für alle Symbole, die den Elementen des gesuchten Musters entsprechen. Für jedes Element im Muster wird eine Liste aller möglichen Codeworte erstellt, die mit ihm übereinstimmen können. Das erfolgt mittels eines sequentiellen Durchlaufs des Huffmanwortschatzes und dem Erfassen aller passenden Codeworte. Diese Technik ist schon bekannt und wurde bei der Blockadressierung angewendet. Da die Wortschatzgröße, verglichen mit der Textgröße, klein ist, ist die sequentielle Suchzeit im Huffmanwortschatz nebensächlich, und es gibt keine zusätzlichen Kosten bei der Ausführung komplexer Abfragen. Abhängig von der Komplexität des gesuchten Musters wurden zwei verschiedene Suchalgorithmen untersucht. Für Phrasenmuster, die k-Fehler (k >= 0 ) erlauben und aus den Sets von einfachen Zeichen bestehen, wird ein Algorithmus angewendet, der in [BYN99] beschrieben wurde. Wenn v die Wortschatzgröße ist und w die Länge eines Wortes W, dann dauert die - 64 -

Diplomarbeit

Suchverfahren

Suche des Wortes W O(v + w) . Für komplexere Muster, die ebenfalls k-Fehler erlauben, aber aus regulären Ausrücken, Wildcards usw. bestehen, wird ein Algorithmus verwendet, der in [WM92] beschrieben wurde. Seine Suchzeit beträgt O(kv + w) . Ein einfaches Wort wird in der Zeit von O(w) mit Hilfe von z. B. Haschtabellen gesucht.

6.2.2 Zweite Phase: Suche Für die genaue Suche können verschiedene bekannte Suchalgorithmen verwendet werden. Laut [MNZB00] wurden einige Experimente durchgeführt. Die besten Resultate lieferte der Sunday Algorithmus [SUN90] von Boyer-Moore. In Falle einer ungenauen Suche wird der Multipattern Algorithmus angewendet. Eine effiziente Technik wurde von Baeza-Yates und Navarro [BYN99] vorgeschlagen. Dieser Algorithmus stellt eine Erweiterung des Sunday Algorithmus dar und kann dann eine gewisse Anzahl von Mustern schnell finden, wenn diese nicht zu groß ist. Wenn aber eine große Anzahl von Mustern gesucht werden soll, dann ist der Algorithmus von Aho-Corasick zu empfehlen [AC75], da er unabhängig von der Musteranzahl eine Suche in der Zeit von O(n ) ermöglicht. Um zu zeigen, wie schnell eine direkte Suche im komprimierten Text zustande kommt, wurde in [MNZB00] ein Experiment durchgeführt. Man hat angenommen: •

Eine nicht komprimierte Datei (T) hat die Bytelänge (u)



Eine komprimierte Datei (Z) hat die Bytelänge (n).

Bei dem Codewort (c) eines Musters der Länge (m) werden laut dem Bayer-Moore Algorithmus bestenfalls

n Bytes der komprimierten Datei untersucht. Dieses Untersuchungsergebnis c

entspricht dem durchschnittlichen einer komprimierten Datei, weil das Alphabet, im Unterschied zum kleinen Muster der Länge c (typischerweise 3 bis 4 Bytes) groß (die Größe von 128 oder 256) und gleichmäßig verteilt ist. In einer unkomprimierten Datei müssen im besten Fall

u n Zeichen untersucht werden. Da das Komprimierungsverhältnis ungefähr durchm u

- 65 -

Diplomarbeit

schnittlich sein soll, ist

Suchverfahren

n c ≈ . Deswegen ist die Anzahl der untersuchten Bytes der kompriu m

mierten Datei ungefähr dieselbe wie die der unkomprimierten. Trotzdem gibt es drei Gründe wieso die Suche in den komprimierten Dateien schneller sein sollte. •

n