深圳大学数据结构OJ-哈夫曼编码及应用

深圳大学数据结构OJ-哈夫曼编码及应用

问题A:DS二叉树–赫夫曼树的构建与编码

题目描述

给定n个权值,根据这些权值构造huffman树,并进行huffman编码

大家参考课本算法6.12为主,注意数组访问是从位置1开始

要求:赫夫曼的构建中,默认左孩子权值不大于右孩子权值

输入

第一行先输入n,表示有n个权值,即有n个叶子

第二行输入n个权值,权值全是小于1万的正整数

输出

逐行输出每个权值对应的编码,格式如下:权值-编码
即每行先输出1个权值,再输出一个短划线,再输出对应编码

接着下一行输出下一个权值和编码,以此类推

输入样例

5
15 4 4 3 2

输出样例

15-1
4-010
4-011
3-001
2-000

AC代码

#include <iostream>
#include <algorithm>
using namespace std;
int num;
//结点定义
typedef struct HNode
{
	int weight;
	int lchild, rchild, parent;
	string code;//存储哈夫曼编码
	HNode()
	{
		lchild = 0;
		rchild = 0;
		parent = 0;
	}
}*HuffmanTree;
//返回首次出现的权值最小根结点的下标
int SelectHNode(HuffmanTree& ht, int i)
{
	int min = 10001;
	int flag = 1;//用来记录当前权值最小结点的下标
	for (int j = 1; j < i; j++)
	{
		if (ht[j].parent == 0 && ht[j].weight < min)
		{
			min = ht[j].weight;
			flag = j;
		}
	}
	return flag;
}
//创建哈夫曼树
void CreatHuffmanTree(HuffmanTree& ht)
{
	//输入每个结点的权值(从1开始存储)
	for (int i = 1; i <= num; i++)
	{
		cin >> ht[i].weight;
	}
	//开始构造哈夫曼树
	for (int i = num + 1; i < 2 * num; i++)//从num+1处开始建立新结点直到2*num处的结点创建完毕为止
	{
		//处理新结点的左孩子
		int left = SelectHNode(ht, i);
		ht[left].parent = i;
		ht[i].lchild = left;
		//处理新结点的右孩子
		int right = SelectHNode(ht, i);
		ht[right].parent = i;
		ht[i].rchild = right;
		//处理新结点的权值
		ht[i].weight = ht[left].weight + ht[right].weight;
	}
}
//得到哈夫曼编码
void GetHuffmanCode(HuffmanTree& ht)
{
	int parent_index;
	int now_index;
	//处理每一个结点
	for (int i = 1; i <= num; i++)
	{
		parent_index = i;
		while (ht[parent_index].parent!=0)
		{
			now_index = parent_index;
			parent_index = ht[parent_index].parent;
			ht[i].code += (ht[parent_index].lchild == now_index) ? "0" : "1";
		}
		//需要包含头文件<algorithm>
		//将存放了编码的字符串反转
		reverse(ht[i].code.begin(), ht[i].code.end());
		cout << ht[i].weight << "-" << ht[i].code << endl;
	}
}
int main()
{
	cin >> num;
	HuffmanTree ht = new HNode[2 * num];
    //创建哈夫曼树
	CreatHuffmanTree(ht);
	//获取哈夫曼编码
    GetHuffmanCode(ht);

	return 0;
}

问题B:DS二叉树–赫夫曼树解码

题目描述

已知赫夫曼编码算法和程序,在此基础上进行赫夫曼解码

可以增加一个函数:int Decode(const string codestr, char txtstr[]);//输入编码串codestr,输出解码串txtstr

该方法如果解码成功则返回1,解码失败则返回-1,本程序增加宏定义ok表示1,error表示-1

赫夫曼解码算法如下:

定义指针p指向赫夫曼树结点,指针i指向编码串,定义ch逐个读取编码串的字符

初始操作包括读入编码串str,设置p指向根结点,i为0表示指向串首,执行以下循环:

1)取编码串的第i个字符放入ch

2)如果ch是字符0,则p跳转到左孩子;如果ch是字符1,则p跳转到右孩子;如果ch非0非1,表示编码串有错误,报错退出

3)如果p指的结点是叶子,输出解码字符,p跳回根结点,i++,跳步骤1

4)如果p指的结点不是叶子且i未到编码串末尾,i++,跳步骤1

5)如果p指的结点不是叶子且i到达编码串末尾,报错退出

当i到达编码串末尾,解码结束。

输入

第一行先输入n,表示有n个叶子

第二行输入n个权值,权值全是小于1万的正整数
第三行输入n个字母,表示与权值对应的字符
第四行输入k,表示要输入k个编码串
第五行起输入k个编码串

输出

每行输出解码后的字符串,如果解码失败直接输出字符串“error”,不要输出部分解码结果

输入样例

5
15 4 4 3 2
A B C D E
3
11111
10100001001
00000101100

输出样例

AAAAA
ABEAD
error

AC代码

#include <iostream>
using namespace std;
int num;
//哈夫曼树结点的定义
typedef struct HNode
{
	char data;
	int weight;
	int lchild, rchild, parent;
	string code;
	HNode()
	{
		lchild = 0;
		rchild = 0;
		parent = 0;
	}
}*HuffmanTree;
//哈夫曼树初始化
void InitHuffmanTree(HuffmanTree& ht)
{
	for (int i = 1; i <= num; i++)
	{
		cin >> ht[i].weight;
	}
	for (int i = 1; i <= num; i++)
	{
		cin >> ht[i].data;
	}
}
//返回本轮次中先出现的权值最小根结点的下标
int SelectHNode(HuffmanTree& ht, int i)
{
	int min = 10000;
	int flag = 1;
	for (int j = 1; j < i; j++)
	{
		if (ht[j].parent == 0 && ht[j].weight < min)
		{
			min = ht[j].weight;
			flag = j;
		}
	}
	return flag;
}
//创建哈夫曼树
void CreatHuffmanTree(HuffmanTree& ht)
{
	//将哈夫曼树初始化
	InitHuffmanTree(ht);

	//创建第i个新结点,直到2*n处的结点处理完毕
	for (int i = num + 1; i < 2 * num; i++)
	{
		//处理新结点的左子树
		int left = SelectHNode(ht, i);
		ht[left].parent = i;
		ht[i].lchild = left;
		//处理新结点的右子树
		int right = SelectHNode(ht, i);
		ht[right].parent = i;
		ht[i].rchild = right;
		//处理新结点的权重
		ht[i].weight = ht[left].weight + ht[right].weight;
	}
}
//哈夫曼解码
int DeCode(HuffmanTree& ht, string str)
{
	//初始化两个指针
	int p_ht = 2 * num - 1;
	int p_str = 0;
	int len = str.size();
	//用来暂时存放解码结果
	string temp;
	while (p_str < len)
	{
		//注意这一条语句:不可以放在下面的“遇到根结点”的if语句中,否则在结尾处p_ht又指回根结点,也就是该结点的左右孩子不都为空!!所以始终输出error
        
        //2*num-1是根结点在数组里的下标
		p_ht = 2 * num - 1;
		while (1)
		{
			//遇到根结点了
			if (ht[p_ht].lchild == 0 && ht[p_ht].rchild == 0)
			{
				temp += ht[p_ht].data;
				break;
			}
			//该数字非0非1,解码错误
			if (!(str[p_str] == '0' || str[p_str] == '1'))
			{
				cout << "error" << endl;
				return -1;
			}
			else
			{
				if (str[p_str] == '0') p_ht = ht[p_ht].lchild;
				else p_ht = ht[p_ht].rchild;
				p_str++;
			}
		}
	}
	//表示编码串结束时哈夫曼树还未到达叶子结点
	if (!(ht[p_ht].lchild == 0 && ht[p_ht].rchild == 0))
	{
		cout << "error" << endl;
		return -1;
	}
	else
	{
		cout << temp << endl;
		return 1;
	}
}
int main()
{
	cin >> num;
	HuffmanTree ht = new HNode[2 * num];
	CreatHuffmanTree(ht);

	int str_num;//待测试字符串的个数
	string str;
	cin >> str_num;
	while (str_num--)
	{
		cin >> str;
		DeCode(ht, str);
	}

	return 0;
}

