森林与二叉树的转换
树与二叉树之间存在着必然联系——任意给定一棵树,可以找到唯一一棵二叉树与之对应。
二叉链表存储结构相同的树和二叉树相互对应。
任何一棵和树对应的二叉树,其右子树必空。
-
树和二叉树各部分的对应关系(左孩子右兄弟)
- 森林和二叉树各部分的对应关系:若把森林中第二棵树的根结点看成是第一棵树的根结点的兄弟,则同样可导出森林和二叉树的对应关系。
森林和二叉树有着一一对应的关系,树和森林的问题就可以转化为二叉树的问题。
树与森林的遍历
- 先根(次序) 遍历: 若树不空,则先访问根结点,然后依次先根遍历根的每棵子树。
- 后根(次序) 遍历: 若树不空,则先依次后根遍历根的每棵子树,然后访问根结点。
- 层次(次序) 遍历: 若树不空,则自上而下从左至右按层次访问树中每个结点。
赫夫曼树
赫夫曼编码是首个实用的压缩编码方案,在数据通信中,用二进制给每个字符进行编码时不得不面对的一个问题是如何使电文总长最短且不产生二义性。根据字符出现频率,利用赫夫曼编码可以构造出一种不等长的二进制,使编码后的电文长度最短,且保证不产生二义性。
——给定一组 n 个实数,以它们作为各叶子结点上的权,可以构成不同的有 n 个叶子结点的二叉树,其中带权路径长度最小的二叉树称为最优二叉树或赫夫曼树。
最优二叉树(赫夫曼树)——
• 结点的路径长度:从根到该结点路径上的分支数目。
• 树的路径长度:树中每个结点的路径长度之和。
• 结点的带权路径长度:结点上的权与该结点路径长度的乘积。
例如:
赫夫曼算法 (Huffman )
1)根据给定的n 个权值 { w1, w2, …, wn },构造 n 棵二叉树的集合 F= {T1, T2, …, Tn},其中每棵二叉树Ti 中均只含一个带权值为wi 的根结点,其左、右子树均为空树;
2)在F 中选取两棵其根结点的权值为最小的二叉树,分别作为左、右子树构造一棵新的二叉树,并置这棵新二叉树根结点的权值为其左、右子树根结点的权值之和;
3)从F 中删去这两棵树,同时加入刚生成的新树;
4)重复2) 和3) 两步,直至 F 中只含一棵树为止。
例如: 已知权值 W={ 5, 6, 2, 9, 7 }
对于给定的一组权值,根据赫夫曼算法构造的最优树可能不是唯一的。 由赫夫曼算法可知,赫夫曼树是一棵严格二叉树,故有 n 个叶子结点的赫夫曼树共有2n-1个结点。
赫夫曼树应用之一 —— 设计最佳判定算法
80% 以上的数据需进行3 次或3 次以上的比较才能得出结果
赫夫曼树应用之一——赫夫曼编码
任一字符的编码都不是同一字符集中另一个字符编码的前缀,满足该条件的不等长编码称作前缀编码。
可以利用二叉树来设计二进制的前缀编码。
如何得到使电文编码的总长度最短的二进制前缀编码?
设{ d1, d2, …, dn } 为需编码的字符集合;wk为字符dk 在电文中的使用频率;lk 为字符dk 的编码长度;则电文编码总长度为:
设计电文编码总长最短的二进制前缀编码的问题,即为以n种字符出现的频率作权,设计一棵赫夫曼树的问题,由此得到的二进制前缀编码称为赫夫曼编码。
赫夫曼编码方法:
1)分别用字符 d1, d2, …, dn 作为叶子结点,各字符的使用频率w1, w2, …, wn 作为叶子结点上的权,构造赫夫曼树。
2)约定,赫夫曼树中所有左分支表示 0,右分支表示 1,则从根到每个叶子结点路径上的二进制数字组成的序列就是该叶子结点中字符的编码。
赫夫曼编码 C语言实现:
Project目录结构
queue.h
#ifndef QUEUE_H_INCLUDED
#define QUEUE_H_INCLUDED
#include "huffman.h"
#define TYPE htNode *
#define MAX_SZ 256
//队列结点
typedef struct _pQueueNode
{
TYPE val; //TYPE类型指针 树结点
unsigned int priority; //(无符号) 存放该结点的优先级(出现多少次)
struct _pQueueNode *next; //指向下一个结点
}pQueueNode;
typedef struct _pQueue{
unsigned int size; //队列长度
pQueueNode *first;
}pQueue;
//指向队列的一个指针
void initPQueue(pQueue **queue);
void addPQueue(pQueue **queue,TYPE val, unsigned int priority);
TYPE getPQueue(pQueue **queue);
#endif // QUEUE_H_INCLUDED
huffman.h
#ifndef HUFFMAN_H_INCLUDED
#define HUFFMAN_H_INCLUDED
//Huffman树结点
typedef struct _htNode{
char symbol;
struct _htNode *left, *right;
}htNode;
typedef struct _htTree{
htNode *root;
}htTree;
//huffman表结点(链表实现)
typedef struct _hlNode{
char symbol;
char *code;
struct _htNode *next;
}hlNode;
typedef struct _hlTable {
hlNode *first;
hlNode *last;
}hlTable;
htTree * buildTree(char *inputString);
hlTable * buildTable(htTree *huffmanTree);
void encode(hlTable *table, char *stringToEncode);
void decode(htTree *tree, char *stringToDecode);
#endif // HUFFMAN_H_INCLUDED
queue.c
#include"queue.h"
#include<stdio.h>
#include<stdlib.h>
void initPQueue(pQueue **queue)
{
//我们为优先队列类型分配内存
//然后我们初始化字段的值
//(*queue)相当于存放队列的地址
(*queue) = (pQueue *)malloc(sizeof(pQueue));
(*queue)->first = NULL;
(*queue)->size = 0;
return;
}
void addPQueue(pQueue **queue, TYPE val,unsigned int priority)
{
if((*queue)->size == MAX_SZ)
{
printf("\nQueue is full.\n");
//队列满了
return;
}
pQueueNode *aux = (pQueueNode *)malloc(sizeof(pQueueNode));
aux->priority = priority;
aux->val = val;
//如果队列为空,将添加第一个值。
//验证了两次,以防结构在运行时被异常修改(很少发生)。
if((*queue)->size ==0 || (*queue)->first ==NULL)
{
aux->next == NULL;
(*queue)->first = aux;
(*queue)->size = 1;
return;
}
else
{
//如果队列中已有元素
//注意:队列需要按照优先级降序排列
//如果优先级小于第一个元素的优先级,则将其插入在其之前
if(priority <= (*queue)->first->priority)
{
aux->next = (*queue)->first;
(*queue)->first = aux;
(*queue)->size++;
return;
}
else
{
//设置了一个迭代器,初始化为第一个结点
//寻找一个适合元素的位置
pQueueNode * iterator = (*queue)->first;
while(iterator->next !=NULL)
{
//小于,插入在其之前
if(priority <= iterator->next->priority)
{
aux->next = iterator->next;
iterator->next = aux;
(*queue)->size++;
return;
}
//迭代器往后退一格,再比较
iterator = iterator->next;
}
//队列中没有比你更大,则做尾结点
if(iterator->next == NULL)
{
aux->next = NULL;
iterator->next = aux;
(*queue)->size++;
return;
}
}
}
}
//获取队列中的数据
TYPE getPQueue(pQueue **queue)
{
TYPE returnValue;
if((*queue)->size >0)
{
returnValue = (*queue)->first->val;
(*queue)->first = (*queue)->first->next;
(*queue)->size--;
}
else
{
printf("\nQueue is empty.\n");
}
return returnValue;
}
huffman.c
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "huffman.h"
#include "queue.h"
void traversTree(htNode *treeNode, hlTable **table,int k, char code[256])
{
if(treeNode->left == NULL && treeNode->right == NULL)
{
code[k] = '\0';
//构成一个字符串
hlNode *aux = (hlNode *)malloc(sizeof(hlNode));
aux->code = (char *)malloc(sizeof(char)*(strlen(code)+1));
strcpy(aux->code,code);
aux->symbol = treeNode->symbol;
aux->next = NULL;
if((*table)->first == NULL)
{
(*table)->first = aux;
(*table)->last = aux;
}
else
{
(*table)->last->next = aux;
(*table)->last = aux;
}
}
//往左加0往右加1
if(treeNode->left !=NULL)
{
code[k] = '0';
traversTree(treeNode->left , table,k+1,code);
}
if(treeNode->right !=NULL)
{
code[k] = '1';
traversTree(treeNode->right,table,k+1,code);
}
}
hlTable *buildTable(htTree * huffmanTree)
{
hlTable *table = (hlTable *)malloc(sizeof(hlTable));
table->first = NULL;
table->last = NULL;
char code[256];
int k=0;
//k表示目前位于第几层
traversTree(huffmanTree->root,&table , k, code);
return table;
}
htTree *buildTree(char *inputString)
{
int * probability = (int *)malloc(sizeof(int)*256);
//256个整型指针,记录256种ASCII码出现的次数
for(int i=0 ; i<256 ;i++)
{
probability[i] = 0;
}
//统计待编码的字符串各个字符出现的次数
for(int j=0; inputString[j]!='\0' ; j++)
{
probability[(unsigned char) inputString[j]]++;
//unsigned char
}
pQueue * huffmanQueue;
//指向队列的头结点,指针 存放的是地址
initPQueue(&huffmanQueue); //初始化
//传入 存放地址的地址
//填充队列
for(int k=0; k<256; k++)
{
if(probability[k]!= 0)
{
htNode *aux = (htNode *)malloc(sizeof(htNode));
aux->left = NULL;
aux->right = NULL;
aux->symbol = (char) k;
addPQueue(&huffmanQueue , aux, probability[k]);
}
}
free(probability);
//生成huffman树
while(huffmanQueue->size !=1)
{
//新的优先值=最小的两个优先值之和
int priority = huffmanQueue->first->priority;
priority += huffmanQueue->first->next->priority;
//每一次获取指针头地址都会修改
htNode *left = getPQueue(&huffmanQueue);
htNode *right = getPQueue(&huffmanQueue);
htNode *newNode = (htNode *)malloc(sizeof(htNode));
newNode->left = left;
newNode->right = right;
addPQueue(&huffmanQueue, newNode,priority);
}
//建树
htTree *tree = (htTree *)malloc(sizeof(htTree));
tree->root = getPQueue(&huffmanQueue);
return tree;
}
void encode(hlTable *table, char *stringToEncode)
{
hlNode *traversal;
printf("\nEncoding\nInput string : %s\nEncoded string : \n",stringToEncode);
for(int i=0; stringToEncode[i]!='\0';i++)
{
traversal = table->first;
while(traversal->symbol != stringToEncode[i])
{
traversal = traversal->next;
}
printf("\n");
}
}
void decode(htTree *tree,char *stringToDecode)
{
htNode *traversal = tree->root;
printf("\nDecoding\nInput string : %s\nDecoded string : \n",stringToDecode);
//对于要解码的字符串的每个“位”
//我们向左走一步,得到0
//或向右取1
for(int i=0; stringToDecode[i]!='\0'; i++)
{
if(traversal->left == NULL && traversal->right == NULL)
{
printf("%c",traversal->symbol);
traversal = tree->root;
}
if(stringToDecode[i] == '0')
traversal = traversal->left;
if(stringToDecode[i] == '1')
traversal = traversal->right;
if(stringToDecode[i]!='0'&&stringToDecode[i]!='1')
{
printf("The input string is not coded correctly!\n");
return;
}
}
if(traversal->left == NULL && traversal->right == NULL)
{
printf("%c",traversal->symbol);
traversal = tree->root;
}
printf("\n");
}
main.c
#include <stdio.h>
#include <stdlib.h>
#include"huffman.h"
int main()
{
//根据字符串构建树,返回树的根
htTree *codeTree = buildTree("Do You Love Me?");
//根据Huffman树来构建表
hlTable *codeTable = buildTable(codeTree);
我们使用表进行编码
encode(codeTable,"Do You Love Me?"); //待压缩字符
//使用huffman树解码
//可以解码字符串,只使用初始字符串中的符号
decode(codeTree,"0011111000111");
return 0;
}
最后运行还是有点小问题,大致就是这样。