Huffman-Komprimierungsalgorithmus

Im Vorfeld des Kursbeginns haben "Algorithmen fĂŒr Entwickler" fĂŒr Sie eine Übersetzung eines weiteren nĂŒtzlichen Materials vorbereitet.




Die Huffman-Codierung ist ein Datenkomprimierungsalgorithmus, der die Grundidee der Dateikomprimierung formuliert. In diesem Artikel werden wir ĂŒber Codierung mit fester und variabler LĂ€nge, eindeutig decodierte Codes, PrĂ€fixregeln und die Konstruktion eines Huffman-Baums sprechen.

Wir wissen, dass jedes Zeichen als Folge von 0 und 1 gespeichert ist und 8 Bits benötigt. Dies wird als Codierung mit fester LÀnge bezeichnet, da jedes Zeichen dieselbe feste Anzahl von Bits zum Speichern verwendet.

Nehmen wir an, der Text ist gegeben. Wie können wir den Speicherplatz reduzieren, der zum Speichern eines Zeichens erforderlich ist?

Die Grundidee ist die Codierung mit variabler LÀnge. Wir können die Tatsache nutzen, dass einige Zeichen im Text hÀufiger vorkommen als andere ( siehe hier ), um einen Algorithmus zu entwickeln, der dieselbe Zeichenfolge mit weniger Bits darstellt. Beim Codieren einer variablen LÀnge weisen wir den Zeichen eine variable Anzahl von Bits zu, abhÀngig von der HÀufigkeit ihres Auftretens in diesem Text. Letztendlich können einige Zeichen nur 1 Bit und andere 2 Bit, 3 oder mehr, aufnehmen. Das Problem bei der Codierung mit variabler LÀnge ist nur die nachfolgende Decodierung der Sequenz.

Wie kann man die Reihenfolge der Bits kennen, um sie eindeutig zu dekodieren?

Betrachten Sie die Zeichenfolge "aabacdab" . Es hat 8 Zeichen und beim Codieren einer festen LÀnge werden 64 Bit benötigt, um es zu speichern. Beachten Sie, dass die HÀufigkeit der Zeichen "a", "b", "c" und "d" 4, 2, 1, 1 betrÀgt. Versuchen wir uns "aabacdab" mit weniger Bits vorzustellen, wobei wir die Tatsache verwenden, dass "a" hÀufiger als "b" und "b" hÀufiger als "c" und "d" ist . ZunÀchst codieren wir "a" mit einem Bit gleich 0, "b" weisen wir einen Zwei-Bit-Code 11 zu und codieren mit drei Bits 100 und 011 "c" und"D" .

Als Ergebnis werden wir erfolgreich sein:

ein0
belf
c100
d011


Daher codieren wir die Zeichenfolge "aabacdab" unter Verwendung der oben angegebenen Codes als 00110100011011 (0 | 0 | 11 | 0 | 100 | 011 | 0 | 11) . Das Hauptproblem wird jedoch die Dekodierung sein. Wenn wir versuchen, die Zeile 00110100011011 zu dekodieren , erhalten wir ein mehrdeutiges Ergebnis, da es wie folgt dargestellt werden kann:

0|011|0|100|011|0|11    adacdab
0|0|11|0|100|0|11|011   aabacabd
0|011|0|100|0|11|0|11   adacabab 

...
etc.

Um diese Mehrdeutigkeit zu vermeiden, mĂŒssen wir sicherstellen, dass unsere Codierung einem Konzept wie einer PrĂ€fixregel entspricht , was wiederum impliziert, dass Codes auf nur eine eindeutige Weise decodiert werden können. Eine PrĂ€fixregel stellt sicher, dass kein Code ein PrĂ€fix eines anderen ist. Mit Code meinen wir Bits, die zur Darstellung eines bestimmten Zeichens verwendet werden. Im obigen Beispiel ist 0 das PrĂ€fix 011 , das gegen die PrĂ€fixregel verstĂ¶ĂŸt. Wenn unsere Codes die PrĂ€fixregel erfĂŒllen, können wir sie eindeutig dekodieren (und umgekehrt).

