Komprese dat pomocí Huffmanova algoritmu

Vstup

V tomto článku budu hovořit o známém Huffmanově algoritmu a také o jeho aplikaci v kompresi dat.

V důsledku toho napíšeme jednoduchý archivátor. Toto již bylo článek o Habrém, ale bez praktické realizace. Teoretický materiál aktuálního příspěvku je převzat z hodin školní informatiky a knihy Roberta Laforeta "Datové struktury a algoritmy v Javě". Takže všechno je pod řezem!

Trochu reflexe

V normálním textovém souboru je jeden znak zakódován 8 bity (kódování ASCII) nebo 16 (kódování Unicode). Dále se budeme zabývat kódováním ASCII. Například vezměte řetězec s1 = "SUSIE SAYS IT IS EASYn". Celkem je v řádku 22 znaků, samozřejmě včetně mezer a znaku nového řádku - 'n'. Soubor obsahující tento řádek bude vážit 22*8 = 176 bitů. Okamžitě se nabízí otázka: je racionální použít všech 8 bitů ke kódování 1 znaku? Nepoužíváme všechny znaky ASCII. I kdyby tomu tak bylo, bylo by racionálnější zadat nejčastější písmeno - S - nejkratší možný kód, a pro nejvzácnější písmeno - T (nebo U, nebo 'n') - dát kód autentičtější. Toto je Huffmanův algoritmus: musíte najít nejlepší možnost kódování, ve které bude mít soubor minimální váhu. Je zcela normální, že různé znaky budou mít různé délky kódu - to je základ algoritmu.

Kódování

Proč nedat znaku 'S' kód, například dlouhý 1 bit: 0 nebo 1. Nechť to je 1. Pak druhý nejčastěji se vyskytující znak - ' ' (mezera) - dáme 0. Představte si, že jste začali dekódovat svou zprávu - zakódovaný řetězec s1 - a vidíte, že kód začíná 1. Takže, co dělat: je to nějaký jiný znak, A? Proto platí důležité pravidlo:

Žádný kód nesmí být předponou jiného kódu

Toto pravidlo je klíčem k algoritmu. Tvorba kódu proto začíná tabulkou frekvencí, která udává frekvenci (počet výskytů) každého symbolu:

Komprese dat pomocí Huffmanova algoritmu Znaky s největším počtem výskytů by měly být kódovány s nejmenším počtem možné počet bitů. Uvedu příklad jedné z možných kódových tabulek:

Komprese dat pomocí Huffmanova algoritmu Zakódovaná zpráva tedy bude vypadat takto:

10 01111 10 110 1111 00 10 010 1110 10 00 110 0110 00 110 10 00 1111 010 10 1110 01110

Kód každého znaku jsem oddělil mezerou. To se v komprimovaném souboru opravdu nestane!
Nabízí se otázka: jak tento nováček přišel s kódem, jak vytvořit tabulku kódů? To bude probráno níže.

Stavba Huffmanova stromu

Zde přicházejí na pomoc binární vyhledávací stromy. Nebojte se, zde nebudete potřebovat metody vyhledávání, vkládání a mazání. Zde je stromová struktura v Javě:

public class Node {
    private int frequence;
    private char letter;
    private Node leftChild;
    private Node rightChild;
    ...
}

class BinaryTree {
    private Node root;

    public BinaryTree() {
        root = new Node();
    }
    public BinaryTree(Node root) {
        this.root = root;
    }
    ...
}

Toto není úplný kód, celý kód bude níže.

Zde je algoritmus pro stavbu stromu:

  1. Vytvořte objekt Node pro každý znak ze zprávy (řádek s1). V našem případě to bude 9 uzlů (Node objects). Každý uzel se skládá ze dvou datových polí: symbolu a frekvence
  2. Vytvořte objekt stromu (BinaryTree) pro každý z uzlů Node. Uzel se stává kořenem stromu.
  3. Vložte tyto stromy do prioritní fronty. Čím nižší frekvence, tím vyšší priorita. Při extrakci se tedy vždy vybere strom s nejnižší frekvencí.