问题C:DS树–带权路径和

题目描述

二叉树的创建使用含空树表示的先序遍历序列,计算一棵二叉树的带权路径总和,即求赫夫曼树的带权路径和。

已知一棵二叉树的叶子权值,该二叉树的带权路径和WPL等于叶子权值乘于根节点到叶子的分支数,然后求总和。

如下图中,叶子都用大写字母表示,权值对应为:A-7,B-6,C-2,D-3

树的带权路径和 = 71 + 62 + 23 + 33 = 34

img

输入

第一行输入一个整数t,表示有t个二叉树

第二行输入一棵二叉树的先序遍历结果,空树用字符‘0’表示,注意输入全是英文字母和0,其中大写字母表示叶子

第三行先输入n表示有n个叶子,接着输入n个数据表示n个叶子的权值,权值的顺序和前面输入的大写字母顺序对应

以此类推输入下一棵二叉树

输出

输出每一棵二叉树的带权路径和

输入样例

2
xA00tB00zC00D00
4 7 6 2 3
ab0C00D00
2 10 20

输出样例

34
40

整体思路

将叶子结点存在一个数组中,待哈夫曼树建立完毕(即题中所说的先序遍历创建的二叉树)后,遍历叶子结点数组中的每一个叶子结点,求其到根结点的距离,两者相乘并累加

AC代码

#include <iostream>
using namespace std;
typedef struct BiTNode
{
	char data;
	BiTNode* lchild;
	BiTNode* rchild;
	BiTNode* parent;
	BiTNode()
	{
		lchild = NULL;
		rchild = NULL;
		parent = NULL;
	}
}*BiTree;
//用来辅助建立二叉树
string str;
int len;
int num1;
//与叶子结点有关的数据
int leaf_num;
int num2;//记录当前遇到的叶子结点数
BiTree leaf_Node[100];
int weights[100];
//由先序序列创建二叉树
void CreatBiTree(BiTree& t)
{
	if (str[num1]!='\0')
	{
		if (str[num1] == '0')
		{
			//注意这里num1也要++
			num1++;
			t = NULL;
		}
		else
		{
			t->data = str[num1];
			//判断是否是叶子结点,若是,把叶子结点存到数组中
			if (t->data >= 'A' && t->data <= 'Z')
			{
				leaf_Node[num2] = t;
				num2++;
			}
			t->lchild = new BiTNode;
			t->rchild = new BiTNode;
			t->lchild->parent = t;
			t->rchild->parent = t;
			num1++;
			CreatBiTree(t->lchild);
			CreatBiTree(t->rchild);
		}
	}
}
//求带权路径和
void Get_WPL(BiTree& t)
{
	int temp = 0;
	BiTree t_temp;
	int sum = 0;
	for (int i = 0; i < leaf_num; i++)
	{
		temp = 0;
		t_temp = leaf_Node[i];
		while (t_temp->parent!=NULL)
		{
			t_temp = t_temp->parent;
			temp++;
		}
		sum += weights[i] * temp;
	}
	cout << sum << endl;
}
int main()
{
	int t;
	cin >> t;
	while (t--)
	{
		cin >> str;
		cin >> leaf_num;
		for (int i = 0; i < leaf_num; i++)
		{
			cin >> weights[i];
		}
		len = str.size();
		num1 = 0;
		num2 = 0;

		BiTree t = new BiTNode;
		CreatBiTree(t);
		Get_WPL(t);
	}

	return 0;
}

问题D:DS树–二叉树之最大路径

题目描述

给定一颗二叉树的逻辑结构(先序遍历的结果,空树用字符‘0’表示,例如AB0C00D00),建立该二叉树的二叉链式存储结构

二叉树的每个结点都有一个权值,从根结点到每个叶子结点将形成一条路径,每条路径的权值等于路径上所有结点的权值和。编程求出二叉树的最大路径权值。如下图所示,共有4个叶子即有4条路径,

路径1权值=5 + 4 + 11 + 7 = 27路径2权值=5 + 4 + 11 + 2 = 22