Sehen wir uns das obige Beispiel an. Dieses Mal werden wir die Zeichen "a", "b", "c" und "d" zuweisen. Codes, die die PrĂ€fixregel erfĂŒllen.

ein0
b10
c110
d111


Bei Verwendung dieser Codierung wird die Zeichenfolge "aabacdab" als 00100100011010 (0 | 0 | 10 | 0 | 100 | 011 | 0 | 10) codiert . Und hier 00100100011010 können wir eindeutig dekodieren und zu unserer ursprĂŒnglichen Zeile „aabacdab“ zurĂŒckkehren .

Huffman-Codierung


Nachdem wir die Codierung mit variabler LĂ€nge und eine PrĂ€fixregel herausgefunden haben, sprechen wir ĂŒber die Huffman-Codierung.

Die Methode basiert auf der Erstellung von BinĂ€rbĂ€umen. Darin kann ein Knoten entweder endlich oder intern sein. Anfangs werden alle Knoten als BlĂ€tter (BlĂ€tter) betrachtet, die das Symbol selbst und sein Gewicht (dh die HĂ€ufigkeit des Auftretens) darstellen. Interne Knoten enthalten die Gewichtung des Zeichens und beziehen sich auf zwei untergeordnete Knoten. Nach allgemeiner Vereinbarung steht das Bit "0" fĂŒr eine Verfolgung im linken Zweig und "1" fĂŒr die rechte. In einem vollstĂ€ndigen Baum gibt es N BlĂ€tter und N-1 interne Knoten. Es wird empfohlen, beim Erstellen eines Huffman-Baums nicht verwendete Zeichen zu verwerfen, um Codes mit optimaler LĂ€nge zu erhalten.

Wir werden die PrioritÀtswarteschlange verwenden, um den Huffman-Baum zu erstellen, wobei dem Knoten mit der niedrigsten Frequenz die höchste PrioritÀt zugewiesen wird. Die Konstruktionsschritte werden nachfolgend beschrieben:

  1. Erstellen Sie fĂŒr jedes Zeichen einen Blattknoten und fĂŒgen Sie ihn der PrioritĂ€tswarteschlange hinzu.
  2. Gehen Sie wie folgt vor, wĂ€hrend Sie fĂŒr mehr als ein Blatt in der Schlange stehen:
    • Entfernen Sie die beiden Knoten mit der höchsten PrioritĂ€t (mit der niedrigsten Frequenz) aus der Warteschlange.
    • Erstellen Sie einen neuen internen Knoten, in dem diese beiden Knoten Erben sind und die HĂ€ufigkeit des Auftretens der Summe der HĂ€ufigkeiten dieser beiden Knoten entspricht.
    • FĂŒgen Sie der PrioritĂ€tswarteschlange einen neuen Knoten hinzu.
  3. Der einzige verbleibende Knoten ist die Wurzel. Dadurch wird die Baumkonstruktion abgeschlossen.

Stellen Sie sich vor, wir haben einen Text, der nur aus den Zeichen "a", "b", "c", "d" und "e" besteht, und die HÀufigkeit ihres Auftretens betrÀgt 15, 7, 6, 6 bzw. 5. Unten finden Sie Abbildungen, die die Schritte des Algorithmus widerspiegeln.











Der Pfad von der Wurzel zu einem beliebigen Endknoten speichert den optimalen PrÀfixcode (auch als Huffman-Code bezeichnet), der dem diesem Endknoten zugeordneten Zeichen entspricht.


Huffman-Baum

Nachfolgend finden Sie die Implementierung des Huffman-Komprimierungsalgorithmus in C ++ und Java:

#include <iostream>
#include <string>
#include <queue>
#include <unordered_map>
using namespace std;

// A Tree node
struct Node
{
	char ch;
	int freq;
	Node *left, *right;
};

// Function to allocate a new tree node
Node* getNode(char ch, int freq, Node* left, Node* right)
{
	Node* node = new Node();

	node->ch = ch;
	node->freq = freq;
	node->left = left;
	node->right = right;

	return node;
}