Dále musíte cyklicky provádět následující:

  1. Získejte dva stromy z prioritní fronty a udělejte z nich potomky nového uzlu (nově vytvořeného uzlu bez písmene). Frekvence nového uzlu je rovna součtu frekvencí dvou sestupných stromů.
  2. Pro tento uzel vytvořte strom zakořeněný v tomto uzlu. Vložte tento strom zpět do prioritní fronty. (Jelikož má strom novou frekvenci, s největší pravděpodobností se dostane na nové místo ve frontě)
  3. Pokračujte kroky 1 a 2, dokud ve frontě nezůstane jeden strom – Huffmanův strom

Zvažte tento algoritmus na řádku s1:

Komprese dat pomocí Huffmanova algoritmu

Zde symbol "lf" (linefeed) označuje nový řádek, "sp" (mezera) je mezera.

A co dál?

Máme Huffmanův strom. OK. A co s tím dělat? Nevezmou to zadarmo. A pak musíte vysledovat všechny možné cesty od kořene k listům stromu. Souhlasíme s označením hrany 0, pokud vede k levému dítěti, a 1, pokud vede k pravému. Přísně vzato, v těchto zápisech je kód znaku cesta od kořene stromu k listu obsahujícímu stejný znak.

Komprese dat pomocí Huffmanova algoritmu

Tak dopadla tabulka kódů. Všimněte si, že pokud vezmeme v úvahu tuto tabulku, můžeme dojít k závěru o "váze" každého znaku - to je délka jeho kódu. Poté bude zdrojový soubor v komprimované podobě vážit: 2 * 3 + 2 * 4 + 3 * 3 + 6 * 2 + 1 * 4 + 1 * 5 + 2 * 4 + 4 * 2 + 1 * 5 = 65 bitů. Nejprve vážil 176 bitů. Proto jsme jej snížili až o 176/65 = 2.7krát! Ale to je utopie. Takový poměr je nepravděpodobné dosáhnout. Proč? O tom bude řeč o něco později.

Dekódování

No, možná nejjednodušší věc, která zbývá, je dekódování. Myslím, že mnozí z vás uhodli, že je nemožné jednoduše vytvořit komprimovaný soubor bez jakýchkoli náznaků o tom, jak byl kódován - nebudeme ho schopni dekódovat! Ano, ano, bylo pro mě těžké si to uvědomit, ale musím vytvořit textový soubor table.txt s kompresní tabulkou:

01110
 00
A010
E1111
I110
S10
T0110
U01111
Y1110

Zápis do tabulky ve tvaru 'znak'"kód znaku". Proč je 01110 bez symbolu? Ve skutečnosti je to se symbolem, jen pomocí java nástrojů, které používám při výstupu do souboru, je znak nového řádku - 'n' - převeden na nový řádek (bez ohledu na to, jak hloupě to zní). Prázdný řádek výše je tedy znakem pro kód 01110. Pro kód 00 je znakem mezera na začátku řádku. Okamžitě musím říci, že tento způsob ukládání tabulky může být pro náš khanův koeficient nejiracionálnější. Ale je snadné to pochopit a implementovat. Rád si vyslechnu vaše doporučení v komentářích ohledně optimalizace.

S touto tabulkou je velmi snadné dekódovat. Připomeňme si, jakým pravidlem jsme se řídili při vytváření kódování:

Žádný kód nesmí být předponou jiného kódu

Zde hraje roli usnadňující. Čteme postupně bit po bitu, a jakmile se výsledný řetězec d složený z načtených bitů shoduje s kódováním odpovídajícím znaku znaku, okamžitě víme, že znak znaku (a jen on!) byl zakódován. Dále zapíšeme znak do dekódovacího řetězce (řetězec obsahující dekódovanou zprávu), resetujeme řetězec d a dále čteme zakódovaný soubor.

uskutečnění

Je čas pokořit můj kód napsáním archivátoru. Říkejme tomu kompresor.

Začít znovu. Nejprve napíšeme třídu Node:

public class Node {
    private int frequence;//частота
    private char letter;//буква
    private Node leftChild;//левый потомок
    private Node rightChild;//правый потомок

   

    public Node(char letter, int frequence) { //собственно, конструктор
        this.letter = letter;
        this.frequence = frequence;
    }

    public Node() {}//перегрузка конструтора для безымянных узлов(см. выше в разделе о построении дерева Хаффмана)
    public void addChild(Node newNode) {//добавить потомка
        if (leftChild == null)//если левый пустой=> правый тоже=> добавляем в левый
            leftChild = newNode;
        else {
            if (leftChild.getFrequence() <= newNode.getFrequence()) //в общем, левым потомком
                rightChild = newNode;//станет тот, у кого меньше частота
            else {
                rightChild = leftChild;
                leftChild = newNode;
            }
        }

        frequence += newNode.getFrequence();//итоговая частота
    }

