Algoritmi i kompresimit Huffman

Në prag të fillimit të kursit «Algoritmet për zhvilluesit» kanë përgatitur për ju përkthimin e një materiali tjetër të dobishëm.

Kodimi i Huffman-it është një algoritëm kompresimi të dhënash që formulon idenë kryesore të kompresimit të skedarëve. Në këtë artikull do të flasim për kodimin me gjatësi fikse dhe të ndryshme, kode që dekodohen unikisht, rregulla prefiksuese dhe ndërtimin e drurit të Huffman-it.

Ne e dimë se çdo simbol ruhet si një sërë prej 0 dhe 1 dhe zë 8 bite. Kjo quhet kodim me gjatësi fikse, pasi çdo simbol përdor të njëjtin numër fikse bitësh për ruajtje.

Supozoni se kemi një tekst. Si mund ta pakësojmë sasinë e hapësirës që kërkohet për ruajtjen e një simboli?

Ideja kryesore qëndron në kodimin me gjatësi të ndryshme. Ne mund të përdorim faktin se disa simbole në tekst shfaqen më shpesh se të tjerat (shih këtu), për të krijuar një algoritëm që do të përfaqësonte të njëjtën sekuencë karakteresh me një numër më të vogël bitësh. Kur kodifikojmë me gjatësi të ndryshueshme, ne i caktojmë karaktereve një numër të ndryshueshëm bitësh në varësi të frekuencës së shfaqjes së tyre në këtë tekst. Në fund, disa karaktere mund të zënë vetëm 1 bit, ndërsa të tjerët 2, 3 ose më shumë. Problemi me kodifikimin e gjatësi të ndryshueshme qëndron vetëm te dekodimi i mëvonshëm i sekuencës.

Si, duke ditur sekuencën e bitëve, ta dekodojmë atë në mënyrë të qartë?

Le tĂ« shqyrtojmĂ« stringun «aabacdab». NĂ« tĂ«, ka 8 karaktere, dhe kur kodifikohet me gjatĂ«si tĂ« fiksuar, ne do tĂ« kemi nevojĂ« pĂ«r 64 bitĂ« pĂ«r ta ruajtur. VĂ«reni se frekuenca e karaktereve «a», «b», «c» dhe «d» Ă«shtĂ« 4, 2, 1, 1 pĂ«rkatĂ«sisht. Le tĂ« provojmĂ« ta pĂ«rfaqĂ«sojmĂ« «aabacdab» me njĂ« numĂ«r mĂ« tĂ« vogĂ«l bitĂ«sh, duke pĂ«rdorur faktin se «a» shfaqet mĂ« shpesh se «b», dhe «b» shfaqet mĂ« shpesh se «c» dhe «d». Ne do tĂ« fillojmĂ« duke kodifikuar «a» me njĂ« bit, tĂ« cilin e caktojmĂ« si 0, «b» ne do t’i caktojmĂ« kodin me dy bitĂ« 11, dhe me tre bitĂ« 100 dhe 011 do ta kodifikojmĂ« «c» dhe «d».

Në fund do të kemi:

a
0

b
11

me
100

d
011

Pra, stringun «aabacdab» ne do ta kodifikojmë si 00110100011011 (0|0|11|0|100|011|0|11), duke kodet e paraqitura më lartë. Megjithatë, problemi kryesor do të jetë dekodimi. Kur përpiqemi të dekodojmë vargun 00110100011011, do të kemi një rezultat të paqartë, sepse mund të paraqitet si:

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 



etj.

PĂ«r tĂ« shmangur kĂ«tĂ« paqartĂ«si, duhet tĂ« garantojmĂ« qĂ« kodimi ynĂ« plotĂ«son njĂ« koncept si regulli i prefiksit, i cili, nga ana tjetĂ«r, nĂ«nkupton se kodet mund tĂ« dekodohen vetĂ«m nĂ« njĂ« mĂ«nyrĂ« unike. Rregulli i prefiksit garanton qĂ« asnjĂ« kod nuk do tĂ« jetĂ« prefiks i njĂ« tjetri. Me kod nĂ«nkuptojmĂ« bits qĂ« pĂ«rdoren pĂ«r tĂ« pĂ«rfaqĂ«suar njĂ« simbol tĂ« caktuar. NĂ« shembullin e mĂ«sipĂ«rm 0 – Ă«shtĂ« njĂ« prefiks 011, qĂ« shkel rregullin e prefiksit. Pra, nĂ«se kodet tona pĂ«rmbushin rregullin e prefiksit, atĂ«herĂ« dekodimi mund tĂ« bĂ«het njĂ«kuptimshĂ«m (dhe anasjelltas).

