【C语言】利用哈夫曼树、小顶堆、堆栈进行进行不等长编码

【卑微十二,在线求赞】

一、目标

输入字母以及权值,进行不等长编码

二、思路

1、输入字母以及权值,以权值形成小顶堆

2、利用小顶堆形成哈夫曼树

3、创建堆栈用于输出编码

4、前序递归遍历哈夫曼树,并进行如下操作

①向左递归:0进栈

②向右递归:1进栈

③返回:出栈

④遇到叶节点:反向遍历堆栈并输出

三、具体流程

1、输入字母以及权值,以权值形成小顶堆

所需创建结构如下:

①treenode:结点结构,weight为权值,letter为对应的字母,left和right是为了后面能够创建哈夫曼树设立,如果只需要建一个最小堆就不用设置

②minheap:小顶堆结构,其中size为现在的长度,capacity为最大长度,data是指向treenode指针

typedef struct treenode
{
	int weight;
	char letter;
	struct treenode* left;
	struct treenode* right;
}treenode;


typedef struct minheap
{
	treenode* data[50];
	int size;
	int capacity;
}minheap;

所需创建函数如下:

minheap* buildmin(minheap* h);
minheap* adjust(minheap* h);
int minnum(int a, int b);
minheap* exchange(minheap* h, int nowparent, int nowchild);
void ergodic(minheap* p);
minheap* insert(minheap* p, treenode* h);
treenode* delmin(minheap* h);

①buildmin:输入一个minheap的指针,在其中顺序插入data,再利用adjust调整成一个最小堆

minheap* buildmin(minheap* h)
{
	int i;
	h->data[0] = (treenode*)malloc(sizeof(treenode));
	printf("please enter the capacity of the mintree\n");
	scanf("%d", &h->capacity);
	getchar();
	h->data[0]->weight = -9999;
	printf("please enter the leeter with a weight separated by space,weight:999meansend\n");
	for (i = 1; i <= h->capacity; i++)
	{
		h->data[i] = (treenode*)malloc(sizeof(treenode));
		scanf("%c %d", &h->data[i]->letter, &h->data[i]->weight);
		getchar();
		h->data[i]->left = h->data[i]->right = NULL;
		if (h->data[i]->weight == 999)
		{
			break;
		}
	}
	h->size = i - 1;
	h = adjust(h);
	printf("the mintree is established\n");
	return h;
}

②adjust:输入一个minheap的指针,从最后一个父节点开始调整,如果父节点大于子节点,就利用exchange函数将两个节点的位置调整

minheap* adjust(minheap* h)
{
	int parent, child;
	parent = h->size / 2;
	child = h->size;
	if (h->size == 1)
	{
		return h;
	}
	for (; parent != 0; parent--)
	{
		int nowparent = parent;
		int nowchild = nowparent * 2;
		for (; nowparent * 2 <= h->size; nowparent = nowchild)
		{
			nowchild = nowparent * 2;
			if (nowparent * 2 == h->size)
			{
				if (h->data[nowchild]->weight < h->data[nowparent]->weight)
				{
					h = exchange(h, nowparent, nowchild);
				}
			}
			else
			{
				if (h->data[nowparent]->weight > minnum(h->data[nowchild]->weight, h->data[nowchild + 1]->weight))
				{
					if (h->data[nowchild + 1]->weight < h->data[nowchild]->weight)
					{
						nowchild++;
						h = exchange(h, nowparent, nowchild);
					}
					else
					{
						h = exchange(h, nowparent, nowchild);
					}
				}
			}

		}
	}
	return h;
}

③minnum:输出两个值里面的最小值,没啥说的

int minnum(int a, int b)
{
	return a > b ? b : a;
}

④exchange:输入一个最小堆的指针和两个结点的位置,将两个结点对调

minheap* exchange(minheap* h, int nowparent, int nowchild)
{
	treenode* left = (treenode*)malloc(sizeof(treenode));
	treenode* right = (treenode*)malloc(sizeof(treenode));
	char tmpletter = h->data[nowchild]->letter;
	int tmpweight = h->data[nowchild]->weight;
	left = h->data[nowchild]->left;
	right = h->data[nowchild]->right;
	h->data[nowchild]->letter = h->data[nowparent]->letter;
	h->data[nowchild]->weight = h->data[nowparent]->weight;
	h->data[nowchild]->left = h->data[nowparent]->left;
	h->data[nowchild]->right = h->data[nowparent]->right;
	h->data[nowparent]->letter = tmpletter;
	h->data[nowparent]->weight = tmpweight;
	h->data[nowparent]->left = left;
	h->data[nowparent]->right = right;
	return h;
}

⑤ergodic:遍历一下最小堆,测试用的,编码的话用不到这个,思路很简单,因为是个最小堆,所以直接循环遍历一下就行

void ergodic(minheap* h)
{
	printf("ergodic as follow\n");
	for (int i = 1; i <= h->size; i++)
	{
		printf("%c %d\n", h->data[i]->letter, h->data[i]->weight);
	}
	return 0;
}

⑥insert:将叶节点插入最小堆,思路就是插在最后一个,然后用上面的adjust函数调整成一个最小堆

minheap* insert(minheap* h, treenode* p)
{
	if (h->size == h->capacity)
	{
		printf("the minheap is full\n");
		return h;
	}
	h->data[h->size + 1] = (treenode*)malloc(sizeof(treenode));
	h->data[h->size + 1]->letter = p->letter;
	h->data[h->size + 1]->weight = p->weight;
	h->data[h->size + 1]->left = p->left;
	h->data[h->size + 1]->right = p->right;
	h->size++;
	h = adjust(h);
	return h;
}