    public Node getLeftChild() {
        return leftChild;
    }

    public Node getRightChild() {
        return rightChild;
    }

    public int getFrequence() {
        return frequence;
    }

    public char getLetter() {
        return letter;
    }

    public boolean isLeaf() {//проверка на лист
        return leftChild == null && rightChild == null;
    }
}

Nyní strom:

class BinaryTree {
    private Node root;

    public BinaryTree() {
        root = new Node();
    }

    public BinaryTree(Node root) {
        this.root = root;
    }

    public int getFrequence() {
        return root.getFrequence();
    }

    public Node getRoot() {
        return root;
    }
}

Prioritní fronta:

import java.util.ArrayList;//да-да, очередь будет на базе списка

class PriorityQueue {
    private ArrayList<BinaryTree> data;//список очереди
    private int nElems;//кол-во элементов в очереди

    public PriorityQueue() {
        data = new ArrayList<BinaryTree>();
        nElems = 0;
    }

    public void insert(BinaryTree newTree) {//вставка
        if (nElems == 0)
            data.add(newTree);
        else {
            for (int i = 0; i < nElems; i++) {
                if (data.get(i).getFrequence() > newTree.getFrequence()) {//если частота вставляемого дерева меньше 
                    data.add(i, newTree);//чем част. текущего, то cдвигаем все деревья на позициях справа на 1 ячейку                   
                    break;//затем ставим новое дерево на позицию текущего
                }
                if (i == nElems - 1) 
                    data.add(newTree);
            }
        }
        nElems++;//увеличиваем кол-во элементов на 1
    }

    public BinaryTree remove() {//удаление из очереди
        BinaryTree tmp = data.get(0);//копируем удаляемый элемент
        data.remove(0);//собственно, удаляем
        nElems--;//уменьшаем кол-во элементов на 1
        return tmp;//возвращаем удаленный элемент(элемент с наименьшей частотой)
    }
}

Třída, která vytváří Huffmanův strom:

public class HuffmanTree {
    private final byte ENCODING_TABLE_SIZE = 127;//длина кодировочной таблицы
    private String myString;//сообщение
    private BinaryTree huffmanTree;//дерево Хаффмана
    private int[] freqArray;//частотная таблица
    private String[] encodingArray;//кодировочная таблица


    //----------------constructor----------------------
    public HuffmanTree(String newString) {
        myString = newString;

        freqArray = new int[ENCODING_TABLE_SIZE];
        fillFrequenceArray();

        huffmanTree = getHuffmanTree();

        encodingArray = new String[ENCODING_TABLE_SIZE];
        fillEncodingArray(huffmanTree.getRoot(), "", "");
    }

    //--------------------frequence array------------------------
    private void fillFrequenceArray() {
        for (int i = 0; i < myString.length(); i++) {
            freqArray[(int)myString.charAt(i)]++;
        }
    }

    public int[] getFrequenceArray() {
        return freqArray;
    }

