codeup 9.8小节——树算法专题->哈夫曼树

问题 A: 算法6-12:自底向上的赫夫曼编码

时间限制: 1 Sec  内存限制: 32 MB
提交: 96  解决: 40
[提交][状态][讨论版][命题人:外部导入]

题目描述

在通讯领域,经常需要将需要传送的文字转换成由二进制字符组成的字符串。在实际应用中,由于总是希望被传送的内容总长尽可能的短,如果对每个字符设计长度不等的编码,且让内容中出现次数较多的字符采用尽可能短的编码,则整个内容的总长便可以减少。另外,需要保证任何一个字符的编码都不是另一个字符的编码前缀,这种编码成为前缀编码。

而赫夫曼编码就是一种二进制前缀编码,其从叶子到根(自底向上)逆向求出每个字符的算法可以表示如下:

codeup 9.8小节——树算法专题->哈夫曼树_第1张图片

在本题中,读入n个字符所对应的权值,生成赫夫曼编码,并依次输出计算出的每一个赫夫曼编码。

 

输入

输入的第一行包含一个正整数n,表示共有n个字符需要编码。其中n不超过100。

第二行中有n个用空格隔开的正整数,分别表示n个字符的权值。

输出

共n行,每行一个字符串,表示对应字符的赫夫曼编码。

样例输入

8
5 29 7 8 14 23 3 11

样例输出

0110
10
1110
1111
110
00
0111
010

提示

赫夫曼树又名最优二叉树,它是一类带权路径长度最小的二叉树。通过构造赫夫曼树,我们可以得到赫夫曼编码,从而使得通信能够得到更高的效率。在本题中,构造赫夫曼树的过程使用了从叶子到根的逆向顺序,另外,还有一种从根出发直到叶子的赫夫曼编码构造算法,这将在下一题中进行讨论。

方法一:纯c++风格的HuffManCode

#include
#include
#include
using namespace std;

const int maxn=110;
int n,w[maxn];//结点个数 和 权值数组
string code[maxn];//存放编码  顺序就是元素输入权值的顺序(不是权值排序后的顺序)

//线性结构存储
struct huffManNode{
	int weight;
	int lchild,rchild,parent;//需要知道父亲 以判断是否是叶子结点
}HMNode[2*maxn-1];
//注:lchild,rchild为0表示无孩子结点   parent为0表示无父结点

//(HMNode[]静态数组的)1~i号结点里 找根结点  找到两个权值最小的返回其下标
void Min(int index,int &s1,int &s2){//查找范围[1,index]
	int minw=INT_MAX;
	//找最小s1
	for(int i=1;i<=index;i++){
		if(HMNode[i].parent==0&&HMNode[i].weights2) swap(s1,s2);//此句必要
}

//最终的HuffMan树是HMNode[]数组 (最终1~n只有一个parent=0的根) 
//构造依据是int w[] 和 n   
//编码放到全局字符串数组string code[]里
void HuffManEncoding(){
	if(n<1) return;//一个结点 随便编码
	int m=2*n-1;//Huffman树总结点数

	//初始化原始n个叶子结点
	for(int i=1;i<=n;i++){
		HMNode[i].weight=w[i];
		HMNode[i].parent=HMNode[i].lchild=HMNode[i].rchild=0;
	}

	//并不需要初始化 将要分配的m-n个结点

	//开始正式构建Huffman二叉树
	int s1,s2;
	for(int i=n+1;i<=m;i++){
		Min(i-1,s1,s2);
		HMNode[i].lchild=s1;
		HMNode[i].rchild=s2;
		HMNode[i].parent=0;
		HMNode[i].weight=HMNode[s1].weight+HMNode[s2].weight;
		HMNode[s1].parent=HMNode[s2].parent=i;
	}
	//二叉树构建完毕 (最后第m个时一定恰好找到最后仅剩的两个根结点s1,s2)

	//求HuffMan编码  自底向上求 
	//前提 已知静态数组中前n个一定初始的叶子结点
	char* temp=new char[n];
	int start;
	for(int i=1;i<=n;i++){
		start=n;temp[--start]='\0';	  //f==0到根了
		for(int j=i,f=HMNode[j].parent;f!=0;f=HMNode[j].parent){//j是孩子 f是父亲 一直往上走 直到走到根
			if(HMNode[f].lchild==j) temp[--start]='0';//左0
			else temp[--start]='1';//右1
			j=f;
		}
		code[i]=temp+start;
	}
	delete temp;
}


//本程序 0号结点统一不用
int main(){
	// freopen("inputa.txt","r",stdin);
	while(cin>>n){
		for(int i=1;i<=n;i++){
			cin>>w[i];
		}
		HuffManEncoding();
		for(int i=1;i<=n;i++){
			cout<

精简版:

#include
#include
using namespace std;
const int maxn=110;
int n,w[maxn];//结点个数 和 权值数组
string code[maxn];//存放编码  顺序就是元素输入权值的顺序(不是权值排序后的顺序)
struct huffManNode{
	int weight,lchild,rchild,parent;//需要知道父亲 以判断是否是叶子结点
}HMNode[2*maxn-1];
void Min(int index,int &s1,int &s2){//查找范围[1,index]
	int minw=INT_MAX;
	for(int i=1;i<=index;i++){
		if(HMNode[i].parent==0&&HMNode[i].weights2) swap(s1,s2);//此句必要
}
void HuffManEncoding(){
	if(n<1) return;//一个结点 随便编码
	int m=2*n-1;//Huffman树总结点数
	for(int i=1;i<=n;i++){
		HMNode[i].weight=w[i];
		HMNode[i].parent=HMNode[i].lchild=HMNode[i].rchild=0;
	}
	int s1,s2;
	for(int i=n+1;i<=m;i++){
		Min(i-1,s1,s2);
		HMNode[i].lchild=s1;HMNode[i].rchild=s2;
		HMNode[i].parent=0;HMNode[s1].parent=HMNode[s2].parent=i;
		HMNode[i].weight=HMNode[s1].weight+HMNode[s2].weight;
	}
	char* temp=new char[n];
	int start;
	for(int i=1;i<=n;i++){
		start=n;temp[--start]='\0';	  //f==0到根了
		for(int j=i,f=HMNode[j].parent;f!=0;f=HMNode[j].parent){//j是孩子 f是父亲 一直往上走 直到走到根
			if(HMNode[f].lchild==j) temp[--start]='0';//左0
			else temp[--start]='1';//右1
			j=f;
		}
		code[i]=temp+start;
	}
	delete temp;
}
int main(){
	while(cin>>n){
		for(int i=1;i<=n;i++)cin>>w[i];
		HuffManEncoding();
		for(int i=1;i<=n;i++) cout<

数据结构纯c版

#include
#include
#include
#include
#include
using namespace std;

typedef struct {
	unsigned int weight;
	unsigned int parent, rchild, lchild;
}HTNode, *HuffmanTree;// 动态分配数组存储赫夫曼树
typedef char** HuffmanCode;// 动态分配数组存储赫夫曼编码表

int min(HuffmanTree t, int i) {
	// 返回i个结点中权值最小的树的根结点序号,函数select()调用
	int j, flag;
	unsigned int k = UINT_MAX;// 取k为不小于可能的值(无符号整型最大值)
	for (j = 1; j <= i; j++) {//不能重定义int j=1;屏蔽了外面的j
		if (t[j].weight < k&&t[j].parent == 0) // t[j]是树的根结点
			k = t[j].weight, flag = j;
		//int m=t[j].weight;int x=t[j].parent;
	}
	t[flag].parent = 1; // 给选中的根结点的双亲赋1,避免第2次查找该结点
	return flag;
}

void select(HuffmanTree t, int i, int &s1, int &s2)
{ // 在i个结点中选择2个权值最小的树的根结点序号, s1为其中序号小的那个
	int j;
	s1 = min(t, i);
	s2 = min(t, i);
	if (s1>s2)
	{
		j = s1;
		s1 = s2;
		s2 = j;
	}
}

void HuffmanCoding(HuffmanTree &HT, HuffmanCode &HC, int *w, int n) {
	//w存放n个结点的权值,(最后的叶子结点,且均大于0),
	//构造huffman树HT,并求出n个字符的Huffman编码HC
	int m, i, s1, s2, start;
	unsigned c, f;
	HuffmanTree p;
	char* cd;//存字符串(每个字符的Huffman编码)
	if (n <= 1)
		return;
	m = 2 * n - 1;//二叉树结点数=2*叶子节点数-1
	HT = (HuffmanTree)malloc((m + 1) * sizeof(HTNode));//0号单元未用
	//初始化Huffman树 都是根结点 也都是叶子结点
	for (p = HT + 1, i = 1;i <= n;++i, ++p, ++w) {
		p->weight = *w;
		p->parent = 0;
		p->lchild = 0;
		p->rchild = 0;//千万不要写成HT->rchild  应是(HT+1)->rchild
	}
	//上面只初始化了n个叶子结点 实际上最终和Huffman有m=2*n-1个结点  下面先对未初始化的m-n个结点初始化下
	for (;i <= m;i++, p++) {
		(*p).parent = 0;//后面遍历时要用到parent是否为0
		(*p).lchild = 0;
		(*p).rchild = 0;
		(*p).weight = 0;
	}
	//建Huffman树
	for (i = n + 1;i <= m;i++) {
			//每次执行 前i-1个是已经生成的结点  在已经生成的结点中找权值最小的根结点
		// 在HT[1~i-1]中选择parent为0且weight最小的两个结点,其序号分别为s1和s2
		select(HT, i - 1,s1,s2);
		HT[s1].parent = HT[s2].parent = i;//两个最小权值的根结点组成一个新的根结点
		HT[i].lchild = s1, HT[i].rchild = s2;
		HT[i].weight = HT[s1].weight + HT[s2].weight;
	}


	// 从叶子到根逆向求每个字符的赫夫曼编码

	/*
	注意char*:
	1.HuffmanCode是二级指针所以单个元素值为char*
	2.Huffman数组HC[1~n]存n个字符串,n个Huffman码
	*/	
	HC = (HuffmanCode)malloc((n + 1) * sizeof(char*));//[0]不用

	/*	
	先用字符指针cd暂存好单个Huffman码,然后一一存进HC数组中
	不定义字符数组了,动态分配,节省空间
	*/	
	cd = (char*)malloc(n*sizeof(char));
	
	cd[n - 1] = '\0';//c语言字符串必备结束符
	for (i = 1;i <= n;i++) {// 逐个字符求赫夫曼编码
		start = n - 1;//编码结束位置,倒着求并赋进编码
		for (c = i, f = HT[i].parent;f != 0;c = f, f = HT[f].parent) {// 从叶子到根逆向求编码
			if (HT[f].lchild == c)//c不能写成i,仅第一次不错,后来更新的是c,i值一直未变
				cd[--start] = '0';
			else if (HT[f].rchild == c)
				cd[--start] = '1';//字符‘0’,不是数字0
			HC[i] = (char*)malloc((n - start) * sizeof(char));//分配恰好存Huffman码的空间
			strcpy(HC[i], &cd[start]);//从cd赋值有效编码串到HC[i]
		}
	}
	free(cd);//循环结束,动态申请的cd该释放了
}

int main() {
	HuffmanCode HC;
	HuffmanTree HT;
	int n;
	while(cin >> n){
		int* w = (int*)malloc(n*sizeof(int));
		for (int i = 0;i < n;i++) {
			cin >> w[i];
		}
		HuffmanCoding(HT,HC,w,n);
		for (int i = 1;i <= n;i++) {//i又从0开始遍历了,找了好久才找出错
			puts(HC[i]);
		}
		free(w);
	}
	return 0;
}

个人比较喜欢第一种写法

 

问题 B: 算法6-13:自顶向下的赫夫曼编码

时间限制: 1 Sec  内存限制: 32 MB
提交: 56  解决: 40
[提交][状态][讨论版][命题人:外部导入]

题目描述

在本题中,我们将要讨论的是自顶向下的赫夫曼编码算法。从根出发,遍历整棵赫夫曼树从而求得各个叶子结点所表示的字符串。算法的关键部分可以表示如下:

codeup 9.8小节——树算法专题->哈夫曼树_第2张图片

在本题中,读入n个字符所对应的权值,生成赫夫曼编码,并依次输出计算出的每一个赫夫曼编码。

 

输入

输入的第一行包含一个正整数n,表示共有n个字符需要编码。其中n不超过100。

第二行中有n个用空格隔开的正整数,分别表示n个字符的权值。

输出

共n行,每行一个字符串,表示对应字符的赫夫曼编码。

样例输入

8
5 29 7 8 14 23 3 11

样例输出

0110
10
1110
1111
110
00
0111
010

提示

在本题中,与上一题不同的是在求赫夫曼编码的过程中,使用了从根出发开始遍历整棵赫夫曼树的自顶向下的算法。通过这两道题目的联系,应该能够熟练的掌握赫夫曼树和赫夫曼编码的构造和使用方法了。

本题与上一题为一区别在于求赫夫曼编码方向变了,既然要自顶向下,很自然想到深搜,一个DFS解决了

仅在上题基础上对75~80行做了修改 用DFS实现求编码

#include
#include
#include
using namespace std;

const int maxn=110;
int n,w[maxn];//结点个数 和 权值数组
string code[maxn];//存放编码  顺序就是元素输入权值的顺序(不是权值排序后的顺序)

//线性结构存储
struct huffManNode{
	int weight;
	int lchild,rchild,parent;//需要知道父亲 以判断是否是叶子结点
}HMNode[2*maxn-1];
//注:lchild,rchild为0表示无孩子结点   parent为0表示无父结点

//(HMNode[]静态数组的)1~i号结点里 找根结点  找到两个权值最小的返回其下标
void Min(int index,int &s1,int &s2){//查找范围[1,index]
	int minw=INT_MAX;
	//找最小s1
	for(int i=1;i<=index;i++){
		if(HMNode[i].parent==0&&HMNode[i].weights2) swap(s1,s2);//此句必要
}

void DFS(int i,string temp){
	if(HMNode[i].lchild==0){//竟然写成这样 MNode[i].lchild==HMNode[i].rchild==0 蠢死了
		code[i]=temp;//注意该编码赋值到该节点对应下标处 否则哪个编码对应哪个字符就乱了
		return;//不要忘了
	}
	DFS(HMNode[i].lchild,temp+"0");
	DFS(HMNode[i].rchild,temp+"1");
}

//最终的HuffMan树是HMNode[]数组 (最终1~n只有一个parent=0的根) 
//构造依据是int w[] 和 n   
//编码放到全局字符串数组string code[]里
void HuffManEncoding(){
	if(n<1) return;//一个结点 随便编码
	int m=2*n-1;//Huffman树总结点数

	//初始化原始n个叶子结点
	for(int i=1;i<=n;i++){
		HMNode[i].weight=w[i];
		HMNode[i].parent=HMNode[i].lchild=HMNode[i].rchild=0;
	}

	//开始正式构建Huffman二叉树
	int s1,s2;
	for(int i=n+1;i<=m;i++){
		Min(i-1,s1,s2);
		HMNode[i].lchild=s1;
		HMNode[i].rchild=s2;
		HMNode[i].parent=0;
		HMNode[i].weight=HMNode[s1].weight+HMNode[s2].weight;
		HMNode[s1].parent=HMNode[s2].parent=i;
	}
	//二叉树构建完毕 (最后第m个时一定恰好找到最后仅剩的两个根结点s1,s2)

	//与A题相比 仅以下代码不同
	//求HuffMan编码  自顶向下
	//由前面代码逻辑可知 最后一个结点即:m号结点为根结点 则从根开始先序遍历 DFS
	//由于要用到递归,则新建一个函数
	string temp="";
	DFS(m,temp);
}

//本程序 0号结点统一不用
int main(){
	// freopen("inputb.txt","r",stdin);
	while(cin>>n){
		for(int i=1;i<=n;i++){
			cin>>w[i];
		}
		HuffManEncoding();
		for(int i=1;i<=n;i++){
			cout<

 

问题 C: 哈夫曼树

时间限制: 1 Sec  内存限制: 32 MB
提交: 162  解决: 97
[提交][状态][讨论版][命题人:外部导入]

题目描述

哈夫曼树,第一行输入一个数n,表示叶结点的个数。需要用这些叶结点生成哈夫曼树,根据哈夫曼树的概念,这些结点有权值,即weight,题目需要输出所有结点的值与权值的乘积之和。

输入

输入有多组数据。
每组第一行输入一个数n,接着输入n个叶节点(叶节点权值不超过100,2<=n<=1000)。

输出

输出权值。

样例输入

2
2 8 
3
5 11 30 

样例输出

10
62

本题可以转化为合并果子问题,来简化逻辑,不需要构建HuffMan树了

#include
#include
using namespace std;
int main(){
	// freopen("inputc.txt","r",stdin);
	int n,t,x,y,ans;
	priority_queue,greater > q;
	while(cin>>n){
		ans=0;
		for(int i=0;i>t;
			q.push(t);
		}
		while(q.size()>1){
			x=q.top();q.pop();
			y=q.top();q.pop();
			q.push(x+y);
			ans+=(x+y);
		}
		q.pop();
		cout<

 

问题 D: Haffman编码

时间限制: 1 Sec  内存限制: 128 MB
提交: 62  解决: 48
[提交][状态][讨论版][命题人:外部导入]

题目描述

哈弗曼编码大家一定很熟悉吧(不熟悉也没关系,自己查去。。。)。现在给你一串字符以及它们所对应的权值,让你构造哈弗曼树,从而确定每个字符的哈弗曼编码。当然,这里有一些小规定:

1.规定哈弗曼树的左子树编码为0,右子树编码为1;

2.若两个字符权值相同,则ASCII码值小的字符为左孩子,大的为右孩子;

3.创建的新节点所代表的字符与它的做孩子的字符相同;

4.所有字符为ASCII码表上32-96之间的字符(即“ ”到“`”之间的字符)。

输入

输入包含多组数据(不超过100组)
每组数据第一行一个整数n,表示字符个数。接下来n行,每行有一个字符ch和一个整数weight,表示字符ch所对应的权值,中间用空格隔开。
输入数据保证每组测试数据的字符不会重复。

输出

对于每组测试数据,按照输入顺序输出相应的字符以及它们的哈弗曼编码结果,具体格式见样例。

样例输入

3
a 10
b 5
c 8
4
a 1
b 1
c 1
d 1

样例输出

a:0
b:10
c:11
a:00
b:01
c:10
d:11

改一下Min的逻辑和新建结点的逻辑即可

改了8 40 65 91行

#include
#include
#include
using namespace std;

const int maxn=110;
int n,w[maxn];//结点个数 和 权值数组
char ch[maxn];
string code[maxn];//存放编码  顺序就是元素输入权值的顺序(不是权值排序后的顺序)

//线性结构存储
struct huffManNode{
	int weight;
	int lchild,rchild,parent;//需要知道父亲 以判断是否是叶子结点
}HMNode[2*maxn-1];
//注:lchild,rchild为0表示无孩子结点   parent为0表示无父结点

//(HMNode[]静态数组的)1~i号结点里 找根结点  找到两个权值最小的返回其下标
void Min(int index,int &s1,int &s2){//查找范围[1,index]
	int minw=INT_MAX;
	//找最小s1
	for(int i=1;i<=index;i++){
		if(HMNode[i].parent==0&&HMNode[i].weights2) swap(s1,s2);//此句必要 (eg:两个权值一样大如何?)
	if(ch[s1]>ch[s2]) swap(s1,s2);//改 ascii小的在前 而不是序号小的在前了
}

//最终的HuffMan树是HMNode[]数组 (最终1~n只有一个parent=0的根) 
//构造依据是int w[] 和 n   
//编码放到全局字符串数组string code[]里
void HuffManEncoding(){
	if(n<1) return;//一个结点 随便编码
	int m=2*n-1;//Huffman树总结点数

	//初始化原始n个叶子结点
	for(int i=1;i<=n;i++){
		HMNode[i].weight=w[i];
		HMNode[i].parent=HMNode[i].lchild=HMNode[i].rchild=0;
	}

	//开始正式构建Huffman二叉树
	int s1,s2;
	for(int i=n+1;i<=m;i++){
		Min(i-1,s1,s2);
		HMNode[i].lchild=s1;
		HMNode[i].rchild=s2;
		HMNode[i].parent=0;
		HMNode[i].weight=HMNode[s1].weight+HMNode[s2].weight;
		HMNode[s1].parent=HMNode[s2].parent=i;
		ch[i]=ch[s1];
	}
	//二叉树构建完毕 (最后第m个时一定恰好找到最后仅剩的两个根结点s1,s2)

	//求HuffMan编码  自底向上求 
	//前提 已知静态数组中前n个一定初始的叶子结点
	char* temp=new char[n];
	int start;
	for(int i=1;i<=n;i++){
		start=n;temp[--start]='\0';	  //f==0到根了
		for(int j=i,f=HMNode[j].parent;f!=0;f=HMNode[j].parent){//j是孩子 f是父亲 一直往上走 直到走到根
			if(HMNode[f].lchild==j) temp[--start]='0';//左0
			else temp[--start]='1';//右1
			j=f;
		}
		code[i]=temp+start;
	}
	delete temp;
}


//本程序 0号结点统一不用
int main(){
	// freopen("inputd.txt","r",stdin);
	// char c;
	while(cin>>n){
		for(int i=1;i<=n;i++){
			cin>>ch[i]>>w[i];//c用来缓冲空格
		}
		HuffManEncoding();
		for(int i=1;i<=n;i++){
			cout<

 

问题 E: 合并果子-NOIP2004TGT2

时间限制: 1 Sec  内存限制: 128 MB
提交: 130  解决: 87
[提交][状态][讨论版][命题人:外部导入]

题目描述

合并果子

(fruit.pas/c/cpp)

  在一个果园里,多多已经将所有的果子打了下来,而且按果子的不同种类分成了不同的堆。多多决定把所有的果子合成一堆。每一次合并,多多可以把两堆果子合并到一起,消耗的体力等于两堆果子的重量之和。可以看出,所有的果子经过n-1次合并之后,就只剩下一堆了。多多在合并果子时总共消耗的体力等于每次合并所耗体力之和。

  因为还要花大力气把这些果子搬回家,所以多多在合并果子时要尽可能地节省体力。假定每个果子重量都为1,并且已知果子的种类数和每种果子的数目,你的任务是设计出合并的次序方案,使多多耗费的体力最少,并输出这个最小的体力耗费值。
  例如有3种果子,数目依次为1,2,9。可以先将1、2堆合并,新堆数目为3,耗费体力为3。接着,将新堆与原先的第三堆合并,又得到新的堆,数目为12,耗费体力为12。
所以多多总共耗费体力=3+12=15。可以证明15为最小的体力耗费值。

输入

  输入包括两行,第一行是一个整数n(1<=n<=10000),表示果子的种类数。
  第二行包含n个整数,用空格分隔,第i个整数ai(1<=ai<=20000)是第i种果子的数目。

输出

  输出包括一行,这一行只包含一个整数,也就是最小的体力耗费值。输入数据保证这个值小于2^31。

样例输入

3
1 2 9

样例输出

15

提示


本题请用两张算法完成:
1.堆的应用
2.单调队列的应用

 

c题代码就可以过

#include
#include
using namespace std;
int main(){
	// freopen("inputc.txt","r",stdin);
	int n,t,x,y,ans;
	priority_queue,greater > q;
	while(cin>>n){
		ans=0;
		for(int i=0;i>t;
			q.push(t);
		}
		while(q.size()>1){
			x=q.top();q.pop();
			y=q.top();q.pop();
			q.push(x+y);
			ans+=(x+y);
		}
		q.pop();
		cout<

 

你可能感兴趣的:(算法竞赛,codeup墓地,数据结构,#,树)