Datakompression med Huffman-algoritmen

Introduktion

I denne artikel vil jeg fortælle om den velkendte Huffman-algoritme samt dens anvendelse til datakomprimering.

Som et resultat vil vi skrive en simpel arkiveringsprogram. Dette har allerede været artikel på Habré, men uden praktisk implementering. Det teoretiske materiale i dette indlæg er taget fra skolelektioner i informatik og Robert Lafore's bog "Data Structures and Algorithms in Java". Så lad os komme i gang!

Nogle overvejelser

I en almindelig tekstfil kodes et tegn med 8 bit (ASCII-kodning) eller 16 (Unicode-kodning). Vi vil her fokusere på ASCII-kodning. Som eksempel tager vi strengen s1 = "SUSIE SAYS IT IS EASYn". I alt er der 22 tegn i strengen, inklusive mellemrum og tegn, der angiver linjeskift — 'n'. En fil, der indeholder denne streng, vil veje 22*8 = 176 bit. Spørgsmålet opstår straks: er det rationelt at bruge alle 8 bit til kodning af 1 tegn? Vi bruger jo ikke alle tegnene i ASCII-kodningen. Selv hvis vi gjorde, ville det være mere rationelt at give det mest almindelige bogstav — S — den kortest mulige kode, mens det sjældneste bogstav — T (eller U, eller 'n') — ville få en længere kode. Det er her Huffman-algoritmen kommer ind: man skal finde den optimale kodningsvariant, hvor filen vejer mindst muligt. Det er helt normalt, at længden af koden for forskellige symboler varierer — det er grundlaget for algoritmen.

Kodning

Hvorfor skulle symbolet 'S' ikke få en kode, for eksempel en længde på 1 bit: 0 eller 1. Lad os sige, at det er 1. Så skal vi give den næstmest almindelige symbol — ' ' (mellemrum) — 0. Forestil dig, at du begyndte at dekode dit budskab — den kodede streng s1 — og ser, at koden starter med 1. Så hvad skal man gøre: er det tegnet S, eller er det et andet symbol, for eksempel A? Derfor opstår der en vigtig regel:

Ingen kode skal være et præfiks for en anden

Denne regel er central i algoritmen. Derfor begynder oprettelsen af koder med en frekvenstabel, der angiver frekvensen (antal forekomster) af hvert symbol:

Datakompression med Huffman-algoritmen Symboler med det højeste antal forekomster skal kodes med det mindste mulige antal bits. Her er et eksempel på en mulig kodetabel:

Datakompression med Huffman-algoritmen Således vil den kodede besked 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

Koden for hvert symbol er adskilt af et mellemrum. I en komprimeret fil vil dette ikke være tilfældet!
Spørgsmålet opstår: hvordan kom denne nybegynder på idéen om at skrive en kode til at oprette kode-tabeller? Dette vil blive uddybet nedenfor.

Opbygning af Huffman-træ

Her træder binære søgetræer til hjælp. Bekymr dig ikke, her vil metoder til søgning, indsættelse og sletning ikke være nødvendige. Her er strukturen for træet i Java:

public class Node {
    private int frekvens;
    private char bogstav;
    private Node venstreBørn;
    private Node højreBørn;
    ...
}

class BinaryTree {
    private Node rod;

    public BinaryTree() {
        rod = new Node();
    }
    public BinaryTree(Node rod) {
        this.rod = rod;
    }
    ...
}

Dette er ikke hele koden, den komplette kode vil komme senere.

Her er selve algoritmen til opbygning af træet:

  1. Opret et Node-objekt for hvert tegn fra beskeden (streng s1). I vores tilfælde vil der være 9 noder (Node-objekter). Hver node består af to dat felter: tegn og frekvens.
  2. Opret et BinaryTree-objekt for hver af Node-noderne. Noden bliver rod af træet.
  3. Indsæt disse træer i en prioritetskø. Jo lavere frekvens, desto højere prioritet. På denne måde vælges det træ med den laveste frekvens ved udtræk.

