C++用指针写哈夫曼树

目录

1.前言

2.节点

3.字符统计

4.挑选两个最小值

5.构建Huffman树

6.遍历函数

7.源代码


1.前言

目的:利用C++特性 用指针解决问题

注:由于编码、译码等功能函数是基于Huffman初始化、构造和遍历实现的,此处不再进行展示,仅展示关键部分Huffman构造和遍历函数,对关键部分进行解读

声明:笔者受前人启发,这篇博客部分是对前辈代码的解读,也有部分是笔者在前人代码基础上略作改动而得。

原作者传送门:哈夫曼树的建立_Reticent_Man的博客-CSDN博客_创建哈夫曼树

2.节点

template<class T>
struct AlphaNode {
	T data;
	int freq=0;
	string code;//编码
	AlphaNode<T>* parent;
	AlphaNode<T>* lchild;
	AlphaNode<T>* rchild;
};

1.使用模板类:便于后续存储自定义数据

2.freq:frequency的缩写。因为代码的输入端是输入任意字符串并做统计,于是在此处先声明用于存储统计结果的数据域。

3.code:编码结果(在本篇博客中不做展示编码部分内容)

4.节点遵循三叉树结构:根节点和左右子树。

3.字符统计

//字符串统计函数
//需要注意 这里是从arr_str[1]开始放东西的  arr_str[0]是空的

AlphaNode<char> arr_str[400];//建立一个节点数组

static int length = 1;
void statisticAlpha(string str) {
	
	int i = 0;//从string[0]开始遍历
	while (str[i]) {
		if (0 == i) {
			arr_str[i+1].data = str[i]; 
			arr_str[i+1].freq++;
			length++;
		}
		else {
			int j = 1;//从新建数组的第二个存放数据的节点开始遍历

            //判定是否有重复的数据
			for (; arr_str[j].data!=NULL; j++) {
				if (str[i] == arr_str[j].data)break;
			}
			//锁定应该存放东西的位置 即j位置


			if (j >length-1) {
				arr_str[length].data = str[i];
				arr_str[length].freq++;
				length++;
			}
			else {
				arr_str[j].freq++;
			}
		}
		i++;
	}
	int j = 1;
	cout << "您输入的字符串  统计为:" << endl;
	while (arr_str[j].data!=NULL) {
		cout << arr_str[j].data << ':' << arr_str[j].freq << endl;
		j++;
	}
}


//注意 以下代码只是示例
main(){
    cout <<"请在下方输入一个任意字符串"<< endl;
    string temp;
    getline(cin, temp);
    statisticAlpha(temp);
}

1.在main()函数中用temp来暂存(最后的示例)

   读取的时候要求可以读取空格、以回车结尾,故 使用getline(cin,str)函数。

2.特判:(0==i)只是因为第一个必定没有与之匹配的字符,所以直接进行存储。

3.

            //判定是否有重复的数据
			for (; arr_str[j].data!=NULL; j++) {
				if (str[i] == arr_str[j].data)break;
			}
			//锁定应该存放东西的位置 即j位置

由于str[]是暂时用于存储的数组,暂时不会变动,所以把它放在前面。

当比较到结果的时候 由于数组从1开始存东西 所以循环结果j可以直接拿到下面去使用,因为在正常位置[j]是空的。

4.下面就是特判j是不是到末尾了,然后进行一系列相关操作。

4.挑选两个最小值

【要点】

1.最小值是随着数组增长而变动的,新节点 (由两个孩子的freq)的加和也会被纳入计算范围

eg:第一次:2 1 1

        第二次:2 1 1 2(末尾的2=1+1)(两个最小权值(freq)的孩子加和)

        第三次:2 1 1 2 4(末尾的4=2+2)(由于两个1 已经用了,最小权值轮到老的2和新的2了)

2.如何判断某个节点是否已经作为孩子?

        引入哨兵p[200];

int p[200] = { 0 };//用来判断节点是否被选取过

        哨兵先全部初始化为0,表示没有被选取过

3.

