БлогNot. C++: пытаемся посчитать коды Хаффмана...

C++: пытаемся посчитать коды Хаффмана...

А ведь код Хаффмана естественней всего реализовать как бинарное дерево и может получиться не хуже, чем "на пальцах".

Увы, мне так и не удалось в имеющиеся для этого пару-тройку часиков получить того, что я хотел, размещаю две "недоделки" здесь для памяти.

Первое приложение довольно примитивно, зато умеет показывать результаты своей работы в простом и понятном виде. Обратите внимание на "странную" функцию index в коде, зачем она нужна - станет ясно в конце статьи.

Вот листинг и результат его прогона.

#define _CRT_SECURE_NO_WARNINGS
#include <Windows.h> /*Библиотека Studio, а не из стандарта*/
#include <clocale>
#include <cstdio>
#include <cstring>

typedef struct node_t {
 struct node_t *left, *right;
 int freq;
 char c;
} *node;

struct node_t pool[256] = { { 0 } };
node qqq[255], *q = qqq - 1;
int n_nodes = 0, qend = 1;
char *code[256] = { 0 }, buf[2048];

int index(char c) { //!!!
 return c < 0 ? c + 256 : c;
}

node createNode (int freq, char c, node a, node b) {
 node n = pool + n_nodes++;
 if (freq) n->c = c, n->freq = freq;
 else {
  n->left = a, n->right = b;
  n->freq = a->freq + b->freq;
 }
 return n;
}

void insertNode (node n) {
 int j, i = qend++;
 while ((j = i / 2)) {
  if (q[j]->freq <= n->freq) break;
  q[i] = q[j], i = j;
 }
 q[i] = n;
}

node removeNode() {
 int i, l;
 node n = q[i = 1];
 if (qend < 2) return 0;
 qend--;
 while ((l = i * 2) < qend) {
  if (l + 1 < qend && q[l + 1]->freq < q[l]->freq) l++;
  q[i] = q[l]; 
  i = l;
 }
 q[i] = q[qend];
 return n;
}

void buildTree(node n, char *s, int len) {
 static char *out = buf;
 if (n->c) {
  s[len] = 0;
  strcpy(out, s);
  code[index(n->c)] = out;
  out += len + 1;
  return;
 }
 s[len] = '0'; buildTree(n->left, s, len + 1);
 s[len] = '1'; buildTree(n->right, s, len + 1);
}

void initTree (const char *s) {
 int i, freq[256] = { 0 };
 char c[16];
 
 for (int i = 0; i < strlen(s); i++) freq[index(s[i])]++;
 //while (*s) freq[(int)*s++]++;

 for (i = 0; i < 256; i++) if (freq[i]) insertNode (createNode(freq[i], i, 0, 0));

 while (qend > 2) insertNode (createNode(0, 0, removeNode(), removeNode()));
 buildTree (q[1], c, 0);
}

void encodeData(const char *s, char *out) {
 while (*s) {
  strcpy(out, code[index(*s)]);
  out += strlen(code[index(*s++)]);
 }
}

void decodeData(const char *s, node t) {
 node n = t;
 while (*s) {
  if (*s++ == '0') n = n->left;
  else n = n->right;
  if (n->c) putchar(n->c), n = t;
 }
 putchar('\n');
 if (t != n) printf("Неверные данные для decodeData\n");
}

int main () {
 setlocale (LC_ALL, "Rus");
 SetConsoleCP (1251); SetConsoleOutputCP(1251); //Эти функции - только в Studio

 int i;
 const char *str = "Носорожек, носорожек, пробежался вдоль дорожек!"; //данные для кодирования
 char buf[1024];

 initTree (str);
 for (i = 0; i < 256; i++) if (code[i]) printf("'%c': %s\n", i, code[i]);

 encodeData(str, buf);
 printf("Закодировано: %s\n", buf);

 printf("Декодировано: ");
 decodeData(buf, q[1]);

 getchar();  return 0;
}
' ': 0010
'!': 100101
',': 00011
'Н': 100100
'а': 01001
'б': 01000
'в': 10110
'д': 00010
'е': 1000
'ж': 0111
'к': 0011
'л': 1010
'н': 10111
'о': 11
'п': 10011
'р': 0110
'с': 0000
'ь': 01011
'я': 01010
Закодировано: 100100110000110110110111100000110001100101011111000011011011011110
00001100011001010011011011010001000011101001101000000101000101011000010111010010
1100100001011011011011110000011100101
Декодировано: Носорожек, носорожек, пробежался вдоль дорожек!

Второй листинг формирует дерево в формате, пригодном для отображения средствами Google API, однако, добиться автоматического запуска браузера и передачи в него данных мне не удалось из-за двойных кавычек, мои попытки задокументированы внизу листинга в многострочном комментарии /* ... */.

#include <Windows.h> /*Библиотека Studio, а не из стандарта*/
#include <queue>
#include <iostream>
#include <sstream>
#include <algorithm>
using namespace std;

