Datakomprimering med Huffman-algoritmen

Indrejse

I denne artikel vil jeg tale om den velkendte Huffman-algoritme, såvel som dens anvendelse i datakomprimering.

Som et resultat vil vi skrive et simpelt arkiver. Dette har allerede været artikel om Habré, men uden praktisk implementering. Det teoretiske materiale til den aktuelle stilling er hentet fra skoletimer i datalogi og Robert Laforets bog "Data Structures and Algorithms in Java". Så alt er under skæring!

En lille refleksion

I en normal tekstfil er ét tegn kodet med 8 bit (ASCII-kodning) eller 16 (Unicode-kodning). Dernæst vil vi overveje ASCII-kodningen. Tag f.eks. strengen s1 = "SUSIE SAYS DET ER NEMT". I alt er der selvfølgelig 22 tegn i linjen, inklusive mellemrum og nylinjetegnet - 'n'. Filen, der indeholder denne linje, vejer 22*8 = 176 bit. Spørgsmålet melder sig straks: er det rationelt at bruge alle 8 bits til at kode 1 tegn? Vi bruger ikke alle ASCII-tegn. Selv hvis de var det, ville det være mere rationelt at give det hyppigste bogstav - S - den kortest mulige kode, og for det sjældneste bogstav - T (eller U eller 'n') - give koden mere autentisk. Dette er Huffman-algoritmen: du skal finde den bedste kodningsmulighed, hvor filen vil være af minimumvægt. Det er helt normalt, at forskellige tegn vil have forskellige kodelængder - det er grundlaget for algoritmen.

Coding

Hvorfor ikke give tegnet 'S' en kode, f.eks. 1 bit lang: 0 eller 1. Lad det være 1. Så vil det næstmest forekommende tegn - ' ' (mellemrum) - vi giver 0. Forestil dig, at du begyndte at afkode din besked - kodet streng s1 - og du ser, at koden starter med 1. Så hvad skal du gøre: er det tegnet S, eller er det et andet tegn, såsom A? Derfor opstår en vigtig regel:

Ingen kode må være et præfiks for en anden

Denne regel er nøglen til algoritmen. Derfor begynder oprettelsen af ​​koden med en frekvenstabel, som angiver frekvensen (antal forekomster) af hvert symbol:

Datakomprimering med Huffman-algoritmen Tegnene med flest forekomster skal kodes med færrest muligt antallet af bits. Jeg vil give et eksempel på en af ​​de mulige kodetabeller:

Datakomprimering med Huffman-algoritmen Så den kodede besked vil se sådan ud:

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

Jeg adskilte koden for hvert tegn med et mellemrum. Dette vil ikke rigtig ske i en komprimeret fil!
Spørgsmålet opstår: hvordan kom denne rookie op med en kode, hvordan man opretter en kodetabel? Dette vil blive diskuteret nedenfor.

At bygge et Huffman-træ

Det er her, binære søgetræer kommer til undsætning. Bare rolig, du behøver ikke søge-, indsæt- og sletmetoderne her. Her er træstrukturen i 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;
    }
    ...
}

Dette er ikke den komplette kode, den fulde kode vil være nedenfor.

Her er algoritmen til at bygge et træ:

  1. Opret et Node-objekt for hvert tegn fra beskeden (linje s1). I vores tilfælde vil der være 9 noder (Node-objekter). Hver node består af to datafelter: symbol og frekvens
  2. Opret et træobjekt (BinaryTree) for hver af knudepunkterne. Noden bliver træets rod.
  3. Indsæt disse træer i prioritetskøen. Jo lavere frekvens, jo højere prioritet. Ved udtrækning vælges således altid træet med den laveste frekvens.

Dernæst skal du cyklisk gøre følgende:

  1. Hent to træer fra prioritetskøen og gør dem til børn af en ny node (en nyoprettet node uden bogstav). Frekvensen af ​​den nye node er lig med summen af ​​frekvenserne af de to efterkommertræer.
  2. For denne node skal du oprette et træ forankret ved denne node. Indsæt dette træ tilbage i prioritetskøen. (Da træet har en ny frekvens, vil det højst sandsynligt komme ind på et nyt sted i køen)
  3. Fortsæt trin 1 og 2, indtil der er et træ tilbage i køen - Huffman-træet

Overvej denne algoritme på linje s1:

Datakomprimering med Huffman-algoritmen

Her betegner symbolet "lf" (linefeed) en ny linje, "sp" (mellemrum) er et mellemrum.

Og hvad er næste?

