// Пример использования
#include <string>
#include <iostream>
#include "HuffmanTree.cpp"
using std::string;
using std::cout;
using std::endl;
int main() {
string text = "KOL_OKOLO_KOLOKOLA";
HuffmanTree* tree = new HuffmanTree(text);
string compress = tree->getCompressed();
cout << compress << endl;
// Выведет 001101101110011011110100110111001101100
system("pause");
}
#include <vector>
#include <map>
#include <string>
using std::string;
using std::vector;
using std::pair;
using std::map;
// Алгоритм:
// 1. Получаем все символы строки и кол-во их вхождений
// 2. Составляем поддеревья из всех символов
// 3. Составляем дерево Хаффмана из всех поддеревьев
// 4. Обходим дерево Хаффмана, создавая набор
// какой символ на что заменить
class HuffmanTree {
private:
// Узел дерева
// Хранит вес
// Хранит либо указатели на поддеревья (один или два)
// Либо символы (один или два)
struct Node {
int weight; // Вес вершины
// Первый и второй символ
char first_symbol;
char second_symbol;
// Указатели на левое и правое поддеревья
Node* left;
Node* right;
// Конструктор создания поддерева
// Где будут хранится один или два символа
// Указатели на поддеревья, соответственно равны nullptr
Node(int weight, char first_symbol, char second_symbol = '\0')
: left(nullptr), right(nullptr) {
this->weight = weight;
this->first_symbol = first_symbol;
this->second_symbol = second_symbol;
}
// Конструктор для создания поддерева
// Где будут хранится указатели на правое и левое поддерево
// Символы, соответственно будут равны \0
Node(int weight, Node* left, Node* right = nullptr)
: first_symbol('\0'), second_symbol('\0') {
this->weight = weight;
this->left = left;
this->right = right;
}
// Функция возаращает true, если узел хранит только один символ
// Нужно при обходе дерева Хаффмана
bool isSingleLeaf() {
return (first_symbol != '\0' && second_symbol == '\0') ||
(first_symbol == '\0' && second_symbol != '\0');
}
// В деструкторе удаляем поддеревья
~Node() {
if (left != nullptr) delete left;
if (right != nullptr) delete right;
}
};
// Увеличиваем кол-во вхождений символа symbol в векторе frequencie на единицу
// Для этого ищем нужный pair. Если такого не нашлось - создаем
void increaseCount(vector<pair<char, int>> &frequencie, char symbol) {
for (auto &pair : frequencie) {
// Если нашли
if (pair.first == symbol) {
pair.second += 1;
return;
}
}
// Если не нашли - добавляем {symbol, 1} в конец вектора
frequencie.push_back(pair<char, int>(symbol, 1));
}
// Возвращает неотсортированный вектор из набора
// символов и кол-ва их вхождений в строку string
// Этот набор реализуется классом std::pair
//
// Из "AABBCA" получим {A, 3}, {B, 2}, {C, 1}
vector<pair<char, int>> getFrequencies(string const &string) {
vector<pair<char, int>> frequencie;
for (auto symbol : string) {
increaseCount(frequencie, symbol);
}
return frequencie;
}
// Функция нужна для извлечения (возврата + удаления)
// набора {Символ => Кол-во вхождений}, с минимальным кол-вом вхождений
pair<char, int> executeMinimalPair(vector<pair<char, int>> &frequencie) {
pair<char, int> result = frequencie[0];
int result_position = 0; // Позиция минимального (возаращаемого) элемента
int i = 0;
for (auto &pair : frequencie) {
if (pair.second < result.second) {
result = pair;
result_position = i;
}
i += 1;
}
// Удаляем минимальный (возвращаемый) элемент
frequencie.erase(frequencie.begin() + result_position);
return result;
}
// Функция нужна дли извлечения (возврата + удаления)
// поддерева с минимальным весом
Node* executeMinimalTree() {
Node* result = nodes[0];
int result_position = 0; // Позиция поддерева с минимальным весом
int position = 0;
for (auto &tree : nodes) {
if (tree->weight < result->weight) {
result = tree;
result_position = position;
}
position += 1;
}
// Удаляем поддерево с минимальным весом из списка вершин
nodes.erase(nodes.begin() + result_position);
return result;
}
// Функция нужна для создания дерева Хаффмана из поддеревьев
// 1. Пока в nodes больше одной вершины - создается их родитель,
// с весом, равным их суммарному весу
// 2. Родитель добавляется в список вершин,
// а его два потомка удаляются из этого списка
// 3. Вызываем функцию, которая обойдет дерево Хаффмана
// и для каждого символа создатс строку, на которую его нужно заменить
void createHuffmanTree() {
// Пока в nodes хранится не одна вершина - не корень дерева
Node* first; // Первое поддерево
Node* second; // Второе поддерево
while (nodes.size() != 1) {
first = executeMinimalTree();
second = executeMinimalTree();
Node* newNode = new Node(first->weight + second->weight, // Вес двух поддеревьев
first, second); // Два поддерева
nodes.push_back(newNode); // Добавляем родителя в список вершин
}
// На данном этапе у нас в nodes хранится одна
// вершина, которая является деревом Хаффмана
// Вызываем функцию, которая обойдет все вершины
// и для каждого символа присвоит свою замену
parseHuffmanTree(nodes[0], "");
}
// Рекурсивная функция для обхода дерева Хаффмана
// 1. Если дерево - узел, то обходим левое поддерево, добавив 0
// обходим правое поддерево, добавив 1
// 2. Если дерево - лист, то надо ли прибавлять 0 или 1 зависит от того
// сколько символов содержит этот узел.
// Если дерево содержит один символ - то 0 или 1 прибавлять не нужно
// Если дерево содержит два символа - то нужно
void parseHuffmanTree(Node* node, string prefix) {
// Если дерево - лист. Т.е. содержит символ, но не содержит поддеревья
if (node->first_symbol != '\0') {
if (node->isSingleLeaf()) replace[node->first_symbol] = prefix;
else replace[node->first_symbol] = prefix + "0";
}
if (node->second_symbol != '\0') {
if (node->isSingleLeaf()) replace[node->second_symbol] = prefix;
else replace[node->second_symbol] = prefix + "1";
}
// Если дерево - узел. Т.е. не содержит символ, а содержит поддеревья
if (node->left != nullptr) {
parseHuffmanTree(node->left, prefix + "0");
}
if (node->right != nullptr) {
parseHuffmanTree(node->right, prefix + "1");
}
}
string text;
vector<Node*> nodes; // Хранится указатели на все поддеревья
map<char, string> replace; // Хранит набор из символов и строк, на которые их заменяем
public:
// Входные данныех: строка
// 1. Для каждого символа вычисляем, сколько раз он встречается в тексте
// Получаем набор из значений {Cимвол => Кол-во вхождений}
// Этот набор реализуется классом std::pair
// 2. Из этого набора получаем все поддеревья и помещаются в вектор nodes
// Поддеревья получаем так:
// I. Выбираем два символа с наименьшими весами
// II. Создаем из них поддерево, с весом, равным их суммарному весу
// III. Помешаем поддерево в в вектор nodes
// Так же нужно обработать ситуацию, когда остался только один символ.
// Тогда создаем поддерево только с одним символом и помещаем его в nodes
// 3. Вызываем функцию, в которой их всех поддеревьев соберем дерево Хаффмана
HuffmanTree(string text) {
this->text = text;
// Получаем набор из {Символ => Сколько раз он встречается в тексте}
vector<pair<char, int>> frequencie = getFrequencies(text);
pair<char, int> first;
pair<char, int> second;
Node* newTree = nullptr;
// При создании поддерева у нас будет два случая
// 1. Когда кол-во оставшихся символов больше или равно двум
// Тогда создается поддерево из двух символов
while (frequencie.size() > 1) {
first = executeMinimalPair(frequencie);
second = executeMinimalPair(frequencie);
Node* newTree = new Node(first.second + second.second, // Сумма весов
first.first, second.first); // Два символа
nodes.push_back(newTree);
}
// 2. Когда кол-во элементов равно единице
// Тогда создаем подедерво с одним символом
if (frequencie.size() == 1) {
first = executeMinimalPair(frequencie);
Node* newTree = new Node(first.second, first.first); // Вес и и один символ
nodes.push_back(newTree);
}
// Вызываем функцию, в которой из всех
// поддеревьев создадим дерево Хаффмана
createHuffmanTree();
}
// Пользовательская функция, возвращает
// набор из симвов и строк, на которые из заменяем
map<char, string> getReplacements() {
return replace;
}
// Пользовательская функция, возвращает сжатую строку
string getCompressed() {
string result;
for (auto symbol : text) {
result += replace[symbol];
}
return result;
}
// В деструкторе удаляем дерево Хаффмана
~HuffmanTree() {
delete nodes[0];
}
};
Comments
0 B
|👍
/👎