路径3权值=5 + 8 + 13 = 26路径4权值=5 + 8 + 4 + 1 = 18

可计算出最大路径权值是27。

该树输入的先序遍历结果为ABCD00E000FG00H0I00,各结点权值为:

A-5,B-4,C-11,D-7,E-2,F-8,G-13,H-4,I-1

image-20241101221203457

输入

第一行输入一个整数t,表示有t个测试数据

第二行输入一棵二叉树的先序遍历,每个结点用字母表示

第三行先输入n表示二叉树的结点数量,然后输入每个结点的权值,权值顺序与前面结点输入顺序对应

以此类推输入下一棵二叉树

输出

每行输出每棵二叉树的最大路径权值,如果最大路径权值有重复,只输出1个

输入样例

2
AB0C00D00
4 5 3 2 6
ABCD00E000FG00H0I00
9 5 4 11 7 2 8 13 4 1

输出样例

11
27

AC代码

#include <iostream>
using namespace std;
typedef struct BiTNode
{
	char data;
	int weight;
	BiTNode* lchild;
	BiTNode* rchild;
	BiTNode* parent;
	BiTNode()
	{
		lchild = NULL;
		rchild = NULL;
		parent = NULL;
	}
}*BiTree;
//用于构造二叉树
string str;
int len;
int num1;
//用于为二叉树的非空结点赋上权重值
int cont;
int* weight;
//叶子结点
BiTree leaf_Node[100];
int cont1;
//由先序序列创建二叉树
void CreatBiTree(BiTree& t)
{
	if (str[num1] != '\0')
	{
		if (str[num1] == '0')
		{
			num1++;
			t = NULL;
		}
		else
		{
			t->data = str[num1];
			t->lchild = new BiTNode;
			t->rchild = new BiTNode;
			t->lchild->parent = t;
			t->rchild->parent = t;
			num1++;
			CreatBiTree(t->lchild);
			CreatBiTree(t->rchild);
		}
	}
}
//先序遍历二叉树为结点赋上权重值,并设置好叶子结点数组
void PreOrder(BiTree& t)
{
	if (t != NULL)
	{
		t->weight = weight[cont];
		cont++;
		if (t->lchild == NULL && t->rchild == NULL)
		{
			leaf_Node[cont1] = t;
			cont1++;
		}
		PreOrder(t->lchild);
		PreOrder(t->rchild);
	}
}
//求最大路径
void Get_MAX_Path_Length()
{
	int max = -1;
	BiTree t_temp;
	int sum = 0;
	for (int i = 0; i < cont1; i++)
	{
		sum = 0;
		t_temp = leaf_Node[i];
		sum += t_temp->weight;
		while (t_temp->parent != NULL)
		{
			t_temp = t_temp->parent;
			sum += t_temp->weight;
		}
		if (sum > max) max = sum;
	}
	cout << max << endl;
}
int main()
{
	int t;
	cin >> t;
	while (t--)
	{
		//创建二叉树
		cin >> str;
		len = str.size();
		num1 = 0;
		BiTree t = new BiTNode;
		CreatBiTree(t);
		//为二叉树赋权重值,收集叶子结点数组
		int num;
		cin >> num;
		weight = new int[num];//权重数组出现顺序为先序遍历遇到的结点的顺序
		for (int i = 0; i < num; i++)
		{
			cin >> weight[i];
		}
		cont = 0;
		cont1 = 0;
		PreOrder(t);
		//求最大路径
		Get_MAX_Path_Length();
	}
	return 0;
}

问题E:DS二叉树—二叉树镜面反转

题目描述

假设二叉树用二叉链表存储,用含空子树遍历的先序序列结果创建。空子树用字符#表示

输入二叉树的先序序列,请你先创建二叉树,并对树做个镜面反转,再输出反转后的二叉树的先序遍历、中序遍历、后序遍历和层序遍历的序列。所谓镜面反转,是指将所有非叶结点的左右孩子对换。

–程序要求–

程序中不允许使用STL库等第三方对象或函数实现本题的要求

输入

测试次数t

每组测试数据是一个二叉树的先序遍历序列,#表示空树