class Node {
public:
 string value, code;
 unsigned amount;
 Node *left, *right;

 bool operator() (const Node& x, const Node& y) const { //сравнение
  return x.amount > y.amount;
 }

 Node(const string& value = "", unsigned amount = 0, Node * left = 0, Node * right = 0) {
  this->value = value; //исходная строка
  this->code = ""; //битовое представление
  this->amount = amount; //частота встречаемости
  this->left = left; //потомки
  this->right = right;
 }

 string toString() { //Описание дерева для Гугля
  ostringstream ss;
  if (left != 0 || right != 0) { //Есть или оба потомка, или не одного
   ss << "\t\"" << code << ": " << value << "[" << amount <<
    "]\" -> \"" << left->code << ": " << left->value << "[" << left->amount << "]\";\n";
   ss << left->toString();
   ss << "\t\"" << code << ": " << value << "[" << amount <<
    "]\" -> \"" << right->code << ": " << right->value << "[" << right->amount << "]\";\n";
   ss << right->toString();
  }
  else {
   ss << "\t\"" << code << ": " << value << "[" << amount <<
    "]\" [shape=box, style=filled, fillcolor=green];\n";
  }
  return ss.str();
 }

 Node * join(Node x) { //Объединение деревьев
  return new Node(x.value + value, x.amount + amount, new Node(x), this);
 }
 
 static Node * buildTree(priority_queue<Node, vector<Node>, Node> graph) { //Построитель дерева
  while (graph.size() > 1) {
   Node *n = new Node(graph.top());
   graph.pop();
   graph.push(*n->join(*new Node(graph.top())));
   graph.pop();
  }
  return new Node(graph.top());
 }

 void generateCode (string code) { //Сгенерировать битовые коды
  this->code = code;
  if (left != 0 || right != 0) {
   left->generateCode(code + "1");
   right->generateCode(code + "0");
  }
 }
};

unsigned int amounts[256]; // Массив частоты встречаемости символов

int main() {
 setlocale(LC_ALL, "Rus");
 SetConsoleCP(1251); SetConsoleOutputCP(1251); //Эти функции - только в Studio

 string s("Кодировали-кодировали и выкодировали"); //Исходная строка

                                                   /* char в Studio - знаковые! */
 for (int i = 0; i < s.size(); i++) amounts[s[i]<0 ? s[i] + 256 : s[i]]++;

 priority_queue <Node, vector<Node>, Node> graphData;
 for (int i = 0; i < 256; i++) // Записываем в очередь с приоритетами 
  if (amounts[i] > 0) graphData.emplace(s = (char)i, amounts[i]);
 Node *n = Node::buildTree(graphData);
 n->generateCode("");
 
 //Данные для Google API и открытие ссылки
 string str = "http://chart.apis.google.com/chart?cht=gv&chl=\nDigraph G {\n" +
  n->toString() + "}";
 cout << str << endl; //Ссылка выводится в консоль

 /*
 std::replace(str.begin(),str.end(),'\n', ' '); //избавляемся от переводов строки
 //std::replace(str.begin(), str.end(), string("\""), string("\\"""));
 str = "cmd /c start \"link\" \"" + str + "\""; //пытаемся передать данные в браузер
 //не вышло из-за двойных кавычек
 system(str.c_str()); //Пытаемся вывести команду в браузер
 
 //ShellExecute (0, "open", str.c_str(), 0, 0, SW_SHOWNORMAL); Sleep(1000);
 //так тоже не работает
*/
 cin.sync(); cin.get(); return 0;
}

Однако если скопировать из окна консоли вывод программы (щёлкнуть на кнопку системного меню окна, выбрать подменю Изменить, пункт Пометить, при зажатой Shift клавишами со стрелками выделить весь прямоугольник с текстом в окне, нажать Enter, переключиться в браузер, нажать Ctrl+V в пустой адресной строке, скорее всего, сработает в любом браузере, кроме Internet Explorer), то проблема снимается и мы увидим картинку с деревом, для тестового примера она будет вот такой:

картинка, полученная с URL, формируемого второй программой
картинка, полученная с URL, формируемого второй программой

На этой картинке показан режим выделения в окне консоли, момент перед нажатием Enter для копирования кода диаграммы в буфер обмена.

момент перед нажатием Enter :)
момент перед нажатием Enter :)

Ещё одно узкое место программы, как ни странно, формирование массива частот встречаемости символов amounts. Дело в том, что тип char в Studio - знаковый, и коды букв из второй половины кодовой таблицы ASCII (имеющие числовые значения от 128 до 255) могут оказаться отрицательными значениями, а с явно указанным типом данных unsigned char в Studio не будет работать ряд стандартных функций, да хоть та же strcpy. В стандарте, вообще говоря, ясно не сказано, должен ли тип данных char быть знаковым или беззнаковым.

Оба приложения выполнялись в консоли Studio 2015.

26.10.2017, 16:01 [5143 просмотра]


теги: c++ программирование ошибка памятка числа studio google

К этой статье пока нет комментариев, Ваш будет первым