Le të rishikojmë shembullin e mësipërm. Këtë herë do t'i caktojmë simboleve «a», «b», «c» dhe «d» kodat që përmbushin rregullin e prefiksit.

a
0

b
10

me
110

d
111

Me këtë lloj kodimi, vargu «aabacdab» do të kodifikohet si 00100100011010 (0|0|10|0|100|011|0|10). Ja se si 00100100011010 ne do të mund të dekodojmë pa dyshim dhe të kthehemi në vargjin tonë origjinal «aabacdab».

Kodimi i Huffmanit

Tani që u nihilizuam me kodimin me gjatësi të ndryshueshme dhe rregullin e prefikseve, le të flasim për kodimin e Huffmanit.

Metoda bazohet nĂ« krijimin e pemĂ«ve binare. NĂ« tĂ«, njĂ« nyjĂ« mund tĂ« jetĂ« ose pĂ«rfundimtare ose brendshme. Fillimisht, tĂ« gjitha nyjat merren si gjethe (pĂ«rfundimtare), qĂ« paraqesin vetĂ« simbolin dhe peshĂ«n e tij (dmth, frekuencĂ«n e shfaqjes). Nyjat e brendshme mbajnĂ« peshĂ«n e simbolit dhe referohen nĂ« dy nyjat trashĂ«guese. Sipas marrĂ«veshjes, biti "0" pĂ«rfaqĂ«son ndjekjen e degĂ«s sĂ« majtĂ«, ndĂ«rsa "1" — pĂ«r degĂ«n e djathtĂ«. NĂ« njĂ« pemĂ« tĂ« plotĂ« N gjethe dhe N-1 nyje tĂ« brendshme. Rekomandohet qĂ« gjatĂ« ndĂ«rtimit tĂ« pemĂ«s sĂ« Huffmanit tĂ« pĂ«rjashtohen simbolet e pa pĂ«rdorura pĂ«r tĂ« marrĂ« kodet e gjatĂ«si optimale.

Ne do të përdorim një radhë me prioritete për të ndërtuar pemën e Huffmanit, ku nyjës me frekuencën më të ulët do t'i caktohet prioriteti më i lartë. Më poshtë janë hapat e ndërtimit:

  1. Krijoni një nyjë-gjethe për çdo simbol dhe shtoni ato në radhë me prioritete.
  2. Përderisa në radhë ka më shumë se një gjethe, bëni si më poshtë:
    • Hiqni dy nyje me prioritetin mĂ« tĂ« lartĂ« (me frekuencĂ«n mĂ« tĂ« ulĂ«t) nga radhĂ«t;
    • Krijoni njĂ« nyje tĂ« re tĂ« brendshme, ku kĂ«to dy nyje do tĂ« jenĂ« trashĂ«gimtarĂ«t, dhe frekuenca e shfaqjes do tĂ« jetĂ« e barabartĂ« me shumĂ«n e frekuencave tĂ« kĂ«tyre dy nyjeve.
    • Shtoni njĂ« nyje tĂ« re nĂ« radhĂ«n e prioriteteve.
  3. Nyja e vetme që mbetet do të jetë rrënjore, dhe ndërtimi i pemës do të përfundojë këtu.

Le të supozojmë se kemi një tekst, i cili përbëhet vetëm nga simbolet «a», «b», «c», «d» dhe «e», dhe frekuencat e shfaqjes së tyre janë të barabarta me 15, 7, 6, 6 dhe 5 përkatësisht. Më poshtë janë ilustruar hapat e algoritmit.

Algoritmi i kompresimit Huffman

Algoritmi i kompresimit Huffman

Algoritmi i kompresimit Huffman

Algoritmi i kompresimit Huffman

Algoritmi i kompresimit Huffman

Rruga nga rrënja deri në çdo nyje përfundimtare do të ruajë kodin optimal të prefiksit (i njohur gjithashtu si kodi Huffman), që i përket simbolit të lidhur me këtë nyje përfundimtare.

Algoritmi i kompresimit Huffman
Pema Huffman

