Datakomprimering ved hjelp av Huffman-algoritmen

Entry

I denne artikkelen vil jeg snakke om den berømte Huffman-algoritmen, så vel som dens anvendelse i datakomprimering.

Som et resultat vil vi skrive en enkel arkiver. Dette er allerede diskutert artikkel om Habré, men uten praktisk gjennomføring. Det teoretiske materialet til det nåværende innlegget er hentet fra skoletimer i informatikk og Robert Laforets bok "Data Structures and Algorithms in Java". Så alt er kuttet!

Noen tanker

I en vanlig tekstfil er ett tegn kodet med 8 bits (ASCII-koding) eller 16 (Unicode-koding). Deretter vil vi vurdere ASCII-kodingen. For eksempel, ta linjen s1 = "SUSIE SAYS DET ER LETT". Det er totalt 22 tegn på linjen, naturligvis, inkludert mellomrom og det nye linjetegnet - 'n'. Filen som inneholder denne linjen vil veie 22*8 = 176 biter. Spørsmålet melder seg umiddelbart: er det rasjonelt å bruke alle 8 bitene for å kode 1 tegn? Vi bruker ikke alle ASCII-tegn. Selv om de gjorde det, ville det være mer rasjonelt at den vanligste bokstaven - S - fikk kortest mulig kode, og at den sjeldneste bokstaven - T (eller U, eller 'n') - fikk en lengre kode. Dette er hva Huffman-algoritmen består av: det er nødvendig å finne det optimale kodingsalternativet der filen vil ha minimumsvekt. Det er ganske normalt at kodelengdene vil variere for ulike symboler – det er dette algoritmen er basert på.

Koding

Hvorfor ikke gi tegnet 'S' en kode, for eksempel 1 bit lang: 0 eller 1. La det være 1. Så det nest vanligste tegnet - ' ' (mellomrom) - gi 0. Tenk deg at du begynte å dekode meldingen - den kodede strengen s1 - og du ser at koden starter med 1. Så, hva gjør du: er dette tegnet S, eller er det et annet tegn, for eksempel A? Derfor oppstår en viktig regel:

Ingen av koden skal være et prefiks til en annen

Denne regelen er nøkkelen i algoritmen. Derfor begynner å lage en kode med en frekvenstabell, som indikerer frekvensen (antall forekomster) av hvert symbol:

Datakomprimering ved hjelp av Huffman-algoritmen Tegn med flest forekomster bør kodes minst mulig antall biter. Jeg vil gi et eksempel på en av de mulige kodetabellene:

Datakomprimering ved hjelp av Huffman-algoritmen Så den kodede meldingen vil se slik ut:

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

Jeg skilte koden til hvert tegn med et mellomrom. Dette vil ikke skje i en virkelig komprimert fil!
Spørsmålet oppstår: hvordan kom denne unge fyren på koden for å lage en tabell med koder? Dette vil bli diskutert nedenfor.

Konstruerer et Huffman-tre

Det er her binære søketrær kommer til unnsetning. Ikke bekymre deg, du trenger ikke søke, sette inn eller slette metodene her. Her er trestrukturen 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 koden, den fullstendige koden vil være nedenfor.

Her er algoritmen for å konstruere treet:

  1. Lag et nodeobjekt for hvert tegn fra meldingen (linje s1). I vårt tilfelle vil det være 9 noder (Nodeobjekter). Hver node består av to datafelt: symbol og frekvens
  2. Lag et treobjekt (BinaryTree) for hver node. Noden blir roten til treet.
  3. Sett inn disse trærne i prioritetskøen. Jo lavere frekvens, jo høyere prioritet. Ved ekstrahering velges derfor alltid dervoen med den laveste frekvensen.

Deretter må du gjøre følgende syklisk:

  1. Fjern to trær fra prioritetskøen og gjør dem til barn av den nye noden (den nyopprettede noden uten bokstaven). Frekvensen til den nye noden er lik summen av frekvensene til de to etterkommertrærne.
  2. For denne noden, lag et tre med roten ved denne noden. Sett dette treet tilbake i prioritetskøen. (Siden treet har en ny frekvens, vil det mest sannsynlig vises på et nytt sted i køen)
  3. Fortsett trinn 1 og 2 til det bare er ett tre igjen i køen - Huffman-treet

Tenk på denne algoritmen på linje s1:

Datakomprimering ved hjelp av Huffman-algoritmen

Her betyr symbolet "lf" (linjemating) en ny linje, "sp" (mellomrom) er et mellomrom.

Og hva er neste?

