题目:
使用C语言/C++实现哈夫曼树的建立与应用。
参考代码:
cpp
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
//哈夫曼树结构体
typedef struct HFTNode
{
//1.子树数据域
int data;
//2.左、右孩子指针
struct HFTNode *lchild,*rchild;
//3.记录当前子树是否是其他子树合成的(是:true;不是:false)
bool flag;
//4.定义变量记录当前结点(子树)的哈夫曼编码
char code[10];
}HFTNode,*HFTree;
//存储森林的顺序表
typedef struct
{
//1.树的数组
HFTree list[20];
//2.记录当前树的个数
int lengeh;
//3.顺序表的头、尾指针
int front,rear;
}HFTlist;
/*-------------------------------赵旭文-------------------------------*/
//森林顺序表添加元素的函数
/*形参是顺序表、要添加的数据域的值、数据所添加的位置*/
void InsertTree(HFTlist &L,int data,int index)
{
//1.给森林顺序表的其中一个元素申请空间
HFTree a = (HFTree)malloc( sizeof(HFTNode) );
//2.分配数据
a->data = data;
//3.设置左、右子树,初始都为NULL
a->lchild = NULL;
a->rchild = NULL;
//4.设置是否为合成后的树,初始都不是
a->flag = false;
//5.设置位置
L.list[index] = a;
//6.森林顺序表的长度加一
L.lengeh++;
}
//初始化森林顺序表的函数
HFTlist InitList(HFTlist &L)
{
//1.添加元素
InsertTree(L,10,0);
InsertTree(L,12,1);
InsertTree(L,16,2);
InsertTree(L,21,3);
InsertTree(L,30,4);
InsertTree(L,22,5);
//2.设置头尾指针
L.front = 0;
L.rear = L.lengeh - 1;
//3.返回
return L;
}
/*-------------------------------赵旭文-------------------------------*/
/*-------------------------------黄黄-------------------------------*/
//求出划分索引的函数
int Partition(HFTlist &L,int low,int high)
{
//1.将当前表中第一个元素设为枢轴,对表进行划分
HFTree pivotNode = L.list[low];
int pivot = pivotNode->data;
/*没有直接使用int pivot = L.list[low]->data; 是防止之后产生指针被覆盖*/
//2.开始循环
while( low < high )
{
//2.1.找出比pivot小的元素
while( low < high && L.list[high]->data >= pivot ) high--;
//2.2.移到左边
L.list[low] = L.list[high];
//2.3.找出比pivot大的元素
while( low < high && L.list[low]->data <= pivot ) low++;
//2.4.移到右边
L.list[high] = L.list[low];
}
//3.枢轴元素放到最终位置即low==high的位置上
L.list[low] = pivotNode;
//4.返回存放枢轴元素的最终位置
return low;
}
//用于对森林顺序表里的元素进行升序排序的函数(采用冒泡排序)
/*采用快速排序是因为数据元素个数可控,采用递归安全且高效*/
/*这里头、尾指针不能直接用L.front和L.rear,用的话对后续森林顺序表的使用有影响,
因此传入形参int low,int high,记录头尾指针的值*/
void QuickSort(HFTlist &L,int low,int high)
{
//只要头指针小于尾指针,就一直递归调用函数进行排序,头指针等于尾指针时就说明确定了一个元素的位置
if( low < high )
{
//1.得出划分索引
int pivotpos = Partition( L , low , high );
//2.对左子表进行排序
QuickSort( L , low , pivotpos-1 );
//3.对右子表进行排序
QuickSort( L , pivotpos+1 , high );
}
}
/*-------------------------------黄黄-------------------------------*/
/*-------------------------------青格勒-------------------------------*/
//创建哈夫曼树的函数
/*形参需要root即哈夫曼树的子树、森林顺序表即插入的子树*/
HFTree CreateHFTree(HFTree &root,HFTlist &L)
{
//1.首先对初始森林顺序表进行排序
/*初始时只需要这一次整体排序使得森林顺序表有序即可,
因为后续的操作都是在保证有序的情况下进行,就无需再整体排序了 */
QuickSort(L, L.front, L.rear);
//2.当森林中还有多于1棵树时,继续合并
while (L.rear - L.front + 1 > 1)
{
//3.取森林顺序表前两个最小的元素
/*因为每一次合并最多并两个元素*/
HFTree t1 = L.list[L.front];
HFTree t2 = L.list[L.front + 1];
//4.创建新的父结点(本质是一个更大的树)
/*4.1.给父结点申请空间*/
HFTree parent = (HFTree)malloc(sizeof(HFTNode));
/*4.2.给该父结点赋数据值*/
parent->data = t1->data + t2->data;
/*4.3.设置左、右孩子,并且记录其中一个编码(左为0,右为1)*/
parent->lchild = t1;
parent->rchild = t2;
/*4.4.标记为合成树*/
parent->flag = true;
//5.从森林顺序表中移除已合并的两棵树
L.front = L.front + 2;
//6.将新合成的树加入到森林顺序表中
/*6.1.开始找到合适的位置插入新树(保持有序即升序),初始位置设置为头部*/
int insertPos = L.front;
/*6.2.遍历森林顺序表查找合适的插入位置*/
for (int i = L.front; i <= L.rear; i++)
{
/*6.3.此时新生成的父结点数据域小于等于i索引上的数据域,
因此要插在i的位置上,循环可以结束了,
而且插在i上就保证了新生成的父结点数据域永远比之前i索引上的数据域先参与构成哈夫曼树*/
if (parent->data <= L.list[i]->data)
{
insertPos = i;
break;
}
/*6.4.此时说明父结点数据域比森林顺序表里的所有数据域都大,因此插在尾指针的后一个位置上*/
if (i == L.rear)
{
insertPos = L.rear + 1;
}
}
//7.移动元素为新树腾出位置
for (int i = L.rear; i >= insertPos; i--)
{
L.list[i + 1] = L.list[i];
}
//8.插入新树
L.list[insertPos] = parent;
//9.修改尾指针
L.rear++;
//10.更新根结点为最新的父结点
root = parent;
}
return root;
}
/*-------------------------------青格勒-------------------------------*/
/*-------------------------------吴佳峻-------------------------------*/
//遍历得到的哈夫曼树(先序遍历)
void PreHFTree(HFTree &root)
{
//判断
if( root != NULL )
{
printf("%d->",root->data);
PreHFTree(root->lchild);
PreHFTree(root->rchild);
}
}
//用来求出叶子结点即参与构成哈夫曼树的结点的编码的函数
/*第二个形参是字符指针,指向地址,是一个字符串型,
而且本例中传的是字符串常量,所以要用const修饰,如果不用const修饰的话传常量会出现类型转换奔溃,
const修饰后直接传字符串常量即可,
如果不用const修饰的话也可以,只需要传递第二个参数时强制转换为char*型即可*/
void getHFTNodeCode(HFTree root,const char* parentCode)
{
//1.判断当前哈夫曼树是否为空,为空不存在哈夫编码
if (root == NULL) return;
//2.此时哈夫曼树不为空
//3.定义变量记录哈夫曼编码
char currentCode[20];
//4.给currentCode赋初值,不赋初值会出现随机数据,对于之后的操作会有影响
strcpy(currentCode, parentCode);
//5.对左孩子进行递归判断
if (root->lchild != NULL) {
/*5.1.定义变量记录左编码*/
char leftCode[20];
/*5.2.此时左孩子不为空,就把0拼接上去*/
strcpy(leftCode, currentCode);
strcat(leftCode, "0");
/*5.3.递归调用拼接下一个*/
getHFTNodeCode(root->lchild, leftCode);
}
//6.对右孩子进行递归判断
if (root->rchild != NULL) {
/*6.1.定义变量记录右编码*/
char rightCode[20];
/*6.2.此时右孩子不为空,就把1拼接上去*/
strcpy(rightCode, currentCode);
strcat(rightCode, "1");
/*6.3.递归调用拼接下一个*/
getHFTNodeCode(root->rchild, rightCode);
}
//7.如果是叶子结点就打印编码,因为叶子结点存储完整编码
if (root->lchild == NULL && root->rchild == NULL) {
strcpy(root->code, currentCode);
printf("数据%d的哈夫曼编码: %s\n", root->data, root->code);
}
}
/*-------------------------------吴佳峻-------------------------------*/
int main()
{
//1.创建一个森林顺序表,森林就是树的集合
HFTlist L;
//2.森林顺序表未初始化时长度为0
L.lengeh = 0; /*InitList函数接收的是未初始化的L,其length不赋初值可能包含随机值*/
//2.初始化森林顺序表
L = InitList( L );
/*定义一个森林顺序表,把L复制过去,为了之后*/
/*遍历初始化得到的森林顺序表*/
printf("当前参与构成哈夫曼树的子树有:");
for( int i=L.front ; i<=L.rear ; i++ ) printf("%d,",L.list[i]->data);
printf("\n");
//3.创建一个树
HFTree root = NULL;
//4.调用CreateHFTree函数创建哈夫曼树
root = CreateHFTree( root , L );
//5.遍历得到的哈夫曼树
printf("由当前森林顺序表得到的哈夫曼树的先序遍历序列为:");
PreHFTree(root);
printf("\n");
//6.打印参与构成哈夫曼树的结点的编码
getHFTNodeCode( root , "" );
/*这里强制转换为char*型是因为getHFTNodeCode函数第二个形参是字符指针,
不强制转换会出现不太安全的类型转换*/
return 0;
}