Transformata Burrowsa-Wheelera

Transformata Burrowsa-Wheeleraalgorytm użyteczny przy bezstratnej kompresji danych. Dane po przetworzeniu tą transformacją dają się znacznie lepiej skompresować za pomocą klasycznych algorytmów kompresji. Operuje ona na blokach, przy czym jest tym efektywniejsza im bloki te są większe. Zazwyczaj używa się bloków o rozmiarach kilkuset kilobajtów.

Transformata Burrowsa-Wheelera jest podstawą algorytmu BZIP2.

Dla potrzeb kompresji, zwykle po transformacie Burrowsa-Wheelera używa się algorytmu Move To Front, po czym kompresuje się dowolną metodą kompresji bezstratnej, np. algorytmem Huffmana.

Algorytm transformaty

Na początku mamy blok danych o rozmiarze N bajtów, np.:

Polska Wikipedia

Generujemy wszystkie N rotacji kompresowanego bloku. Wymaga to jedynie O(N) pamięci, nie zaś O(N²), ponieważ generujemy indeksy, a nie kopiujemy.

S0Polska Wikipedia
S1olska WikipediaP
S2lska WikipediaPo
S3ska WikipediaPol
S4ka WikipediaPols
S5a WikipediaPolsk
S6 WikipediaPolska
S7WikipediaPolska 
S8ikipediaPolska W
S9kipediaPolska Wi
S10ipediaPolska Wik
S11pediaPolska Wiki
S12ediaPolska Wikip
S13diaPolska Wikipe
S14iaPolska Wikiped
S15aPolska Wikipedi

Sortujemy łańcuchy leksykograficznie, czyli najpierw według wartości pierwszego bajta, następnie drugiego itd.

FL
Pozycja 0S6 WikipediaPolska
Pozycja 1S0Polska Wikipedia
Pozycja 2S7WikipediaPolska 
Pozycja 3S5a WikipediaPolsk
Pozycja 4S15aPolska Wikipedi
Pozycja 5S13diaPolska Wikipe
Pozycja 6S12ediaPolska Wikip
Pozycja 7S14iaPolska Wikiped
Pozycja 8S8ikipediaPolska W
Pozycja 9S10ipediaPolska Wik
Pozycja 10S4ka WikipediaPols
Pozycja 11S9kipediaPolska Wi
Pozycja 12S2lska WikipediaPo
Pozycja 13S1olska WikipediaP
Pozycja 14S11pediaPolska Wiki
Pozycja 15S3ska WikipediaPol

Zachowujemy ostatni bajt każdej rotacji, w kolejności ich leksykograficznego wystąpienia (kolumna L), oraz pozycję, na której w L znajduje się pierwszy znak kompresowanego bloku danych (indeks P), czyli po prostu pozycję, na której jest łańcuch S1. W tym przypadku indeksem P jest numer 13 oraz blok:

aa kiepdWksioPil

Z bloku oraz indeksu P można odtworzyć pierwotne dane.

Algorytm transformaty odwrotnej

Mamy indeks P = 13 i blok L.

 0123456789101112131415
Laa kiepdWksioPil

Należy w tym momencie zauważyć, że sortując bajty w L uzyskamy F, czyli pierwszą kolumnę posortowanej macierzy łańcuchów.

 0123456789101112131415
F PWaadeiiikklops

Ponieważ bajt przyjmuje 256 możliwych wartości, a bajtów zwykle jest kilkaset tysięcy, najłatwiej zrobić to za pomocą sortowania kubełkowego lub podobnego algorytmu o liniowym czasie wykonywania.

Następnie konstruujemy wektor sąsiedztw T, który posłuży nam do odzyskania pierwotnego ciągu. Wektor T jest to tablica przejść taka, że L[i] i L[T[i]] są kolejnymi znakami pierwotnego ciągu. Dla przykładu:

FL  T
Pozycja 0S6 WikipediaPolska S6→S72
Pozycja 1S0Polska Wikipedia S0→S113
Pozycja 2S7WikipediaPolska  S7→S88
Pozycja 3S5a WikipediaPolsk S5→S60
Pozycja 4S15aPolska Wikipedi S15→S01
Pozycja 5S13diaPolska Wikipe S13→S147
Pozycja 6S12ediaPolska Wikip S12→S135
Pozycja 7S14iaPolska Wikiped S14→S154
Pozycja 8S8ikipediaPolska W S8→S911
Pozycja 9S10ipediaPolska Wik S10→S1114
Pozycja 10S4ka WikipediaPols S4→S53
Pozycja 11S9kipediaPolska Wi S9→S109
Pozycja 12S2lska WikipediaPo S2→S315
Pozycja 13S1olska WikipediaP S1→S212
Pozycja 14S11pediaPolska Wiki S11→S126
Pozycja 15S3ska WikipediaPol S3→S410

