树和森林:
树:有且只有一个根节点,其余节点互不相交。
森林:是M棵树互不相交的树的集合。
树的存储方式:
1.双亲表示法:
定义结构数组,里面有一个数据域和一个双亲域,用数组下标记录节点位子,然后用双亲域记录父节点的下标在哪。
特点:找双亲容易,找孩子难
2.孩子链表:
方法:把每个节点的的孩子结点排列起来,看成是一个线性表连接。然后用一个数组把他们头结点记录下来,每个头结点里面可以看到他们的还在结点在那个位置。然后用数组下表记录结点位置。
特点:找孩子容易,找双亲难
优化下:在数组中加入记录双亲的结点
3.孩子兄弟表示法:
实现:用二叉链表做树的存储结构,链表中每个结点有两个指针域,一个指向第一个孩子结点一个指向兄弟结点,就是左孩子右兄弟表示一颗树。
typedef strcut CSNode{
ElemType Data;
struct CSNode * FirstChild,NextSibling;
}CSNode,*CSTree;
树和二叉树的转换:
给定一个树可以找到唯一一颗对应的二叉树,因为树的孩子兄弟表示法。
将树转为二叉树:
- 加线:在兄弟之间加上连线。
- 去线:对于每个结点,除了左孩子外,去除其与其余孩子之间的关系。就是去掉双亲的线
- 旋转:以树根为轴心,顺时针旋转45°。
树变二叉树口诀:兄弟相连留长子。
将二叉树变为树:
- 加线:若结点P是双亲结点的左孩子,则将P的右孩子,右孩子的右孩子。。。。。。沿着分支找到所的右孩子,都与P的双亲用线连起来。
- 去线:去除原二叉树中双亲与右孩子之间的连线。
- 调整:将节点按层次排列,形成树结构。
二叉树变树口诀:左孩右右连双亲,去掉原来右孩线。
森林和二叉树的转换:
将森林变为二叉树:
涉及到二叉树和多棵树的关系,实现方法:
- 将各课树分别转成二叉树
- 每棵树的根节点用线相连
- 以第一颗树的根节点为二叉树的根,再以根节点为轴心顺时针旋转。
森林变二叉树口诀:树变二叉根相连
将二叉树变成森林:
- 去线:将二叉树中根节点与其右孩子相连,及沿着右分支搜索到的所有右孩子之间的连线全部出去,使之成为一颗孤立的二叉树
- 还原:再将孤立的二叉树还原成树
二叉树变森林口诀:去掉全部右孩线,孤立二叉再还原
树和森林的遍历
树的遍历(三种方式):
- 先根遍历:先访问根节点再按照先根的方式遍历各棵子树
- 后根遍历:从后往前,从左到右的次序遍历节点,直到访问到根节点为止。
- 层次遍历:从上到下从左到右访问每个节点
森林的遍历:
遍历方式:
- 先序遍历:将森林中的树从左到右,每一棵树按照树的先序遍历的方式去遍历
- 中序遍历:将森林中的树从左到右,每一棵按照树的后序遍历的方式去遍历
堆(优先队列):
堆是特殊的队列,从队中取出元素是依照元素的优先级大小进行的。
堆的两个特性:
- 结构性:用数组表示的完全二叉树
- 有序性:任何一结点元素的数值与其子节点存储的值是相关的
最大堆:任一结点的值大于或等于其子结点的值。
最小堆:任一结点的值小于或等于其子节点的值。
堆中的元素在数组中是按完全二叉树的层序储存的,根节点存放在数组的起始处,接着是其子节点,一层一层下去到最后一个节点。注意,所用的数组起始单元为1,这样做的目的是从子节点容易找到父节点。
根据二叉树顺序存储二叉树的性质,对于下标为i的结点,其父节点的下标为i/2。而反过来,找第i个结点的左右节点,左节点为2i,右节点为2i-1。
堆的调整:
输出堆顶元素后,用最后一个位置的元素去顶替他,然后从堆顶开始排序,如果是最大堆,进行的就是比较左右树和最大的交换,然后沿着交换的方向进行,一直到叶节点为止。
堆的建立:
就是对于一个下标从一开始的数组所表示的完全二叉树,我们记他的结点数为N,对于建立一个堆就是从最后一个非叶子结点开始跳针,位置是N/2,去比较这个节点的左右子树进行交换,如果是最大堆就和比根节点大元素交换,在交换完成后,就往前一个结点重复此操作,一直到头部根节点位置。
算法分析:
不论是最好还是最坏情况,时间复杂度都是O(NLog2N).空间复杂度O(1).适用于元素的情况。
以最大堆为例
堆的结构
//建立堆的结构体
struct HNode {
int* Data;//存放数据的数组
int Size;//堆中的当前位置
int Capacity;//堆的最大容量
};
typedef struct HNode* Heap;
typedef Heap MaxHeap;//最大堆
//定义两个监视变量在堆数组下标为0的位置,作为哨兵
//这样做的好处就是可以减少在调整的堆根节点位置的判断,从而节省空间
#define MaxMonitor 100000
创建一个最大堆空间:
//创建一个最大堆
MaxHeap CreatMaxHeap(int Volume) {
//给定容量创建一个堆
MaxHeap HH = (MaxHeap)malloc(sizeof(struct HNode));//分配一个堆结点空间
HH->Data = (int*)malloc(sizeof(int) * (Volume + 1));//生成存放数据的数组
HH->Capacity = Volume;
HH->Size = 0;
HH->Data[0] = MaxMonitor;//作为最大堆中的哨兵,一定大于队中的所有元素
return HH;
}
//判断堆是否那满了
堆的插入:
//判断堆是否那满了
bool IsFullHeap(Heap HH) {
return (HH->Size == HH->Capacity);
}
//堆的插入
bool MaxHeapInsert(MaxHeap HH, int X) {
//首先判断下堆是否满了,满了就不插入
if (IsFullHeap(HH)) {
printf("堆满了");
return 0;
}
int i;//用来标记树的结点位置
//插入是插在最后一个位置
HH->Size++;//插入数组末尾指针后移
i = HH->Size;//记录末尾位置
//插入后判断是否满足最大堆,不满做就做相应的调整
for (; HH->Data[i / 2] < X; i /= 2) {
/*从末尾结点开始比较他的父节点i / 2与插入元素值得大小
如果比父节点大就和他换位置,一直比到根节点*/
HH->Data[i] = HH->Data[i / 2];//为X找一个合适的插入节点,做一个X上滤
}
HH->Data[i] = X;//循环结束说明找到合适X插入的位置了,插入X
return 1;
}
堆的出堆删除:
int OutHeap(MaxHeap HH) {
int MaxItem;//最大堆的堆顶最大值出堆
int parent, child;//代表父节点和左孩子的结点位置
//先判断堆是否为空
if (HH->Size == 0) {
printf("最大堆为空");
return 0;
}
//不然获取最大堆元素
MaxItem = HH->Data[1];
//出堆后下标减一,然后末尾元素顶替出堆元素位置,最后进行调整
int record = HH->Data[HH->Size];//record记录末尾元素
HH->Size--;
//方式是从堆顶开始下滤找X插入的位置
for (parent = 1; parent * 2 <= HH->Size; parent = child) {
//从堆顶开始遍历。判断是否有左孩子,因为这样可以判断是否到了最后一个父节点
child = 2 * parent;//获取父节点的左子树
if ((child != HH->Size) && (HH->Data[child] < HH->Data[child + 1])) {
//如果左子树不是数组末尾处,就说明还有右子树存在,那就与右子树比较
//如果右子树大,那child++标记为右子树的位置
child++;//标记为右子树
}
if (record >= HH->Data[child]) break;//
//如果顶替元素大于child,则说明那在这个位置做父节点比他们的左右子树都大,那就退出插入
else {
//否则再往下一层做判断
HH->Data[parent] = HH->Data[child];//大的子节点代替父节点
//进入下一层循环子节点成为新的父节点进行判断,找到是record插入的位置
}
}
HH->Data[parent] = record;//如果parent在最后一个结点就直接赋值了
return MaxItem;//返回堆顶元素
}
堆的建立:
int OutHeap(MaxHeap HH) {
int MaxItem;//最大堆的堆顶最大值出堆
int parent, child;//代表父节点和左孩子的结点位置
//先判断堆是否为空
if (HH->Size == 0) {
printf("最大堆为空");
return 0;
}
//不然获取最大堆元素
MaxItem = HH->Data[1];
//出堆后下标减一,然后末尾元素顶替出堆元素位置,最后进行调整
HH->Size--;
int record = HH->Data[HH->Size];//record记录末尾元素
//方式是从堆顶开始下滤找X插入的位置
for (parent = 1; parent * 2 <= HH->Size; parent = child) {
//从堆顶开始遍历。判断是否有左孩子,因为这样可以判断是否到了最后一个父节点
child = 2 * parent;//获取父节点的左子树
if ((child != HH->Size) && (HH->Data[child] < HH->Data[child + 1])) {
//如果左子树不是数组末尾处,就说明还有右子树存在,那就与右子树比较
//如果右子树大,那child++标记为右子树的位置
child++;//标记为右子树
}
if (record >= HH->Data[child]) break;//
//如果顶替元素大于child,则说明那在这个位置做父节点比他们的左右子树都大,那就退出插入
else {
//否则再往下一层做判断
HH->Data[parent] = HH->Data[child];//大的子节点代替父节点
//进入下一层循环子节点成为新的父节点进行判断,找到是record插入的位置
}
}
HH->Data[parent] = record;//如果parent在最后一个结点就直接赋值了
return MaxItem;//返回堆顶元素
}
汇总 (包括最小堆):
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h>
//建立堆的结构体
struct HNode {
int* Data;//存放数据的数组
int Size;//堆中的当前位置
int Capacity;//堆的最大容量
};
typedef struct HNode* Heap;
typedef Heap MaxHeap;//最大堆
typedef Heap MinHeap;//最小堆
//定义两个监视变量在堆数组下标为0的位置,作为哨兵
//这样做的好处就是可以减少在调整的堆根节点位置的判断,从而节省空间
#define MaxMonitor 100000
#define MinMonitor -100000
//创建一个最大堆
MaxHeap CreatMaxHeap(int Volume) {
//给定容量创建一个堆
MaxHeap HH = (MaxHeap)malloc(sizeof(struct HNode));//分配一个堆结点空间
HH->Data = (int*)malloc(sizeof(int) * (Volume + 1));//生成存放数据的数组
HH->Capacity = Volume;
HH->Size = 0;
HH->Data[0] = MaxMonitor;//作为最大堆中的哨兵,一定大于队中的所有元素
return HH;
}
//判断堆是否那满了
bool IsFullHeap(Heap HH) {
return (HH->Size == HH->Capacity);
}
//堆的插入
bool MaxHeapInsert(MaxHeap HH, int X) {
//首先判断下堆是否满了,满了就不插入
if (IsFullHeap(HH)) {
printf("堆满了");
return 0;
}
int i;//用来标记树的结点位置
//插入是插在最后一个位置
HH->Size++;//插入数组末尾指针后移
i = HH->Size;//记录末尾位置
//插入后判断是否满足最大堆,不满做就做相应的调整
for (; HH->Data[i / 2] < X; i /= 2) {
/*从末尾结点开始比较他的父节点i / 2与插入元素值得大小
如果比父节点大就和他换位置,一直比到根节点*/
HH->Data[i] = HH->Data[i / 2];//为X找一个合适的插入节点,做一个X上滤
}
HH->Data[i] = X;//循环结束说明找到合适X插入的位置了,插入X
return 1;
}
int OutHeap(MaxHeap HH) {
int MaxItem;//最大堆的堆顶最大值出堆
int parent, child;//代表父节点和左孩子的结点位置
//先判断堆是否为空
if (HH->Size == 0) {
printf("最大堆为空");
return 0;
}
//不然获取最大堆元素
MaxItem = HH->Data[1];
//出堆后下标减一,然后末尾元素顶替出堆元素位置,最后进行调整
int record = HH->Data[HH->Size];//record记录末尾元素
HH->Size--;
//方式是从堆顶开始下滤找X插入的位置
for (parent = 1; parent * 2 <= HH->Size; parent = child) {
//从堆顶开始遍历。判断是否有左孩子,因为这样可以判断是否到了最后一个父节点
child = 2 * parent;//获取父节点的左子树
if ((child != HH->Size) && (HH->Data[child] < HH->Data[child + 1])) {
//如果左子树不是数组末尾处,就说明还有右子树存在,那就与右子树比较
//如果右子树大,那child++标记为右子树的位置
child++;//标记为右子树
}
if (record >= HH->Data[child]) break;//
//如果顶替元素大于child,则说明那在这个位置做父节点比他们的左右子树都大,那就退出插入
else {
//否则再往下一层做判断
HH->Data[parent] = HH->Data[child];//大的子节点代替父节点
//进入下一层循环子节点成为新的父节点进行判断,找到是record插入的位置
}
}
HH->Data[parent] = record;//如果parent在最后一个结点就直接赋值了
return MaxItem;//返回堆顶元素
}
//根据一个数组建立一个堆
//对一个数组从最后一个非叶子结点开始处理,把这个节点看成一个小堆
//然后对这个对进行调整即可
void AdjustMaxHeaep(MaxHeap HH, int cnt) {
//对最大堆进行调整,输入cnt非叶节点的位置
int parent, child;
int record;//记录元素用于交换
record = HH->Data[cnt];
for (parent = cnt; parent * 2 <= HH->Size; parent = child) {
//到最后一个非叶子节点上
child = 2 * parent;
if ((child != HH->Size) && (HH->Data[child] < HH->Data[child + 1])) {
child++;
}//右子树大的话child指向右子树
//与根节点记录的元素做判断看要不要交换
if (record >= HH->Data[child]) break;
else {
//交换然后继续往下比较
HH->Data[parent] = HH->Data[child];
}
}
HH->Data[parent] = record;
}
void BuildMaxHeap(MaxHeap HH) {
int i;
for (i = HH->Size / 2; i > 0; i--) {
AdjustMaxHeaep(HH, i);
}
}
//这边开始写最小堆的操作集
MinHeap CreatMinHeap(int Volume) {
MinHeap HH = (MinHeap)malloc(sizeof(struct HNode));
HH->Data = (int*)malloc(sizeof(int) * (Volume + 1));
HH->Size = 0; HH->Capacity = Volume;
HH->Data[0] = MinMonitor;
return HH;
}
bool MinHeapInsert(MinHeap HH, int X) {
if (HH->Size == HH->Capacity) {
printf("堆满了\n");
return 0;
}
HH->Size++;
int i = HH->Size;
//i现在记录着最后叶节点的位置
//做一个和父节点比较是否上移的操作
for (i; HH->Data[i / 2] > X; i /= 2) {
HH->Data[i] = HH->Data[i / 2];
}
HH->Data[i] = X;
return 1;
}
void AdjustMinHeap(MinHeap HH, int cnt) {
int parent, child;
int record = HH->Data[cnt];
for (parent = cnt; parent * 2 <= HH->Size; parent = child) {
child = parent * 2;
if ((child != HH->Size) && (HH->Data[child] > HH->Data[child + 1])) {
child++;
}//找到一个小的结点
if (record <= HH->Data[child])break;
else {
HH->Data[parent] = HH->Data[child];
}
}
HH->Data[parent] = record;
}
void BuildMinHeap(MinHeap HH) {
int i;
for (i = HH->Size / 2; i > 0; i--) {
AdjustMinHeap(HH, i);
}
}
int MinHeapOut(MinHeap HH) {
if (HH->Size == 0) {
printf("堆为空");
return 0;
}
int cnt = HH->Data[1];
int instead = HH->Data[HH->Size];
HH->Size--;
HH->Data[1] = instead;
AdjustMinHeap(HH, 1);
return cnt;
}
int main() {
int Volume;
scanf("%d", &Volume);
MinHeap HH = CreatMinHeap(Volume);
for (int i = 1; i <= Volume; i++) {
scanf("%d", &HH->Data[i]);
HH->Size++;
printf(" \n");
}
BuildMinHeap(HH);
printf("\n 调整后打印: \n");
for (int i = 1; i <= HH->Size; i++) {
printf("%d ", HH->Data[i]);
}
int x = MinHeapOut(HH);
printf("\n 删除后打印: \n");
for (int i = 1; i <= HH->Size; i++) {
printf("%d ", HH->Data[i]);
}
printf("\t删除的是:%d", x);
printf("\n 插入后打印: \n\t");
MinHeapInsert(HH, 1);
for (int i = 1; i <= HH->Size; i++) {
printf("%d ", HH->Data[i]);
}
return 0;
}
哈夫曼树:
哈夫曼树是带权路径最短的树。(注意:是在度相同的树中比较)
带权路径长度(WPL):设二叉树有N个叶子结点,每个叶子节点都带权值,从根节点到每个叶子结点的长度为
,则每个叶子结点的带权路径之和就是:
就是根据结点的路径长度和结点所占的权重去计算WPL,注意的是满二叉树不一定是最优二叉树。权值越大的叶子离根越近。具有相同带权结点的哈夫曼树不唯一。
构造哈夫曼树的思路及口诀:
思路:用贪心算法的思路找局部最优解,对于给定的权重序列每次选取两个最小值,制定成一个新的二叉树,根节点为这两个节点的权重和,然后把和的值放回序列里继续比较操作,直到序列中只剩下一个结点值,就说明哈夫曼树构造完成了。
因此在生成哈夫曼树的时候我们可以采用最小堆的思想去构造。
口诀:1.构造森林全是根 2.选用两小造新树 3.删除两小添新人 4.重复二三剩单根
哈夫曼树的性质:
- 哈夫曼树的度为1或者2,没有结点为1的度
- N个叶子结点的哈夫曼树共有2N-1个节点(因为N棵树的森林要经过N-1次合并才能形成哈夫曼树,其中共产生了N-1个新结点)
- 任意的非叶节点的左右子树交换后仍然还是哈夫曼树。
- N2=N0-1(N2:为度是2的结点,N0为度是0的结点)
- 时间复杂度T=O(NLogN)
应用:哈夫曼编码
就是利用哈夫曼树得性质去编码,对于要编码的字符,在得到他们的概率分布情况后,根据权重建立哈夫曼树,这些字符都在树的叶节点位置,然后因为哈夫曼树没有度为1的结点,所以从根节点开始往下找,左子树记为0,右子树记为1.遍历到叶节点字符所经过的路径就是这个字符的编码。
代码实现:
因为采用的是链式的哈夫曼树,所以输出数中的所有路径,只要使用堆栈做回溯就可以了,图的时候应该会讲。
结构:
typedef struct HTNode* Huffman;
struct HTNode {
int Weight;
char Symbol;
Huffman Left;
Huffman Right;
};
//最小堆结构
struct Heap {
Huffman* Data;//堆里面放的是哈夫曼类型的数组
int Capcity;
int Size;
};
typedef struct Heap* MinHeap;
#define MinMonitor -1000000;//权重哨兵
最小堆操作集:
MinHeap CreatMinHeap(int Volume) {
MinHeap HH = (MinHeap)malloc(sizeof(struct Heap));//创建一个堆空间
HH->Data = (Huffman*)malloc(sizeof(Huffman) * (Volume + 1));//分配空间存放哈夫曼树的数组
for (int i = 0; i <= Volume; i++) {
HH->Data[i] = (Huffman)malloc(sizeof(struct HTNode));
}
HH->Capcity = Volume;//最大容量
HH->Size = 0; HH->Data[0]->Weight = MinMonitor;
return HH;
}
bool MinHeapInsert(MinHeap HH, Huffman X) {
if (HH->Size == HH->Capcity) {
printf("堆满了!");
return 0;
}
HH->Size++;
int i = HH->Size;//用i记录末尾下标
//找合适插入的位置
for (i; HH->Data[i / 2]->Weight > X->Weight; i /= 2) {
//和父节点比较,比父节点要小就让父节点元素下移然后指标指向父节点
HH->Data[i] = HH->Data[i / 2];
}//循环出来指标指的是插入的位置
HH->Data[i] = X;
return 1;
}
//堆的调整
void AdjustMinHeap(MinHeap HH, int cnt) {
int parent, child;
Huffman Record = HH->Data[cnt];
for (parent = cnt; parent * 2 <= HH->Size; parent = child) {
child = 2 * parent;//根节点的左子树为他的2倍位置处
//判断左右子树哪个更小进行交换
if ((child != HH->Size) && (HH->Data[child]->Weight > HH->Data[child + 1]->Weight)) {
child++;//如果左子树不是最后一个位置则说明存在右节点,那就比较两个位置的权重
}
//此时child指向的值weight最小的子树位置
if (Record->Weight <= HH->Data[child]->Weight) break;//父节点的值比子树结点小
else {
HH->Data[parent] = HH->Data[child];
//子树的比父节点小,那就做一个下滤,让子树结点的值给父节点,然后指标指向子节点把他作为父亲
}
}//循环退出表示找到适合插入的位置
HH->Data[parent] = Record;
}
void BuilMinheap(MinHeap HH) {
int i = HH->Size / 2;//从最后一个非叶子结点开始去调整堆
for (i; i > 0; i--) {
AdjustMinHeap(HH, i);
}
}
Huffman MinHeapOut(MinHeap HH) {
if (HH->Size == 0) {
printf("堆为空!");
return 0;
}
//注意如果是record直接取Data[1]的地址那么,对Data[1]的修改会影响record的值
//所以返回的record因该是一个独立结点
Huffman record = HH->Data[1];
Huffman instead = HH->Data[HH->Size];
HH->Size--;
HH->Data[1] = instead;
AdjustMinHeap(HH, 1);
return record;
}
哈夫曼树的构造:
//构造哈夫曼树
Huffman BuildHuffmanTree(MinHeap HH) {
//注意堆的类型为Huffman
int start = 1, end = HH->Size;//指向堆头和堆尾
Huffman TT;//用来充当连接最小堆中的两个结点
BuilMinheap(HH);//调整最小堆
for (start; start < end; start++) {
//因为N个结点做N-1次合并,所以不取等号
TT = (Huffman)malloc(sizeof(struct HTNode));//生成一个用来合并的结点
TT->Left = MinHeapOut(HH);//获取最小堆顶元素
TT->Right = MinHeapOut(HH);//获取最小堆顶元素
TT->Weight = TT->Left->Weight + TT->Right->Weight;//合并结点的权值为子树权值的和
MinHeapInsert(HH, TT);//将合并后的结点放回堆中继续比较找两个最小的合并成树
}
return MinHeapOut(HH);//堆中只剩下最后一个元素的树建好了,返回树根
}
void PreOrderLeaves(Huffman TT) {
if (TT) {//树不空就判断是否叶子节点
if (!TT->Left && !TT->Right) {
printf("%c|%d ", TT->Symbol, TT->Weight);
}
PreOrderLeaves(TT->Left);
PreOrderLeaves(TT->Right);
}
}
汇总:
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h>
//哈夫曼编码实现
//哈夫曼树的结构
typedef struct HTNode* Huffman;
struct HTNode {
int Weight;
char Symbol;
Huffman Left;
Huffman Right;
};
//最小堆结构
struct Heap {
Huffman* Data;//堆里面放的是哈夫曼类型的数组
int Capcity;
int Size;
};
typedef struct Heap* MinHeap;
#define MinMonitor -1000000;//权重哨兵
MinHeap CreatMinHeap(int Volume) {
MinHeap HH = (MinHeap)malloc(sizeof(struct Heap));//创建一个堆空间
HH->Data = (Huffman*)malloc(sizeof(Huffman) * (Volume + 1));//分配空间存放哈夫曼树的数组
for (int i = 0; i <= Volume; i++) {
HH->Data[i] = (Huffman)malloc(sizeof(struct HTNode));
}
HH->Capcity = Volume;//最大容量
HH->Size = 0; HH->Data[0]->Weight = MinMonitor;
return HH;
}
bool MinHeapInsert(MinHeap HH, Huffman X) {
if (HH->Size == HH->Capcity) {
printf("堆满了!");
return 0;
}
HH->Size++;
int i = HH->Size;//用i记录末尾下标
//找合适插入的位置
for (i; HH->Data[i / 2]->Weight > X->Weight; i /= 2) {
//和父节点比较,比父节点要小就让父节点元素下移然后指标指向父节点
HH->Data[i] = HH->Data[i / 2];
}//循环出来指标指的是插入的位置
HH->Data[i] = X;
return 1;
}
//堆的调整
void AdjustMinHeap(MinHeap HH, int cnt) {
int parent, child;
Huffman Record = HH->Data[cnt];
for (parent = cnt; parent * 2 <= HH->Size; parent = child) {
child = 2 * parent;//根节点的左子树为他的2倍位置处
//判断左右子树哪个更小进行交换
if ((child != HH->Size) && (HH->Data[child]->Weight > HH->Data[child + 1]->Weight)) {
child++;//如果左子树不是最后一个位置则说明存在右节点,那就比较两个位置的权重
}
//此时child指向的值weight最小的子树位置
if (Record->Weight <= HH->Data[child]->Weight) break;//父节点的值比子树结点小
else {
HH->Data[parent] = HH->Data[child];
//子树的比父节点小,那就做一个下滤,让子树结点的值给父节点,然后指标指向子节点把他作为父亲
}
}//循环退出表示找到适合插入的位置
HH->Data[parent] = Record;
}
void BuilMinheap(MinHeap HH) {
int i = HH->Size / 2;//从最后一个非叶子结点开始去调整堆
for (i; i > 0; i--) {
AdjustMinHeap(HH, i);
}
}
Huffman MinHeapOut(MinHeap HH) {
if (HH->Size == 0) {
printf("堆为空!");
return 0;
}
//注意如果是record直接取Data[1]的地址那么,对Data[1]的修改会影响record的值
//所以返回的record因该是一个独立结点
Huffman record = HH->Data[1];
Huffman instead = HH->Data[HH->Size];
HH->Size--;
HH->Data[1] = instead;
AdjustMinHeap(HH, 1);
return record;
}
//构造哈夫曼树
Huffman BuildHuffmanTree(MinHeap HH) {
//注意堆的类型为Huffman
int start = 1, end = HH->Size;//指向堆头和堆尾
Huffman TT;//用来充当连接最小堆中的两个结点
BuilMinheap(HH);//调整最小堆
for (start; start < end; start++) {
//因为N个结点做N-1次合并,所以不取等号
TT = (Huffman)malloc(sizeof(struct HTNode));//生成一个用来合并的结点
TT->Left = MinHeapOut(HH);//获取最小堆顶元素
TT->Right = MinHeapOut(HH);//获取最小堆顶元素
TT->Weight = TT->Left->Weight + TT->Right->Weight;//合并结点的权值为子树权值的和
MinHeapInsert(HH, TT);//将合并后的结点放回堆中继续比较找两个最小的合并成树
}
return MinHeapOut(HH);//堆中只剩下最后一个元素的树建好了,返回树根
}
void PreOrderLeaves(Huffman TT) {
if (TT) {//树不空就判断是否叶子节点
if (!TT->Left && !TT->Right) {
printf("%c|%d ", TT->Symbol, TT->Weight);
}
PreOrderLeaves(TT->Left);
PreOrderLeaves(TT->Right);
}
}
int main() {
int Volume;
scanf("%d", &Volume);
MinHeap HH = CreatMinHeap(Volume);
for (int i = 1; i <= Volume; i++) {
HH->Size++; HH->Data[i]->Left = HH->Data[i]->Right = NULL;
scanf("%s %d", &HH->Data[i]->Symbol, &HH->Data[i]->Weight);
}
printf("\n");
Huffman TT = BuildHuffmanTree(HH);
PreOrderLeaves(TT);
return 0;
}
并查集:
对一个集合进行合并和查找的算法
实现方式是通过双亲表示法利用数组记录父结点和数据。
结构:
struct SetType
{
int Data;
int Praent;
};
typedef struct SetType Set;
void Initial(Set S[],int Number) {
for (int i = 0; i <= Number; i++) {
S[i].Praent = -1;
}
}
并操作:
void Union(Set S[], int X1, int X2) {
int Root1, Root2;
Root1 = Find(S, X1);
Root2 = Find(S, X2);
if (Root1 != Root2)
S[Root2].Praent = Root1;
}
查找操作:
int Find(Set S[], int X) {
//X为要查找元素的下标
while (S[X].Praent >= 0) {
X = S[X].Praent;
}
return X;
}
优化并操作(按秩合并):
void Pro_Union(Set S[], int X1, int X2) {
int Root1, Root2;
Root1 = Find(S, X1);
Root2 = Find(S, X2);
if (Root1 != Root2) {
if (S[Root2].Praent > S[Root1].Praent) {
S[Root1].Praent += S[Root2].Praent;
S[Root2].Praent = Root1;
}
else {
S[Root2].Praent += S[Root1].Praent;
S[Root1].Praent = Root2;
}
}
}
优化查找操作(路径压缩):
int Pro_Find(Set S[], int x) {
int root = x;
while (S[root].Praent>=0)
{
root = S[root].Praent;
}
while (x != root) {
int tempt = S[x].Praent;
S[x].Praent = root;
x = tempt;
}
return root;
}