void Huffman::SelectMin(int &x, int &y,int end) {
	//x、y用于返回两个 最小值权值的 位置
	
	long int min1 = 999999;
	for (int i = 1; i <= end; i++) {
		if (a[i].freq < min1 && p[i] == 0) {
			min1 = a[i].freq;
			x = i;
		}
	}
	min1 = 999999;
	for (int i = 1; i <= end; i++) {
		if (a[i].freq < min1 && i != x && p[i] == 0) {
			min1= a[i].freq;
			y = i;
		}
	}
}

注意到两个特判中 都有p[i]==0  //为了保证没有被选取

定义一个最小值 初始化为999999,以保证会有最小值产生

4.为什么传引用int &x,int &y

        便于随时更改数据,因为到了后面部分代码的位置不同,作用域不一样,为避免麻烦采用引用的方式。

5.end是什么

前面1. 有讲到:比较的长度会不断变长。这一次的end在后面会传入原长度,但下一次会传入length+1(因为新的节点产生了,要将新节点纳入比较)所以end是个不定长数字。

5.构建Huffman树

//示例:避免看不懂在写什么
class Huffman{
public:
    AlphaNode<char>* Creation(int);//创建哈夫曼树
private:
    AlphaNode<char>* a;
}
int x, y;


//构建的函数正文:
AlphaNode<char>* Huffman::Creation(int n) {
	int i;
	//初始化
	for (int i = 1; i <= 2 * n - 1; i++) {
		a[i].lchild = a[i].parent = a[i].rchild = NULL;
	}
	for (i = n; i < 2 * n - 2; i++) {
		SelectMin(x, y, i-1);
		p[x] = p[y] = 1;
		a[i].lchild = &a[x];
		a[i].rchild = &a[y];
		a[i].freq = a[x].freq + a[y].freq;
		
	}
	return &a[i - 1];
}


//示例:
main(){
    AlphaNode<char> *p = hf.Creation(length);
    //length就是存储之后的存储数组的长度 比如abca的长度为4(从1开始存  +1)
}

函数的参数int n就是数组长度(后面示例里面传入)

着重讲解中间一段代码:

1. SelectMin( x , y , i-1 );

        确定最小权值节点的位置 并且返回为x、y,其中x节点权值≤y节点权值

        (默认x为左节点)

2.(i <  2*n  -  2)

为什么是这个判定?

这个循环的次数一定有严格限制 

因为在最后一次循环的时候 两个倒数第一第二大的权重总会进行加和,而这种加和会产生一个新的存储点,即:

a[i].freq = a[x].freq + a[y].freq;

这个句子仍然会运行。

如果次数比这个多一次,那么就会造成y值无法确定,从而进入死循环

(因为传的是y的引用,而SelectMin函数也没有写明“如果y没有匹配的值”会如何,结果是y会继续用原来的地址值继续这个for循环)

循环次数=数组长度 - 1 ;

字符串abca  统计后的数组长度为3(合并同类项) 那么比较次数为3-1=2。

回到代码里,比较次数为(2*n - 2 - n)+1=n - 1。

3.为什么是renturn &a[i - 1];?

因为按照这个算法:直接往数组后面开辟空间存东西

a数组前面的部分(a[1] ~a[n])都是存的数据,没有孩子和根节点。

从a[n+1]开始才有的指针运算,有孩子和根节点的连接

而这个数组的末尾,即最终加权和节点为头结点。

头结点存储了相应的孩子节点。

所以直接返回它的地址。

6.遍历函数

void Huffman::DispTree(AlphaNode<char>* a) {
	if (a != NULL) {
		cout << a->data;
		if (a->lchild != NULL || a->rchild != NULL) {
			cout << '(';
			DispTree(a->lchild);
			if (a->rchild != NULL)cout << ',';
			DispTree(a->rchild);
			cout << ')';
		}
	}
}

引用原作者的话:

后来发现纯数字的输出形式看不出子树与双亲之间的对应关系。翻阅数据结构书时,看到广义表,于是我想到用加括号的方法,即先判断一个节点是否有左子树(在赫夫曼树中有左子树就一定有右子树),如果有就输出括号,左右子树之间用逗号相隔。这样便增加了直观可读性。

