作者:shmily
赫夫曼树:假设有n个权值 {W1,W2,…,Wn}
,试构造一颗有n个叶子结点的二叉树,每个叶子结点带权为Wi,则其中带权路径长度WPL最小的二叉树称为。
例如下图:有3棵二叉树,都有4个叶子结点a,b,c,d,分别带权7,5,2,4,它们的带权路径长度分别为:
(a)WPL = 72 + 52 + 22 + 42 = 36
(b)WPL = 73 + 53 + 21 + 42 = 46
©WPL = 71 + 52 + 23 + 43 = 35
其中,©树的WPL最小,它便是赫夫曼树,即其带权路径长度在所有带权为7、5、2、4的4个叶子结点的二叉树中居最小。
构造赫夫曼数可以利用赫夫曼最早给出的一个带有一般规律的算法,即赫夫曼算法。步骤如下:
(1)根据给定的n个权值 {W1,W2,…, Wn}
构成n棵二叉树的集合 F={T1,T2,…,Tn}
,其中每棵二叉树Ti中只有一个带权为Wi的根结点,其左右子树均为空;
(2)在F中选取两棵根结点的权值最小的树作为左右子树构造一棵新的二叉树,且置新的二叉树的根结点的权值为其左右子树上根结点的权值之和;
(3)在F中删除这两棵树,同事将新得到的二叉树加入F中;
(4)重复(2)和(3),直到F只含一棵树为止。这棵树便是赫夫曼树。
例如下图展示了赫夫曼树的构造过程:
赫夫曼编码是赫夫曼树的应用,通常采用前缀编码,使得解码时不会产生混淆。具体方法为;从叶子出发走一条从叶子到根的路径,如上图字符C所在的叶子到根结点的路径为字符串011,再将所得的字符串反转得到110,便是该字符C的编码。继续分析上例:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zcWPmNLr-1585656751344)(C:\Users\lenovo\Desktop\3.PNG)]
赫夫曼译码是编码的逆向工程,具体做法为:从根出发走一条从根到叶子的路径,如根据字符C的编码110,从根出发,按字符’0’或’1’确定找左孩子或右孩子,直至叶子结点,便求得该编码字符串对应的字符。
源代码:
#include
#include
#include "stdlib.h"
#include "stdio.h"
#include "string.h"
//haffman 树的结构
typedef struct
{
//叶子结点权值
unsigned int weight;
//指向双亲,和孩子结点的指针
unsigned int parent;
unsigned int lChild;
unsigned int rChild;
} Node, *HuffmanTree;
//动态分配数组,存储哈夫曼编码
typedef char *HuffmanCode;
//选择两个parent为0,且weight最小的结点s1和s2的方法实现
//n 为叶子结点的总数,s1和 s2两个指针参数指向要选取出来的两个权值最小的结点
void select(HuffmanTree *huffmanTree, int n, int *s1, int *s2)
{
//标记 i
int i = 0;
//记录最小权值
int min;
//遍历全部结点,找出单节点
for(i = 1; i <= n; i++)
{
//如果此结点的没有父节点,那么把结点号赋值给 min,跳出循环
if((*huffmanTree)[i].parent == 0)
{
min = i;
break;
}
}
//继续遍历全部结点,找出权值最小的单节点
for(i = 1; i <= n; i++)
{
//如果此结点的父亲为空,则进入 if
if((*huffmanTree)[i].parent == 0)
{
//如果此结点的权值比 min 结点的权值小,那么更新 min 结点,否则就是最开始的 min
if((*huffmanTree)[i].weight < (*huffmanTree)[min].weight)
{
min = i;
}
}
}
//找到了最小权值的结点,s1指向
*s1 = min;
//遍历全部结点
for(i = 1; i <= n; i++)
{
//找出下一个单节点,且没有被 s1指向,那么i 赋值给 min,跳出循环
if((*huffmanTree)[i].parent == 0 && i != (*s1))
{
min = i;
break;
}
}
//继续遍历全部结点,找到权值最小的那一个
for(i = 1; i <= n; i++)
{
if((*huffmanTree)[i].parent == 0 && i != (*s1))
{
//如果此结点的权值比 min 结点的权值小,那么更新 min 结点,否则就是最开始的 min
if((*huffmanTree)[i].weight < (*huffmanTree)[min].weight)
{
min = i;
}
}
}
//s2指针指向第二个权值最小的叶子结点
*s2 = min;
}
//创建哈夫曼树并求哈夫曼编码的算法如下,w数组存放已知的n个权值
void createHuffmanTree(HuffmanTree *huffmanTree, int w[], int n)
{
//m 为哈夫曼树总共的结点数,n 为叶子结点数
int m = 2 * n - 1;
//s1 和 s2 为两个当前结点里,要选取的最小权值的结点
int s1;
int s2;
//标记
int i;
// 创建哈夫曼树的结点所需的空间,m+1,代表其中包含一个头结点
*huffmanTree = (HuffmanTree)malloc((m + 1) * sizeof(Node));
//1--n号存放叶子结点,初始化叶子结点,结构数组来初始化每个叶子结点,初始的时候看做一个个单个结点的二叉树
for(i = 1; i <= n; i++)
{
//其中叶子结点的权值是 w【n】数组来保存
(*huffmanTree)[i].weight = w[i];
//初始化叶子结点(单个结点二叉树)的孩子和双亲,单个结点,也就是没有孩子和双亲,==0
(*huffmanTree)[i].lChild = 0;
(*huffmanTree)[i].parent = 0;
(*huffmanTree)[i].rChild = 0;
}// end of for
//非叶子结点的初始化
for(i = n + 1; i <= m; i++)
{
(*huffmanTree)[i].weight = 0;
(*huffmanTree)[i].lChild = 0;
(*huffmanTree)[i].parent = 0;
(*huffmanTree)[i].rChild = 0;
}
printf("\n HuffmanTree: \n");
//创建非叶子结点,建哈夫曼树
for(i = n + 1; i <= m; i++)
{
//在(*huffmanTree)[1]~(*huffmanTree)[i-1]的范围内选择两个parent为0
//且weight最小的结点,其序号分别赋值给s1、s2
select(huffmanTree, i-1, &s1, &s2);
//选出的两个权值最小的叶子结点,组成一个新的二叉树,根为 i 结点
(*huffmanTree)[s1].parent = i;
(*huffmanTree)[s2].parent = i;
(*huffmanTree)[i].lChild = s1;
(*huffmanTree)[i].rChild = s2;
//新的结点 i 的权值
(*huffmanTree)[i].weight = (*huffmanTree)[s1].weight + (*huffmanTree)[s2].weight;
printf("%d (%d, %d)\n", (*huffmanTree)[i].weight, (*huffmanTree)[s1].weight, (*huffmanTree)[s2].weight);
}
printf("\n");
}
//哈夫曼树建立完毕,从 n 个叶子结点到根,逆向求每个叶子结点对应的哈夫曼编码
void creatHuffmanCode(HuffmanTree *huffmanTree, HuffmanCode *huffmanCode, int n)
{
//指示标记
int i;
//编码的起始指针
int start;
//指向当前结点的父节点
int p;
//遍历 n 个叶子结点的指示标记 c
unsigned int c;
//分配n个编码的头指针
huffmanCode=(HuffmanCode *)malloc((n+1) * sizeof(char *));
//分配求当前编码的工作空间
char *cd = (char *)malloc(n * sizeof(char));
//从右向左逐位存放编码,首先存放编码结束符
cd[n-1] = '\0';
//求n个叶子结点对应的哈夫曼编码
for(i = 1; i <= n; i++)
{
//初始化编码起始指针
start = n - 1;
//从叶子到根结点求编码
for(c = i, p = (*huffmanTree)[i].parent; p != 0; c = p, p = (*huffmanTree)[p].parent)
{
if( (*huffmanTree)[p].lChild == c )
{
//从右到左的顺序编码入数组内
cd[--start] = '0'; //左分支标0
}
else
{
cd[--start] = '1'; //右分支标1
}
}// end of for
//为第i个编码分配空间
huffmanCode[i] = (char *)malloc((n - start) * sizeof(char));
strcpy(huffmanCode[i], &cd[start]);
}
free(cd);
//打印编码序列
for(i = 1; i <= n; i++)
{
printf("HuffmanCode of %3d is %s\n", (*huffmanTree)[i].weight, huffmanCode[i]);
}
printf("\n");
}
int main(void)
{
HuffmanTree HT;
HuffmanCode HC;
int *w,i,n,wei,m;
printf("n = " );
scanf("%d",&n);
w=(int *)malloc((n+1)*sizeof(int));
printf("\ninput the %d element's weight:\n",n);
for(i=1; i<=n; i++)
{
printf("%d: ",i);
fflush(stdin);
scanf("%d",&wei);
w[i]=wei;
}
createHuffmanTree(&HT, w, n);
creatHuffmanCode(&HT,&HC,n);
return 0;
}
运行结果:
循环冗余校验码,简称CRC码,CRC校验码是一种高性能的检错码,具有检错能力强,实现简单容易,良好的代数结构等特点,比如奇偶校验和恒比码不能检测出来的置换型错误,CRC校验能够很好的检测出来。
现实的通信链路都不是理想的,信息在传输过程中可能会产生差错:1可能变成0,0也可能变成1。这叫做比特差错。为了保证数据传输的可靠性,在计算机网络传输数据时,必须采用一些差错检测措施。
目前在数据链路层广泛使用的是循环冗余校验CRC的检错技术,其基本原理为:在K位信息码后再拼接R位的校验码,整个编码长度为N(N=K+R)
位,因此,这种编码也叫(N,K)码。对于一个给定的(N,K)码,可以证明存在一个最高次幂为N-K=R的多项式G(x)。根据G(x)可以生成K位信息的校验码,而G(x)叫做这个CRC码的生成多项式。
校验码的具体生成过程为:假设要发送的信息用多项式C(X)表示,将C(x)左移R位(可表示成C(x)xR),这样C(x)的右边就会空出R位,这就是校验码的位置。用C(x)xR除以生成多项式G(x)得到的余数就是校验码。例如,目前广泛使用的CRC-16的生成多项式为:CRC-16=X^16+X^15+X^2+1
。
源代码:
#include
unsigned char test[16] = {0x10,0x10,0x05,0xef,0x78,0x05,0x89,0x12,0x45,0x78,0xac,0xef,0xd5,0xe0,0x5a,0xff};
unsigned char len = 16;
int main()
{
unsigned long temp=0;
unsigned int crc;
unsigned char i;
unsigned char *ptr=test;
while(len--)
{
for(i=0x80;i!=0;i=i>>1)
{
temp=temp*2;
if((temp&0x10000)!=0)
{
temp=temp^0x11021;
}
if((*ptr&i)!=0)
{
temp=temp^(0x10000^0x11021);
}
}
ptr++;
}
crc=temp;
printf("本段数据的CRC冗余校验码为:\n0x%x\n",crc);
return 0;
}
对数据段 {0x10,0x10,0x05,0xef,0x78,0x05,0x89,0x12,0x45,0x78,0xac,0xef,0xd5,0xe0,0x5a,0xff}
进行实验后,得到运行结果如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bLBEX96n-1585656751346)(C:\Users\lenovo\Desktop\5.PNG)]
因此,实际发送的数据是{0x10,0x10,0x05,0xef,0x78,0x05,0x89,0x12,0x45,0x78,0xac,0xef,0xd5,0xe0,0x5a,0xff,0xfcd4},
接收端接收到数据后,根据相同的生成多项式做同样的计算,得到的校验码若为0,则说明传输过程中比特没有产生差错,否则出错。
,0x10,0x05,0xef,0x78,0x05,0x89,0x12,0x45,0x78,0xac,0xef,0xd5,0xe0,0x5a,0xff}` 进行实验后,得到运行结果如下:
因此,实际发送的数据是{0x10,0x10,0x05,0xef,0x78,0x05,0x89,0x12,0x45,0x78,0xac,0xef,0xd5,0xe0,0x5a,0xff,0xfcd4},
接收端接收到数据后,根据相同的生成多项式做同样的计算,得到的校验码若为0,则说明传输过程中比特没有产生差错,否则出错。