实验10.2 霍夫曼编码
格式
输入
一串小写字母组成的字符串(不超过1000000)。
输出
输出这个字符串通过Huffman编码后的长度。
思路和探讨
优先级队列相关知识
整体思路描述
-
首先是链队列的应用
-
然后完成小根堆相关准备(和 10.1 原理相同不过是链表形式)
-
最后定义实现霍夫曼树(主要是建树函数以及计算霍夫曼编码长度函数)
构建霍夫曼树
- 初始化二叉树集合,每个二叉树含一个外部节点,每个外部节点代表字符串中一个不同的字符
- 从集合中选择两棵具有最小权值的二叉树,并把它们合并成一棵新的二叉树。
- 合并方法是把这两棵二叉树分别作为左右子树
- 然后增加一个新的根节点,新二叉树的权值为两棵子树权值之和。
- 重复第2步,直到仅剩下一棵树为止。
代码流程示例
若已看懂思路,试着自己写~
实现代码
#include<iostream>
#include<string>
using namespace std;
/*------链队列的应用start(实验七代码挪用)------*/
//链表节点的结构定义
template <class T>
struct chainNode
{
//数据成员
T element;
chainNode<T> *next;
//方法
chainNode() {}
chainNode(const T& element)
{this->element = element;}
chainNode(const T& element, chainNode<T>* next)
{this->element = element;
this->next = next;}
};
//链队列定义
template<class T>
class linkedQueue
{
public:
linkedQueue(int initialCapacity = 10)//初始化
{queueFront = NULL; queueSize = 0;}
~linkedQueue();//析构函数
bool empty() const
{return queueSize == 0;}
int size() const
{return queueSize;}
T& front()//返回头元素的引用
{
if (queueSize == 0)
exit(1);
return queueFront->element;
}
T& back()//返回尾元素的引用
{
if (queueSize == 0)
exit(1);
return queueBack->element;
}
void pop();//删除首元素
void push(const T& theElement);//把元素theElement加入队尾
private:
chainNode<T>* queueFront;//头结点
chainNode<T>* queueBack;//尾结点
int queueSize;//队列元素的个数
};
//析构函数
template<class T>
linkedQueue<T>::~linkedQueue()
{
while (queueFront != NULL)
{//原理即往后一个,删掉上一个,直至删完
chainNode<T>* nextNode = queueFront->next;
delete queueFront;
queueFront = nextNode;
}
}
//删除首元素
template<class T>
void linkedQueue<T>::pop()
{
if (queueFront == NULL)
exit(1);
chainNode<T>* nextNode = queueFront->next;
delete queueFront;
queueFront = nextNode;
queueSize--;
}
//把元素theElement加入队尾
template<class T>
void linkedQueue<T>::push(const T& theElement)
{
chainNode<T>* newNode = new chainNode<T>(theElement, NULL);
if (queueSize == 0)
queueFront = newNode;
else
queueBack->next = newNode;
queueBack = newNode;
queueSize++;
}
/*------链队列的应用finish(实验七代码挪用)------*/
/*------小根堆相关准备start(原理同10.1不过是链表表示)------*/
//定义霍夫曼节点
struct HuffmanNode
{
int element;
HuffmanNode *leftChild, *rightChild;//左右孩子
int weight;
HuffmanNode(){}
};
//小根堆定义及实现
class minHeap
{
public:
minHeap() { length = 0; p = new HuffmanNode*[26]; }//构造
~minHeap() { delete[]p; }//析构
void initialize(HuffmanNode** a, int n);//初始化
void insert(HuffmanNode* num);//插入
void pop();//删除
HuffmanNode* top() { return p[1];}//返回堆顶
private:
int length;//堆的元素个数
HuffmanNode **p;
};
//初始化
void minHeap::initialize(HuffmanNode **a, int theSize)
{
length = theSize;
for (int i = 0; i < theSize; i++)
p[i + 1] = a[i];//把给定的a赋到p
//堆化
for (int root = length / 2; root >= 1; root--)
{
HuffmanNode* rootElement = p[root];
//为元素 rootElement寻找位置
int child = 2 * root;//孩子child的双亲是元素rootElement的位置
while (child <= length)
{
//heap[child]应该是兄弟中的较小者
if (child < length && p[child]->element > p[child + 1]->element)
{
child++;
}
//可以把rootElement放在heap[child / 2]吗?
//可以
if (rootElement->element <= p[child]->element)
break;
//不可以
p[child / 2] = p[child];
child = child * 2;
}
p[child / 2] = rootElement;
}
}
//插入函数
void minHeap::insert(HuffmanNode* num)
{
int current = length + 1;//元素将要插入的位置(从最底下开始
length++;
while (current > 1 && p[current / 2]->element > num->element)
{//这个时候老叶子比新叶子大,不能把元素放在这
p[current] = p[current / 2];//把大的那个元素赋给current,相当于把大的元素往下移
current = current / 2;//同时把current(一个打算插入theElement的位置)移向双亲,就往上移
}
//循环结束,即找到合适的位置插入
p[current] = num;
}
//删除函数
void minHeap::pop()
{
HuffmanNode* lastElement = p[length];
int root = 1;
int child = 2;
length--;
while (child <= length)
{
//p[child]应该是current的更大的孩子(就是说它的值太大了,应该往后头放)
if (child < length && p[child]->element > p[child + 1]->element)
{
child++;
}
//可以把lastElement放在heap[currentNode]吗?
//可以
if (lastElement->element <= p[child]->element)
break;
//不可以
p[root] = p[child];//把孩子child向上移动
root = child;//向下移动一层寻找位置
child = child * 2;
}
p[root] = lastElement;
}
/*------小根堆相关准备finish(原理同10.1不过是链表表示)------*/
/*------霍夫曼树实现start------*/
//霍夫曼树类定义
class Huffmantree
{
public:
HuffmanNode *root;//定义根
Huffmantree() {}//构造函数
~Huffmantree();//析构函数
void maketree(int *a,int n);//n为字符串的size
void length();//霍夫曼编码长度
private:
int size;
};
//析构函数
Huffmantree::~Huffmantree()
{
linkedQueue<HuffmanNode*> q;
q.push(root);
for (int i = 0; i < size; i++)
{
HuffmanNode* currentNode = q.front();
q.pop();
if (currentNode->leftChild != NULL)
{
q.push(currentNode->leftChild);
}
if (currentNode->rightChild != NULL)
{
q.push(currentNode->rightChild);
}
delete currentNode;
}
}
//建立一棵霍夫曼树 (参考p320)
void Huffmantree::maketree(int *a, int n)
{
HuffmanNode **hufnode = new HuffmanNode*[n];
for (int i = 0; i < n; i++)
{//置空
hufnode[i] = new HuffmanNode();
hufnode[i]->element = a[i];
hufnode[i]->leftChild = hufnode[i]->rightChild = NULL;
}
//构造一个小根堆
minHeap h;
h.initialize(hufnode, n);
//不断从小根堆中提取两个树合并,直到剩下一棵树
HuffmanNode *w, *x, *y;
for (int i = 1; i < n; i++)
{
//从小根堆中取出两棵最轻的树
x = h.top(); h.pop();
y = h.top(); h.pop();
//合并成一棵树
w = new HuffmanNode();
w->leftChild = x;
w->rightChild = y;
w->element = x->element + y->element;
x = NULL;
y = NULL;
h.insert(w);
}
size = n;
root = h.top();
h.pop();
}
//计算霍夫曼编码的长度
void Huffmantree::length()
{
int result = 0;
linkedQueue<HuffmanNode*> Q;
HuffmanNode *currentNode;
Q.push(root);
root->weight = 0;
/*长度计算原理:出现次数(在这里是element)×对应的编码长度(就是weight),最后依次相加*/
while (!Q.empty())
{
currentNode = Q.front();
Q.pop();
if (currentNode->leftChild != NULL)
{
Q.push(currentNode->leftChild);
currentNode->leftChild->weight = currentNode->weight + 1;
}
if (currentNode->rightChild != NULL)
{
Q.push(currentNode->rightChild);
currentNode->rightChild->weight = currentNode->weight + 1;
}
if (currentNode->leftChild == NULL && currentNode->rightChild == NULL)
{
result = result + currentNode->weight*currentNode->element;
}
}
cout << result;
}
/*------霍夫曼树实现finish------*/
int main()
{
string str;
int index = 0,n= 0;
cin >> str;
//统计各个字母出现频率
int *a = new int[26];
//初始化
for (int i = 0; i < 26; i++)
a[i] = 0;
//遍历一次,找出出现的字母种类n
for (int j = 0; j < str.size(); j++)
{
a[(int)str[j] - 97]++;
if (a[(int)str[j] - 97] == 1)
{//字母计数,出现了一次那么n++
n++;
}
}
//从26缩到n
int *b = new int[n];
for (int k = 0;k < 26; k++)
{
if (a[k] != 0)
{
b[index] = a[k];//直接把次数存进去,拿去构建霍夫曼树
index++;
}
}
Huffmantree Huff;
Huff.maketree(b,n);
Huff.length();
return 0;
}