Huffman'i tihendamisalgoritm

Kursuse alguse eelĂ”htul «Algoritmid arendajatele» valmistasid teile tĂ”lke veel ĂŒhest kasulikust materjalist.

Hafmanni kodeerimine on andmete tihendusalgoritm, mis vÀljendab failide tihendamise pÔhikontseptsiooni. Selles artiklis rÀÀgime fikseeritud ja muutliku pikkusega kodeerimisest, unikaalselt dekodeeritavatest koodidest, prefiksireeglitest ja Hafmanni puu leidmisest.

Teame, et iga sĂŒmbol salvestatakse 0 ja 1 jĂ€rjekorrana ja see vĂ”tab 8 bitti. Seda nimetatakse fikseeritud pikkusega kodeerimiseks, kuna iga sĂŒmbol kasutab salvestamiseks sama fikseeritud arvu bitte.

Oletame, et meil on tekst. Kuidas saame vĂ€hendada vajaminevat ruumi ĂŒhe sĂŒmboli salvestamiseks?

Peamine idee seisneb muutliku pikkusega kodeerimises. Saame kasutada asjaolu, et mĂ”ned sĂŒmbolid tekstis esinevad sagedamini kui teised (vt siit), et vĂ€lja töötada algoritm, mis esindab sama sĂŒmbolite jada vĂ€iksema arvu bittidega. Muutliku pikkusega kodeerimisel mÀÀrame sĂŒmbolitele muutuva arvu bitte sĂ”ltuvalt nende esinemissagedusest antud tekstis. LĂ”ppkokkuvĂ”ttes vĂ”ivad mĂ”ned sĂŒmbolid vĂ”tta vaid 1 bitti, teised 2 bitti, 3 vĂ”i rohkem. Muutliku pikkusega kodeerimise probleem seisneb vaid edaspidises jĂ€rjestuse dekodeerimises.

Kuidas dekodeerida bittide jĂ€rjestust ĂŒhemĂ”tteliselt, teades seda?

VĂ”tame arvesse jĂ€rje, «aabacdab». Selles on 8 sĂŒmbolit, ja fikseeritud pikkusega kodeerimisel on selle salvestamiseks vajalik 64 bitti. TĂ€helepanu, et sĂŒmbolite sagedus «a», «b», «c» ja «d» vĂ”rdub vastavalt 4, 2, 1, 1. Proovime kujutada seda «aabacdab» vĂ€iksema bittide arvuga, kasutades fakti, et «a» esineb sagedamini kui «b», vaid «b» esineb sagedamini kui «c» ja «d». Alustame sellega, et kodeerime «a» ĂŒhe bitiga, mis on 0, «b» anname kahele bitile koodi 11 ja kolme bitiga kodeerime 100 ja 011 «c» ja «d».

KokkuvÔttes saame:

a
0

b
11

c
100

d
011

Nii kodeerime jÀrje «aabacdab» nagu 00110100011011 (0|0|11|0|100|011|0|11), kasutades eelnevalt esitatud koode. Kuid peamine probleem on dekodeerimisel. Kui proovime dekodeerida jÀrje 00110100011011, saame kahemÔttelise tulemuse, kuna seda saab esitada kui:

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 



jne.

Selle ebamugavuse vĂ€ltimiseks peame tagama, et meie kodeerimine rahuldaks sellist mĂ”istet nagu eelseisva reegli, mis omakorda tĂ€hendab, et koode saab dekodeerida ainult ĂŒhe ainulaadse viisi kaudu. Eelseisva reegel tagab, et ĂŒkski kood ei ole teise eelseisja. Koodi all mĂ”tleme me bite, mida kasutatakse konkreetse sĂŒmboli esindamiseks. Ülaltoodud nĂ€ites 0 – on eelseisja 011, mis rikub eelseisva reeglit. Nii, kui meie koodid vastavad eelseisvale reeglile, siis on vĂ”imalik dekodeerimine ĂŒheselt (ja vastupidi).

Vaadakem ĂŒlaltoodud nĂ€idet uuesti. Seekord mÀÀrame sĂŒmbolitele «a», «b», «c» ja «d» koode, mis rahuldavad eelseisva reegli.

