文章目录
树和森林
森林是m(m>=0)棵互不相交的树
一.树的存储结构
1.双亲表示法
上篇有所介绍,这里需要补充的是,本方法找双亲容易,找孩子难。
2.孩子链表
把每个结点的孩子结点排列起来,看成是一个线性表,用单链表存储,则n
个结点有n个孩子链表(叶子的孩子链表为空表)。而n个头指针又组成一个线性表,用顺序表(含n个元素的结构数组)存储。
- 孩子结点结构:
找孩子容易,找双亲难
typedef struct CTNode
{
int child; //这个孩子在单链表中的数组下标
struct CTNode *next;//指向双亲的下一个孩子
)ChildPtr;
- 双亲结点结构
typedef struct
{
DataType data;
ChildPtr *firstchild;//孩子链表头指针
}CTBox;
- 树结构
typedef struct
{
CTBox nodes[MAXSIZE];
int n,r; //结点个数和根节点位置
}CTree;
3.带双亲的孩子链表
我们在数组中加入一列双亲结点的下标
4.孩子兄弟表示法(二叉树表示法,二叉链表表示法)
用二叉链表作树的存储结构,链表中的每个结点的两个指针域分别指向其第一个孩子结点和下一个兄弟结点
typedef struct CSNode
{
DataType data;
struct CSNode* firstchild;
struct CSNode* nextsibling;
}CSNode;
二.树与二叉树的转换
给定一颗树,可以找到唯一的二叉树与之对应
将树转化成二叉树:
兄弟相连留长子
1.加线:在兄弟之间加一根线。
2.抹线:对于每个节点,除了左孩子,去除双亲与其余孩子之间的关系。
3.旋转:以树的根节点为轴心,将整个树顺时针转45度
将二叉树转化成树:
左孩右右连双亲,去掉原来右孩线
1.加线:若p是双亲节点左孩子,则将p的右孩子,右孩子的右孩子…沿分支找到所有的右孩子,都与p的双亲用线连起来。
2.抹线:抹掉原二叉树中双亲与右孩子之间的连线
3.调整:将节点逆时针旋转45度
三.森林与二叉树的转换
森林变成二叉树:
树变二叉根相连
1.将各棵树分别转换成二叉树
2.将每棵树的根节点用线相连
3.以第一棵树为根节点,再以根节点为轴心,顺时针旋转,构成二叉树型结构
二叉树变成森林
去掉全部右孩线,孤立二叉再还原
1.抹线:将二叉树中根节点与其右孩子,及沿分支搜索到的所有右孩子全部抹掉,使其变成孤立的二叉树
2.还原:将孤立二叉树还原成树
四.树与森林的遍历
1.树的遍历(三种)
1.先根遍历:若树不空,则先访问根节点,然后依次先根遍历各个子树
2.后根遍历:若树不空,先依次后根遍历各个子树,然后访问根节点
3.按层次遍历:若树不空,则从上而下,从左到右访问树中每个节点
2.森林的遍历
1.先序遍历:
若森林不为空,访问第一棵树的根节点->先序遍历森林中第一棵树的子树森林->先序遍历森林中其余树构成的森林。(即从左到右对森林中每一棵树进行先序遍历)
2.中序遍历:
若森林不为空,中序遍历森林中第一个树的子树森林->访问森林中第一棵树的根节点->中序遍历森林中其余树构成的森林。(依次从左到右对森林中每一棵树进行后根遍历)
五.哈夫曼树(最优二叉树)
1.基本概念:
- 路径:从一个节点到另一个节点之间的分支构成两个节点之间的路径
- 节点的路径长度:两节点间路径上的分支数
- 树的路径长度:从树根到每一个节点的路径长度之和
节点数目相同的二叉树中,完全二叉树是路径长度最短的二叉树 - 权:将树中节点赋给一个有着某种含义的数值,称这个数值为该节点的权
- 节点的带权路径长度:从根节点到该节点之间的路径长度与该节点的权的乘积
- 树的带权路径长度:树中所有叶子节点的带权路径长度之和,记作:WPL
哈夫曼树:最优树(带权路径长度WPL最短的树)
2.值得注意的是:
1.“带权路径长度最短是在度相同的树中比较而得”
2.满二叉树不一定是哈夫曼树
3.哈夫曼树中权越大的叶子离根很近
4.具有相同带权节点的哈夫曼树不唯一
3.哈夫曼树构造算法
哈夫曼树中权越大的叶子离根很近
贪心算法:构造哈夫曼树中首先选择权值小的叶子节点
1.构造森林全是根
2.选用两小造新树
3.删除两小添新人
4.重复2 3剩单根
- 包含n个叶子节点的哈夫曼树中共有(2n-1)个节点
- 包含n棵树的森林要经过(n-1)次合并才能形成哈夫曼树,共产生(n-1)个新节点,新产生的都是具有两个孩子的分支节点
- 哈夫曼树的节点度为0或者2,没有度为1的节点,哈夫曼树中共有(2n-1)个节点
4.哈夫曼树构造算法的实现
采用顺序存储结构–一维结构数组
节点类型定义:
#include<stdio.h>
#include<stdlib.h>
typedef struct TreeNode
{
int weight;
int parent; //父节点下标
int lch, rch; //分别标识左右孩子的下标
}TreeNode;
typedef struct HFTree
{
TreeNode* data;
int length;
}HFTree;
HFTree* initTree(int* weight, int length)
{
HFTree* T = (HFTree*)malloc(sizeof(HFTree));
T->data = (TreeNode*)malloc(sizeof(TreeNode) * (2 * length - 1));
T->length = length;
for (int i = 1; i <= (2*length-1); i++)
{
//下标为0的为了方便没有储存数据
T->data[i].weight = weight[i-1];
T->data[i].parent = 0;
T->data[i].lch = 0;
T->data[i].rch = 0;
}
return T;
}
int* selectmin(HFTree* T)
{
int min = 10000; //权重
int secondmin = 10000;
int minindex=0; //下标
int secondminindex=0;
for (int i = 1; i <= T->length; i++)
{
if (T->data[i].parent == 0)
{
if (T->data[i].weight < min)
{
min = T->data[i].weight;
minindex = i;
}
}
}
for (int i = 1; i <= T->length; i++)
{
if (T->data[i].parent == 0 && i != minindex)
{
if (T->data[i].weight < secondmin)
{
secondmin = T->data[i].weight;
secondminindex = i;
}
}
}
int* res = (int*)malloc(sizeof(int*) * 2);
res[0] = minindex;
res[1] = secondminindex;
return res;
}
void createHFTree(HFTree* T)
{
int* res;
int minindex=0;
int secondminindex=0;
int length = T->length * 2 - 1;
for (int i = T->length+1; i <= length; i++)
{
res = selectmin(T);
minindex = res[0];
secondminindex = res[1];
T->data[i].weight = T->data[minindex].weight + T->data[secondminindex].weight;
T->data[i].lch = minindex;
T->data[i].rch = secondminindex;
T->data[minindex].parent = i;
T->data[secondminindex].parent = i;
T->length++;
}
}
void preOrder(HFTree* T,int index)
{
if (index != 0)
{
printf("%d ", T->data[index].weight);
preOrder(T,T->data[index].lch);
preOrder(T,T->data[index].rch);
}
}
int main()
{
int weight[4] = {3,2,4,1};
HFTree* T = initTree(weight, 4);
createHFTree(T);
preOrder(T, T->length);
printf("\n");
return 0;
}
5.哈夫曼编码
哈夫曼编码是一种可变长度且无损压缩的编码方式。在哈夫曼树中,出现频率高的字符将被编码为较短的二进制编码,而出现频率低的字符将被编码为较长的二进制编码。通过这种方式,可以有效地减少数据的存储空间。
关键要设计长度不等的编码,必须使得任一字符的编码都不是另一个字符的前缀不会出现重码
例如
6.哈夫曼编码的算法实现
void CreatHuffmanCode(HFTree* HT, char str[], int n)
{
char cd[100];
cd[99] = '\0';
for (int i = 1; i <= n; i++) {
int c = i, p = HT->data[i].parent, start = 99;
while (p != 0)
{
start--;
if (c == HT->data[p].lch)
{
cd[start] = '0';
}
else
{
cd[start] = '1';
}
c = p; //逆向回溯到上一个节点
p = HT->data[p].parent;
}
for (int j = start; j <= 99; j++)
{
if (cd[j] == '0' || cd[j] == '1')
{
printf("%c", cd[j]);
}
}
memset(cd, '\0', n);
}
}
7.哈夫曼树的译码实现
//哈夫曼树的译码
void HuffmanCode(HFTree*T, int n)
{
char code[100];
scanf("%s", code);
//下面来找到该哈夫曼树的根节点
int temp = 9999;
for (int i = 1; i <= n * 2 - 1; i++)
{
if (T->data[i].parent == 0)
{
temp = i;
}
}
int a = temp;//定义变量来标记现在走到结点
for (int i = 0; code[i] != '\0'; i++)
{
if (code[i] == '0')
{
a = T->data[a].lch;
}
else if (code[i] == '1')
{
a = T->data[a].rch;
}
if (T->data[a].lch == 0)
{
printf("%c", T->data[a].ch);
a = temp;//走到头之后再从根节点开始
}
}
}