哈夫曼树
- 哈夫曼树又称最优树,是一类带权路径长度WPL最短的二叉树
- 在哈夫曼树中,权值越大的结点离根结点越近
哈夫曼树的构造算法
构造核心:贪心法
在构造哈夫曼树时,首先选择权小的,这样保证权大的离根较近,这样一来,在计算树的带权路径长度时,自然会得到最小带权长度,这样的生成算法就是一种典型的贪心法。
构造步骤:
1.构造森林全是数:根据给定的n个权值,构造n棵只有根结点的二叉树,这n棵二叉树构成一个森林F。
2.选用两小造新树:在森林F中选取两颗根结点的权值最小的树作为左右子树构造一棵新的二叉树,且置新的二叉树的根结点的权值为其左右子树上根结点的权值之和。
3.删除两小添新人:在森林F中删除这两棵树,同时将新得到的二叉树加入F中。
4.重复2、3、剩单根:重复2、3,直到F只含一棵树为止。这棵树便是哈夫曼树。
/*创建哈夫曼树,返回记录表*/
record_mapPtr creatHuff(ListPtr list) {
//0号不使用,所以2n+1;1~n给叶子结点,剩余n-1给其他非叶子中间结点
record_mapPtr root = (record_mapPtr)malloc(sizeof(record_map) * ((list->length) * 2 + 1));
//循环total(2n-1)次,把所有结点(叶子节点+中间结点共2n个结点)初始化为0
int total = 2 * list->length - 1;
int i;
for (i = 0; i <= list->length; i++) {
root[i].parent = 0;
root[i].lchild = 0;
root[i].rchild = 0;
root[list->length + i].parent = 0;
root[list->length + i].lchild = 0;
root[list->length + i].rchild = 0;
root[i+1].weight = list->data[i].count;//weight即i的ASCII码对应的字符出现子树
root[i+1].data = list->data[i].ch;//data保存该字符本身
//printf("%c ", list->data[i].ch);
}
int min[2];//保存权值最小的两个结点下标
//为哈夫曼树的非叶子结点赋值,然后更新叶子结点的parent值
for ( i = list->length + 1; i <= total; i++) {
select(i - 1, min, root);
root[i].lchild = min[0];
root[i].rchild = min[1];
root[i].weight = root[min[0]].weight + root[min[1]].weight;
root[min[0]].parent = i;
root[min[1]].parent = i;
}
return root;
}
哈夫曼树的应用:哈夫曼编码
构造哈夫曼编码:构造哈夫曼树之后,依次以叶子节点为出发点,向上回溯至根结点为止。回溯时走左分支则生成代码0,走右分支生成代码1.
/*编码文件*/
void codefile(code_graphPtr graph, ListPtr list,record_mapPtr root,char* source) {
FILE* fp = fopen("D:\\Mycode\\数据结构\\HTcodefile.txt", "w+");
if (fp == NULL) return;
int length = list->length;
char* code_str = (char*)malloc(sizeof(char) * (length + 1));//存储结点中字符对应的编码串
code_str[length] = '\0';
int start = length - 1;//方便倒着存0和1
int i,j;
for (i = 1; i <= length; i++) {
graph[i-1].data = root[i].data;
int Parent = root[i].parent;
int child = i;
while (Parent != 0) {
if (root[Parent].lchild == child) code_str[start--] = '0';//为右孩子的结点对应的编码为0
else code_str[start--] = '1';//为右孩子的结点对应的编码为1
child = Parent;//进行下一个结点的判断,直到parent为0(即到达了叶子结点)
Parent = root[Parent].parent;
}
strcpy(graph[i-1].str, &code_str[start+1]);//start指向了编码串起始位置之前的地址,输出时需要从start+1
start = length - 1;
}
for (i = 0; i < strlen(source); i++) {
for (j = 0; j < length; j++) {
if(source[i]==graph[j].data) fputs(graph[j].str, fp);//将字符转化为编码串
}
}
fclose(fp);
}
代码:
#include<stdio.h>
#include<string.h>
#include<malloc.h>
#define Max 1024
#define catage 128//数字 32–126 分配给了能在键盘上找到的字符,当您查看或打印文档时就会出现
#define INT_MAX 1000
/*带有出现次数的字符结点*/
typedef struct {
char ch;
int count;
}node;
/*带有字符表表长的结构体数组*/
typedef struct {
node data[catage];//128种
int length;//
}List,*ListPtr;
/*哈夫曼树结点信息记录表*/
typedef struct {
int parent;
int lchild;
int rchild;
int weight;
char data;
}record_map,*record_mapPtr;//哈夫曼树结点信息记录表
/*哈夫曼编码表*/
typedef struct {//unoder_map <char,int>
char data;//对应结点信息记录表的data字符
char* str;//保存data字符对应的编码串
}code_graph, * code_graphPtr;//编码表
/*初始化字符表*/
ListPtr Init() {
ListPtr list = (ListPtr)malloc(sizeof(List));
list->length = 0;
return list;
}
/*从文件中读取字符串*/
void Read(char* source) {
FILE* fp = fopen("D:\\Mycode\\数据结构\\in.txt", "r+");
if (fp == NULL) return;
else {
fgets(source, Max, fp);//将文件中的字符串 放在source字符串中
}
fclose(fp);
}
/*利用ASCII码记录字符出现的次数*/
void CharCount(ListPtr list, char* source) {
int ASII[catage];//128个字符,128个ASCII码
memset(ASII, 0, sizeof(int) * catage);//全部初始化为0
int i;
for (i = 0; i < strlen(source); i++) {
ASII[(int)source[i]]++;//记录source[i]这个字符对应ASCII码出现了多少次
//printf("%c --%d \n", source[i],ASII[source[i]]);
}
for (i = 0; i < catage; i++) {
if (ASII[i] != 0) {
//printf("%c --%d \n", i, ASII[i]);
list->data[list->length].ch = (char)i;//在list表尾添加字符
list->data[list->length].count = ASII[i];//记录添加的字符有多少个
list->length++;//表长增1
}
}
}
/*寻找权值最小的两个结点*/
void select(int len, int* tow_ints, record_mapPtr root) {
int min = INT_MAX;int i;
for (i = 1; i <= len; i++) {//寻找第一个最小值
if (root[i].weight < min && root[i].parent==0) {
tow_ints[0] = i;//记录最小值的下标
min = root[i].weight;//记录最小权值
}
}
min = INT_MAX;
for (i = 1; i <= len; i++) {//寻找第二个最小值
if (root[i].weight < min && i != tow_ints[0] && root[i].parent == 0) {
tow_ints[1] = i;//记录最小值的下标
min = root[i].weight;//记录最小权值
}
}
}
/*创建哈夫曼树,返回记录表*/
record_mapPtr creatHuff(ListPtr list) {
//0号不使用,所以2n+1;1~n给叶子结点,剩余n-1给其他非叶子中间结点
record_mapPtr root = (record_mapPtr)malloc(sizeof(record_map) * ((list->length) * 2 + 1));
//循环total(2n-1)次,把所有结点(叶子节点+中间结点共2n个结点)初始化为0
int total = 2 * list->length - 1;
int i;
for (i = 0; i <= list->length; i++) {
root[i].parent = 0;
root[i].lchild = 0;
root[i].rchild = 0;
root[list->length + i].parent = 0;
root[list->length + i].lchild = 0;
root[list->length + i].rchild = 0;
root[i+1].weight = list->data[i].count;//weight即i的ASCII码对应的字符出现子树
root[i+1].data = list->data[i].ch;//data保存该字符本身
//printf("%c ", list->data[i].ch);
}
int min[2];//保存权值最小的两个结点下标
//为哈夫曼树的非叶子结点赋值,然后更新叶子结点的parent值
for ( i = list->length + 1; i <= total; i++) {
select(i - 1, min, root);
root[i].lchild = min[0];
root[i].rchild = min[1];
root[i].weight = root[min[0]].weight + root[min[1]].weight;
root[min[0]].parent = i;
root[min[1]].parent = i;
}
return root;
}
/*代码表初始化(分配空间)*/
code_graphPtr Init_graph(int len) {
code_graphPtr graph = (code_graphPtr)malloc(sizeof(code_graph)*len);
int i;
for (i = 0; i < len; i++) {
graph[i].str = (char*)malloc(sizeof(char) * len);
}
return graph;
}
/*编码文件*/
void codefile(code_graphPtr graph, ListPtr list,record_mapPtr root,char* source) {
FILE* fp = fopen("D:\\Mycode\\数据结构\\HTcodefile.txt", "w+");
if (fp == NULL) return;
int length = list->length;
char* code_str = (char*)malloc(sizeof(char) * (length + 1));//存储结点中字符对应的编码串
code_str[length] = '\0';
int start = length - 1;//方便倒着存0和1
int i,j;
for (i = 1; i <= length; i++) {
graph[i-1].data = root[i].data;
int Parent = root[i].parent;
int child = i;
while (Parent != 0) {
if (root[Parent].lchild == child) code_str[start--] = '0';//为右孩子的结点对应的编码为0
else code_str[start--] = '1';//为右孩子的结点对应的编码为1
child = Parent;//进行下一个结点的判断,直到parent为0(即到达了叶子结点)
Parent = root[Parent].parent;
}
strcpy(graph[i-1].str, &code_str[start+1]);//start指向了编码串起始位置之前的地址,输出时需要从start+1
start = length - 1;
}
for (i = 0; i < strlen(source); i++) {
for (j = 0; j < length; j++) {
if(source[i]==graph[j].data) fputs(graph[j].str, fp);//将字符转化为编码串
}
}
fclose(fp);
}
/*解码文件*/
void decode(int len,record_mapPtr root) {
FILE* fp = fopen("D:\\Mycode\\数据结构\\HTcodefile.txt", "r");
if (fp == NULL) return;
FILE* fp2 = fopen("D:\\Mycode\\数据结构\\out.txt", "w+");
if (fp2 == NULL) return;
char* code = (char*)malloc(sizeof(char) * Max * len);
fgets(code, Max * len, fp);
int R = 2 * len - 1;
int i;
for (i = 0; i < strlen(code); i++) {
if (code[i] == '0') R = root[R].lchild;//从叶子节点往根节点方向的路径
if (code[i] == '1') R = root[R].rchild;
if (root[R].lchild == 0 && root[R].rchild == 0) // 如果已经是叶子节点,输出到输出文件中,然后重新回到根节点
{
fputc(root[R].data, fp2);
R = 2 * len - 1;
}
}
fclose(fp);
fclose(fp2);
}
int main() {
char source[1000];
printf("---- Reading data from sourcefile. ----\r\n");
Read(source);//从文件中读取字符
printf("---- reading data ends ----\r\n");
printf("---- initList begins. ----\r\n");
ListPtr list = Init();//初始化字符表
printf("---- initList ends. ----\r\n");
printf("---- Counting the chars begins. ----\r\n");
CharCount(list, source);//统计字符表中字符个数(包括每种字符个数和所有字符总数)
printf("---- Counting the chars ends. ----\r\n");
printf("---- Creating HuffmanTree begins. ----\r\n");
record_mapPtr root = creatHuff(list);//生成哈夫曼树所有结点信息记录表
printf("---- Creating HuffmanTree ends. ----\r\n");
printf("---- initialize the HuffmanTreeCodeList begins. ----\r\n");
code_graphPtr graph = Init_graph(list->length);//初始化代码表(分配空间)
printf("---- initialize the HuffmanTreeCodeList ends. ----\r\n");
printf("---- encode file begins. ----\r\n");
codefile(graph, list, root,source);//编码文件
printf("---- encode file ends. ----\r\n");
printf("---- decode file begins. ----\r\n");
decode(list->length,root);//解码文件
printf("---- decode file ends. ----\r\n");
return 0;
}
运行结果:
N皇后问题
关键:剪枝与回溯
以4皇后为例,如图所示的4*4棋盘格,若第一行第一个已经放了一个皇后,则她的同列(最左边的图示情况就被剪掉了)和她的对角线(左2图示情况也被剪掉了),这样原本种可能迅速变小,这就是所谓”剪枝“
当上图的左三想要继续在第三行放第三个皇后时,有下图所示四种可能,但是每一种都不符合N皇后的放置规则(皇后所在行列,对角线不能有其他皇后),此时就会回到第二个皇后的放置问题上:将第二个皇后向下一列移动一个位置(即上图的左4情况),再来放第三个皇后....这就是所谓”回溯“
我的代码
#include<stdio.h>
#include<malloc.h>
#include<math.h>
#include<stdbool.h>
/*判断当前皇后paraSolution[paraT]能否放置于当前位置(列号)*/
bool place(int* paraSolution,int paraT){
int j;
for(j=1;j<paraT;j++){//当前皇后paraSolution[paraT]与已放好了的皇后paraSolution[j]比较
//对角线(斜率相等)、同列 (列号相等)
if((abs(paraT-j)==abs(paraSolution[j]-paraSolution[paraT])) || paraSolution[j]==paraSolution[paraT])
return false;
}
return true;
}
/*回溯?*/
void backtracking(int* paraSolution,int paraN,int paraT){
int i;
if(paraT>paraN){//paraN个皇后全部放好了,就输出每个皇后的位置(也即列号)
for(i=1;i<=paraN;i++){
printf("%d",paraSolution[i]);
}
printf("\r\n");
}else{
for(i=1;i<=paraN;i++){
paraSolution[paraT]=i;//更新第paraT个皇后的列号
if(place(paraSolution,paraT)){//判断更新后的列号位置能否放置该皇后
backtracking(paraSolution,paraN,paraT+1);//当前皇后paraT放置成功,接着放下一个皇后(paraT+1)
}
}
}
}
void nQueen(paraN){
//分配空间
int* solution=(int*)malloc((paraN+1)*sizeof(int));//因为0号不用,所以分配paraN+1个空间
//初始化
int j;
for(j=0;j<=paraN;j++){
solution[j]=0;//每个皇后的初始列号设为零
}
//开始放第一个皇后
backtracking(solution,paraN,1);
}
int main(){
nQueen(5);
return 1;
}
运行结果: