Compressió de dades amb l'algoritme Huffman

Entrada

En aquest article, parlaré del conegut algorisme de Huffman, així com de la seva aplicació en la compressió de dades.

Com a resultat, escriurem un arxivador senzill. Això ja ha estat article sobre Habré, però sense implementació pràctica. El material teòric de l'actual post està extret de les lliçons d'informàtica de l'escola i del llibre "Data Structures and Algorithms in Java" de Robert Laforet. Per tant, tot està sota el tall!

Una mica de reflexió

En un fitxer de text normal, un caràcter està codificat amb 8 bits (codificació ASCII) o 16 (codificació Unicode). A continuació, considerarem la codificació ASCII. Per exemple, prengui la cadena s1 = "SUSIE DIU QUE ÉS FÀCILn". En total, hi ha 22 caràcters a la línia, per descomptat, inclosos els espais i el caràcter de nova línia - 'n'. El fitxer que conté aquesta línia pesarà 22*8 = 176 bits. De seguida sorgeix la pregunta: és racional utilitzar els 8 bits per codificar 1 caràcter? No fem servir tots els caràcters ASCII. Encara que ho fossin, seria més racional donar a la lletra més freqüent -S- el codi més curt possible, i per a la lletra més rara -T (o U, o 'n')- donar el codi més autèntic. Aquest és l'algorisme de Huffman: cal trobar la millor opció de codificació, en la qual el fitxer tindrà un pes mínim. És bastant normal que diferents caràcters tinguin longituds de codi diferents: aquesta és la base de l'algorisme.

Codificació

Per què no donar-li al caràcter "S" un codi, per exemple, d'1 bit de llarg: 0 o 1. Deixeu que sigui 1. Aleshores, el segon caràcter que més apareix - " " (espai) - donarem 0. Imagineu-vos, heu començat a descodifiqueu el vostre missatge -cadena codificada s1- i veureu que el codi comença per 1. Aleshores, què heu de fer: és el caràcter S o algun altre caràcter, com ara A? Per tant, sorgeix una regla important:

Cap codi ha de ser un prefix d'un altre

Aquesta regla és la clau de l'algorisme. Per tant, la creació del codi comença amb una taula de freqüències, que indica la freqüència (nombre d'ocurrències) de cada símbol:

Compressió de dades amb l'algoritme Huffman Els caràcters amb més ocurrències s'han de codificar amb menys possible el nombre de bits. Posaré un exemple d'una de les possibles taules de codis:

Compressió de dades amb l'algoritme Huffman Així, el missatge codificat tindrà aquest aspecte:

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

He separat el codi de cada caràcter amb un espai. Això no passarà realment en un fitxer comprimit!
Sorgeix la pregunta: com va sortir aquest principiant un codi com crear una taula de codis? Això es comentarà a continuació.

Construint un arbre Huffman

Aquí és on els arbres de cerca binaris vénen al rescat. No us preocupeu, no necessitareu els mètodes de cerca, inserció i supressió aquí. Aquí teniu l'estructura de l'arbre a Java:

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;
    }
    ...
}

Aquest no és el codi complet, el codi complet es trobarà a continuació.

Aquest és l'algorisme per construir un arbre:

  1. Creeu un objecte Node per a cada caràcter del missatge (línia s1). En el nostre cas, hi haurà 9 nodes (objectes Node). Cada node consta de dos camps de dades: símbol i freqüència
  2. Creeu un objecte Tree (BinaryTree) per a cadascun dels nodes Node. El node es converteix en l'arrel de l'arbre.
  3. Inseriu aquests arbres a la cua de prioritats. Com més baixa sigui la freqüència, més alta serà la prioritat. Així, quan s'extreu, sempre es selecciona l'arbre amb la freqüència més baixa.