    //------------------------huffman tree creation------------------
    private BinaryTree getHuffmanTree() {
        PriorityQueue pq = new PriorityQueue();
        //алгоритм описан выше
        for (int i = 0; i < ENCODING_TABLE_SIZE; i++) {
            if (freqArray[i] != 0) {//если символ существует в строке
                Node newNode = new Node((char) i, freqArray[i]);//то создать для него Node
                BinaryTree newTree = new BinaryTree(newNode);//а для Node создать BinaryTree
                pq.insert(newTree);//вставить в очередь
            }
        }

        while (true) {
            BinaryTree tree1 = pq.remove();//извлечь из очереди первое дерево.

            try {
                BinaryTree tree2 = pq.remove();//извлечь из очереди второе дерево

                Node newNode = new Node();//создать новый Node
                newNode.addChild(tree1.getRoot());//сделать его потомками два извлеченных дерева
                newNode.addChild(tree2.getRoot());

                pq.insert(new BinaryTree(newNode);
            } catch (IndexOutOfBoundsException e) {//осталось одно дерево в очереди
                return tree1;
            }
        }
    }

    public BinaryTree getTree() {
        return huffmanTree;
    }

    //-------------------encoding array------------------
    void fillEncodingArray(Node node, String codeBefore, String direction) {//заполнить кодировочную таблицу
        if (node.isLeaf()) {
            encodingArray[(int)node.getLetter()] = codeBefore + direction;
        } else {
            fillEncodingArray(node.getLeftChild(), codeBefore + direction, "0");
            fillEncodingArray(node.getRightChild(), codeBefore + direction, "1");
        }
    }

    String[] getEncodingArray() {
        return encodingArray;
    }

    public void displayEncodingArray() {//для отладки
        fillEncodingArray(huffmanTree.getRoot(), "", "");

        System.out.println("======================Encoding table====================");
        for (int i = 0; i < ENCODING_TABLE_SIZE; i++) {
            if (freqArray[i] != 0) {
                System.out.print((char)i + " ");
                System.out.println(encodingArray[i]);
            }
        }
        System.out.println("========================================================");
    }
    //-----------------------------------------------------
    String getOriginalString() {
        return myString;
    }
}

Třída obsahující, která kóduje/dekóduje:

public class HuffmanOperator {
    private final byte ENCODING_TABLE_SIZE = 127;//длина таблицы
    private HuffmanTree mainHuffmanTree;//дерево Хаффмана (используется только для сжатия)
    private String myString;//исходное сообщение
    private int[] freqArray;//частотаная таблица
    private String[] encodingArray;//кодировочная таблица
    private double ratio;//коэффициент сжатия 


    public HuffmanOperator(HuffmanTree MainHuffmanTree) {//for compress
        this.mainHuffmanTree = MainHuffmanTree;

        myString = mainHuffmanTree.getOriginalString();

        encodingArray = mainHuffmanTree.getEncodingArray();

        freqArray = mainHuffmanTree.getFrequenceArray();
    }

    public HuffmanOperator() {}//for extract;

    //---------------------------------------compression-----------------------------------------------------------
    private String getCompressedString() {
        String compressed = "";
        String intermidiate = "";//промежуточная строка(без добавочных нулей)
        //System.out.println("=============================Compression=======================");
        //displayEncodingArray();
        for (int i = 0; i < myString.length(); i++) {
            intermidiate += encodingArray[myString.charAt(i)];
        }
        //Мы не можем писать бит в файл. Поэтому нужно сделать длину сообщения кратной 8=>
        //нужно добавить нули в конец(можно 1, нет разницы)
        byte counter = 0;//количество добавленных в конец нулей (байта в полне хватит: 0<=counter<8<127)
        for (int length = intermidiate.length(), delta = 8 - length % 8; 
        		counter < delta ; counter++) {//delta - количество добавленных нулей
            intermidiate += "0";
        }
        
        //склеить кол-во добавочных нулей в бинарном предаствлении и промежуточную строку 
        compressed = String.format("%8s", Integer.toBinaryString(counter & 0xff)).replace(" ", "0") + intermidiate;
        		
        //идеализированный коэффициент
        setCompressionRatio();
        //System.out.println("===============================================================");
        return compressed;
    }
    
    private void setCompressionRatio() {//посчитать идеализированный коэффициент 
        double sumA = 0, sumB = 0;//A-the original sum
        for (int i = 0; i < ENCODING_TABLE_SIZE; i++) {
            if (freqArray[i] != 0) {
                sumA += 8 * freqArray[i];
                sumB += encodingArray[i].length() * freqArray[i];
            }
        }
        ratio = sumA / sumB;
    }

    public byte[] getBytedMsg() {//final compression
        StringBuilder compressedString = new StringBuilder(getCompressedString());
        byte[] compressedBytes = new byte[compressedString.length() / 8];
        for (int i = 0; i < compressedBytes.length; i++) {
                compressedBytes[i] = (byte) Integer.parseInt(compressedString.substring(i * 8, (i + 1) * 8), 2);
        }
        return compressedBytes;
    }
    //---------------------------------------end of compression----------------------------------------------------------------
    //------------------------------------------------------------extract-----------------------------------------------------
    public String extract(String compressed, String[] newEncodingArray) {
        String decompressed = "";
        String current = "";
        String delta = "";
        encodingArray = newEncodingArray;
        
        //displayEncodingArray();
        //получить кол-во вставленных нулей
        for (int i = 0; i < 8; i++) 
        	delta += compressed.charAt(i);
        int ADDED_ZEROES = Integer.parseInt(delta, 2);
       
        for (int i = 8, l = compressed.length() - ADDED_ZEROES; i < l; i++) {
            //i = 8, т.к. первым байтом у нас идет кол-во вставленных нулей
            current += compressed.charAt(i);
            for (int j = 0; j < ENCODING_TABLE_SIZE; j++) {
                if (current.equals(encodingArray[j])) {//если совпало
                    decompressed += (char)j;//то добавляем элемент
                    current = "";//и обнуляем текущую строку
                }
            }
        }

        return decompressed;
    }