⑦delmin:给一个最小堆的指针,删除并返回其最小值,思路:因为是最小堆,所以直接删除返回根节点,再把最后一个结点放到第一个,再利用adjust将其调整成一个最小堆(最后的h->size--)是为了直接把size-1

treenode* delmin(minheap* h)
{
	treenode* p = (treenode*)malloc(sizeof(treenode));
	p->letter = h->data[1]->letter;
	p->weight = h->data[1]->weight;
	p->left = h->data[1]->left;
	p->right = h->data[1]->right;
	h->data[1]->letter = h->data[h->size]->letter;
	h->data[1]->weight = h->data[h->size]->weight;
	h->data[1]->left = h->data[h->size]->left;
	h->data[1]->right = h->data[h->size--]->right;
	h = adjust(h);
	return p;
}

2、利用小顶堆形成哈夫曼树

所需函数如下:

treenode* huffman(minheap* h);

①huffman:给一个最小堆,将其转换为哈夫曼树,并返回其树根,思路:每次从最小堆中利用delmin函数取出两个treenode结点(最小堆树根),并将其组合成新结点,权值为两个结点权值相加,左右互换无影响(这里就是为什么之前treenode要设置left和right的原因),最后把新节点插入最小堆,循环size-1次(注意要一开始把size-1赋值给某个变量哈,因为循环过程中size是会变的)就形成了哈夫曼树

treenode* huffman(minheap* h)
{
	int i;
	treenode* p;
	int times = h->size - 1;
	for (i = 0; i < times; i++)
	{
		p = (treenode*)malloc(sizeof(treenode));
		p->left = delmin(h);
		p->right = delmin(h);
		p->letter = NULL;
		p->weight = p->left->weight + p->right->weight;
		insert(h, p);
	}
	p = delmin(h);
	return p;
}

3、创建堆栈用于输出编码

所需结构如下:

typedef struct stack
{
	int code;
	struct stack* next;
}stack;

①stack:就普通链表堆栈结构,没啥说的

所需函数如下:

stack* buildstack();
void push(stack* s, int code);
void pop(stack* s);
void ergodicstack(stack* s);
stack* reservestack(stack* s, stack* p);
void reergodicstack(stack* s);

①buildstack:建一个堆栈(但是感觉有点多余,就只是建立了一个头结点而已)

stack* buildstack()
{
	stack* s = (stack*)malloc(sizeof(stack));
	s->code = NULL;
	s->next = NULL;
	return s;
}

②push:入栈:常规操作,思路:建立一个栈结点存放数据,把新结点的next指向头结点指向的next,把头结点的next指向新结点

void push(stack* s, int code)
{
	stack* t = (stack*)malloc(sizeof(stack));
	t->code = code;
	t->next = s->next;
	s->next = t;
	return 0;
}

③pop:出栈:常规操作,思路:建立一个临时结点存放头结点指向的next,将头结点的next指向头结点的next的next,再把临时结点free掉;

void pop(stack* s)
{
	stack* tmp = (stack*)malloc(sizeof(stack));
	tmp = s->next;
	s->next = s->next->next;
	free(tmp);
	return 0;
}

④ergodicstack:遍历堆栈,思路:如果结点的next存在就递归遍历,但是因为堆栈的特性,遍历出来是反的,我们编码需要他正向输出,所以之后会把堆栈的数据再次放入另一个堆栈再进行遍历,就可以正向输出了

void ergodicstack(stack* s)
{
	if (s->next != NULL)
	{
		printf("%d", s->next->code);
		ergodicstack(s->next);
	}
	return 0;
}

⑤reservestack:调转堆栈,思路:其实就是把一个堆栈的数据放到另一个堆栈,但因为堆栈的特性,捯饬一下之后,输出的顺序就反过来了,变成我们想要的输出

stack* reservestack(stack* s, stack* p)
{
	if (s->next != NULL)
	{
		push(p, s->next->code);
		reservestack(s->next, p);
	}
	return p;
}

⑥reergodicstack:反向输出堆栈,思路:其实就是先创建一个头结点,再把我们想要反向输出的堆栈利用reservestack函数再放到新节点里面,最后把新的堆栈遍历一下就行了

void reergodicstack(stack* s)
{
	stack* p = (stack*)malloc(sizeof(stack));
	p->code = NULL;
	p->next = NULL;
	p = reservestack(s, p);
	ergodicstack(p);
	return 0;
}

4、前序递归遍历哈夫曼树,并进行如下操作

①向左递归:0进栈

②向右递归:1进栈

③返回:出栈

④遇到叶节点:反向遍历堆栈并输出

思路:利用哈夫曼树编码,其实就是走左边输出0,右边输出1(或者相反)直到遇到叶节点,之前输出的就是它的编码,很自然的我们能够想到直接遍历哈夫曼树,遇到叶节点就输出叶节点的相应字母,问题是递归返回时我们如何知道它的上一个状态,那么很自然的就能想到用堆栈存储0和1的数据,所以就有了上面的四项操作

所需函数如下:

void coding(treenode* p, stack* s);

①coding:给一个哈夫曼树的根节点,递归遍历哈夫曼树,并且执行上面所说的四步

void coding(treenode* p, stack* s)
{
	if (p->letter != NULL)
	{
		printf("%c:", p->letter);
		reergodicstack(s);
		printf("\n");
	}
	if (p->left)
	{
		push(s, 0);
		coding(p->left, s);
		pop(s);
	}
	if (p->right)
	{
		push(s, 1);
		coding(p->right, s);
		pop(s);
	}
	return 0;
}

具体效果如下:

 最后附上main函数

int main()
{
	minheap* h = (minheap*)malloc(sizeof(minheap));
	h = buildmin(h);
	treenode* p;
	p = huffman(h);
	stack* s = (stack*)malloc(sizeof(stack));
	s = buildstack(s);
	coding(p, s);
}

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值