A continuació, heu de fer cíclicament el següent:

  1. Recupera dos arbres de la cua de prioritats i fes-los fills d'un nou node (un node de creació recent sense lletra). La freqüència del nou node és igual a la suma de les freqüències dels dos arbres descendents.
  2. Per a aquest node, creeu un arbre arrelat en aquest node. Torneu a inserir aquest arbre a la cua de prioritats. (Com que l'arbre té una nova freqüència, el més probable és que entri en un nou lloc de la cua)
  3. Continueu amb els passos 1 i 2 fins que quedi un arbre a la cua: l'arbre Huffman

Considereu aquest algorisme a la línia s1:

Compressió de dades amb l'algoritme Huffman

Aquí el símbol "lf" (pass de línia) denota una nova línia, "sp" (espai) és un espai.

I què ve?

Tenim l'arbre de Huffman. D'ACORD. I què fer-hi? No s'ho prendran de franc. I després, cal traçar tots els camins possibles des de l'arrel fins a les fulles de l'arbre. Acceptem etiquetar una vora 0 si porta al fill esquerre i 1 si porta a la dreta. En sentit estricte, en aquestes anotacions, el codi de caràcters és el camí des de l'arrel de l'arbre fins a la fulla que conté aquest mateix caràcter.

Compressió de dades amb l'algoritme Huffman

Així, va resultar la taula de codis. Tingueu en compte que si tenim en compte aquesta taula, podem concloure sobre el "pes" de cada caràcter: aquesta és la longitud del seu codi. Aleshores, en forma comprimida, el fitxer font pesarà: 2 * 3 + 2 * 4 + 3 * 3 + 6 * 2 + 1 * 4 + 1 * 5 + 2 * 4 + 4 * 2 + 1 * 5 = 65 bits . Al principi pesava 176 bits. Per tant, l'hem reduït fins a 176/65 = 2.7 vegades! Però això és una utopia. És poc probable que s'aconsegueixi aquesta proporció. Per què? Això es parlarà una mica més endavant.

Descodificació

Bé, potser el més senzill que queda és la descodificació. Crec que molts de vosaltres heu endevinat que és impossible crear simplement un fitxer comprimit sense cap indicació sobre com es va codificar: no podrem descodificar-lo! Sí, sí, em va costar adonar-me d'això, però he de crear un fitxer de text table.txt amb una taula de compressió:

01110
 00
A010
E1111
I110
S10
T0110
U01111
Y1110

Entrada de taula en la forma "caràcter" "codi de caràcter". Per què 01110 no té símbol? De fet, amb un símbol, només les eines java que faig servir quan entri a un fitxer, el caràcter de nova línia -'n'- es converteix en una nova línia (per molt que sembli estúpid). Per tant, la línia buida de dalt és el caràcter del codi 01110. Per al codi 00, el caràcter és un espai al començament de la línia. He de dir de seguida que aquest mètode d'emmagatzematge de la taula pot afirmar que és el més irracional per al nostre coeficient khan. Però és fàcil d'entendre i implementar. Estaré encantat d'escoltar les vostres recomanacions als comentaris sobre l'optimització.

Amb aquesta taula, és molt fàcil descodificar. Recordem quina regla ens va guiar a l'hora de crear la codificació:

Cap codi ha de ser un prefix d'un altre

Aquí és on juga un paper facilitador. Llegim seqüencialment bit a bit, i tan bon punt la cadena resultant d, formada pels bits llegits, coincideix amb la codificació corresponent al caràcter de caràcter, de seguida sabem que el caràcter de caràcter (i només ell!) estava codificat. A continuació, escrivim el caràcter a la cadena de descodificació (la cadena que conté el missatge descodificat), restablirem la cadena d i llegim més endavant el fitxer codificat.

Implementació

És hora d'humiliar el meu codi escrivint un arxivador. Diguem-ne compressor.

Comença de nou. Primer de tot, escrivim la classe 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;
    }
}

Ara l'arbre:

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;
    }
}

Cua de prioritat:

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;//возвращаем удаленный элемент(элемент с наименьшей частотой)
    }
}

La classe que crea l'arbre de Huffman:

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;
    }
}

La classe que conté quina codifica/descodifica:

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("========================================================");
    }
    }

Una classe que facilita l'escriptura en un fitxer:

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();
    }
}

Una classe que facilita la lectura d'un fitxer:

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();
    }
}

Bé, i la classe principal:

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());
    }
}

Haureu d'escriure el fitxer amb instruccions readme.txt vosaltres mateixos 🙂

Conclusió

Suposo que això és tot el que volia dir. Si teniu alguna cosa a dir sobre la meva incompetència de millores en el codi, algorisme, en general, qualsevol optimització, no dubteu a escriure. Si no he explicat alguna cosa, escriu també. M'encantaria saber de vosaltres als comentaris!

PS

Sí, sí, encara sóc aquí, perquè no m'he oblidat del coeficient. Per a la cadena s1, la taula de codificació pesa 48 bytes, molt més que el fitxer original, i no s'han oblidat dels zeros addicionals (el nombre de zeros afegits és 7) => la relació de compressió serà inferior a un: 176 /(65 + 48*8 + 7) = 0.38. Si també us heu adonat d'això, no a la cara heu acabat. Sí, aquesta implementació serà extremadament ineficient per a fitxers petits. Però què passa amb els fitxers grans? Les mides dels fitxers són molt més grans que la mida de la taula de codificació. Aquí és on l'algoritme funciona com hauria de ser! Per exemple, per El monòleg de Faust l'arxivador dóna un coeficient real (no idealitzat) igual a 1.46, gairebé una vegada i mitja! I sí, el fitxer havia d'estar en anglès.

Font: www.habr.com

Afegeix comentari