霍夫曼树(最优二叉树)
所有带权值的节点都是叶节点
(1)将每一个带权值的节点看做一颗树的根,将其放入到链表中(放入的过程,将带权权值的节点按照 权值的大小顺序插入链表)
(2)重复 从链表中取出前两节点(前两个节点也就是权值相对最小的两个节点),作为叶子节点,再创建一个新的树根节点,作为
这两个叶子的父节点,父节点的权值 = 左子权值 + 右子权值,再将刚才形成的新树,插入到链表,最终循环结束时,链表中
只有一个节点,也就是霍夫曼树的根节点
(3)打印霍夫曼树 (层次遍历)
(4)霍夫编码:子节点先将父节点的编码拷贝过来,如果当前子节点是左子,那么再在code尾巴连接"0",如果当前子节点是右子,
那么再在code尾巴连接"1",
bitree.c
#include "bitree.h"
#include "linkqueue.h"
#include <stdio.h>
#include <stdlib.h>
#include "linklist.h"
#include <string.h>
//前序遍历
void preOrder(bitree_t *r)//r二叉树根节点的指针
{
if(r == NULL)//递归函数的结束条件
return;
printf("%c ",r->data);//根
preOrder(r->lchild);//左
preOrder(r->rchild);//右
}
//中序遍历
void inOrder(bitree_t * r)
{
if(r == NULL)//递归的结束条件
return;
inOrder(r->lchild);//左
printf("%c ",r->data);//根
inOrder(r->rchild);//右
}
//后序遍历
void postOrder(bitree_t *r)
{
if(r == NULL)//递归函数的结束条件
return;
postOrder(r->lchild);//左
postOrder(r->rchild);//右
printf("%c ",r->data);//根
}
//遍历二叉树
//s 代表的是打印提示, void (*p)(bitree_t *)函数指针 r遍历的树
void showBitree(char *s,void (*p)(bitree_t *),bitree_t *r)
{
printf("%s",s);
p(r);
printf("\n");
}
//创建二叉树,用递归函数创建
bitree_t *createBitree()
{//root
// ABD###CE##F##
bitree_t *r = NULL;//用来保存二叉树的根节点
char ch;
scanf("%c",&ch);
if(ch == '#')//输入是'#',代表没有左子或右子
return NULL;
r = (bitree_t *)malloc(sizeof(bitree_t));
if(NULL == r)
{
perror("r malloc failed");
return NULL;
}
r->data = ch;
r->lchild = createBitree();
r->rchild = createBitree();
return r;
}
//层次遍历
void unOrder(bitree_t *r)
{
//1.创建一个队列,队列的数据域变成指向树节点的指针
linkqueue_t *p = createEmptyLinkQueue();
if(r != NULL)
inLinkQueue(p,r);
//2.循环打印
while(!isEmptyLinkQueue(p))
{
r = outLinkQueue(p);
printf("%c ",r->data);
if(r->lchild != NULL)//只要左子不为空,就入列,之后出列的时候打印
inLinkQueue(p,r->lchild);
if(r->rchild != NULL)//只要右子不为空,就入列,之后出列的时候打印
inLinkQueue(p,r->rchild);
}
}
//创建霍夫曼树 返回值是霍夫曼树的根节点,参数为链表的头指针
bitree_t *createHuffmanTree(link_list_t p)
{
bitree_t *r = NULL;
while(p->next != NULL && p->next->next != NULL)//只要链表中有两个节点以上,循环就继续执行
{
r = (bitree_t *)malloc(sizeof(bitree_t));
if(NULL == r)
{
perror("r malloc failed");
return NULL;
}
r->data = '#';
r->code[0] = '\0';
r->path = 0;
r->lchild = p->next->data;//从链表中取出第一个权值相对小的节点
deletePostLinkList(p,0);//删除之后,下一个节点又是相对权值小的那个节点
r->rchild = p->next->data;//从链表中去除第二个权值相对小的节点
deletePostLinkList(p,0);
r->weight = r->lchild->weight + r->rchild->weight;//新节点的权值等于其左子和右子的权值的和
insertIntoOrderLinkList(p,r);
}
//循环结束后,链表中只有一个节点,也就是霍夫曼树的根节点
return r;
}
//打印霍夫曼树
void showHuffmanTree(bitree_t *r)
{
int wpl = 0;//用来保存最短带权路径长度
//用层次遍历的思想来打印霍夫曼树
linkqueue_t *p = createEmptyLinkQueue();
if(r != NULL)
inLinkQueue(p,r);
//循环打印
while(!isEmptyLinkQueue(p))
{
r = outLinkQueue(p);
//打印放在这,会将所有节点打印输出
// printf("%c[%d]---%d\n",r->data,r->weight,r->path);
if(r->lchild == NULL && r->rchild == NULL)//说明当前节点是叶节点
{//计算权值与路径长度乘积,并累加
wpl += r->weight * r->path;
//把打印放在这,说明只打印叶子节点
printf("%c[%d]:%s\n",r->data,r->weight,r->code);
// printf("%d * %d\n",r->weight,r->path);
}
if(r->lchild != NULL)
{
inLinkQueue(p,r->lchild);
r->lchild->path = r->path;//左子将父节点的路径长度拷贝过来
r->lchild->path++;//将路径长度+1
strcpy(r->lchild->code,r->code);//将父节点的编码拷贝过来
strcat(r->lchild->code,"0");//当前节点为左子,所以尾巴连接的是0
}
if(r->rchild != NULL)
{
inLinkQueue(p,r->rchild);
r->rchild->path = r->path;//右子将父节点的路径长度拷贝过来
r->rchild->path++;//将路径长度+1
strcpy(r->rchild->code,r->code);//将父节点的编码拷贝过来
strcat(r->rchild->code,"1");//当前节点为右子,所以尾巴连接的是0
}
}
printf("\n");
printf("WPL is %d\n",wpl);
}
bitree.h
#ifndef _BITREE_H_
#define _BITREE_H_
typedef char datatype_tree;
typedef struct tree_node_t
{
int path;//用来保存路径长度
int weight;//权值
char code[20];//定义一个字符数组,用来保存霍夫编码
datatype_tree data;//数据域
struct tree_node_t *lchild;//左子指针
struct tree_node_t *rchild;//右子指针
}bitree_t;
//链表的数据域是指向树节点的指针
typedef bitree_t * datatype;
typedef struct node_t
{
datatype data;//数据域
struct node_t *next;//指针域,指向自身结构体的指针
}link_node_t,*link_list_t;
//前序遍历
void preOrder(bitree_t *r);//r二叉树根节点的指针
//中序遍历
void inOrder(bitree_t * r);
//后序遍历
void postOrder(bitree_t *r);
//遍历二叉树
//s 代表的是打印提示, void (*p)(bitree_t *)函数指针 r遍历的树
void showBitree(char *s,void (*p)(bitree_t *),bitree_t *r);
//创建二叉树,用递归函数创建
bitree_t *createBitree();
//层次遍历
void unOrder(bitree_t *r);
//创建霍夫曼树 返回值是霍夫曼树的根节点,参数为链表的头指针
bitree_t *createHuffmanTree(link_list_t p);
//打印霍夫曼树
void showHuffmanTree(bitree_t *r);
#endif
linklist.c
#include "linklist.h"
#include <stdio.h>
#include <stdlib.h>
//1.创建一个空的单向链表(有头单向链表)
link_node_t *createEmptyLinkList()
{
//思想:创建一个节点,作为链表的头结点
link_node_t *p = (link_node_t *)malloc(sizeof(link_node_t));
if(NULL == p)
{
perror("createEmptyLinkList malloc failed");
return NULL;
}
p->next = NULL;
return p;
}
//3.求单向链表长度的函数
int lengthLinkList(link_node_t *p)
{
int len = 0;
while(p->next != NULL)
{
p = p->next;
len++;
}
return len;
}
//4.遍历单向链表
void showLinkList(link_node_t *p)
{
while(p->next != NULL)
{
p = p->next;
//注意此处,数据域是指向树节点的指针
printf("(%c-%d) ",p->data->data,p->data->weight);
}
printf("\n----------------------\n");
}
//5.删除单向链表中指定位置的数据 post 代表的是删除的位置
int deletePostLinkList(link_node_t *p, int post)
{
int i;
link_node_t *pdel = NULL;
//1.容错处理
if(isEmptyLinkList(p) || post < 0 || post >= lengthLinkList(p))
{
printf("deletePostLinkList post is failed!!\n");
return -1;
}
//2.将头指针指向删除节点的前一个位置
for(i = 0; i < post; i++)
p = p->next;
//3.进行删除操作
//(1)定义一个指针pdel,指向被删除节点
pdel = p->next;
//(2)跨国被删除节点
p->next = pdel->next;
//(3)释放被删除节点
free(pdel);
pdel = NULL;
return 0;
}
//6.判断单向链表是否为空 1代表空 0代表非空
int isEmptyLinkList(link_node_t *p)
{
return p->next == NULL;//条件为真, C语言中真用1表达 假用0表达
}
//7.向链表插入数据,插入数据按照权值的小插入,形成有序
int insertIntoOrderLinkList(link_node_t *p,datatype data)
{
link_node_t *pnew = NULL;
//1。将p指向插入节点权值刚好不大于的前一个位置
// data->weight 代表即将插入链表节点的权值
// p->next->data->weight 代表当前链表中节点的权值
while(p->next != NULL && data->weight > p->next->data->weight)
p = p->next;
//2.创建新的节点装上数据
pnew = (link_node_t *)malloc(sizeof(link_node_t));
if(NULL == pnew)
{
perror("pnew malloc failed");
return -1;
}
//3.装上数据
pnew->data = data;
pnew->next = NULL;
//4.将新节点连接到链表中,先连后面再连前面
pnew->next = p->next;
p->next = pnew;
return 0;
}
linklist.h
#ifndef _LIINKLIST_H_
#define _LIINKLIST_H_
#include "bitree.h"
//1.创建一个空的单向链表(有头单向链表)
link_node_t *createEmptyLinkList();
//3.求单向链表长度的函数
int lengthLinkList(link_node_t *p);
//4.遍历单向链表
void showLinkList(link_node_t *p);
//5.删除单向链表中指定位置的数据 post 代表的是删除的位置
int deletePostLinkList(link_node_t *p, int post);
//6.判断单向链表是否为空 1代表空 0代表非空
int isEmptyLinkList(link_node_t *p);
//7.向链表插入数据,插入数据按照权值的小插入,形成有序
int insertIntoOrderLinkList(link_node_t *p,datatype data);
#endif
linkqueue.c
#include "linkqueue.h"
#include <stdio.h>
#include <stdlib.h>
//1.创建一个空的队列
linkqueue_t *createEmptyLinkQueue()
{
linkqueue_t *p = (linkqueue_t *)malloc(sizeof(linkqueue_t));
if(NULL == p)
{
perror("createEmptyLinkQueue p malloc failed");
return NULL;
}//申请空间就是为了装东西
//申请链表的头节点空间,让rear和front都指向头结点
p->front = p->rear = (linkqueue_list_t)malloc(sizeof(linkqueue_node_t));
if(NULL == p->rear)
{
perror("p->rear malloc failed");
return NULL;
}
p->rear->next = NULL;//或者用p->front->next = NULL;因为p->rear 和 p->front 指向同一个位置即头节点
return p;
}
//2.入列 data代表入列的数据
int inLinkQueue(linkqueue_t *p,datatype_linkqueue data)
{
//1.创建一个新的节点,用来保存即将插入的数据
linkqueue_list_t pnew = (linkqueue_list_t)malloc(sizeof(linkqueue_node_t));
if(NULL == pnew)
{
perror("inLinkQueue pnew malloc failed");
return -1;
}
//2.将入列的数据放入到新的节点中
pnew->data = data;
pnew->next = NULL;
//3.将新节点链链接到链表的尾巴
p->rear->next = pnew;//新节点链接到链表的尾
p->rear = pnew;//rear移动,因为rear永远指向当前链表的尾
return 0;
}
//3.出列
datatype_linkqueue outLinkQueue(linkqueue_t *p)
{
linkqueue_list_t pdel = NULL;//指向被删除的节点
//1.容错判断
if(isEmptyLinkQueue(p))
{
printf("isEmptyLinkQueue !!\n");
return NULL;
}
//2.出列数据
//(1)定义pdel指向即将被删除的节点就是front指向的节点,出列每次删除的都是front指向的那个节点
pdel = p->front;
//(2)将front向后移动一个位置
p->front = p->front->next;
//(3)释放被删除节点
free(pdel);
pdel = NULL;
//(4)将数据出列
return p->front->data;
}
//4.判断队列是否为空
int isEmptyLinkQueue(linkqueue_t *p)
{
return p->front == p->rear;
}
//5.求队列长度的函数
int lengthLinkQueue(linkqueue_t *p)
{
int len = 0;
linkqueue_list_t h = p->front;//将链表的头指针保存的地址给h,如果直接用front,求长度之后会找不到链表的头,用h的移动代替front的移动
//求长度,相当于遍历有头的单向链表
while(h->next != NULL)
{
h = h->next;
len++;
}
return len;
}
//6.清空队列
void clearLinkQueue(linkqueue_t *p)
{
while(!isEmptyLinkQueue(p))//只要不为空,就出列
outLinkQueue(p);
}
linkqueue.h
#ifndef _LINKQUEUE_H_
#define _LINKQUEUE_H_
#include "bitree.h"
//将 bitree_t * 改名 为datatype_linkqueue
typedef bitree_t * datatype_linkqueue;//把队列的数据域变成指向树节点的指针
typedef struct node
{
datatype_linkqueue data;//数据域
struct node *next;//指针域
}linkqueue_node_t,*linkqueue_list_t;
//linkqueue_list_t p === linkqueue_node_t *
typedef struct//将队列头指针和尾指针封装到一个结构体里
{
linkqueue_list_t front;//相当于队列的头指针
linkqueue_list_t rear;//相当于队列的尾指针
//有了链表的头指针和尾指针,那么我们就可以操作这个链表
}linkqueue_t;
//1.创建一个空的队列
linkqueue_t *createEmptyLinkQueue();
//2.入列 data代表入列的数据
int inLinkQueue(linkqueue_t *p,datatype_linkqueue data);
//3.出列
datatype_linkqueue outLinkQueue(linkqueue_t *p);
//4.判断队列是否为空
int isEmptyLinkQueue(linkqueue_t *p);
//5.求队列长度的函数
int lengthLinkQueue(linkqueue_t *p);
//6.清空队列
void clearLinkQueue(linkqueue_t *p);
#endif
main.c
#include "bitree.h"
#include <stdio.h>
#include <stdlib.h>
#include "linklist.h"
int main(int argc, const char *argv[])
{
bitree_t *r = NULL;
char ch;//用来保存输入的数据
int weight;//用来保存输入的权值
//1.创建一个空的链表
link_list_t h = createEmptyLinkList();
printf("请您输入数据和权值(A,7):\n");
while(scanf("(%c,%d) ",&ch,&weight) == 2)
{
r = (bitree_t *)malloc(sizeof(bitree_t));
if(NULL == r)
{
perror("r malloc failed");
return -1;
}
r->path = 0;
r->code[0] = '\0';
r->data = ch;//数据
r->weight = weight;//权值
r->lchild = NULL;
r->rchild = NULL;
//将r根据权值的大小插入到链表表,让树的节点根据权值大小呈现有序
insertIntoOrderLinkList(h,r);
}
//打印调试程序,根据权值形成一个有序的链表
showLinkList(h);
r = createHuffmanTree(h);//创建霍夫曼树
showHuffmanTree(r);
return 0;
}