7.源代码

#include<iostream>
#include<string>
using namespace std;

template<class T>
struct AlphaNode {
	T data;
	int freq=0;
	string code;//编码
	AlphaNode<T>* parent;
	AlphaNode<T>* lchild;
	AlphaNode<T>* rchild;
};
AlphaNode<char> arr_str[400];
int p[200] = { 0 };//用来判断节点是否被选取过
void statisticAlpha(string str);
int x, y;

struct HCode {
	char data;
	string code;
};

class Huffman {
private:
	AlphaNode<char>* a;
	int N;//叶子节点数量
	void code(int i, string newcode);//递归函数 对应第i个节点编码
public:
	Huffman(AlphaNode<char>*);
	AlphaNode<char>* Creation(int);//创建哈夫曼树
	void DispTree(AlphaNode<char>*);//遍历
	void CreateCodeTable();//创建编码表
	void Encode(char *s, char *d);//编码
	void Decode(char *s, char *d);//译码
	void SelectMin( int &x, int &y, int end);
};

Huffman::Huffman(AlphaNode<char>* temp) {
	a = temp;
}


//字符串统计函数
//需要注意 这里是从arr_str[1]开始放东西的  arr_str[0]是空的
static int length = 1;
void statisticAlpha(string str) {
	
	int i = 0;//从string[0]开始遍历
	while (str[i]) {
		if (0 == i) {
			arr_str[i+1].data = str[i]; 
			arr_str[i+1].freq++;
			length++;
		}
		else {
			int j = 1;//从新建数组的第二个存放数据的节点开始遍历
			for (; arr_str[j].data!=NULL; j++) {
				if (str[i] == arr_str[j].data)break;
			}
			//锁定应该存放东西的位置
			if (j >length-1) {
				arr_str[length].data = str[i];
				arr_str[length].freq++;
				length++;
			}
			else {
				arr_str[j].freq++;
			}
		}
		i++;
	}
	int j = 1;
	cout << "您输入的字符串  统计为:" << endl;
	while (arr_str[j].data!=NULL) {
		cout << arr_str[j].data << ':' << arr_str[j].freq << endl;
		j++;
	}
}

//返回统计后的数组的长度


//挑选两个最小值的函数
void Huffman::SelectMin(int &x, int &y,int end) {
	//x、y用于返回两个 最小值权值的 位置
	
	long int min1 = 999999;
	for (int i = 1; i <= end; i++) {
		if (a[i].freq < min1 && p[i] == 0) {
			min1 = a[i].freq;
			x = i;
		}
	}
	min1 = 999999;
	for (int i = 1; i <= end; i++) {
		if (a[i].freq < min1 && i != x && p[i] == 0) {
			min1= a[i].freq;
			y = i;
		}
	}
}
//abbcccddddeeeeeffffffggggggg
AlphaNode<char>* Huffman::Creation(int n) {
	int i;
	//初始化
	for (int i = 1; i <= 2 * n - 1; i++) {
		a[i].lchild = a[i].parent = a[i].rchild = NULL;
	}
	for (i = n; i < 2 * n - 2; i++) {
		SelectMin(x, y, i-1);
		p[x] = p[y] = 1;
		a[i].lchild = &a[x];
		a[i].rchild = &a[y];
		a[i].freq = a[x].freq + a[y].freq;
		
	}
	return &a[i - 1];
}

void Huffman::DispTree(AlphaNode<char>* a) {
	if (a != NULL) {
		cout << a->data;
		if (a->lchild != NULL || a->rchild != NULL) {
			cout << '(';
			DispTree(a->lchild);
			if (a->rchild != NULL)cout << ',';
			DispTree(a->rchild);
			cout << ')';
		}
	}
}

int main() {
	cout <<"请在下方输入一个任意字符串"<< endl;
	string temp;
	getline(cin, temp);
	statisticAlpha(temp);

	cout << length << endl;
	Huffman hf(arr_str);
	AlphaNode<char> *p = hf.Creation(length);
	hf.DispTree(p);
	return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值