Kompresija podataka Huffmanovim algoritmom

Ulazak

U ovom članku ću govoriti o dobro poznatom Huffman algoritmu, kao i njegovoj primjeni u kompresiji podataka.

Kao rezultat, mi ćemo napisati jednostavan arhiver. Ovo je već bilo članak na Habréu, ali bez praktične primjene. Teorijski materijal za trenutni post preuzet je iz školske nastave informatike i knjige Roberta Laforeta "Strukture podataka i algoritmi u Javi". Dakle, sve je pod rezom!

Malo razmišljanje

U normalnoj tekstualnoj datoteci, jedan znak je kodiran s 8 bita (ASCII kodiranje) ili 16 (Unicode kodiranje). Zatim ćemo razmotriti ASCII kodiranje. Na primjer, uzmite niz s1 = "SUSIE KAŽE DA JE LAKOn". Ukupno u retku ima 22 znaka, naravno, uključujući razmake i znak za novi red - 'n'. Datoteka koja sadrži ovaj red težit će 22*8 = 176 bita. Odmah se postavlja pitanje: je li racionalno koristiti svih 8 bitova za kodiranje 1 znaka? Ne koristimo sve ASCII znakove. Čak i da jesu, bilo bi racionalnije najčešćem slovu - S dati najkraću moguću šifru, a za najrjeđe slovo - T (ili U, ili 'n') - dati šifru autentičniju. Ovo je Huffmanov algoritam: morate pronaći najbolju opciju kodiranja, u kojoj će datoteka imati minimalnu težinu. Sasvim je normalno da različiti znakovi imaju različite duljine koda - to je osnova algoritma.

Kodiranje

Zašto ne biste znaku 'S' dali kod, na primjer, dug 1 bit: 0 ili 1. Neka to bude 1. Onda ćemo znaku koji se najčešće pojavljuje - ' ' (razmak) - dati 0. Zamislite, počeli ste dekodirajte svoju poruku - kodirani niz s1 - i vidite da kod počinje s 1. Dakle, što učiniti: je li to znak S ili neki drugi znak, kao što je A? Stoga se nameće važno pravilo:

Nijedan kod ne smije biti prefiks drugog

Ovo pravilo je ključ algoritma. Stoga izrada koda započinje tablicom učestalosti koja označava učestalost (broj pojavljivanja) svakog simbola:

Kompresija podataka Huffmanovim algoritmom Znakovi s najviše pojavljivanja trebaju biti kodirani s najmanje moguće broj bitova. Dat ću primjer jedne od mogućih tablica kodova:

Kompresija podataka Huffmanovim algoritmom Dakle, kodirana poruka će izgledati ovako:

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

Kod svakog znaka odvojio sam razmakom. To se stvarno neće dogoditi u komprimiranoj datoteci!
Postavlja se pitanje: kako je ovaj početnik došao do šifre kako napraviti tablicu kodova? O tome će biti riječi u nastavku.

Izgradnja Huffmanovog stabla

Ovdje u pomoć dolaze stabla binarnog pretraživanja. Ne brinite, ovdje vam neće trebati metode pretraživanja, umetanja i brisanja. Ovo je struktura stabla u Javi:

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

Ovo nije potpuni kod, cijeli će se kod naći ispod.

Evo algoritma za izgradnju stabla:

  1. Napravite objekt Node za svaki znak iz poruke (linija s1). U našem slučaju bit će 9 čvorova (Node objects). Svaki čvor se sastoji od dva podatkovna polja: simbol i frekvencija
  2. Stvorite objekt stabla (BinaryTree) za svaki od čvorova čvora. Čvor postaje korijen stabla.
  3. Umetnite ova stabla u prioritetni red. Što je niža frekvencija, veći je prioritet. Dakle, prilikom izdvajanja uvijek se odabire stablo s najnižom frekvencijom.

Zatim morate ciklički učiniti sljedeće:

  1. Dohvatite dva stabla iz reda prioriteta i učinite ih djecom novog čvora (novostvorenog čvora bez slova). Frekvencija novog čvora jednaka je zbroju frekvencija dvaju stabala potomaka.
  2. Za ovaj čvor stvorite stablo ukorijenjeno u ovom čvoru. Umetnite ovo stablo natrag u prioritetni red. (Budući da stablo ima novu frekvenciju, najvjerojatnije će doći na novo mjesto u redu čekanja)
  3. Nastavite s koracima 1 i 2 dok u redu čekanja ne ostane jedno stablo - Huffmanovo stablo

Razmotrite ovaj algoritam na liniji s1:

Kompresija podataka Huffmanovim algoritmom

Ovdje simbol "lf" (linefeed) označava novi red, "sp" (space) je razmak.

I što onda?

