深圳大学数据结构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
输入
第一行输入一个整数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
输入
第一行输入一个整数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;
}