数据结构 C 代码 6.2: 哈夫曼树&6.3: N 后问题

学习目标:Huffman树编码&回溯算法

学习指导:帆神的代码

学习任务:

  1. 抄写代码
  2. 学习成果目录

    1数据结构 C 代码 6.2: 哈夫曼树

    1.1全部代码

    1.2测试结果

    1.3Huffman树实现思路

    1.3.1做好Huffman树

    1.3.2编码Huffman树

    1.3.3解码Huffman树

    1.3.4图解Huffman树

    2数据结构 C 代码 6.3: N 后问题

    2.1全部代码

    2.2测试结果

    2.3做题思路


代码说明:

  1. 从文件中读文本;
  2. 统计字符频次, 并建立字母表;
  3. 构造 Huffman 树;
  4. 将文本编码;
  5. 解码.

学习时间:

2022.5.19

1数据结构 C 代码 6.2: 哈夫曼树

1.1全部代码

#include <iostream>
#include <fstream>
#include <string.h>
using namespace std;

#define MaxSize 1024
/**
 * 记录字数个数的数组
 */
typedef struct wordcnt
{
	char ch;
	double cnt=0;
}Count;
typedef struct CountOccurrencesOfLetters
{
	Count count[MaxSize];
	int length=0;
}CountOccurrencesOfLetters;

/**
 * Huffman树结构体
 */
typedef struct HTree
{
	char data;
	double weight;
	int parent,leftChildren,rightChildren;
}HTNode,*HuffmanTree;
typedef struct HCode
{
	char data;
	char* str;
}*HuffmanCode;

void ReadData(char *source);  // 读入文件
void WordCount(char *data,CountOccurrencesOfLetters *paraCount); // 统计次数
void Show(CountOccurrencesOfLetters *paraCount);   // 展示次数
void CreateHuffmanTree(HuffmanTree &HT,int length,CountOccurrencesOfLetters cntarray);  // 创建哈夫曼树
void select(HuffmanTree HT,int top,int *s1,int *s2);  // 选择权重最小的两个节点
void CreateHuffmanCode(HuffmanTree HT,HuffmanCode &HC,int length);  // 创建哈夫曼编码
void Encode(char *data,HuffmanCode HC,int length);  // 将读入的文件编码,写到txt文件
void Decode(HuffmanTree HT,int length);  //读入编码文件,解码

int main(int argc, char** argv) {
	char data[MaxSize];
	CountOccurrencesOfLetters CountArray;
	ReadData(data);  // 读入数据
	WordCount(data,&CountArray);  // 统计次数
	Show(&CountArray); //可以查看每个单词出现的对应次数
	HuffmanTree tree;
	CreateHuffmanTree(tree,CountArray.length,CountArray);  // 建树
	HuffmanCode code;
	CreateHuffmanCode(tree,code,CountArray.length);  // 创建编码
	Encode(data,code,CountArray.length);  // 生成编码文件
	Decode(tree,CountArray.length);  // 解码
	cout<<"请查看文件确认解码后的文件"<<endl;
	return 0;
}
/**
 * @brief 读取文件中数据
 *
 * @param source
 */
void ReadData(char *source)
{
	//打开文件读入数据
	ifstream infile;
	infile.open("inMessage.txt");
	cout<<"文件内容是:"<<endl;
	infile.getline(source,MaxSize);
	cout<<source<<endl;
	infile.close();
	cout<<endl;
}
/**
 * @brief 记录txt文件中字母出现次数
 *
 * @param data
 * @param paraCount
 */
void WordCount(char *data,CountOccurrencesOfLetters *paraCount)
{
	int flag;// 标识是否已经记录
	int len = strlen(data);
	for(int i = 0;i < len;++i)
	{
		flag = 0;
		for(int j = 0;j < paraCount->length;++j)
		{
			if(paraCount->count[j].ch == data[i]) // 若已有记录,直接++
			{
				++paraCount->count[j].cnt;
				flag = 1;
				break;
			}

		}
		if(!flag) // 没有记录,则新增
		{
			paraCount->count[paraCount->length].ch = data[i];
			paraCount->count[paraCount->length].cnt++;
			paraCount->length++;
		}
	}

}
/**
 * @brief 输出文本中各个字母出现次数
 *
 * @param paraCount
 */
void Show(CountOccurrencesOfLetters* paraCount)
{
	cout<<"inMessage.txt文件的字符长度是 "<<paraCount->length<<endl;
	for(int i = 0;i < paraCount->length;++i)
	{
		cout<<"字符 "<<paraCount->count[i].ch<<"  出现了  "<<paraCount->count[i].cnt<<"  次"<<endl;
	}
	cout<<endl;
}
/**
 * @brief 创建Huffman树
 *
 * @param HT
 * @param cntarray
 * @param length
 */