Vi har et Huffman-tre. OK. Og hva skal man gjøre med det? De vil ikke engang ta det gratis. Og så må du spore alle mulige stier fra roten til bladene på treet. La oss bli enige om å betegne en kant 0 hvis den fører til venstre barn og 1 hvis den fører til høyre. Strengt tatt, i denne notasjonen, er koden til et symbol veien fra roten til treet til bladet som inneholder nettopp dette symbolet.

Datakomprimering ved hjelp av Huffman-algoritmen

Slik ble kodetabellen. Merk at hvis vi vurderer denne tabellen, kan vi konkludere om "vekten" til hvert symbol - dette er lengden på koden. Deretter, i komprimert form, vil den originale filen veie: 2 * 3 + 2*4 + 3 * 3 + 6 * 2 + 1 * 4 + 1 * 5 + 2 * 4 + 4 * 2 + 1 * 5 = 65 biter . Først veide den 176 bits. Følgelig reduserte vi den med så mye som 176/65 = 2.7 ganger! Men dette er en utopi. En slik koeffisient vil neppe oppnås. Hvorfor? Dette vil bli diskutert litt senere.

Dekoding

Vel, kanskje det enkleste som er igjen er dekoding. Jeg tror mange av dere har gjettet at vi ikke bare kan lage en komprimert fil uten noen hint om hvordan den ble kodet - vi vil ikke være i stand til å dekode den! Ja, ja, det var vanskelig for meg å innse dette, men jeg må lage en tekstfil table.txt med en komprimeringstabell:

01110
 00
A010
E1111
I110
S10
T0110
U01111
Y1110

Tabelloppføring i formen 'symbol' 'tegnkode'. Hvorfor er 01110 uten symbol? Faktisk er det med et symbol, det er bare at java-verktøyene jeg bruker når jeg sender ut til en fil, nylinjetegnet - 'n' - konverteres til en ny linje (uansett hvor dumt det høres ut). Derfor er den tomme linjen øverst tegnet for kode 01110. For kode 00 er tegnet et mellomrom i begynnelsen av linjen. Jeg vil si med en gang at for vår Khan-koeffisient kan denne metoden for å lagre et bord hevde å være den mest irrasjonelle. Men det er lett å forstå og implementere. Jeg vil gjerne høre dine anbefalinger i kommentarene angående optimalisering.

Å ha denne tabellen gjør det veldig enkelt å dekode. La oss huske hvilken regel vi fulgte da vi opprettet kodingen:

Ingen av koden skal være et prefiks til en annen

Det er her det spiller en tilretteleggende rolle. Vi leser sekvensielt bit for bit, og så snart den resulterende strengen d, bestående av lesebitene, samsvarer med kodingen som tilsvarer tegntegnet, vet vi umiddelbart at tegntegnet (og bare det!) ble kodet. Deretter skriver vi tegn inn i dekodingslinjen (linjen som inneholder den dekodede meldingen), tilbakestiller linje d, og leser deretter den kodede filen.

implementering

Det er på tide å ydmyke koden min og skrive en arkiver. La oss kalle det kompressor.

Begynne på nytt. Først av alt 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;
    }
}

Nå treet:

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 som lager Huffman-treet:

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

Klasse som inneholder som koder/dekoder:

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 som gjør det lettere å skrive 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 som gjør det lettere å lese 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();
    }
}

Vel, 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 må skrive readme.txt-filen selv :)

Konklusjon

Jeg antar at det var alt jeg ville si. Hvis du har noe å si om min inkompetanse til å forbedre koden, algoritmen eller optimalisering generelt, så skriv gjerne. Hvis jeg ikke har forklart noe, skriv gjerne også. Jeg vil gjerne høre fra deg i kommentarfeltet!

PS

Ja, ja, jeg er her fortsatt, for jeg glemte ikke koeffisienten. For streng s1 veier kodingstabellen 48 byte - mye større enn kildefilen, og vi glemte ikke de ekstra nullene (antall ekstra nuller er 7) => komprimeringsforholdet vil være mindre enn én: 176/ (65 + 48*8 + 7) = 0.38. Hvis du også la merke til dette, så er det ikke bare ansiktet ditt som er flott. Ja, denne implementeringen vil være ekstremt ineffektiv for små filer. Men hva skjer med store filer? Filstørrelsene er mye større enn størrelsen på kodingstabellen. Det er her algoritmen fungerer som den skal! For eksempel for Fausts monolog Arkiveren produserer en reell (ikke idealisert) koeffisient på 1.46 - nesten en og en halv gang! Og ja, filen skulle være på engelsk.

Kilde: www.habr.com

Legg til en kommentar