a
0

b
10

c
110

d
111

Sellise kodeerimisega kooditakse string «aabacdab» nĂŒĂŒd ĂŒheks ĂŒheselt dekodeeritavaks ja naaseme oma algsele stringile 00100100011010 (0|0|10|0|100|011|0|10)Programmi loomiseks Nanonyami jaoks tuleb kasutada spetsiaalseid raamatukogusid, kus on rakendatud kĂ”ik praegu saadaval olevad virtuaalsed funktsioonid. Laadige alla virtuaalne masin Nanonyam ja raamatukogud selle jaoks 00100100011010 Huffmani kodeerimine «aabacdab».

NĂŒĂŒd, kui oleme tutvunud muutuva pikkusega kodeerimise ja eelseisva reegliga, rÀÀgime Huffmani kodeerimisest.

Meetod pĂ”hineb binaarpuude loomisel. Sellel vĂ”ib olla sĂ”lm, mis on kas lĂ”plik vĂ”i sise-sĂ”lm. Esialgu kĂ”ik sĂ”lmed peetakse lehtedeks (lĂ”plikud), mis esindavad sĂŒmbolit ja selle kaalu (ehk esinemissagedust). SisesĂ”lmed sisaldavad sĂŒmboli kaalu ja viitavad kahele pĂ€randsĂ”lmele. Üldiselt, bit

„0” esindab vasaku haru jĂ€rgimist ja „1” – parema jĂ€rgimist. TĂ€ielikus puus on lehti ja N N-1 sisesĂ”lme. Soovitav on, et Huffmani puu ehitamisel kĂ”rvaldada kasutamata sĂŒmbolid, et saavutada parima pikkusega koodid. Kasutame prioriteedipuhvrit Huffmani puu ehitamiseks, kus madalaimaga sageduse sĂ”lmele antakse kĂ”rgeim prioriteet. Allpool on toodud ehitamise sammud:

Loo iga sĂŒmboli jaoks lehesĂ”lm ja lisa need prioriteedipuhvrisse.

  1. Kuni puhvris on rohkem kui ĂŒks leht, teeme jĂ€rgmist:
  2. Eemaldage kaks kÔrgeima prioriteediga (madalaima sagedusega) sÔlme puhvrist;
    • Loo uus sise-sĂ”lm, kus need kaks sĂ”lme on pĂ€rijad, ja esinemissagedus vĂ”rdub nende kahe sĂ”lme sageduste summaga.
    • Lisage uus sĂ”lm prioriteedipuhvrisse.
    • Lisage uus sĂ”lm prioriteedipuhvrisse.
  3. Ainus jÀÀnud sÔlm on juursÔlm, millega puu ehitamine lÔppeb.

Oletame, et meil on mingi tekst, mis koosneb ainult mÀrkidest «a», «b», «c», «d» ja «e», mille esinemissagedused on vastavalt 15, 7, 6, 6 ja 5. Allpool on illustratsioonid, mis kajastavad algoritmi samme.

Huffman'i tihendamisalgoritm

Huffman'i tihendamisalgoritm

Huffman'i tihendamisalgoritm

Huffman'i tihendamisalgoritm

Huffman'i tihendamisalgoritm

Tee juurest igasse lÔpp-sÔlme salvestab optimaalse eesliite koodi (tuntud ka kui Huffmani kood), mis vastab sellele lÔpp-sÔlmele seotud mÀrgile.

Huffman'i tihendamisalgoritm
Huffmani puu

Allolevalt leiate Huffmani tihendamisalgoritmi teostuse keeltes C++ ja 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;