// Comparison object to be used to order the heap
struct comp
{
	bool operator()(Node* l, Node* r)
	{
		// highest priority item has lowest frequency
		return l->freq > r->freq;
	}
};

// traverse the Huffman Tree and store Huffman Codes
// in a map.
void encode(Node* root, string str,
			unordered_map<char, string> &huffmanCode)
{
	if (root == nullptr)
		return;

	// found a leaf node
	if (!root->left && !root->right) {
		huffmanCode[root->ch] = str;
	}

	encode(root->left, str + "0", huffmanCode);
	encode(root->right, str + "1", huffmanCode);
}

// traverse the Huffman Tree and decode the encoded string
void decode(Node* root, int &index, string str)
{
	if (root == nullptr) {
		return;
	}

	// found a leaf node
	if (!root->left && !root->right)
	{
		cout << root->ch;
		return;
	}

	index++;

	if (str[index] =='0')
		decode(root->left, index, str);
	else
		decode(root->right, index, str);
}

// Builds Huffman Tree and decode given input text
void buildHuffmanTree(string text)
{
	// count frequency of appearance of each character
	// and store it in a map
	unordered_map<char, int> freq;
	for (char ch: text) {
		freq[ch]++;
	}

	// Create a priority queue to store live nodes of
	// Huffman tree;
	priority_queue<Node*, vector<Node*>, comp> pq;

	// Create a leaf node for each character and add it
	// to the priority queue.
	for (auto pair: freq) {
		pq.push(getNode(pair.first, pair.second, nullptr, nullptr));
	}

	// do till there is more than one node in the queue
	while (pq.size() != 1)
	{
		// Remove the two nodes of highest priority
		// (lowest frequency) from the queue
		Node *left = pq.top(); pq.pop();
		Node *right = pq.top();	pq.pop();

		// Create a new internal node with these two nodes
		// as children and with frequency equal to the sum
		// of the two nodes' frequencies. Add the new node
		// to the priority queue.
		int sum = left->freq + right->freq;
		pq.push(getNode('\0', sum, left, right));
	}

	// root stores pointer to root of Huffman Tree
	Node* root = pq.top();

	// traverse the Huffman Tree and store Huffman Codes
	// in a map. Also prints them
	unordered_map<char, string> huffmanCode;
	encode(root, "", huffmanCode);

	cout << "Huffman Codes are :\n" << '\n';
	for (auto pair: huffmanCode) {
		cout << pair.first << " " << pair.second << '\n';
	}

	cout << "\nOriginal string was :\n" << text << '\n';

	// print encoded string
	string str = "";
	for (char ch: text) {
		str += huffmanCode[ch];
	}

	cout << "\nEncoded string is :\n" << str << '\n';

	// traverse the Huffman Tree again and this time
	// decode the encoded string
	int index = -1;
	cout << "\nDecoded string is: \n";
	while (index < (int)str.size() - 2) {
		decode(root, index, str);
	}
}

// Huffman coding algorithm
int main()
{
	string text = "Huffman coding is a data compression algorithm.";

	buildHuffmanTree(text);

	return 0;
}

import java.util.HashMap;
import java.util.Map;
import java.util.PriorityQueue;

// A Tree node
class Node
{
	char ch;
	int freq;
	Node left = null, right = null;

	Node(char ch, int freq)
	{
		this.ch = ch;
		this.freq = freq;
	}

	public Node(char ch, int freq, Node left, Node right) {
		this.ch = ch;
		this.freq = freq;
		this.left = left;
		this.right = right;
	}
};

class Huffman
{
	// traverse the Huffman Tree and store Huffman Codes
	// in a map.
	public static void encode(Node root, String str,
							  Map<Character, String> huffmanCode)
	{
		if (root == null)
			return;

		// found a leaf node
		if (root.left == null && root.right == null) {
			huffmanCode.put(root.ch, str);
		}


		encode(root.left, str + "0", huffmanCode);
		encode(root.right, str + "1", huffmanCode);
	}