void CreateHuffmanTree(HuffmanTree &HT,int length,CountOccurrencesOfLetters cntarray)
{
	if(length<=1)
		printf("读取inMessage.txt错误!!!\r\n");

	int s1,s2;
	int m = length*2-1;  // 没有度为1的节点,则总结点是2*叶子节点数-1个
	HT = new HTNode[m+1];
	//造出一堆树的节点
	for(int i = 1;i <= m;++i)  // 初始化
	{
		HT[i].parent = 0;
		HT[i].leftChildren = 0;
		HT[i].rightChildren = 0;
	}
	//将字母存入树的节点中
	for(int i = 1;i <= length;++i)
	{
		HT[i].data = cntarray.count[i-1].ch;
		HT[i].weight = cntarray.count[i-1].cnt/59;
	}
	//将树的节点连起来
	for(int i = length + 1;i <= m;++i)
	{
		//从前面的范围里选择权重最小的两个节点
		select(HT,i-1,&s1,&s2);
		//建立关系,从下往上
		HT[s1].parent = i;
		HT[s2].parent = i;
		HT[i].leftChildren = s1;
		HT[i].rightChildren = s2;
		// 得到一个新节点
		HT[i].weight = HT[s1].weight + HT[s2].weight;
	}
	//打印哈夫曼树中各结点之间的关系
	printf("哈夫曼树为:\n");
	printf("下标   权值(占比)  父结点   左孩子   右孩子\n");

	for (int i = 1; i <= m; i++)
	{
		printf("%-4d     %-6.3lf	      %-6d   %-6d   %-6d\n", i, HT[i].weight, HT[i].parent, HT[i].leftChildren, HT[i].rightChildren);
	}
	printf("\n");
}
/**
 * @brief 选择权重weight最小的两个节点
 *
 * @param HT
 * @param s1
 * @param s2
 * @param top
 */
void select(HuffmanTree HT,int top,int* s1,int* s2)
{
	int min = INT_MAX;
	for(int i = 1;i <= top;++i)  // 选择没有双亲parent的节点中,权重weight最小的节点
	{
		if(HT[i].weight < min && HT[i].parent == 0)
		{
			min = HT[i].weight;
			*s1 = i;
		}
	}

	min = INT_MAX;
	for(int i = 1;i <= top;++i)  // 选择没有双亲parent的节点中,权重weight最小的节点
	{
		if(HT[i].weight < min && i != *s1 && HT[i].parent == 0)
		{
			min = HT[i].weight;
			*s2 = i;
		}
	}
}
/**
 * @brief 创建用于编码的Huffman树,将Huffman树 编码
 *
 * @param HC
 * @param HT
 * @param length
 */
void CreateHuffmanCode(HuffmanTree HT,HuffmanCode &HC,int length)
{
	HC = new HCode[length+1];
	char *cd = new char[length];  // 存储编码的临时空间
	cd[length-1] = '\0';  // 方便之后调用strcpy函数
	int c,f,start;
	printf("生成编码:\n");
	printf("下标  编码 \n");
	for(int i = 1;i <= length;++i)
	{
		start = length-1;  // start表示编码在临时空间内的起始下标,由于是从叶子节点回溯,所以是从最后开始
		c = i;
		f = HT[c].parent;
		while(f != 0)
		{
			--start;  // 由于是回溯,所以从临时空间的最后往回计
			if(HT[f].leftChildren == c)
				cd[start] = '0';
			else
				cd[start] = '1';
			c = f;
			f = HT[c].parent;
		}
		HC[i].str = new char[length-start];  // 最后,实际使用的编码空间大小是length-start
		HC[i].data = HT[i].data;
		strcpy(HC[i].str,&cd[start]);  // 从实际起始地址开始,拷贝到编码结构中

		printf("  %d    %s\n",i,HC[i].str);
	}
	printf("\n");

	delete[] cd;
}
/**
 * @brief 编码存入文件中
 *
 * @param HC
 * @param data
 * @param length
 */
void Encode(char* data,HuffmanCode HC,int length)
{
	ofstream outfile;
	outfile.open("codeMessage.txt");
	for(int i = 0;i < strlen(data);++i)  // 依次读入数据,查找对应的编码,写入编码文件
	{
		for(int j = 1;j <= length;++j)
		{
			if(data[i] == HC[j].data)
			{
				outfile<<HC[j].str;
			}
		}
	}
	outfile.close();
	cout<<"in.txt文件被编码后写入codeMessage.txt文件中"<<endl;
	cout<<endl;
}
/**
 * @brief 将编码后的Huffman树解码
 *
 * @param HT
 * @param length
 */