// PuudutĂŒĂŒp
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
{
	// Traverseerib Huffmani puud ja salvestab Huffmani koodid
	// kaardis.
	public static void encode(Node root, String str,
							  Map huffmanCode)
	{
		if (root == null)
			return;

		// lehtpuu sÔlm leitud
		if (root.left == null && root.right == null) {
			huffmanCode.put(root.ch, str);
		}


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

	// Traverseerib Huffmani puud ja dekodeerib kodeeritud stringi
	public static int decode(Node root, int index, StringBuilder sb)
	{
		if (root == null)
			return index;

		// lehtpuu sÔlm leitud
		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;
	}

	// Koostab Huffmani puu ja huffmanCode ning dekodeerib antud siseteksti
	public static void buildHuffmanTree(String text)
	{
		// arvestab iga tÀhe esinemise sagedust
		// ja salvestab selle kaardis
		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);
		}

		// Loob prioriteetide jÀrjekorra, et salvestada elusolevaid sÔlmi
		// Huffmani puust.
		// Pöörake tÀhelepanu, et kÔrgeima prioriteediga objekt
		// omab madalaimat sagedust
		PriorityQueue pq = new PriorityQueue(
										(l, r) -> l.freq - r.freq);

		// Loob iga tÀhe jaoks lehtsÔlme ja lisab selle
		// prioriteetide jÀrjekorda.
		for (Map.Entry entry : freq.entrySet()) {
			pq.add(new Node(entry.getKey(), entry.getValue()));
		}

		// jĂ€tkab, kuni jĂ€rjekorras on rohkem kui ĂŒks sĂ”lm
		while (pq.size() != 1)
		{
			// Eemaldab jÀrjekorrast kaks kÔige kÔrgema prioriteediga sÔlme
			// (madalaima sagedusega)
			Node left = pq.poll();
			Node right = pq.poll();

			// Loob uue sisemuse sÔlme nende kahe sÔlme lastega
			// ja sagedus, mis on vÔrdsed kahe sÔlme
			// sageduste summaga. Lisab uue sÔlme jÀrjekorda.
			int sum = left.freq + right.freq;
			pq.add(new Node(' ', sum, left, right));
		}

		// Root hoiab viidet Huffmani puu juurele
		Node root = pq.peek();

		// Traverseerib Huffmani puud ja salvestab Huffmani koodid kaardis
		Map huffmanCode = new HashMap();
		encode(root, "", huffmanCode);

		// Prindib Huffmani koodid
		System.out.println("Huffmani koodid on :\n");
		for (Map.Entry entry : huffmanCode.entrySet()) {
			System.out.println(entry.getKey() + " " + entry.getValue());
		}

		System.out.println("\nOriginaalne string oli :\n" + text);

		// Prindib kodeeritud stringi
		StringBuilder sb = new StringBuilder();
		for (int i = 0 ; i < text.length(); i++) {
			sb.append(huffmanCode.get(text.charAt(i)));
		}

		System.out.println("\nKodeeritud string on :\n" + sb);

		// Traverseerib Huffmani puud uuesti ja seekord
		// dekodeerib kodeeritud stringi
		int index = -1;
		System.out.println("\nDekodeeritud string on: \n");
		while (index < sb.length() - 2) {
			index = decode(root, index, sb);
		}
	}

	public static void main(String[] args)
	{
		String text = "Huffmani koding on andmete tihendamise algoritm.";

		buildHuffmanTree(text);
	}
}

MĂ€rkus: Sisendstringi kasutamiseks vajaminev mĂ€lu on 47 * 8 = 376 bitti, samas kui kodeeritud string vĂ”tab ainult 194 bitti, st andmed tihendatakse umbes 48%. Ülaltoodud C++ programmis kasutame kiri klass string, et salvestada kodeeritud string, et muuta programm loetavaks.

Kuna efektiivsed prioriteetsĂŒsteemide andmestruktuurid vajavad lisamiseks O(log(N)) aega ja tĂ€ielikus binaarpuus on N lehti 2N-1 sĂ”lme, ning Huffmani puu on tĂ€ispuhas binaarpuu, siis töötab algoritm ajaga O(Nlog(N)) kus N on sĂŒmbolite arv.

Allikad:

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

Tutvu kursusega lÀhemalt.

Allikas: habr.com

Osta usaldusvÀÀrne hostimine veebilehtede jaoks DDoS-i kaitsega, VPS VDS serverid đŸ”„ Osta usaldusvÀÀrne hostimine veebilehtede jaoks DDoS-i kaitsega, VPS VDS serverid | ProHoster