Vi fik Huffman-træet. OKAY. Og hvad skal man gøre med det? De vil ikke tage det gratis. Og så skal du spore alle mulige stier fra roden til træets blade. Vi er enige om at mærke en kant 0, hvis den fører til venstre barn og 1, hvis den fører til højre. Strengt taget er tegnkoden i disse notationer stien fra roden af ​​træet til bladet, der indeholder det samme tegn.

Datakomprimering med Huffman-algoritmen

Således viste tabellen over koder sig. Bemærk, at hvis vi betragter denne tabel, kan vi konkludere om "vægten" af hvert tegn - dette er længden af ​​dens kode. Derefter, i komprimeret form, vil kildefilen veje: 2 * 3 + 2 * 4 + 3 * 3 + 6 * 2 + 1 * 4 + 1 * 5 + 2 * 4 + 4 * 2 + 1 * 5 = 65 bit . Først vejede den 176 bits. Derfor reducerede vi det med hele 176/65 = 2.7 gange! Men dette er en utopi. Et sådant forhold vil næppe blive opnået. Hvorfor? Dette vil blive diskuteret lidt senere.

Afkodning

Nå, måske er den enkleste ting tilbage afkodning. Jeg tror, ​​at mange af jer har gættet, at det er umuligt blot at lave en komprimeret fil uden nogen antydninger om, hvordan den blev kodet - vi vil ikke være i stand til at afkode den! Ja, ja, det var svært for mig at indse dette, men jeg er nødt til at oprette en tekstfil table.txt med en komprimeringstabel:

01110
 00
A010
E1111
I110
S10
T0110
U01111
Y1110

Tabelindtastning i formen 'tegn'"tegnkode". Hvorfor er 01110 uden et symbol? Faktisk er det med et symbol, bare de java-værktøjer, som jeg bruger, når jeg udskriver til en fil, nylinjetegnet - 'n' - konverteres til en nylinje (uanset hvor dumt det lyder). Derfor er den tomme linje ovenfor tegnet for kode 01110. For kode 00 er tegnet et mellemrum i begyndelsen af ​​linjen. Jeg må sige med det samme, at denne metode til opbevaring af bordet kan hævde at være den mest irrationelle for vores khan-koefficient. Men det er nemt at forstå og implementere. Jeg vil med glæde høre dine anbefalinger i kommentarerne om optimering.

Med denne tabel er det meget nemt at afkode. Lad os huske, hvilken regel vi blev styret af, da vi oprettede kodningen:

Ingen kode må være et præfiks for en anden

Det er her, det spiller en faciliterende rolle. Vi læser bit for bit sekventielt, og så snart den resulterende streng d, bestående af de læste bits, matcher den kodning, der svarer til tegntegnet, ved vi straks, at tegntegnet (og kun det!) blev kodet. Dernæst skriver vi tegn til afkodningsstrengen (strengen, der indeholder den afkodede besked), nulstiller d-strengen og læser den kodede fil yderligere.

implementering

Det er tid til at ydmyge min kode ved at skrive en arkiver. Lad os kalde det kompressor.

Start forfra. Først og fremmest skriver vi Node-klassen:

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

Nu træet:

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

Prioritetskø:

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

Klassen, der skaber Huffman-træet:

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

Klassen, der indeholder, som koder/afkoder:

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

En klasse, der letter skrivning til en fil:

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

En klasse, der letter læsning fra en fil:

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

Nå, og hovedklassen:

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

Du skal selv skrive filen med readme.txt instruktioner 🙂

Konklusion

Det var vist alt, jeg ville sige. Hvis du har noget at sige om min inkompetence af forbedringer i koden, algoritmen, generelt, enhver optimering, så er du velkommen til at skrive. Hvis jeg ikke har forklaret noget, så skriv også gerne. Jeg vil meget gerne høre fra dig i kommentarerne!

PS

Ja, ja, jeg er her stadig, for jeg har ikke glemt koefficienten. For strengen s1 vejer kodningstabellen 48 bytes - meget mere end den originale fil, og de glemte ikke de ekstra nuller (antallet af tilføjede nuller er 7) => komprimeringsforholdet vil være mindre end én: 176 /(65 + 48*8 + 7) = 0.38. Hvis du også bemærkede dette, så er du bare ikke i ansigtet færdig. Ja, denne implementering vil være ekstremt ineffektiv for små filer. Men hvad sker der med store filer? Filstørrelserne er meget større end kodningstabellens størrelse. Det er her algoritmen fungerer som den skal! For eksempel for Fausts monolog arkiveren giver en reel (ikke idealiseret) koefficient svarende til 1.46 - næsten halvanden gang! Og ja, filen skulle være på engelsk.

Kilde: www.habr.com

Tilføj en kommentar