Dernæst skal du gentagne gange udføre følgende:

  1. Træk to træer fra prioritetskøen og gør dem til efterkommere af en ny node (den nyoprettede node uden bogstav). Frekvensen af den nye node er summen af frekvenserne af de to efterkommertræer.
  2. For denne node skal du oprette et træ med roden i denne node. Indsæt dette træ tilbage i prioritetskøen. (Da træet har en ny frekvens, vil det sandsynligvis indtage en ny position i køen.)
  3. Fortsæt med at udføre trin 1 og 2, indtil der kun er ét træ tilbage i køen - Huffman-træet.

Lad os se på denne algoritme på strengen s1:

Datakompression med Huffman-algoritmen

Her betyder symbolet «lf» (linjeskift) skift til en ny linje, «sp» (mellemrum) er et mellemrum.

Og hvad så?

Vi har fået Huffman-træet. Nå ok. Hvad skal vi så gøre med det? Endda det ville ikke være gratis. Derudover skal vi spore alle mulige veje fra roden til bladene af træet. Lad os være enige om at betegne en kant med 0, hvis det fører til det venstre barn, og 1 - hvis det fører til det højre. Strengt taget i denne notering, er koden for et symbol vejen fra roden af træet til bladet, der indeholder det pågældende symbol.

Datakompression med Huffman-algoritmen

På den måde blev kode-tabellen til. Lad os bemærke, at hvis vi ser på denne tabel, kan vi konkludere om "vægten" af hvert tegn - det er længden af dets kode. Således vil den komprimerede version af den originale fil veje: 2 * 3 + 2 * 4 + 3 * 3 + 6 * 2 + 1 * 4 + 1 * 5 + 2 * 4 + 4 * 2 + 1 * 5 = 65 bits. I starten vejede den 176 bits. Derfor har vi reduceret den med 176/65 = 2.7 gange! Men det er utopi. En sådan koefficient vil næppe blive opnået. Hvorfor? Det vil vi tale om lidt senere.

Dekodning

Nå, så er det tilbage at tage fat på den simpleste opgave - dekodning. Jeg tror, mange af jer har gættet, at man ikke bare kan lave en komprimeret fil uden nogen indikation på, hvordan den blev kodet - vi vil ikke kunne dekode den! Ja, det var svært for mig at forstå, men vi må oprette en tekstfil table.txt med komprimeringstabellen:

01110
 00
A010
E1111
I110
S10
T0110
U01111
Y1110

At nedskrive tabellen i formen 'tegn'«kode for tegn». Hvorfor 01110 uden tegn? Faktisk har den et tegn, det er bare de Java-værktøjer, jeg brugte til at skrive til filen, konverterer tegn for newline 'n' til en newline (uanset hvor dumt det lyder). Derfor er den tomme linje der, og det er tegnet for koden 01110. For koden 00 er tegnet et mellemrum i starten af linjen. Jeg må straks sige, at vores koefficient er dødsdømt - denne måde at gemme tabellen på kan anses for den mest uforholdsmæssige. Men den er let at forstå og implementere. Jeg ser frem til jeres forslag i kommentarerne vedrørende optimering.

Med denne tabel er det meget enkelt at dekode. Lad os mindes, hvilken regel vi fulgte, da vi oprettede kodningen:

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

Her spiller det en lettende rolle. Vi læser bit for bit, og så snart den resulterende streng d, bestående af de læste bits, svarer til kodningen, der svarer til tegnet character, ved vi straks, at tegnet character er blevet kodet (og kun det!). Derefter skriver vi character til den dekodede streng (strengen, der indeholder den dekodede besked), nulstiller strengen d og læser videre i den kodede fil.

Implementering

Det er tid til at ydmyge min kode og skrive en kompressor. Lad os kalde den Compressor.

Lad os starte fra begyndelsen. Først skriver vi klassen Node:

public class Node {
    private int frekvens; // frekvens
    private char bogstav; // bogstav
    private Node venstreBarn; // venstre barn
    private Node højreBarn; // højre barn

