树常见的考题主要包括二叉树的遍历算法、建立最优二叉树和实现哈夫曼编码的方法。
---------------2023.8.20----------------
今天主要对如何实现哈夫曼编码进行整理。
1.哈夫曼树的构造算法
1.1根据给定的带权结点,选择最小的两个作为树的左右子树;并生成根节点,节点权值为子树节点之和。 1.2将生成的子树加入原节点中,并删除之前加入的两个节点。 1.3重复上述操作,直到只剩一棵树为止。
2.构建哈夫曼树
2.1哈夫曼树算法的实现
哈夫曼树种不存在度为1的节点,有n个节点的哈夫曼树就有2n-1个结点,用大小为2n-1的一维数组来存储。存储结构如下所示。
________________________________
| weight | parent | lchild | rchild |
________________________________
typedef struct{
int weight; //权值
int parent,lchild,rchild;//父结点,左右孩子结点的下标
}HTNode,*HuffmanTree;
为了实现方便,数组的n号单元不使用,从1号单元,所以数组大小为2n,将叶子节点存储在前1-n个位置,后面n-1个位置存储非叶子结点。
2.2构造哈夫曼树
2.2.1初始化
动态申请2n个单元,然后从1号位置开始,对所有位置进行初始化,将双亲以及左右孩子置为0;再对前n个结点的权重进行赋值。
2.2.2创建树
循环n-1次,从当前森林中选出双亲结点为0,且权重最小的两个树的根节点s1和s2;然后将s1s2在原来的森林中进行删除,也就是将这两个结点的双亲改为n+1;将这两个结点的权值想加,保存在第n+1个位置,并将这个结点的左右孩子的下标改为s1、s2。
2.2.3算法实现
void CreateHuffmanTree(HuffmanTree &HT,int n){
if(n<=1) return ;
m = 2*n-1; //数组一共2n-1个元素
HT = new HTNode[m+1]; //0号未使用
//对n个结点/森林进行初始化
for(i = 1;i<=m;i++){
HT[i].lchild=0;HT[i].rchild=0;HT[i].parent=0;
}
//手动输入各个结点的权重
for(i = 1;i<=n;++i){
cin>>HT[i].weight;
}
//初始化结束,构建哈夫曼树
for(i = n+1;i<=m;i++){ //合并后的结点存放在第n个位置后
Select(HT,i-1,s1,s2);//在HT中前i-1个位置选择双亲为0且权重最小的两个结点,用s1和s2返回
HT[s1].parent=i;HT[s2].parent=i; //删除左右孩子子树
HT[i].lchild=s1;HT[i].rchild=s2; //合并后结点的左右孩子指向
HT[i].weight=HT[s1].weight+HT[s2].weight; //合并后的权重
}
}
3.哈夫曼编码
3.1实现思路
我们采用从叶子结点向上回溯的步骤,从第一个叶子结点开始向上,如果当前结点是这棵树的左孩子,则生成一个编码0,如果是右孩子,则生成一个编码1,将生成的编码存储在一个数组cd中,完成这个步骤后,继续对这课树的根节点向上继续这个过程,这当已经遍历到整个哈夫曼树的根节点时,个过程最多会生成n个编码,这n个编码就是这个叶子结点的哈夫曼编码,不过需要注意的是,如果按照顺序存放在cd中,会导致哈夫曼编码是逆序,因此在存储的时候,我们需要从cd数组的后面向前依次存储,遍历完成一次后,将这个结点生成的哈夫曼编码复制到最终的哈夫曼编码数组里,即可对下一个结点进行上述操作。
3.2代码实现
---------------------------哈夫曼编码表的存储表示----------------------
typedef char **HuffmanCode; //指针数组,用来存放每个哈夫曼编码的首地址
----------------------------------注解------------------------------
//char *s = 'hello world'用来表示整个字符串
//char s[] = 'hello world'是放在数组里,这样可以对每个字符进行操作
--------------------------------------------------------------------
void CreateHuffmanCode(HuffmanTree HT,HuffmanCode &HC,int n){
HC = new char*[n+1];//第一个位置不用,n个元素,n+1的大小
cd = new char[n];//每个结点的编码最长为n-1,最后一个位置来存放'\0'
cd[n-1] = '\0';
for(i = 1;i<=n;++i){ //总共有n个叶子结点,就循环n次
start = n-1;//start是编码放在cd中的位置,从后往前放,所以start要在最后
c = i;f = HT[i].parent;//c指向当前叶子结点,f指向其双亲结点
while(f!=0){
--start;
if(HT[f].lchild == c) cd[start] = '0';//左孩子编码为1
else cd[start] = '1'; //右孩子编码为0
c = f;f=HT[f].parent;//向上回溯
}
HC[i] = new char[n-start];//为HC的第i个结点申请n-start大小的空间存放编码
strcpy(HC[i],&cd[start]);//将cd中临时编码复制到HC中
}
delete cd;//释放临时空间
}
---------------2023.8.21----------------
1.二叉树的存储结构
1.1顺序存储结构
----二叉树的顺序存储表示----
#define MAXTSIZE 100
typedef TElemType SqBiTree[MAXTSIZE];
SqBiTree bt;
这种存储方式仅适用于存储完全二叉树,否则会造成很大的空间浪费。
1.2链式存储结构
大部分树的结点都有左右孩子指针以及数据域,这样的结点构成的存储结构称为二叉链表,如果有指向双亲结点的指针域构成的存储结构,称为三叉链表。由n个结点的二叉链表有n+1个空链域,后续可以对空链域进行操作,例如二叉树的线索化。
----二叉树的二叉链表存储表示----
typedef struct BiTNode{
TElemType data;
struct BiTNode *lchild,*rchild;
}BiTNode,*BiTree;//BiTNode重在表示一个结点,*BiTree重在表示一棵二叉树
2.二叉树的遍历
2.1中序遍历的递归算法
void InOrderTraverse(BiTree T){
if(T){
InOrderTraverse(T->lchild);
cout<<T->data;//对结点进行操作
InOrderTraverse(T->rchild);
}
}
2.2中序遍历的非递归算法
void InOrderTraverse(BiTree T){
InitStack(S);p = T;
q = new BiTNode; //q用来存储弹出来被访问的结点
while(p || !StackEmpty(S)){
if(p){
push(S,p);
p = p->lchild;
}else{
pop(S,q);
cout<<q->data;//访问结点
p = q->rchild;
}
}
}