哈夫曼树和哈夫曼编码
概念:
结点的权:该结点被赋予一个表示某种意义的数值,称为该结点的权
该点的带权路径长度:从树的根结点到该结点所经过的边数与该结点上权值的乘积
该树的带权路径长度(WPL):所有叶结点的带权路径长度之和
WPL最小的二叉树称为 哈夫曼树或最优二叉树
代码:
哈夫曼树存储结构
由于赫夫曼树中没有度为1的节点,则一棵具有n个叶子节点的的赫夫曼树共有2n-1个节点,因此可以将这些节点存储在大小为2n-1的一维数组中
typedef struct Node
{
int weight;//权重
int parent;//父结点序号
int lchild,rchild;//左右孩子结点序号
}HTNode,*HuffmanTree;
初始化哈夫曼树
void initHuffmanTree(HuffmanTree &H,int n)
{
if(n<=1) return;
int m=2*n-1;//n个叶子结点的哈夫曼树有2n-1个结点
H=new HTNode[m];
for(int i=0;i<m;i++)
{
H[i].parent=-1;
H[i].lchild=0;
H[i].rchild=0;
}
for(int i=0;i<m;i++)
{
cin>>H[i].weight;
}
}
Select()函数的作用为每次选择k个结点,挑选两个双亲域为0且权值最小的节点,利用s1和s2返回两个结点的下标。
Select()
//求出数组前k个元素中,最小的两个元素并赋值给min1,min2
void Select(HuffmanTree T,int k,int &min1,&min2)
{
min1=min(T,k);
min2=min(T,k);
}
int min(HuffmanTree T,int k)
{
int i=0;
int min;//用以存放最小的元素下标
int min_weight;//存放最小元素的权重
while(T[i].parent!=-1)
i++;
min_weight=T[i].weight;//经过上述while循环后,i的值表示前k个元素中首个没有父节点的元素
for(;i<k;i++)
{
if(T[i].weight<min_weight && T[i].parent==-1)
{
min_weight=T[i].weight;
min=i;
}
}
//选出weight最小的元素后,将其parent置1,使得下一次比较时将其排除在外。
T[min].parent=1;
return min;
}
构造哈夫曼树
void CreateHuffmanTree(HuffmanTree &H,int n)
{
int m=2*n-1;
int s1,s2;
for(int i=n;i<m;i++)
{
Select(H,i,s1,s2);
H[s1].parent=H[s2].parent=i;
H[i].lchild=s1;
H[i].rchild=s2;
H[i].weight=H[s1].weight+H[s2].weight;
}
}
哈夫曼编码
typedef char** HuffmanCode;//用二级指针存储,一级指针指向叶子结点,二级指针指向叶子结点的哈夫曼编码
void HuffmanCoding(HuffmanTree ht,HuffmanCode &hc,int n)
{
//用来保存指向每个赫夫曼编码串的指针
hC = (HuffmanCode)malloc(n * sizeof(char *));
//临时空间,用来保存每次求得的赫夫曼编码串
//对于有n个叶子节点的赫夫曼树,各叶子节点的编码长度最长不超过n-1
//外加一个'\0'结束符,因此分配的数组长度最长为n即可
char *code = (char *)malloc(n * sizeof(char));
int cur = 2 * n - 2; //当前遍历到的节点的序号,初始时为根节点序号
int code_len = 0; //定义编码的长度
//构建好赫夫曼树后,把weight用来当做遍历树时每个节点的状态标志
//weight=0表明当前节点的左右孩子都还没有被遍历
//weight=1表示当前节点的左孩子已经被遍历过,右孩子尚未被遍历
//weight=2表示当前节点的左右孩子均被遍历过
int i;
for (i = 0; i < cur + 1; i++)
{
hT[i].weight = 0;
}
while(cur!=-1) //只有当cur为根节点时才退出,只有根节点的parent存储值为-1
{
//左右孩子均未被遍历
if(ht[cur].weight == 0)
{
ht[cur].weight=1;//置为1表示左孩子已被遍历
if(ht[cur].lchild!=-1 )
{//表明有左孩子
code[code_len++]="0";
cur = ht[cur].lchild;
}
else{//表明是叶子结点
code[code_len] = '\0';
hc[cur] = (char *)malloc((code_len + 1) * sizeof(char));
strcpy(hc[cur], code);
}
}else if(ht[cur].weight==1)
{
ht[cur].weight=2; //表明其左右孩子均被遍历过了
if (ht[cur].rchild != -1)
{ //如果当前节点不是叶子节点,则记下编码,并继续向右遍历
code[code_len++] = '1';
cur = ht[cur].rchild;
}
}
else{
ht[cur].weight=2;//这里把weight置为2 表示都遍历过,再次从根结点遍历时就不会再走这条路径
cur=ht[cur].parent;
--code_len;
}
}
for (int i = 0; i < n; ++i) {
printf("%s\n", hc[i]);
}
free(code);
}
并查集
是用树的双亲表示作为存储结构的一种数据结构,由用于存储前驱结点pre[]数组和函数find()、union()组成。
find(pre,x)用于查找集合pre中x的所在的子集和返回x所在的根结点。
union(x,y)用于合并x和y所在的不同的子集和。
初始化操作
void inital(int pre[] )
{
for(int i=0;i<size;i++)
{
pre[i]=i //根结点为自己
}
}
find()实现
int find(int pre[],int x)
{
while(pre[x]!=x) //如果前驱节点的值不是自己,则继续查询,
{
x=pre[x];//继续寻找x的前驱,直到找到与自己相等的就是根结点
}
return x;
}
union()实现
void union(int x,int y)
{
int x=find(pre,x); //x所属的子集和的根结点
int y=find(pre,y); //y所属的子集和的根结点
if(x!=y)
{
pre[x]=y; //将x的前驱结点指向y
}
}
优化:
1、find()函数可能会出现树的深度过长,每个结点只有一个子结点,查询会更加耗时,所以可以将每个结点的前驱结点直接变成根结点。
find()修改后代码
int find(int pre[],int x)
{
if(pre[x] == x)
{
return x;
}
return pre[x]=find(pre,pre[x])
}
2、合并两树时,确定谁是谁的前驱结点,就不会导致左右子树的深度差过大。将树中所有节点都增设一个权值,用以表示该节点所在树中的高度(比如用rank[x]=4表示 x 节点所在树的高度为4)。这样x一来,在合并操作的时候就能通过这个权值的大小来决定谁当谁的前驱结点
void union(int x,int y)
{
x=find(x); //寻找 x的根节点
y=find(y); //寻找 y的根节点
if(x==y) return ;
if(rank[x]>rank[y]) pre[y]=x; //如果 x的高度大于 y,则令 y的前驱为 x
else //否则
{
if(rank[x]==rank[y]) rank[y]++; //如果 x的高度和 y的高度相同,则令 y的高度加1
pre[x]=y; //让 x的前驱为 y
}
}
所有代码
int pre[N]; //存储每个结点的前驱结点
int rank[N]; //树的高度
void init(int n) //初始化函数,对录入的 n个结点进行初始化
{
for(int i = 0; i < n; i++){
pre[i] = i; //每个结点的上级都是自己
rank[i] = 1; //每个结点构成的树的高度为 1
}
}
int find(int x) //改进查找算法:完成路径压缩,将 x的上级直接变为根结点,那么树的高度就会大大降低
{
if(pre[x] == x) return x; //递归出口:x的上级为 x本身,即 x为根结点
return pre[x] = find(pre[x]); //此代码相当于先找到根结点 rootx,然后 pre[x]=rootx
}
bool isSame(int x, int y) //判断两个结点是否连通
{
return find(x) == find(y); //判断两个结点的根结点(即代表元)是否相同
}
bool union(int x,int y)
{
x = find(x); //寻找 x的代表元
y = find(y); //寻找 y的代表元
if(x == y) return false; //如果 x和 y的代表元一致,说明他们共属同一集合,则不需要合并,返回 false,表示合并失败;否则,执行下面的逻辑
if(rank[x] > rank[y]) pre[y]=x; //如果 x的高度大于 y,则令 y的上级为 x
else //否则
{
if(rank[x]==rank[y]) rank[y]++; //如果 x的高度和 y的高度相同,则令 y的高度加1
pre[x]=y; //让 x的上级为 y
}
return true; //返回 true,表示合并成功
}