    public Node(char bogstav, int frekvens) { // konstruktør
        this.bogstav = bogstav;
        this.frekvens = frekvens;
    }

    public Node() {} // overbelastning af konstruktør til navnløse noder (se ovenfor i afsnittet om opbygning af Huffman-træet)
    public void tilføjBarn(Node nytBarn) { // tilføj barn
        if (venstreBarn == null) // hvis venstre er tom => højre også tom => tilføj i venstre
            venstreBarn = nytBarn;
        else {
            if (venstreBarn.getFrekvens() <= nytBarn.getFrekvens()) // generelt, venstre barn
                højreBarn = nytBarn; // vil blive den med lavere frekvens
            else {
                højreBarn = venstreBarn;
                venstreBarn = nytBarn;
            }
        }

        frekvens += nytBarn.getFrekvens(); // samlet frekvens
    }

    public Node getVenstreBarn() {
        return venstreBarn;
    }

    public Node getHøjreBarn() {
        return højreBarn;
    }

    public int getFrekvens() {
        return frekvens;
    }

    public char getBogstav() {
        return bogstav;
    }

    public boolean erBlad() { // kontrol af blad
        return venstreBarn == null && højreBarn == null;
    }
}

Nu træet:

class BinaryTree {
    private Node rod;

    public BinaryTree() {
        rod = new Node();
    }

    public BinaryTree(Node rod) {
        this.rod = rod;
    }

    public int getFrekvens() {
        return rod.getFrekvens();
    }

    public Node getRod() {
        return rod;
    }
}

Prioritetskø:

import java.util.ArrayList; // ja-ja, køen vil være baseret på en liste

class PriorityQueue {
    private ArrayList data; // liste over køen
    private int nElem; // antal elementer i køen

    public PriorityQueue() {
        data = new ArrayList();
        nElem = 0;
    }

    public void indsæt(BinaryTree nytTræ) { // indsættelse
        if (nElem == 0)
            data.add(nytTræ);
        else {
            for (int i = 0; i  nytTræ.getFrekvens()) { // hvis frekvensen af det indsatte træ er mindre
                    data.add(i, nytTræ); // end frekvensen af det nuværende, så flytter vi alle træer til højre med 1 position
                    break; // derefter sætter vi det nye træ på pladsen
                }
                if (i == nElem - 1) 
                    data.add(nytTræ);
            }
        }
        nElem++; // øger antallet af elementer med 1
    }

    public BinaryTree fjern() { // fjernelse fra køen
        BinaryTree tmp = data.get(0); // kopierer det fjernede element
        data.remove(0); // fjerner det
        nElem--; // mindsker antallet af elementer med 1
        return tmp; // returnerer det fjernede element (elementet med den mindste frekvens)
    }
}

Klassen, der opretter Huffman-træet:

