Compresión de datos con el algoritmo de Huffman

Entrada

En este artículo hablaré sobre el conocido algoritmo de Huffman, así como su aplicación en la compresión de datos.

Como resultado, escribiremos un archivador simple. esto ya ha sido artículo sobre Habré, pero sin implementación práctica. El material teórico de la publicación actual está tomado de las lecciones de informática de la escuela y del libro de Robert Laforet "Estructuras de datos y algoritmos en Java". Entonces, ¡todo está bajo el corte!

Un poco de pensamiento

En un archivo de texto normal, un carácter se codifica con 8 bits (codificación ASCII) o 16 (codificación Unicode). A continuación, consideraremos la codificación ASCII. Por ejemplo, tome la cadena s1 = "SUSIE DICE QUE ES FÁCILn". En total, hay 22 caracteres en la línea, por supuesto, incluidos los espacios y el carácter de nueva línea - 'n'. El archivo que contiene esta línea pesará 22*8 = 176 bits. Inmediatamente surge la pregunta: ¿es racional usar los 8 bits para codificar 1 carácter? No utilizamos todos los caracteres ASCII. Incluso si lo fueran, sería más racional dar a la letra más frecuente - S - el código más corto posible, y para la letra más rara - T (o U, o 'n') - dar el código más auténtico. Este es el algoritmo de Huffman: debe encontrar la mejor opción de codificación, en la que el archivo tendrá un peso mínimo. Es bastante normal que diferentes caracteres tengan diferentes longitudes de código; esta es la base del algoritmo.

Codificación

¿Por qué no darle al carácter 'S' un código, por ejemplo, de 1 bit de longitud: 0 o 1? Que sea 1. Luego, el segundo carácter más frecuente - ' ' (espacio) - le daremos 0. Imagínese, empezó a decodifique su mensaje - cadena codificada s1 - y verá que el código comienza con 1. Entonces, qué hacer: ¿es el carácter S, o es algún otro carácter, como A? Por lo tanto, surge una regla importante:

Ningún código debe ser prefijo de otro

Esta regla es la clave del algoritmo. Por lo tanto, la creación del código comienza con una tabla de frecuencias, que indica la frecuencia (número de ocurrencias) de cada símbolo:

Compresión de datos con el algoritmo de Huffman Los caracteres con la mayor cantidad de ocurrencias deben codificarse con la menor cantidad posible el número de bits. Daré un ejemplo de una de las posibles tablas de códigos:

Compresión de datos con el algoritmo de Huffman Así que el mensaje codificado se verá así:

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

Separé el código de cada carácter con un espacio. ¡Esto realmente no sucederá en un archivo comprimido!
Surge la pregunta: ¿cómo se le ocurrió a este novato un código para crear una tabla de códigos? Esto será discutido abajo.

Construyendo un árbol de Huffman

Aquí es donde los árboles de búsqueda binarios vienen al rescate. No se preocupe, no necesitará los métodos de búsqueda, inserción y eliminación aquí. Aquí está la estructura de árbol en 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;
    }
    ...
}

Este no es el código completo, el código completo estará debajo.

Aquí está el algoritmo para construir un árbol:

  1. Cree un objeto Nodo para cada carácter del mensaje (línea s1). En nuestro caso, habrá 9 nodos (objetos Nodo). Cada nodo consta de dos campos de datos: símbolo y frecuencia
  2. Cree un objeto Tree (BinaryTree) para cada uno de los nodos Node. El nodo se convierte en la raíz del árbol.
  3. Inserte estos árboles en la cola de prioridad. Cuanto menor sea la frecuencia, mayor será la prioridad. Así, al extraer, siempre se selecciona el árbol con la frecuencia más baja.

A continuación, debe hacer cíclicamente lo siguiente:

  1. Recupere dos árboles de la cola de prioridad y conviértalos en hijos de un nuevo nodo (un nodo recién creado sin una letra). La frecuencia del nuevo nodo es igual a la suma de las frecuencias de los dos árboles descendientes.
  2. Para este nodo, cree un árbol con raíz en este nodo. Inserte este árbol nuevamente en la cola de prioridad. (Dado que el árbol tiene una nueva frecuencia, lo más probable es que entre en un nuevo lugar en la cola)
  3. Continúe con los pasos 1 y 2 hasta que quede un árbol en la cola: el árbol de Huffman

Considere este algoritmo en la línea s1:

Compresión de datos con el algoritmo de Huffman

Aquí el símbolo "lf" (salto de línea) denota una nueva línea, "sp" (espacio) es un espacio.

¿Y luego qué?

