霍夫曼树(最优二叉树)

霍夫曼树(最优二叉树)

所有带权值的节点都是叶节点

(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++;//将路径长度+1strcpy(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;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值