void Decode(HuffmanTree HT,int length)
{
	char codetxt[MaxSize*length];
	ifstream infile;
	infile.open("codeMessage.txt");
	infile.getline(codetxt,MaxSize*length);
	infile.close();

	ofstream outfile;
	outfile.open("outMessage.txt");

	int root = 2*length-1;  // 从根节点开始遍历
	for(int i = 0;i < strlen(codetxt);++i)
	{
		if(codetxt[i] == '0') root = HT[root].leftChildren;  //为0表示向左遍历
		else if(codetxt[i] == '1') root = HT[root].rightChildren; //为1表示向右遍历
		if(HT[root].leftChildren == 0 && HT[root].rightChildren == 0)  // 如果已经是叶子节点,输出到输出文件中,然后重新回到根节点
		{
			outfile<<HT[root].data;
			root = 2*length-1;
		}
	}
	outfile.close();
	cout<<"codeMessage.txt文件被解码后写入outMessage.txt文件中"<<endl;
	cout<<endl;
}

1.2测试结果

 1输入的文本 2运行结果 3编码 4解码后输出的文本

测试结果

1.3Huffman树实现思路

1.3.1做好Huffman树

1.申请一堆节点并初始化

2.读取文件in.txt将值及权重存入这堆节点中

3.找出权weight最小的俩节点

4.将这俩节点作为新节点的子节点,建立联系

5.把新节点权值赋为俩字节点之和weight = weight1 + weight2

1.3.2编码Huffman树

1.按字母出现的次序开始编码

2.回溯编码,若为右孩子记为0,若为左孩子记为1

3.将所有编码存入文件code.txt

1.3.3解码Huffman树

1.读取文件code.txt

2.从根节点开始遍历,为0表示向左遍历 ,为1表示向右遍历 

3.如果遍历到是叶子节点,输出到输出文件中,然后重新回到根节点 

4.文件解码后存入文件out.txt

1.3.4图解Huffman树

ice-cream为例运行结果

2d32acca44834d98add6cb200eda88f5.png

 生成的树及编码ae9d77edeca0489c94bcfeb360bf466d.jpeg

2数据结构 C 代码 6.3: N 后问题

2.1全部代码

#include <stdio.h>
#include <malloc.h>
#include <math.h>
/**
 * @brief 判断该列能否放置皇后,能放返回ture,不能返回false
 *
 * @param paraSolution
 * @param paraT
 *
 * @return true or false
 */
bool place(int* paraSolution, int paraT) {
	int j;
	for (j = 1; j < paraT; j ++) {
		//检查是否在同一列上或者在同一对角线上
		//1.判断同一列:之前放置的皇后与正准备放置的列号是否相同(列号=quenn[i] 的值)
		//2.判断对角线:正方形对角线斜率绝对值为 1;
		if ((abs(paraT - j) == abs(paraSolution[j] - paraSolution[paraT])) || (paraSolution[j] == paraSolution[paraT]))
			return false;
	}
	return true;
}
/**
 * @brief 回溯算法
 *
 * @param paraN
 * @param paraSolution
 * @param paraT
 */
void backtracking(int* paraSolution, int paraN, int paraT) {
	int i;
	//如果该位置可以放置皇后且已经放置了N个皇后,打印方案即可
	if (paraT > paraN) {
		//确定当前行n,列号从 1 开始遍历
		for (i = 1; i <= paraN; i ++)
			printf("%d ", paraSolution[i]);
		printf("\n");
	} else {
		//如果该位置不可以放置皇后,深度+1,行变为n+1;(显然n从 1 开始取)
		for (i = 1; i <= paraN; i ++) {
			paraSolution[paraT] = i;
			//判断该列能否放置皇后
			if (place(paraSolution, paraT)) {
				backtracking(paraSolution, paraN, paraT + 1);
			}
		}
	}
}
/**
 * @brief 创建N皇后并初始化,最后回溯
 *
 * @param paraN
 */
void nQueen(int paraN) {
	int i;
	int* solution = (int*)malloc((paraN + 1) * sizeof(int));
	for (i = 0; i <= paraN; i ++) {
		solution[i] = 0;
	}
	backtracking(solution, paraN, 1);
}
int main() {
	nQueen(5);
	return 1;
}

2.2测试结果

1 3 5 2 4
1 4 2 5 3
2 4 1 3 5
2 5 3 1 4
3 1 4 2 5
3 5 2 4 1
4 1 3 5 2
4 2 5 3 1
5 2 4 1 3
5 3 1 4 2

2.3做题思路

1.用n元组x[1:n]表示n后问题的解。其中,x[i]表示皇后i放在棋盘的第i行的第x[i]列。由于不允许将2个皇后放在同一列上,所以解向量中的x[i]互不相同。2个皇后不能放在同一斜线上时问题的隐约束。

2.若两个皇后放置的位置分别是(i,j)和(k,l),且i-j=k-l或i+j=k+l,则说明这2个皇后处于同一斜线上。由此可知,只要|i-k|=|j-l|成立,就表明2个皇后处于同一斜线上。

3.用回溯法解n后问题时,用完全n叉树表示解空间。可行性约束Place剪去不满足行、列和斜线约束的子树。在算法Backtrack中,当i>n时,算法搜索至叶结点,得到一个新的n皇后互不攻击放置方案,当前已找到的可行方案数sum增1。
 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值