Tenemos el árbol de Huffman. DE ACUERDO. ¿Y qué hacer con eso? No lo tomarán gratis Y luego, debe rastrear todos los caminos posibles desde la raíz hasta las hojas del árbol. Acordamos etiquetar una arista con 0 si conduce al hijo izquierdo y con 1 si conduce al derecho. Estrictamente hablando, en estas notaciones, el código de carácter es el camino desde la raíz del árbol hasta la hoja que contiene este mismo carácter.

Compresión de datos con el algoritmo de Huffman

Así resultó la tabla de códigos. Tenga en cuenta que si consideramos esta tabla, podemos concluir sobre el "peso" de cada carácter: esta es la longitud de su código. Entonces, en forma comprimida, el archivo fuente pesará: 2 * 3 + 2 * 4 + 3 * 3 + 6 * 2 + 1 * 4 + 1 * 5 + 2 * 4 + 4 * 2 + 1 * 5 = 65 bits . Al principio pesaba 176 bits. Por lo tanto, ¡lo redujimos hasta 176/65 = 2.7 veces! Pero esto es una utopía. Es poco probable que se obtenga tal proporción. ¿Por qué? Esto se discutirá un poco más adelante.

Descodificación

Bueno, quizás lo más simple que queda es decodificar. Creo que muchos de ustedes habrán adivinado que es imposible simplemente crear un archivo comprimido sin ninguna pista sobre cómo se codificó; ¡no podremos decodificarlo! Sí, sí, me costó darme cuenta de esto, pero tengo que crear un archivo de texto table.txt con una tabla de compresión:

01110
 00
A010
E1111
I110
S10
T0110
U01111
Y1110

Entrada de tabla en la forma 'carácter'"código de carácter". ¿Por qué 01110 no tiene símbolo? De hecho, es con un símbolo, solo las herramientas de Java que uso cuando exporto a un archivo, el carácter de nueva línea, 'n', se convierte en una nueva línea (no importa cuán estúpido suene). Por lo tanto, la línea vacía de arriba es el carácter del código 01110. Para el código 00, el carácter es un espacio al principio de la línea. Debo decir de inmediato que este método de almacenar la tabla puede afirmarse que es el más irracional para nuestro coeficiente khan. Pero es fácil de entender e implementar. Estaré encantado de escuchar tus recomendaciones en los comentarios sobre optimización.

Con esta tabla, es muy fácil de decodificar. Recordemos qué regla nos guió al crear la codificación:

Ningún código debe ser prefijo de otro

Aquí es donde juega un papel facilitador. Leemos bit a bit secuencialmente, y tan pronto como la cadena resultante d, que consta de los bits leídos, coincide con la codificación correspondiente al carácter carácter, inmediatamente sabemos que el carácter carácter (¡y solo él!) fue codificado. A continuación, escribimos caracteres en la cadena de decodificación (la cadena que contiene el mensaje decodificado), reiniciamos la cadena d y leemos más el archivo codificado.

implementación

Es hora de humillar mi código escribiendo un archivador. Llamémoslo Compresor.

Comenzar de nuevo. En primer lugar, escribimos la clase 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;
    }
}

Ahora el árbol:

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

cola de prioridad:

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 clase que crea el árbol 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 clase que contiene lo que codifica/decodifica:

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 clase que facilita escribir en un archivo:

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 clase que facilita la lectura de un archivo:

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

Bueno, y la clase 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());
    }
}

Tendrá que escribir el archivo con las instrucciones readme.txt usted mismo 🙂

Conclusión

Supongo que eso es todo lo que quería decir. Si tiene algo que decir sobre mi incompetencia de mejoras en el código, el algoritmo, en general, cualquier optimización, no dude en escribir. Si no he explicado algo, por favor también escriba. ¡Me encantaría saber de ti en los comentarios!

PS

Sí, sí, sigo aquí, porque no me olvidé del coeficiente. Para la cadena s1, la tabla de codificación pesa 48 bytes, mucho más que el archivo original, y no se olvidaron de los ceros adicionales (el número de ceros agregados es 7) => la relación de compresión será menor que uno: 176 /(65 + 48*8 + 7) = 0.38. Si también notó esto, entonces simplemente no en la cara, ya terminó. Sí, esta implementación será extremadamente ineficiente para archivos pequeños. Pero, ¿qué sucede con los archivos grandes? Los tamaños de archivo son mucho mayores que el tamaño de la tabla de codificación. ¡Aquí es donde el algoritmo funciona como debería! por ejemplo, para monólogo de fausto el archivador da un coeficiente real (no idealizado) igual a 1.46, ¡casi una vez y media! Y sí, se suponía que el archivo estaba en inglés.

Fuente: habr.com

Añadir un comentario