Algoritma Kompresi Huffman

Untuk mengantisipasi dimulainya kursus "Algoritma untuk Pengembang" telah mempersiapkan untuk Anda terjemahan dari materi lain yang bermanfaat.




Huffman coding adalah algoritma kompresi data yang merumuskan ide dasar kompresi file. Pada artikel ini, kita akan berbicara tentang pengkodean panjang tetap dan variabel, kode yang diterjemahkan secara unik, aturan awalan, dan pembangunan pohon Huffman.

Kita tahu bahwa setiap karakter disimpan sebagai urutan 0 dan 1 dan membutuhkan 8 bit. Ini disebut pengkodean panjang tetap karena setiap karakter menggunakan jumlah bit tetap yang sama untuk menyimpan.

Katakanlah teks diberikan. Bagaimana kita bisa mengurangi jumlah ruang yang dibutuhkan untuk menyimpan satu karakter?

Ide dasarnya adalah pengkodean panjang variabel. Kita dapat menggunakan fakta bahwa beberapa karakter dalam teks lebih umum daripada yang lain ( lihat di sini ) untuk mengembangkan algoritma yang akan mewakili urutan karakter yang sama dengan bit yang lebih sedikit. Saat mengkode panjang variabel, kami menetapkan sejumlah variabel bit untuk karakter tergantung pada frekuensi kemunculannya dalam teks ini. Pada akhirnya, beberapa karakter dapat memakan waktu hanya 1 bit, dan yang lain 2 bit, 3 atau lebih. Masalah dengan pengkodean panjang variabel hanya decoding urutan berikutnya.

Bagaimana, mengetahui urutan bit, mendekodekannya secara unik?

Pertimbangkan string "aabacdab" . Ini memiliki 8 karakter, dan ketika menyandikan panjang tetap, 64 bit akan diperlukan untuk menyimpannya. Perhatikan bahwa frekuensi karakter "a", "b", "c" dan "d" masing-masing adalah 4, 2, 1, 1. Mari kita coba bayangkan "aabacdab" dengan bit lebih sedikit, menggunakan fakta bahwa "a" lebih umum daripada "b" dan "b" lebih umum daripada "c" dan "d" . Untuk memulainya, kita menyandikan "a" menggunakan satu bit sama dengan 0, "b" kita menetapkan kode dua-bit 11, dan menggunakan tiga bit 100 dan 011 kita menyandikan "c" dan"D" .

Sebagai hasilnya, kami akan berhasil:

Sebuah0
bsebelas
c100
d011


Jadi, kami menyandikan string "aabacdab" sebagai 00110100011011 (0 | 0 | 11 | 0 | 100 | 011 | 0 | 11) menggunakan kode yang disajikan di atas. Namun, masalah utamanya adalah decoding. Ketika kami mencoba untuk memecahkan kode garis 00110100011011 , kami mendapatkan hasil yang ambigu, karena dapat direpresentasikan sebagai:

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 

...
dll.

Untuk menghindari ambiguitas ini, kita harus memastikan bahwa pengkodean kita memenuhi konsep seperti aturan awalan , yang pada gilirannya menyiratkan bahwa kode dapat diterjemahkan hanya dalam satu cara yang unik. Aturan awalan memastikan bahwa tidak ada kode yang merupakan awalan dari yang lain. Menurut kode, maksud kami bit yang digunakan untuk mewakili karakter tertentu. Dalam contoh di atas, 0 adalah awalan 011 , yang melanggar aturan awalan. Jadi, jika kode kita memenuhi aturan awalan, maka kita dapat mendekode secara unik (dan sebaliknya).

Mari kita tinjau contoh di atas. Kali ini kami akan menetapkan karakter "a", "b", "c" dan "d" Kode yang memenuhi aturan awalan.

Sebuah0
b10
c110
d111


Menggunakan pengkodean ini, string "aabacdab" akan dikodekan sebagai 00100100011010 (0 | 0 | 10 | 0 | 100 | 011 | 0 | 10) . Dan di sini 00100100011010 kita dapat secara unik mendekode dan kembali ke baris asli kita "aabacdab" .

Coding Huffman


Sekarang kami telah menemukan pengkodean panjang variabel dan aturan awalan, mari kita bicara tentang pengkodean Huffman.

Metode ini didasarkan pada penciptaan pohon biner. Di dalamnya, sebuah simpul dapat berupa terbatas atau internal. Awalnya, semua simpul dianggap daun (daun), yang mewakili simbol itu sendiri dan beratnya (yaitu, frekuensi kejadian). Node internal berisi bobot karakter dan merujuk ke dua node turunan. Secara umum, bit "0" mewakili tindak di cabang kiri, dan "1" mewakili di sebelah kanan. Dalam pohon lengkap ada N daun dan N-1 node internal. Disarankan bahwa ketika membangun pohon Huffman, karakter yang tidak digunakan dibuang untuk mendapatkan kode dengan panjang optimal.

Kami akan menggunakan antrian prioritas untuk membangun pohon Huffman, di mana simpul dengan frekuensi terendah akan diberi prioritas tertinggi. Langkah-langkah konstruksi dijelaskan di bawah ini:

  1. Buat simpul daun untuk setiap karakter dan tambahkan ke antrian prioritas.
  2. Sementara dalam antrean lebih dari satu lembar, lakukan hal berikut:
    • Hapus dua node dengan prioritas tertinggi (dengan frekuensi terendah) dari antrian;
    • Buat simpul internal baru di mana dua node ini akan menjadi ahli waris, dan frekuensi kemunculannya akan sama dengan jumlah frekuensi dua node ini.
    • Tambahkan node baru ke antrian prioritas.
  3. Satu-satunya simpul yang tersisa adalah root, ini akan menyelesaikan konstruksi pohon.

Bayangkan kita memiliki beberapa teks yang hanya terdiri dari karakter "a," ​​"b," "c," "d," dan "e," dan frekuensinya masing-masing adalah 15, 7, 6, 6, dan 5. Di bawah ini adalah ilustrasi yang mencerminkan langkah-langkah algoritma.











Path dari root ke sembarang simpul akan menyimpan kode awalan yang optimal (juga dikenal sebagai kode Huffman) yang sesuai dengan karakter yang terkait dengan simpul akhir itu.


Huffman Tree

Di bawah ini Anda akan menemukan implementasi algoritma kompresi Huffman di C ++ dan 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);
	}
}

Catatan: memori yang digunakan oleh string input adalah 47 * 8 = 376 bit, dan string yang disandikan hanya membutuhkan 194 bit, mis. data dikompresi sekitar 48%. Dalam program C ++ di atas, kami menggunakan kelas string untuk menyimpan string yang disandikan agar program dapat dibaca.

Karena struktur data yang efektif dari antrian prioritas memerlukan O (log (N)) waktu untuk dimasukkan, dan dalam pohon biner lengkap dengan daun N ada 2N-1 node, dan pohon Huffman adalah pohon biner lengkap, algoritma bekerja untuk O (Nlog (Nlog) )) waktu, di mana N adalah jumlah karakter.

Sumber:


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



.



All Articles