输出

对每棵二叉树,输出镜面反转后的先序、中序、后序和层次遍历序列。如果空树,输出四个NULL(后面不加空格,每个NULL独自一行,中间没有空行)。如下:

NULL

NULL

NULL

NULL

输入样例

3
41#32###65##7##
AB#C##D##
AB##C##

输出样例

4 6 7 5 1 3 2
7 6 5 4 3 2 1
7 5 6 2 3 1 4
4 6 1 7 5 3 2
A D B C
D A C B
D C B A
A D B C
A C B
C A B
C B A
A C B

整体思路

基本上就是简单的二叉树遍历,只不过需要将左右孩子的访问顺序调换即可

AC代码

#include <iostream>
#include <queue>
using namespace std;
typedef struct BiTNode
{
	char data;
	BiTNode* lchild;
	BiTNode* rchild;
	BiTNode()
	{
		lchild = NULL;
		rchild = NULL;
	}
}*BiTree;
//由先序遍历序列创建二叉树
void CreatBiTree(BiTree& t)
{
	char data;
	cin >> data;
	if (data != '#')
	{
		t = new BiTNode;
		t->data = data;
		CreatBiTree(t->lchild);
		CreatBiTree(t->rchild);
	}
	else t = NULL;
}
//镜面先序遍历
void Mirror_PreOrder(BiTree& t)
{
	if (t != NULL)
	{
		cout << t->data << " ";
		Mirror_PreOrder(t->rchild);
		Mirror_PreOrder(t->lchild);
	}
}
//镜面中序遍历
void Mirror_InOrder(BiTree& t)
{
	if (t != NULL)
	{
		Mirror_InOrder(t->rchild);
		cout << t->data << " ";
		Mirror_InOrder(t->lchild);
	}
}
//镜面后序遍历
void Mirror_PostOrder(BiTree& t)
{
	if (t != NULL)
	{
		Mirror_PostOrder(t->rchild);
		Mirror_PostOrder(t->lchild);
		cout << t->data << " ";
	}
}
//镜面层序遍历
void Mirror_LevelOrder(BiTree& t)
{
	queue <BiTree> q;
	BiTree p;
	q.push(t);
	while (!q.empty())
	{
		p = q.front();
		q.pop();
		cout << p->data << " ";
		if (p->rchild != NULL) q.push(p->rchild);
		if (p->lchild != NULL) q.push(p->lchild);
	}
}
int main()
{
	int t;
	cin >> t;
	while (t--)
	{
		BiTree t;
		CreatBiTree(t);
		if (t == NULL)
		{
			cout << "NULL" << endl;
			cout << "NULL" << endl;
			cout << "NULL" << endl;
			cout << "NULL" << endl;
		}
		else
		{
			Mirror_PreOrder(t);
			cout << endl;
			Mirror_InOrder(t);
			cout << endl;
			Mirror_PostOrder(t);
			cout << endl;
			Mirror_LevelOrder(t);
			cout << endl;
		}
	}
	return 0;
}

d);
}
}
//镜面后序遍历
void Mirror_PostOrder(BiTree& t)
{
if (t != NULL)
{
Mirror_PostOrder(t->rchild);
Mirror_PostOrder(t->lchild);
cout << t->data << " ";
}
}
//镜面层序遍历
void Mirror_LevelOrder(BiTree& t)
{
queue q;
BiTree p;
q.push(t);
while (!q.empty())
{
p = q.front();
q.pop();
cout << p->data << " ";
if (p->rchild != NULL) q.push(p->rchild);
if (p->lchild != NULL) q.push(p->lchild);
}
}
int main()
{
int t;
cin >> t;
while (t–)
{
BiTree t;
CreatBiTree(t);
if (t == NULL)
{
cout << “NULL” << endl;
cout << “NULL” << endl;
cout << “NULL” << endl;
cout << “NULL” << endl;
}
else
{
Mirror_PreOrder(t);
cout << endl;
Mirror_InOrder(t);
cout << endl;
Mirror_PostOrder(t);
cout << endl;
Mirror_LevelOrder(t);
cout << endl;
}
}
return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值