Die Huffman-Codierung
Bei der Huffman-Codierung handelt es sich um eine einfache
Datenkompression. Dabei macht man sich zu Nutze, daß bei den
meisten Daten nicht alle Zeichen gleich häufig enthalten sind.
Ähnlich wie beim Morse-Code die häufig vorkommenden Buchstaben
mit kürzeren Codes dargestellt werden, als Zeichen die weniger oft
vorkommen, wird beim Huffman-Code auch ein Code passend zu einer
bestimmten Zusammensetzung der Eingangsdaten bestimmt.
Ein für bestimmte Daten ermittelter Huffman-Code führt immer zu
einer optimalen Kompression dieser Daten mittels
Huffman-Codierung. Anders ausgedrückt: ein durch statistische
Analyse ermittelter Code ist für genau die untersuchten Daten
optimal, es gibt keinen besseren. Jedoch ist natürlich nicht
ausgeschlossen, daß ein anderes Kompressions-Verfahren bessere
Ergebnisse liefert. Der LZW-Algorithmus beispielsweise macht sich zu
Nutze, daß sich bestimmte Zeichenfolgen wiederholen,
während beim Huffman-Algorithmus nur einzelne Zeichen
betrachtet werden können. (Das ist nicht ganz wahr, man könnte
Blöcke zu mehreren Zeichen bilden, was allerdings nur am Begriff
"Zeichen" herumdreht, nicht aber an der eigentlichen Tatsache, daß
einzelne Zeichen codiert werden.)
Beim Huffman-Code handelt es sich um einen
präfixfreien Binärcode. Das bedeutet, daß keine
Bitfolge der Beginn einer längeren Bitfolge sein kann. Damit erspart
man sich die Notwendigkeit für ein Trennzeichen, wie beim Morse-Code
z.B. die Pausen zwischen den einzelnen Buchstaben. Diese
Präfixfreiheit wird dadurch erreicht, daß ein Binärbaum
aufgebaut wird, bei dem ausschließlich die Blätter eine
Bezeichnung erhalten. Man kann nun definieren, daß ein linker Zweig
mit einer 0 und ein rechter Zweig mit einer 1 "adressiert" wird, und die
damit erzeugten Codes eine Art Wegbeschreibung darstellen. Liest man nun
aus dem Bitstrom einzelne Bits heraus, und folgt dieser Beschreibung,
stößt man irgendwann auf ein Blatt, und das dort zugeordnete
Zeichen ist das durch den Huffman-Code beschriebene. Das nächste Bit
ist der Beginn der nächsten Wegbeschreibung, d.h. es wird wieder an
der Wurzel angesetzt.
Ein Beispiel
Wir wollen den folgenden Text codieren: "Das Pferd frisst keinen
Gurkensalat" (da ich ursprünglich aus Friedrichsdorf stamme bin ich
wohl ein wenig vorgeschädigt). Zunächst muß die
Häufigkeit der Eingabezeichen bestimmt werden (im Folgenden wird das
Leerzeichen der besseren Lesbarkeit wegen als "_" dargestellt). Um die
gleichen Codes wie das Beispielprogramm zu erhalten wird diese Liste
zusätzlich zunächst einmal nach aufsteigenden Zeichencodes
sortiert. Dies hat keinen besonderen Grund, außer daß im
Programm ein Array verwendet wird, bei dem das Zeichen als Index
verwendet wird, und sich beim sequentiellen Auslesen des Arrays, was
später beim Aufbauen des Baums passiert, automatisch diese
Reihenfolge ergibt.
| Zeichen |
_ |
D |
G |
P |
a |
d |
e |
f |
i |
k |
l |
n |
r |
s |
t |
u |
| Häufigkeit |
4 |
1 |
1 |
1 |
3 |
1 |
4 |
2 |
2 |
2 |
1 |
3 |
3 |
4 |
2 |
1 |
Als nächstes wird diese Liste nach den Häufigkeiten der Zeichen
sortiert, wobei die Reihenfolge der Einträge mit gleicher
Häufigkeit erhalten bleibt (wieder um mit dem Programm
gleichzuziehen, für die Qualität der erzeugten Codes ist nur
die Sortierung nach Häufigkeit relevant):
| Zeichen |
_ |
e |
s |
a |
n |
r |
f |
i |
k |
t |
D |
G |
P |
d |
l |
u |
| Häufigkeit |
4 |
4 |
4 |
3 |
3 |
3 |
2 |
2 |
2 |
2 |
1 |
1 |
1 |
1 |
1 |
1 |
Jetzt kann daraus ein Baum gebaut werden. In meinem Programm gehe ich
dabei so vor, daß ich aus der sortierten Liste (praktischerweise
sind dort die Einträge schon als Baumknoten gespeichert) zwei
Einträge herausnehme, diese als Kindknoten an einen neuen Knoten
anhänge, diesem kombinierten Knoten als Häufigkeitszähler
die Summe der beiden Kind-Einträge zuweise, und wieder einfüge.
Damit wird die Zahl der Knoten in der Liste mit jedem Schritt um eins
verringert, und die dort enthaltenen Baum-Fragmente zusehends
größer. Herausgenommen wird hinten, d.h. die Knoten mit den
kleinsten Häufigkeits-Zählern werden zuerst verbaut, und
wandern damit an die tiefsten Stellen des Baums (um später die
längsten Codes zu erhalten). Wenn nur noch ein Eintrag in der Liste
vorhanden ist, ist der Aufbau des Baums abgeschlossen.
| Wert |
4 |
4 |
4 |
3 |
3 |
3 |
2 |
2 |
2 |
2 |
1 |
1 |
1 |
1 |
1 |
1 |
| Baum |
_ |
e |
s |
a |
n |
r |
f |
i |
k |
t |
D |
G |
P |
d |
l |
u |
| Wert |
4 |
4 |
4 |
3 |
3 |
3 |
2 |
2 |
2 |
2 |
2 |
1 |
1 |
1 |
1 |
| Baum |
_ |
e |
s |
a |
n |
r |
f |
i |
k |
t |
u,l |
D |
G |
P |
d |
| Wert |
4 |
4 |
4 |
3 |
3 |
3 |
2 |
2 |
2 |
2 |
2 |
1 |
1 |
1 |
1 |
| Baum |
_ |
e |
s |
a |
n |
r |
f |
i |
k |
t |
u,l |
D |
G |
P |
d |
| Wert |
4 |
4 |
4 |
3 |
3 |
3 |
2 |
2 |
2 |
2 |
2 |
2 |
1 |
1 |
| Baum |
_ |
e |
s |
a |
n |
r |
f |
i |
k |
t |
u,l |
d,P |
D |
G |
| Wert |
4 |
4 |
4 |
3 |
3 |
3 |
2 |
2 |
2 |
2 |
2 |
2 |
1 |
1 |
| Baum |
_ |
e |
s |
a |
n |
r |
f |
i |
k |
t |
u,l |
d,P |
D |
G |
| Wert |
4 |
4 |
4 |
3 |
3 |
3 |
2 |
2 |
2 |
2 |
2 |
2 |
2 |
| Baum |
_ |
e |
s |
a |
n |
r |
f |
i |
k |
t |
u,l |
d,P |
G,D |
Nun sind alle mit der Häufigkeit 1 verschwunden und zwei neue
Teilbäume mit jeweils zwei Elementen der Häufigkeit 1 und somit
einem Gesamtwert von 2 hinzugekommen. Bitte zu beachten, daß bei
meiner Implementierung der zuerst entnommene (also ganz rechte) Eintrag
auf die linke Seite des neuen Baums gehängt wird, daher diese
Verdrehung. Beim Einhängen in die Liste wird er an die rechteste
Position die seinem Gesamtwert noch gerecht wird platziert. Im
nächsten Schritt werden alle Bäume mit einem Wert von 2
miteinander verhängt.
| Wert |
4 |
4 |
4 |
3 |
3 |
3 |
2 |
2 |
2 |
2 |
2 |
2 |
2 |
| Baum |
_ |
e |
s |
a |
n |
r |
f |
i |
k |
t |
u,l |
d,P |
G,D |
| Wert |
4 |
4 |
4 |
4 |
3 |
3 |
3 |
2 |
2 |
2 |
2 |
2 |
| Baum |
_ |
e |
s |
G,D,,d,P |
a |
n |
r |
f |
i |
k |
t |
u,l |
| Wert |
4 |
4 |
4 |
4 |
3 |
3 |
3 |
2 |
2 |
2 |
2 |
2 |
| Baum |
_ |
e |
s |
G D d P |
a |
n |
r |
f |
i |
k |
t |
u,l |
| Wert |
4 |
4 |
4 |
4 |
4 |
3 |
3 |
3 |
2 |
2 |
2 |
| Baum |
_ |
e |
s |
G,D,,d,P |
u,l,,t |
a |
n |
r |
f |
i |
k |
| Wert |
4 |
4 |
4 |
4 |
4 |
3 |
3 |
3 |
2 |
2 |
2 |
| Baum |
_ |
e |
s |
G,D,,d,P |
u,l,,t |
a |
n |
r |
f |
i |
k |
| Wert |
4 |
4 |
4 |
4 |
4 |
4 |
3 |
3 |
3 |
2 |
| Baum |
_ |
e |
s |
G,D,,d,P |
u,l,,t |
k,i |
a |
n |
r |
f |
Nun sind erstmal alle Knoten verhängt, die einen Gesamtwert kleiner
oder gleich 4 erhalten. Wenn als nächstes die 3 und die 2
verknüpft werden, muß das Ergebnis erstmals zu Beginn der
Liste eingefügt werden. Beim darauffolgenden Verknüpfen der
beiden 3er die dann hinten an stehen werden, entsteht ein Baum mit einem
Gesamtwert von 6, der wiederum ganz vorne eingeordnet wird.
| Wert |
4 |
4 |
4 |
4 |
4 |
4 |
3 |
3 |
3 |
2 |
| Baum |
_ |
e |
s |
G,D,,d,P |
u,l,,t |
k,i |
a |
n |
r |
f |
| Wert |
5 |
4 |
4 |
4 |
4 |
4 |
4 |
3 |
3 |
| Baum |
f,r |
_ |
e |
s |
G,D,,d,P |
u,l,,t |
k,i |
a |
n |
| Wert |
5 |
4 |
4 |
4 |
4 |
4 |
4 |
3 |
3 |
| Baum |
f,r |
_ |
e |
s |
G,D,,d,P |
u,l,,t |
k,i |
a |
n |
| Wert |
6 |
5 |
4 |
4 |
4 |
4 |
4 |
4 |
| Baum |
n,a |
f,r |
_ |
e |
s |
G,D,,d,P |
u,l,,t |
k,i |
Es ist recht schwierig die Zugehörigkeit der Kindknoten in
textueller Form darzustellen, ich hoffe das mit den Kommas ist
verständlich. Ein solcher Ausdruck zerfällt immer in einen
linken und einen rechten Teil der längsten Kommakette, dessen
Kindknoten die Teilausdrücke sind. Dies ist dann rekursiv weiter zu
führen, bis keine Kommas mehr enthalten sind. Am Ende der
Transformationen wird dies nochmal am fertigen Baum verdeutlicht.
Ansonsten ist es eine große Hilfe, jede Menge Papier vollzumalen,
und sich als Skizze die Binärbäume wie gewohnt aufzuzeichnen.
Die Tabellen hier sollen lediglich der Kontrolle dienen.
| Wert |
6 |
5 |
4 |
4 |
4 |
4 |
4 |
4 |
| Baum |
n,a |
f,r |
_ |
e |
s |
G,D,,d,P |
u,l,,t |
k,i |
| Wert |
8 |
6 |
5 |
4 |
4 |
4 |
4 |
| Baum |
k,i,,,u,l,,t |
n,a |
f,r |
_ |
e |
s |
G,D,,d,P |
| Wert |
8 |
6 |
5 |
4 |
4 |
4 |
4 |
| Baum |
k,i,,,u,l,,t |
n,a |
f,r |
_ |
e |
s |
G,D,,d,P |
| Wert |
8 |
8 |
6 |
5 |
4 |
4 |
| Baum |
k,i,,,u,l,,t |
G,D,,d,P,,,s |
n,a |
f,r |
_ |
e |
| Wert |
8 |
8 |
6 |
5 |
4 |
4 |
| Baum |
k,i,,,u,l,,t |
G,D,,d,P,,,s |
n,a |
f,r |
_ |
e |
| Wert |
8 |
8 |
8 |
6 |
5 |
| Baum |
k,i,,,u,l,,t |
G,D,,d,P,,,s |
e,_ |
n,a |
f,r |
| Wert |
8 |
8 |
8 |
6 |
5 |
| Baum |
k,i,,,u,l,,t |
G,D,,d,P,,,s |
e,_ |
n,a |
f,r |
| Wert |
11 |
8 |
8 |
8 |
| Baum |
f,r,,n,a |
k,i,,,u,l,,t |
G,D,,d,P,,,s |
e,_ |
| Wert |
11 |
8 |
8 |
8 |
| Baum |
f,r,,n,a |
k,i,,,u,l,,t |
G,D,,d,P,,,s |
e,_ |
| Wert |
16 |
11 |
8 |
| Baum |
e,_,,,,G,D,,d,P,,,s |
f,r,,n,a |
k,i,,,u,l,,t |
| Wert |
16 |
11 |
8 |
| Baum |
e,_,,,,G,D,,d,P,,,s |
f,r,,n,a |
k,i,,,u,l,,t |
| Wert |
19 |
16 |
| Baum |
k,i,,,u,l,,t,,,,f,r,,n,a |
e,_,,,,G,D,,d,P,,,s |
| Wert |
19 |
16 |
| Baum |
k,i,,,u,l,,t,,,,f,r,,n,a |
e,_,,,,G,D,,d,P,,,s |
| Wert |
35 |
| Baum |
e,_,,,,G,D,,d,P,,,s,,,,,k,i,,,u,l,,t,,,,f,r,,n,a |
Es ist übrigens ein extrem gutes Zeichen, daß der Gesamtwert
der Anzahl der Zeichen im Eingabetext entspricht, das bedeutet
nämlich, daß unterwegs nichts verloren gegangen ist. Als
nächstes werden die Codes an die Blätter des Baums angeheftet.
Wie bereits beschrieben wird von der Wurzel ausgehend bei jedem Schritt
nach links eine 0 und bei jedem Schritt nach rechts eine 1 an den
bisherigen Code angehängt, solange bis ein Blatt erreicht wird, dem
dieser Code zugeordnet wird. Wenn alles richtig gemacht wird kommt sowas
raus:
| Eingabezeichen |
_ |
e |
s |
a |
n |
r |
f |
i |
k |
t |
D |
G |
P |
d |
l |
u |
| Huffman-Code |
001 |
000 |
011 |
1111 |
1110 |
1101 |
1100 |
1001 |
1000 |
1011 |
01001 |
01000 |
01011 |
01010 |
10101 |
10100 |
Jetzt steht dem Codieren nichts mehr im Wege. Zunächst sollen
exemplarisch die ersten 9 Zeichen des Textes codiert werden, danach wird
das Ergebnis der Codierung der ganzen Zeichenkette angegeben (wie sie
auch vom Beispielprogramm erzeugt wird), als Kontrolle für
diejenigen, die den Rest selbst codieren möchten, oder sich an das
ebenfalls jetzt sehr einfache Decodieren wagen wollen.
| D |
a |
s |
|
P |
f |
e |
r |
d |
| 0 |
1 |
0 |
0 |
1 |
1 |
1 |
1 |
1 |
0 |
1 |
1 |
0 |
0 |
1 |
0 |
1 |
0 |
1 |
1 |
1 |
1 |
0 |
0 |
0 |
0 |
0 |
1 |
1 |
0 |
1 |
0 |
1 |
0 |
1 |
0 |
| 4f |
b2 |
bc |
1a |
a |
| 4f |
b2 |
bc |
1a |
a3 |
9b |
2d |
d9 |
81 |
3c |
38 |
a2 |
9b |
03 |
9f |
d7 |
ec |
Von dem letzten 'c' gehören nur die oberen beiden Bits dazu. Diese
Information steht dem Programm zum Decodieren auch implizit zur
Verfügung (die Zahl der enthaltenen Zeichen geht dem Bitstrom
voraus). Wie man sieht konnten 35 Zeichen in knapp 17 Byte dargestellt
werden, das ist ungefähr um die Hälfte kürzer als wenn es
direkt in ASCII gespeichert worden wäre. Allerdings muß man
dazusagen, daß hier nur wenige Zeichen verwendet wurden, und damit
relativ viele Bits ungenutzt bleiben, wenn man ASCII verwendet. Die
tatsächlich erzielbaren Kompressionsraten hängen im
Wesentlichen davon ab, wie unterschiedlich verteilt die Häufigkeit
der vorkommenden Zeichen ist. Dadurch, daß nicht alle
möglichen Binärcodes verwendet werden können (es
dürfen ja keine Mehrdeutigkeiten bezüglich der Präfixe
entstehen), ist es durchaus möglich, daß die Huffman-codierten
Daten mehr Speicher brauchen als die Eingangsdaten.
Downloads
| Datum |
Datei |
Beschreibung |
| 2003-08-24 |
huffman.zip |
Quellcode und Win32-Binary |