Posiadając wektor T, L, oraz index P, możemy odtworzyć ciąg:

  • i = P
  • dopóki nie odzyskamy całej długości ciągu:
    • wypisz L[i]
    • i = T[i]

Przykład:

  • i = P → 13
  • L[i] = L[13] → 'P'
  • i = T[i]= T[13] → 12
  • L[12] → 'o'
  • i = T[12] → 15
  • L[15] → 'l'
  • ...

Aby wygenerować wektor T z L, należy poprzez sortowanie utworzyć również wektor F. Następnie dla każdego kolejnego elementu F wyznaczyć pozycję elementu odpowiadającego w L. Wówczas index elementu F będzie indeksem T, a index wyznaczonego elementu L jego wartością. Wyznaczony element z L zaznaczamy jako użyty.
Najlepiej zobrazować to na przykładzie pięciu kroków algorytmu (kropką • zaznaczone użyte elementy):

Krok Algorytmu012345
FLTFLTFLTFLTFLTFLT
Pozycja 0 a2 a2 a2 a2 2 2
Pozycja 1Pa Pa13Pa13Pa13Pa13P13
Pozycja 2W  W W8W8W8W8
Pozycja 3ak ak ak ak0ak0ak0
Pozycja 4ai ai ai ai ai1ai1
Pozycja 5de de de de de de7
Pozycja 6ep ep ep ep ep ep 
Pozycja 7id id id id id id 
Pozycja 8iW iW iW i i i 
Pozycja 9ik ik ik ik ik ik 
Pozycja 10ks ks ks ks ks ks 
Pozycja 11ki ki ki ki ki ki 
Pozycja 12lo lo lo lo lo lo 
Pozycja 13oP oP o o o o 
Pozycja 14pi pi pi pi pi pi 
Pozycja 15sl sl sl sl sl sl 

Z tak otrzymanego wektora T i indeksu P, z łatwością uzyskujemy pierwotny blok danych.

Przykładowy kod w C

#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <stdio.h>

typedef unsigned char byte;

byte *rotlexcmp_buf = NULL;
int rottexcmp_bufsize = 0;

int rotlexcmp(const void *l, const void *r)
{
    int li = *(const int*)l, ri = *(const int*)r, ac=rottexcmp_bufsize;
    while (rotlexcmp_buf[li] == rotlexcmp_buf[ri])
    {
        if (++li == rottexcmp_bufsize)
            li = 0;
        if (++ri == rottexcmp_bufsize)
            ri = 0;
        if (!--ac)
            return 0;
    }
    if (rotlexcmp_buf[li] > rotlexcmp_buf[ri])
        return 1;
    else
        return -1;
}

void bwt_encode(byte *buf_in, byte *buf_out, int size, int *primary_index)
{
    int indices[size];
    int i;

    for(i=0; i<size; ++i)
        indices[i] = i;
    rotlexcmp_buf = buf_in;
    rottexcmp_bufsize = size;
    qsort (indices, size, sizeof(int), rotlexcmp);

    for (i=0; i<size; ++i)
        buf_out[i] = buf_in[(indices[i]+size-1)%size];
    for (i=0; i<size; ++i)
    {
        if (indices[i] == 1) {
            *primary_index = i;
            return;
        }
    }
    assert (0);
}

void bwt_decode(byte *buf_in, byte *buf_out, int size, int primary_index)
{
    byte F[size];
    int buckets[256];
    int i,j,k;
    int indices[size];

    for (i=0; i<256; ++i)
        buckets[i] = 0;
    for (i=0; i<size; ++i)
        buckets[buf_in[i]] ++;
    for (i=0,k=0; i<256; i++)
        for (j=0; j<buckets[i]; ++j)
            F[k++] = i;
    assert (k==size);
    for (i=0,j=0; i<256; ++i)
    {
        buckets[i] = j; // it will get fake values if there is no i in F, but
                        // that won't bring us any problems
        while (i>=F[j] && j<size)
            ++j;
    }
    for(i=0; i<size; ++i)
        indices[buckets[buf_in[i]]++] = i;
    for(i=0,j=primary_index; i<size; ++i)
    {
        buf_out[i] = buf_in[j];
        j=indices[j];
    }
}

int main()
{
    byte buf1[] = "Polska Wikipedia";
    int size = strlen(buf1);
    byte buf2[size];
    byte buf3[size];
    int primary_index;

    bwt_encode (buf1, buf2, size, &primary_index);
    bwt_decode (buf2, buf3, size, primary_index);

    assert (!memcmp (buf1, buf3, size));
    printf ("Result is the same as input, that is: <%.*s>\n", size, buf3);
    return 0;
}

Zobacz też

Linki zewnętrzne

Media użyte na tej stronie

REF new (questionmark).svg
Autor: Sławobóg, Licencja: LGPL
Icon for missing references