	// traverse the Huffman Tree and decode the encoded string
	public static int decode(Node root, int index, StringBuilder sb)
	{
		if (root == null)
			return index;

		// found a leaf node
		if (root.left == null && root.right == null)
		{
			System.out.print(root.ch);
			return index;
		}

		index++;

		if (sb.charAt(index) == '0')
			index = decode(root.left, index, sb);
		else
			index = decode(root.right, index, sb);

		return index;
	}

	// Builds Huffman Tree and huffmanCode and decode given input text
	public static void buildHuffmanTree(String text)
	{
		// count frequency of appearance of each character
		// and store it in a map
		Map<Character, Integer> freq = new HashMap<>();
		for (int i = 0 ; i < text.length(); i++) {
			if (!freq.containsKey(text.charAt(i))) {
				freq.put(text.charAt(i), 0);
			}
			freq.put(text.charAt(i), freq.get(text.charAt(i)) + 1);
		}

		// Create a priority queue to store live nodes of Huffman tree
		// Notice that highest priority item has lowest frequency
		PriorityQueue<Node> pq = new PriorityQueue<>(
										(l, r) -> l.freq - r.freq);

		// Create a leaf node for each character and add it
		// to the priority queue.
		for (Map.Entry<Character, Integer> entry : freq.entrySet()) {
			pq.add(new Node(entry.getKey(), entry.getValue()));
		}

		// do till there is more than one node in the queue
		while (pq.size() != 1)
		{
			// Remove the two nodes of highest priority
			// (lowest frequency) from the queue
			Node left = pq.poll();
			Node right = pq.poll();

			// Create a new internal node with these two nodes as children 
			// and with frequency equal to the sum of the two nodes
			// frequencies. Add the new node to the priority queue.
			int sum = left.freq + right.freq;
			pq.add(new Node('\0', sum, left, right));
		}

		// root stores pointer to root of Huffman Tree
		Node root = pq.peek();

		// traverse the Huffman tree and store the Huffman codes in a map
		Map<Character, String> huffmanCode = new HashMap<>();
		encode(root, "", huffmanCode);

		// print the Huffman codes
		System.out.println("Huffman Codes are :\n");
		for (Map.Entry<Character, String> entry : huffmanCode.entrySet()) {
			System.out.println(entry.getKey() + " " + entry.getValue());
		}

		System.out.println("\nOriginal string was :\n" + text);

		// print encoded string
		StringBuilder sb = new StringBuilder();
		for (int i = 0 ; i < text.length(); i++) {
			sb.append(huffmanCode.get(text.charAt(i)));
		}

		System.out.println("\nEncoded string is :\n" + sb);

		// traverse the Huffman Tree again and this time
		// decode the encoded string
		int index = -1;
		System.out.println("\nDecoded string is: \n");
		while (index < sb.length() - 2) {
			index = decode(root, index, sb);
		}
	}

	public static void main(String[] args)
	{
		String text = "Huffman coding is a data compression algorithm.";

		buildHuffmanTree(text);
	}
}

Hinweis: Der von der Eingabezeichenfolge verwendete Speicher betrÀgt 47 * 8 = 376 Bit, und die codierte Zeichenfolge benötigt nur 194 Bit, d. H. Daten werden um ca. 48% komprimiert. Im obigen C ++ - Programm verwenden wir die Zeichenfolgenklasse, um die codierte Zeichenfolge zu speichern und das Programm lesbar zu machen.

Da effektive Datenstrukturen der PrioritĂ€tswarteschlange das EinfĂŒgen von O (log (N)) Zeit erfordern und in einem vollstĂ€ndigen BinĂ€rbaum mit N BlĂ€ttern 2N-1 Knoten vorhanden sind und der Huffman-Baum ein vollstĂ€ndiger BinĂ€rbaum ist, arbeitet der Algorithmus fĂŒr O (Nlog (N) )) Zeit, wobei N die Anzahl der Zeichen ist.

Quellen:


en.wikipedia.org/wiki/Huffman_coding
en.wikipedia.org/wiki/Variable-length_code
www.youtube.com/watch?v=5wRPin4oxCo



.



All Articles