实验10.2 霍夫曼编码

实验10.2 霍夫曼编码

格式

输入

一串小写字母组成的字符串(不超过1000000)。

输出

输出这个字符串通过Huffman编码后的长度。

思路和探讨

优先级队列相关知识

笔记补充——第十二章:优先级队列,指路12.6.3

整体思路描述

  • 首先是链队列的应用

  • 然后完成小根堆相关准备(和 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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

啦啦右一

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值