Më poshtë do të gjeni implementimin e algoritmit të kompresimit të Huffman në gjuhët C++ dhe 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(' ', 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;

// Një nod i pemës
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
{
	// kalon pemën Huffman dhe ruan kodet Huffman
	// në një hartë.
	public static void encode(Node root, String str,
							  Map huffmanCode)
	{
		if (root == null)
			return;

		// gjetur një nod gjethe
		if (root.left == null && root.right == null) {
			huffmanCode.put(root.ch, str);
		}

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

	// kalon pemën Huffman dhe dekodifikon stringun e koduar
	public static int decode(Node root, int index, StringBuilder sb)
	{
		if (root == null)
			return index;

		// gjetur një nod gjethe
		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;
	}

	// Ndërton pemën Huffman dhe huffmanCode dhe dekodifikon tekstin e dhënë
	public static void buildHuffmanTree(String text)
	{
		// numëron frekuencën e shfaqjes së çdo karakteri
		// dhe e ruan atë në një hartë
		Map 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);
		}

		// Krijon një radhë prioritare për të ruajtur nodet e gjalla të pemës Huffman
		// Vërejtje që artikulli me prioritet më të lartë ka frekuencën më të ulët
		PriorityQueue pq = new PriorityQueue((l, r) -> l.freq - r.freq);

		// Krijon një nod gjethe për çdo karakter dhe e shton atë
		// në radhën prioritare.
		for (Map.Entry entry : freq.entrySet()) {
			pq.add(new Node(entry.getKey(), entry.getValue()));
		}

		// bëj deri sa të ketë më shumë se një nod në radhë
		while (pq.size() != 1)
		{
			// Heq dy nodet me prioritet më të lartë
			// (frekuencën më të ulët) nga radhë
			Node left = pq.poll();
			Node right = pq.poll();

			// Krijon një nod të ri të brendshëm me këto dy nodet si fëmijë 
			// dhe me frekuencë të barabartë me shumën e dy frekuencave të nodit
			// Shton nodin e ri në radhën prioritare.
			int sum = left.freq + right.freq;
			pq.add(new Node(' ', sum, left, right));
		}

		// rrënja ruan treguesin për rrënjën e pemës Huffman
		Node root = pq.peek();

		// kalon pemën Huffman dhe ruan kodet Huffman në një hartë
		Map huffmanCode = new HashMap();
		encode(root, "", huffmanCode);

		// printon kodet Huffman
		System.out.println("Kodet Huffman janë :\n");
		for (Map.Entry entry : huffmanCode.entrySet()) {
			System.out.println(entry.getKey() + " " + entry.getValue());
		}

		System.out.println("\nStringu origjinal ishte :\n" + text);

		// printon stringun e koduar
		StringBuilder sb = new StringBuilder();
		for (int i = 0 ; i < text.length(); i++) {
			sb.append(huffmanCode.get(text.charAt(i)));
		}

		System.out.println("\nStringu i koduar është :\n" + sb);

		// kalon përsëri pemën Huffman dhe këtë herë
		// dekodifikon stringun e koduar
		int index = -1;
		System.out.println("\nStringu i dekoduar është: \n");
		while (index < sb.length() - 2) {
			index = decode(root, index, sb);
		}
	}

	public static void main(String[] args)
	{
		String text = "Huffman coding është një algoritëm kompresimi të dhënash.";

		buildHuffmanTree(text);
	}
}

Shënim: Memoria e përdorur nga stringu hyrës është 47 * 8 = 376 bit, ndërsa stringu i koduar zë vetëm 194 bit, dmth. të dhënat kompresohen rreth 48%. Në programin në C++ më sipër, ne përdorim klasën string për të ruajtur stringun e koduar, për ta bërë programin më të lexueshëm.

Duke pasur parasysh se strukturat efektive tĂ« tĂ« dhĂ«nave pĂ«r prioritetin kĂ«rkojnĂ« O(log(N)) kohĂ«, dhe nĂ« njĂ« pemĂ« binar tĂ« plotĂ« me N gjethe pĂ«rmbajnĂ« 2N-1 nodi, dhe pema e Huffman-it Ă«shtĂ« njĂ« pemĂ« binar e plotĂ«, algoritmi punon pĂ«r O(Nlog(N)) kohĂ«, ku N – numri i simboleve.

Burimet:

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

Mëso më shumë rreth kursit.

Burimi: habr.com

Bli njĂ« hosting tĂ« besueshĂ«m pĂ«r faqet me mbrojtje DDoS, VPS VDS serverĂ« đŸ”„ Bli njĂ« hosting tĂ« besueshĂ«m pĂ«r faqet me mbrojtje DDoS, VPS VDS serverĂ« | ProHoster