offentlig klasse HuffmanTree {
    private final byte ENCODING_TABLE_SIZE = 127; // længde af kodningstabellen
    private String myString; // besked
    private BinaryTree huffmanTree; // Huffman-træ
    private int[] freqArray; // frekvenstabel
    private String[] encodingArray; // kodningstabel

    // ----------------constructor----------------------
    offentlig 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();
        // algoritmen er beskrevet ovenfor
        for (int i = 0; i < ENCODING_TABLE_SIZE; i++) {
            if (freqArray[i] != 0) { // hvis symbolet eksisterer i strengen
                Node newNode = new Node((char) i, freqArray[i]); // så skal der oprettes en Node for det
                BinaryTree newTree = new BinaryTree(newNode); // og for Node skal der oprettes et BinaryTree
                pq.insert(newTree); // indsætte i køen
            }
        }

        while (true) {
            BinaryTree tree1 = pq.remove(); // hente det første træ fra køen.

            try {
                BinaryTree tree2 = pq.remove(); // hente det andet træ fra køen

                Node newNode = new Node(); // oprette en ny Node
                newNode.addChild(tree1.getRoot()); // gøre de to hentede træer til efterkommere
                newNode.addChild(tree2.getRoot());

                pq.insert(new BinaryTree(newNode);
            } catch (IndexOutOfBoundsException e) { // der er kun et træ tilbage i køen
                return tree1;
            }
        }
    }

    public BinaryTree getTree() {
        return huffmanTree;
    }

    // -------------------encoding array------------------
    void fillEncodingArray(Node node, String codeBefore, String direction) { // fyld kodningstabellen
        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() { // til debugging
        fillEncodingArray(huffmanTree.getRoot(), "", "");

        System.out.println("======================Kodningstabel====================");
        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 kodar/afkodar:

public class HuffmanOperator {
    private final byte ENCODING_TABLE_SIZE = 127; // længde på tabellen
    private HuffmanTree mainHuffmanTree; // Huffman-træet (bruges kun til komprimering)
    private String myString; // original besked
    private int[] freqArray; // frekvenstabel
    private String[] encodingArray; // kodningstabel
    private double ratio; // komprimeringsforhold


    public HuffmanOperator(HuffmanTree MainHuffmanTree) { // til komprimering
        this.mainHuffmanTree = MainHuffmanTree;

        myString = mainHuffmanTree.getOriginalString();

        encodingArray = mainHuffmanTree.getEncodingArray();

        freqArray = mainHuffmanTree.getFrequenceArray();
    }

    public HuffmanOperator() {} // til udtræk;

    // ---------------------------------------komprimering-----------------------------------------------------------
    private String getCompressedString() {
        String compressed = "";
        String intermidiate = ""; // mellemstreng (uden ekstra nuller)
        // System.out.println("=============================Komprimering=======================");
        // displayEncodingArray();
        for (int i = 0; i 
        // skal tilføje nuller til slutningen (kan være 1, ikke forskel)
        byte counter = 0; // antal tilføjede nuller til slutningen (1 byte er rigeligt: 0 <= counter < 8 < 127)
        for (int length = intermidiate.length(), delta = 8 - length % 8; 
                counter < delta ; counter++) { // delta - antal tilføjede nuller
            intermidiate += "0";
        }
        
        // sammenkæd antal tilføjede nuller i binær repræsentation og mellemstrengen 
        compressed = String.format("%8s", Integer.toBinaryString(counter & 0xff)).replace(" ", "0") + intermidiate;
                
        // idealiseret forhold
        setCompressionRatio();
        // System.out.println("===============================================================");
        return compressed;
    }
    
    private void setCompressionRatio() { // beregn det idealiserede forhold 
        double sumA = 0, sumB = 0; // A - den originale 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() { // slutkomprimering
        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;
    }
    // ---------------------------------------slut på komprimering----------------------------------------------------------------
    // ------------------------------------------------------------udtræk-----------------------------------------------------
    public String extract(String compressed, String[] newEncodingArray) {
        String decompressed = "";
        String current = "";
        String delta = "";
        encodingArray = newEncodingArray;
        
        // displayEncodingArray();
        // få antal indlagte nuller
        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, fordi den første byte indeholder antal indlagte nuller
            current += compressed.charAt(i);
            for (int j = 0; j < ENCODING_TABLE_SIZE; j++) {
                if (current.equals(encodingArray[j])) { // hvis det stemmer
                    decompressed += (char)j; // så tilføj elementet
                    current = ""; // og nulstille den nuværende streng
                }
            }
        }

        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() { // til fejlfinding
        System.out.println("======================Kodningstabel====================");
        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 filskrivning:

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("Ugyldig sti, eller filen eksisterer ikke!");
    	}
    }

    @Override
    public void close() throws IOException {
        fileOutputStream.close();
    }

    public void finalize() throws IOException {
        close();
    }
}

En klasse, der letter fillæsning:

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) // hvis filen er slut
    		throw new EOFException();
    	return (byte)cur;
    }
    
    public String readLine() throws IOException {
    	return fileBufferedReader.readLine();
    }
    
    @Override
    public void close() throws IOException{
    	fileInputStream.close();
    }
}

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 { //Angiv instruktion med kommandolinjeargumenter
            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("Ugyldigt indtastningsformat for argumenter ");
            System.out.println("Læs Readme.txt");
            e.printStackTrace();
        }
    }

	public static void compress(String stringPath) throws IOException {
        List 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("Ugyldig sti, eller filen eksisterer ikke!");
            return;
        } catch (MalformedInputException e) {
        	System.out.println("Den nuværende filkode understøttes ikke");
        	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());
        }
        //Opret fil med kodetabel:
        
        table = new File(inputFile.getAbsolutePath() + ".table.txt");
        table.createNewFile();
        try (FileOutputHelper fo = new FileOutputHelper(table)) {
        	fo.writeString(operator.getEncodingTable());
        }
        
        System.out.println("Sti til den komprimerede fil: " + compressedFile.getAbsolutePath());
        System.out.println("Sti til kodetabellen " + table.getAbsolutePath());
        System.out.println("Uden tabellen vil filen være umulig at udtrække!");
        
        double idealRatio = Math.round(operator.getCompressionRatio() * 100) / (double) 100; //idealiseret forhold
        double realRatio = Math.round((double) inputFile.length() 
        		/ ((double) compressedFile.length() + (double) table.length()) * 100) / (double)100; //reelt forhold
        
        System.out.println("Det idealiserede komprimeringsforhold er " + idealRatio);
        System.out.println("Komprimeringsforholdet under hensyntagen til kodetabellen " + 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];
        //læs komprimeret fil
        //!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!kontroller her:
        try (FileInputHelper fi = new FileInputHelper(compressedFile)) {
        	byte b;
        	while (true) {
        		b = fi.readByte(); //metode returnerer EOFException
        		compressed += String.format("%8s", Integer.toBinaryString(b & 0xff)).replace(" ", "0");
        	}
        } catch (EOFException e) {
        	
        }
        
        //--------------------
        
        //læs kodetabel:
        try (FileInputHelper fi = new FileInputHelper(tableFile)) {
        	fi.readLine(); //spring den første tomme streng over
        	encodingArray[(byte)'n'] = fi.readLine(); //læs kode 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();
        //udtræk:
		try (FileOutputHelper fo = new FileOutputHelper(extractedFile)) {
			fo.writeString(operator.extract(compressed, encodingArray));
		}
		
		System.out.println("Sti til den udpakkede fil " + extractedFile.getAbsolutePath());
    }
}