Imamo Huffmanovo drvo. U REDU. I što učiniti s tim? Neće ga besplatno uzeti, a onda treba trasirati sve moguće puteve od korijena do lišća stabla. Slažemo se označiti rub 0 ako vodi do lijevog djeteta i 1 ako vodi do desnog. Strogo govoreći, u ovim zapisima, kod znaka je put od korijena stabla do lista koji sadrži upravo taj znak.

Kompresija podataka Huffmanovim algoritmom

Tako je ispala tablica kodova. Imajte na umu da ako uzmemo u obzir ovu tablicu, možemo zaključiti o "težini" svakog znaka - ovo je duljina njegovog koda. Tada će u komprimiranom obliku izvorna datoteka težiti: 2 * 3 + 2 * 4 + 3 * 3 + 6 * 2 + 1 * 4 + 1 * 5 + 2 * 4 + 4 * 2 + 1 * 5 = 65 bita . Isprva je težio 176 bita. Stoga smo ga smanjili za čak 176/65 = 2.7 puta! Ali ovo je utopija. Takav omjer vjerojatno neće biti postignut. Zašto? O tome će biti riječi malo kasnije.

Dekodiranje

Pa, možda je najjednostavnija stvar koja je ostala dekodiranje. Mislim da su mnogi od vas pogodili da je nemoguće jednostavno stvoriti komprimiranu datoteku bez ikakvih naznaka o tome kako je kodirana - nećemo je moći dekodirati! Da, da, bilo mi je teško to shvatiti, ali moram stvoriti tekstualnu datoteku table.txt s tablicom kompresije:

01110
 00
A010
E1111
I110
S10
T0110
U01111
Y1110

Unos u tablicu u obliku 'znak'"znakovni kod". Zašto je 01110 bez simbola? Zapravo, to je sa simbolom, samo java alati koje koristim kada ispisujem u datoteku, znak novog retka - 'n' - pretvara se u novi red (ma koliko to glupo zvučalo). Stoga je prazan red iznad znak za šifru 01110. Za šifru 00, znak je razmak na početku retka. Moram odmah reći da se ova metoda pohranjivanja tablice može smatrati najiracionalnijom za naš khan koeficijent. Ali to je lako razumjeti i implementirati. Bit će mi drago čuti vaše preporuke u komentarima o optimizaciji.

Pomoću ove tablice vrlo je lako dekodirati. Sjetimo se kojim smo se pravilom vodili prilikom izrade kodiranja:

Nijedan kod ne smije biti prefiks drugog

Ovdje igra ulogu olakšavanja. Čitamo bit po bit sekvencijalno, i čim rezultirajući niz d, koji se sastoji od pročitanih bitova, odgovara kodiranju koje odgovara znakovnom znaku, odmah znamo da je karakterni znak (i ​​samo on!) kodiran. Zatim upisujemo znak u niz za dekodiranje (string koji sadrži dekodiranu poruku), resetiramo niz d i dalje čitamo kodiranu datoteku.

Provedba

Vrijeme je da ponizim svoj kod pisanjem arhivera. Nazovimo ga kompresor.

Početi ispočetka. Prije svega, pišemo klasu 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;
    }
}

Sada drvo:

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

Prioritetni red:

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

Klasa koja stvara Huffmanovo stablo:

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

Klasa koja sadrži koji kodira/dekodira:

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

Klasa koja olakšava pisanje u datoteku:

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

Klasa koja olakšava čitanje iz datoteke:

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

Pa, i glavna klasa:

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

Morat ćete sami napisati datoteku s uputama za readme.txt 🙂

Zaključak

Valjda je to sve što sam htio reći. Ako imate nešto za reći o mojoj nesposobnosti poboljšanja koda, algoritma, općenito, bilo kakve optimizacije, slobodno napišite. Ako nešto nisam objasnio, također napišite. Volio bih čuti vaše mišljenje u komentarima!

PS

Da, da, još sam tu, jer nisam zaboravio na koeficijent. Za niz s1, tablica kodiranja teži 48 bajtova - mnogo više od izvorne datoteke, a nisu zaboravili ni dodatne nule (broj dodanih nula je 7) => omjer kompresije bit će manji od jedan: 176 /(65 + 48*8 + 7) = 0.38. Ako ste i vi ovo primijetili, onda samo ne u lice ste gotovi. Da, ova će implementacija biti iznimno neučinkovita za male datoteke. Ali što se događa s velikim datotekama? Veličine datoteka puno su veće od veličine tablice kodiranja. Ovdje algoritam radi kako treba! Na primjer, za Faustov monolog arhivar daje stvarni (ne idealizirani) koeficijent jednak 1.46 - gotovo jedan i pol puta! I da, datoteka je trebala biti na engleskom.

Izvor: www.habr.com

Dodajte komentar