Метод Хаффмана
Этот алгоритм кодирования информации был предложен Д.А. Хаффманом в 1952 году. Хаффмановское кодирование (сжатие) – это широко используемый метод сжатия, присваивающий символам алфавита коды переменной длины основываясь на вероятностях появления этих символов.
Идея алгоритма состоит в следующем: зная вероятности вхождения символов в исходный текст, можно описать процедуру построения кодов переменной длины, состоящих из целого количества битов. Символам с большей вероятностью присваиваются более короткие коды. Таким образом, в этом методе при сжатии данных каждому символу присваивается оптимальный префиксный код, основанный на вероятности его появления в тексте.
Префиксный код – это код, в котором никакое кодовое слово не является префиксом любого другого кодового слова. Эти коды имеют переменную длину.
Оптимальный префиксный код – это префиксный код, имеющий минимальную среднюю длину.
Алгоритм Хаффмана можно разделить на два этапа.
1) Определение вероятности появления символов в исходном тексте.
Первоначально необходимо прочитать исходный текст полностью и подсчитать вероятности появления символов в нем (иногда подсчитывают, сколько раз встречается каждый символ). Если при этом учитываются все 256 символов, то не будет разницы в сжатии текстового или файла иного формата.
2) Нахождение оптимального префиксного кода.
Далее находятся два символа a и b с наименьшими вероятностями появления и заменяются одним фиктивным символом x, который имеет вероятность появления, равную сумме вероятностей появления символов a и b. Затем, используя эту процедуру рекурсивно, находится оптимальный префиксный код для меньшего множества символов (где символы a и b заменены одним символом x). Код для исходного множества символов получается из кодов замещающих символов путем добавления 0 или 1 перед кодом замещающего символа, и эти два новых кода принимаются как коды заменяемых символов. Например, код символа a будет соответствовать коду x с добавленным нулем перед этим кодом, а для символа b перед кодом символа x будет добавлена единица.
Коды Хаффмана имеют уникальный префикс, что и позволяет однозначно их декодировать, несмотря на их переменную длину.
Пример 1. Программная реализация метода Хаффмана.
#include "stdafx.h"
#include <iostream>
using namespace std;
void Expectancy();
long MinK();
void SumUp();
void BuildBits();
void OutputResult(char **Result);
void Clear();
const int MaxK = 1000;
long k[MaxK + 1], a[MaxK + 1], b[MaxK + 1];
char bits[MaxK + 1][40];
char sk[MaxK + 1];
bool Free[MaxK + 1];
char *res[256];
long i, j, n, m, kj, kk1, kk2;
char str[256];
int _tmain(int argc, _TCHAR* argv[]){
char *BinaryCode;
Clear();
cout << "Введите строку для кодирования : ";
cin >> str;
Expectancy();
SumUp();
BuildBits();
OutputResult(&BinaryCode);
cout << "Закодированная строка : " << endl;
cout << BinaryCode << endl;
system("pause");
return 0;
}
//описание функции обнуления данных в массивах
void Clear(){
for (i = 0; i < MaxK + 1; i++){
k[i] = a[i] = b[i] = 0;
sk[i] = 0;
Free[i] = true;
for (j = 0; j < 40; j++)
bits[i][j] = 0;
}
}
/*описание функции вычисления вероятности вхождения каждого символа в тексте*/
void Expectancy(){
long *s = new long[256];
for ( i = 0; i < 256; i++)
s[i] = 0;
for ( n = 0; n < strlen(str); n++ )
s[str[n]]++;
j = 0;
for ( i = 0; i < 256; i++)
if ( s[i] != 0 ){
j++;
k[j] = s[i];
sk[j] = i;
}
kj = j;
}
/*описание функции нахождения минимальной частоты символа в исходном тексте*/
long MinK(){
long min;
i = 1;
while ( !Free[i] && i < MaxK) i++;
min = k[i];
m = i;
for ( i = m + 1; i <= kk2; i++ )
if ( Free[i] && k[i] < min ){
min = k[i];
m = i;
}
Free[m] = false;
return min;
}
//описание функции подсчета суммарной частоты символов
void SumUp(){
long s1, s2, m1, m2;
for ( i = 1; i <= kj; i++ ){
Free[i] = true;
a[i] = 0;
b[i] = 0;
}
kk1 = kk2 = kj;
while (kk1 > 2){
s1 = MinK();
m1 = m;
s2 = MinK();
m2 = m;
kk2++;
k[kk2] = s1 + s2;
a[kk2] = m1;
b[kk2] = m2;
Free[kk2] = true;
kk1--;
}
}
//описание функции формирования префиксных кодов
void BuildBits(){
strcpy(bits[kk2],"1");
Free[kk2] = false;
strcpy(bits[a[kk2]],bits[kk2]);
strcat( bits[a[kk2]] , "0");
strcpy(bits[b[kk2]],bits[kk2]);
strcat( bits[b[kk2]] , "1");
i = MinK();
strcpy(bits[m],"0");
Free[m] = true;
strcpy(bits[a[m]],bits[m]);
strcat( bits[a[m]] , "0");
strcpy(bits[b[m]],bits[m]);
strcat( bits[b[m]] , "1");
for ( i = kk2 - 1; i > 0; i-- )
if ( !Free[i] ) {
strcpy(bits[a[i]],bits[i]);
strcat( bits[a[i]] , "0");
strcpy(bits[b[i]],bits[i]);
strcat( bits[b[i]] , "1");
}
}
//описание функции вывода данных
void OutputResult(char **Result){
(*Result) = new char[1000];
for (int t = 0; i < 1000 ;i++)
(*Result)[t] = 0;
for ( i = 1; i <= kj; i++ )
res[sk[i]] = bits[i];
for (i = 0; i < strlen(str); i++)
strcat( (*Result) , res[str[i]]);
}
Алгоритм Хаффмана универсальный, его можно применять для сжатия данных любых типов, но он малоэффективен для файлов маленьких размеров (за счет необходимости сохранения словаря). В настоящее время данный метод практически не применяется в чистом виде, обычно используется как один из этапов сжатия в более сложных схемах. Это единственный алгоритм, который не увеличивает размер исходных данных в худшем случае (если не считать необходимости хранить таблицу перекодировки вместе с файлом).