Du skal selv skrive readme.txt-instruktionsfilen 🙂

Konklusion

Måske er det alt, hvad jeg ville sige. Hvis du har noget at sige om min inkompetence med forbedringer i koden, algoritmen eller generelt enhver optimering, så skriv endelig. Hvis jeg har undladt at forklare noget, så skriv også. Jeg ser frem til at høre fra dig i kommentarerne!

P.S.

Ja- ja, jeg er stadig her, for jeg har ikke glemt forholdet. For strengen s1 vejer kodningstabellen 48 byte — meget mere end den oprindelige fil, og vi har ikke glemt de ekstra nuller (antallet af tilføjede nuller er 7) => kompressionsforholdet vil være mindre end én: 176/(65 + 48*8 + 7)=0.38. Hvis du også har bemærket det, så kun ét på ansigtet, du er dygtig. Ja, denne implementering vil være ekstremt ineffektiv for små filer. Men hvad sker der med store filer? Filstørrelserne overstiger langt størrelsen af kodningstabellen. Her fungerer algoritmen som den skal! For eksempel for Fausts monolog giver arkiveringsprogrammet en reel (ikke idealiseret) kompressionsfaktor på 1.46 — næsten halvanden gang! Og ja, det var meningen, at filen skulle være på engelsk.

Kilde: habr.com

Køb pålidelig webhosting med DDoS-beskyttelse, VPS VDS-servere 🔥 Køb pålidelig webhosting med DDoS-beskyttelse, VPS VDS-servere | ProHoster