Transformata Burrowsa-Wheelera

Z Wikipedii, wolnej encyklopedii
Skocz do: nawigacji, wyszukiwania

Transformata Burrowsa-Wheelera to algorytm 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ę jakąś metodą kompresji bezstratnej, np. algorytmem Huffmana.

Algorytm transformaty[edytuj | edytuj kod]

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

P o l s k a   W i k i p e d i a

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

S0 P o l s k a   W i k i p e d i a
S1 o l s k a   W i k i p e d i a P
S2 l s k a   W i k i p e d i a P o
S3 s k a   W i k i p e d i a P o l
S4 k a   W i k i p e d i a P o l s
S5 a   W i k i p e d i a P o l s k
S6   W i k i p e d i a P o l s k a
S7 W i k i p e d i a P o l s k a  
S8 i k i p e d i a P o l s k a   W
S9 k i p e d i a P o l s k a   W i
S10 i p e d i a P o l s k a   W i k
S11 p e d i a P o l s k a   W i k i
S12 e d i a P o l s k a   W i k i p
S13 d i a P o l s k a   W i k i p e
S14 i a P o l s k a   W i k i p e d
S15 a P o l s k a   W i k i p e d i

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

F L
Pozycja 0 S6   W i k i p e d i a P o l s k a
Pozycja 1 S0 P o l s k a   W i k i p e d i a
Pozycja 2 S7 W i k i p e d i a P o l s k a  
Pozycja 3 S5 a   W i k i p e d i a P o l s k
Pozycja 4 S15 a P o l s k a   W i k i p e d i
Pozycja 5 S13 d i a P o l s k a   W i k i p e
Pozycja 6 S12 e d i a P o l s k a   W i k i p
Pozycja 7 S14 i a P o l s k a   W i k i p e d
Pozycja 8 S8 i k i p e d i a P o l s k a   W
Pozycja 9 S10 i p e d i a P o l s k a   W i k
Pozycja 10 S4 k a   W i k i p e d i a P o l s
Pozycja 11 S9 k i p e d i a P o l s k a   W i
Pozycja 12 S2 l s k a   W i k i p e d i a P o
Pozycja 13 S1 o l s k a   W i k i p e d i a P
Pozycja 14 S11 p e d i a P o l s k a   W i k i
Pozycja 15 S3 s k a   W i k i p e d i a P o l

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:

a a   k i e p d W k s i o P i l

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

Algorytm transformaty odwrotnej[edytuj | edytuj kod]

Mamy indeks P = 13 i blok L.

  0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
L a a   k i e p d W k s i o P i l

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

  0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
F   P W a a d e i i i k k l o p s

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:

F L     T
Pozycja 0 S6   W i k i p e d i a P o l s k a   S6→S7 2
Pozycja 1 S0 P o l s k a   W i k i p e d i a   S0→S1 13
Pozycja 2 S7 W i k i p e d i a P o l s k a     S7→S8 8
Pozycja 3 S5 a   W i k i p e d i a P o l s k   S5→S6 0
Pozycja 4 S15 a P o l s k a   W i k i p e d i   S15→S0 1
Pozycja 5 S13 d i a P o l s k a   W i k i p e   S13→S14 7
Pozycja 6 S12 e d i a P o l s k a   W i k i p   S12→S13 5
Pozycja 7 S14 i a P o l s k a   W i k i p e d   S14→S15 4
Pozycja 8 S8 i k i p e d i a P o l s k a   W   S8→S9 11
Pozycja 9 S10 i p e d i a P o l s k a   W i k   S10→S11 14
Pozycja 10 S4 k a   W i k i p e d i a P o l s   S4→S5 3
Pozycja 11 S9 k i p e d i a P o l s k a   W i   S9→S10 9
Pozycja 12 S2 l s k a   W i k i p e d i a P o   S2→S3 15
Pozycja 13 S1 o l s k a   W i k i p e d i a P   S1→S2 12
Pozycja 14 S11 p e d i a P o l s k a   W i k i   S11→S12 6
Pozycja 15 S3 s k a   W i k i p e d i a P o l   S3→S4 10

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 Algorytmu 0 1 2 3 4 5
F L T F L T F L T F L T F L T F L T
Pozycja 0   a 2   a 2   a 2   a 2   2   2
Pozycja 1 P a   P a 13 P a 13 P a 13 P a 13 P 13
Pozycja 2 W     W   W 8 W 8 W 8 W 8
Pozycja 3 a k   a k   a k   a k 0 a k 0 a k 0
Pozycja 4 a i   a i   a i   a i   a i 1 a i 1
Pozycja 5 d e   d e   d e   d e   d e   d e 7
Pozycja 6 e p   e p   e p   e p   e p   e p  
Pozycja 7 i d   i d   i d   i d   i d   i d  
Pozycja 8 i W   i W   i W   i   i   i  
Pozycja 9 i k   i k   i k   i k   i k   i k  
Pozycja 10 k s   k s   k s   k s   k s   k s  
Pozycja 11 k i   k i   k i   k i   k i   k i  
Pozycja 12 l o   l o   l o   l o   l o   l o  
Pozycja 13 o P   o P   o   o   o   o  
Pozycja 14 p i   p i   p i   p i   p i   p i  
Pozycja 15 s l   s l   s l   s l   s l   s l  

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

Przykładowy kod w C[edytuj | edytuj kod]

#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)
    {
        while (i>F[j] && j<size)
            ++j;
        buckets[i] = j; // it will get fake values if there is no i in F, but
                        // that won't bring us any problems
    }
    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ż[edytuj | edytuj kod]

Linki[edytuj | edytuj kod]