    public String getEncodingTable() {
        String enc = "";
    	for (int i = 0; i < encodingArray.length; i++) {
        	if (freqArray[i] != 0) 
        		enc += (char)i + encodingArray[i] + 'n';
        }
    	return enc;
    }

    public double getCompressionRatio() {
        return ratio;
    }


    public void displayEncodingArray() {//для отладки
        System.out.println("======================Encoding table====================");
        for (int i = 0; i < ENCODING_TABLE_SIZE; i++) {
            //if (freqArray[i] != 0) {
                System.out.print((char)i + " ");
                System.out.println(encodingArray[i]);
            //}
        }
        System.out.println("========================================================");
    }
    }

Třída, která usnadňuje zápis do souboru:

import java.io.File;
import java.io.PrintWriter;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.Closeable;

public class FileOutputHelper implements Closeable {
    private File outputFile;
    private FileOutputStream fileOutputStream;

    public FileOutputHelper(File file) throws FileNotFoundException {
        outputFile = file;
        fileOutputStream = new FileOutputStream(outputFile);
    }

    public void writeByte(byte msg) throws IOException {
        fileOutputStream.write(msg);
    }

    public void writeBytes(byte[] msg) throws IOException {
        fileOutputStream.write(msg);
    }

    public void writeString(String msg) {
    	try (PrintWriter pw = new PrintWriter(outputFile)) {
    		pw.write(msg);
    	} catch (FileNotFoundException e) {
    		System.out.println("Неверный путь, или такого файла не существует!");
    	}
    }

    @Override
    public void close() throws IOException {
        fileOutputStream.close();
    }

    public void finalize() throws IOException {
        close();
    }
}

Třída, která usnadňuje čtení ze souboru:

import java.io.FileInputStream;
import java.io.EOFException;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.Closeable;
import java.io.File;
import java.io.IOException;

public class FileInputHelper implements Closeable {
	private FileInputStream fileInputStream;
	private BufferedReader fileBufferedReader;
	
	public FileInputHelper(File file) throws IOException {
		fileInputStream = new FileInputStream(file);
		fileBufferedReader = new BufferedReader(new InputStreamReader(fileInputStream));
	}
	
	
    public byte readByte() throws IOException {
    	int cur = fileInputStream.read();
    	if (cur == -1)//если закончился файл
    		throw new EOFException();
    	return (byte)cur;
    }
    
    public String readLine() throws IOException {
    	return fileBufferedReader.readLine();
    }
    
    @Override
    public void close() throws IOException{
    	fileInputStream.close();
    }
}

No a hlavní třída:

import java.io.File;
import java.nio.charset.MalformedInputException;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.Paths;
import java.util.List;
import java.io.EOFException;
public class Main {
	private static final byte ENCODING_TABLE_SIZE = 127;
	
    public static void main(String[] args) throws IOException {
        try {//указываем инструкцию с помощью аргументов командной строки
            if (args[0].equals("--compress") || args[0].equals("-c"))
                compress(args[1]);
            else if ((args[0].equals("--extract") || args[0].equals("-x"))
            		&& (args[2].equals("--table") || args[2].equals("-t"))) {
            	extract(args[1], args[3]);
            }
            else
                throw new IllegalArgumentException();
        } catch (ArrayIndexOutOfBoundsException | IllegalArgumentException e) {
            System.out.println("Неверный формат ввода аргументов ");
            System.out.println("Читайте Readme.txt");
            e.printStackTrace();
        }
    }

	public static void compress(String stringPath) throws IOException {
        List<String> stringList;
        File inputFile = new File(stringPath);
        String s = "";
        File compressedFile, table;
        
        try {
            stringList = Files.readAllLines(Paths.get(inputFile.getAbsolutePath()));
        } catch (NoSuchFileException e) {
            System.out.println("Неверный путь, или такого файла не существует!");
            return;
        } catch (MalformedInputException e) {
        	System.out.println("Текущая кодировка файла не поддерживается");
        	return;
        }

        for (String item : stringList) {
            s += item;
            s += 'n';
        }

        HuffmanOperator operator = new HuffmanOperator(new HuffmanTree(s));

        compressedFile = new File(inputFile.getAbsolutePath() + ".cpr");
        compressedFile.createNewFile();
        try (FileOutputHelper fo = new FileOutputHelper(compressedFile)) {
        	fo.writeBytes(operator.getBytedMsg());
        }
        //create file with encoding table:
        
        table = new File(inputFile.getAbsolutePath() + ".table.txt");
        table.createNewFile();
        try (FileOutputHelper fo = new FileOutputHelper(table)) {
        	fo.writeString(operator.getEncodingTable());
        }
        
        System.out.println("Путь к сжатому файлу: " + compressedFile.getAbsolutePath());
        System.out.println("Путь к кодировочной таблице " + table.getAbsolutePath());
        System.out.println("Без таблицы файл будет невозможно извлечь!");
        
        double idealRatio = Math.round(operator.getCompressionRatio() * 100) / (double) 100;//идеализированный коэффициент
        double realRatio = Math.round((double) inputFile.length() 
        		/ ((double) compressedFile.length() + (double) table.length()) * 100) / (double)100;//настоящий коэффициент
        
        System.out.println("Идеализированный коэффициент сжатия равен " + idealRatio);
        System.out.println("Коэффициент сжатия с учетом кодировочной таблицы " + realRatio);
    }

    public static void extract(String filePath, String tablePath) throws FileNotFoundException, IOException {
        HuffmanOperator operator = new HuffmanOperator();
        File compressedFile = new File(filePath),
        	 tableFile = new File(tablePath),
        	 extractedFile = new File(filePath + ".xtr");
        String compressed = "";
        String[] encodingArray = new String[ENCODING_TABLE_SIZE];
        //read compressed file
        //!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!check here:
        try (FileInputHelper fi = new FileInputHelper(compressedFile)) {
        	byte b;
        	while (true) {
        		b = fi.readByte();//method returns EOFException
        		compressed += String.format("%8s", Integer.toBinaryString(b & 0xff)).replace(" ", "0");
        	}
        } catch (EOFException e) {
        	
        }
        
        //--------------------
        
        //read encoding table:
        try (FileInputHelper fi = new FileInputHelper(tableFile)) {
        	fi.readLine();//skip first empty string
        	encodingArray[(byte)'n'] = fi.readLine();//read code for 'n'
        	while (true) {
        		String s = fi.readLine();
        		if (s == null)
        			throw new EOFException();
        		encodingArray[(byte)s.charAt(0)] = s.substring(1, s.length());        		
        	}
        } catch (EOFException ignore) {}
        
        extractedFile.createNewFile();
        //extract:
		try (FileOutputHelper fo = new FileOutputHelper(extractedFile)) {
			fo.writeString(operator.extract(compressed, encodingArray));
		}
		
		System.out.println("Путь к распакованному файлу " + extractedFile.getAbsolutePath());
    }
}

Soubor s instrukcemi readme.txt si budete muset napsat sami 🙂

Závěr

Myslím, že to je vše, co jsem chtěl říct. Pokud máte co říct k mé neschopnosti vylepšovat kód, algoritmus, obecně nějakou optimalizaci, tak klidně napište. Pokud jsem něco nevysvětlil, prosím také napište. Budu ráda, když se mi ozvete v komentářích!

PS

Ano, ano, jsem stále tady, protože jsem nezapomněl na koeficient. Pro řetězec s1 váží kódovací tabulka 48 bajtů – mnohem více než původní soubor, a nezapomněli ani na nuly navíc (počet přidaných nul je 7) => kompresní poměr bude menší než jedna: 176/(65 + 48*8 + 7) = 0.38. Pokud jste si toho také všimli, pak jen ne ve tváři máte dobrou práci. Ano, tato implementace bude pro malé soubory extrémně neefektivní. Co se ale stane s velkými soubory? Velikosti souborů jsou mnohem větší než velikost tabulky kódování. Zde algoritmus funguje, jak má! Například pro Faustův monolog archivátor udává reálný (ne idealizovaný) koeficient rovný 1.46 - téměř jedenapůlnásobek! A ano, soubor měl být v angličtině.

Zdroj: www.habr.com

Přidat komentář