数据结构之树(三)

树和森林:

      树:有且只有一个根节点,其余节点互不相交。

      森林:是M棵树互不相交的树的集合。

 树的存储方式:

1.双亲表示法:

    定义结构数组,里面有一个数据域和一个双亲域,用数组下标记录节点位子,然后用双亲域记录父节点的下标在哪。

     特点:找双亲容易,找孩子难 

2.孩子链表:

      方法:把每个节点的的孩子结点排列起来,看成是一个线性表连接。然后用一个数组把他们头结点记录下来,每个头结点里面可以看到他们的还在结点在那个位置。然后用数组下表记录结点位置。

      特点:找孩子容易,找双亲难

 优化下:在数组中加入记录双亲的结点

 

 3.孩子兄弟表示法:

 实现:用二叉链表做树的存储结构,链表中每个结点有两个指针域,一个指向第一个孩子结点一个指向兄弟结点,就是左孩子右兄弟表示一颗树

typedef strcut CSNode{
ElemType Data;
struct CSNode * FirstChild,NextSibling;
}CSNode,*CSTree;

 树和二叉树的转换:

给定一个树可以找到唯一一颗对应的二叉树,因为树的孩子兄弟表示法。

将树转为二叉树:

  1. 加线:在兄弟之间加上连线。
  2. 去线:对于每个结点,除了左孩子外,去除其与其余孩子之间的关系。就是去掉双亲的线
  3. 旋转:以树根为轴心,顺时针旋转45°。

树变二叉树口诀:兄弟相连留长子。

 将二叉树变为树:

  1. 加线:若结点P是双亲结点的左孩子,则将P的右孩子,右孩子的右孩子。。。。。。沿着分支找到所的右孩子,都与P的双亲用线连起来。
  2. 去线:去除原二叉树中双亲与右孩子之间的连线。
  3. 调整:将节点按层次排列,形成树结构。

二叉树变树口诀:左孩右右连双亲,去掉原来右孩线。

 森林和二叉树的转换:

将森林变为二叉树:

      涉及到二叉树和多棵树的关系,实现方法:

  1. 将各课树分别转成二叉树
  2. 每棵树的根节点用线相连
  3. 以第一颗树的根节点为二叉树的根,再以根节点为轴心顺时针旋转。

  森林变二叉树口诀:树变二叉根相连

 将二叉树变成森林:

  1. 去线:将二叉树中根节点与其右孩子相连,及沿着右分支搜索到的所有右孩子之间的连线全部出去,使之成为一颗孤立的二叉树
  2. 还原:再将孤立的二叉树还原成树

二叉树变森林口诀:去掉全部右孩线,孤立二叉再还原

 树和森林的遍历

 树的遍历(三种方式):

  1. 先根遍历:先访问根节点再按照先根的方式遍历各棵子树
  2. 后根遍历:从后往前,从左到右的次序遍历节点,直到访问到根节点为止。
  3. 层次遍历:从上到下从左到右访问每个节点

 森林的遍历:

遍历方式:

  1. 先序遍历:将森林中的树从左到右,每一棵树按照树的先序遍历的方式去遍历
  2. 中序遍历:将森林中的树从左到右,每一棵按照树的后序遍历的方式去遍历


堆(优先队列):

      堆是特殊的队列,从队中取出元素是依照元素的优先级大小进行的。

堆的两个特性:

  1. 结构性:用数组表示的完全二叉树
  2. 有序性:任何一结点元素的数值与其子节点存储的值是相关的

最大堆:任一结点的值大于或等于其子结点的值。
最小堆:任一结点的值小于或等于其子节点的值。

      堆中的元素在数组中是按完全二叉树的层序储存的,根节点存放在数组的起始处,接着是其子节点,一层一层下去到最后一个节点。注意,所用的数组起始单元为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个叶子结点,每个叶子节点都带权值W_k,从根节点到每个叶子结点的长度为L_k,则每个叶子结点的带权路径之和就是:WPL=\sum_{k=1}^n W_kL_k

     就是根据结点的路径长度和结点所占的权重去计算WPL,注意的是满二叉树不一定是最优二叉树。权值越大的叶子离根越近。具有相同带权结点的哈夫曼树不唯一。

 构造哈夫曼树的思路及口诀:

思路:用贪心算法的思路找局部最优解,对于给定的权重序列每次选取两个最小值,制定成一个新的二叉树,根节点为这两个节点的权重和,然后把和的值放回序列里继续比较操作,直到序列中只剩下一个结点值,就说明哈夫曼树构造完成了。
      因此在生成哈夫曼树的时候我们可以采用最小堆的思想去构造。

口诀:1.构造森林全是根 2.选用两小造新树 3.删除两小添新人 4.重复二三剩单根

 哈夫曼树的性质:

  1.  哈夫曼树的度为1或者2,没有结点为1的度
  2. N个叶子结点的哈夫曼树共有2N-1个节点(因为N棵树的森林要经过N-1次合并才能形成哈夫曼树,其中共产生了N-1个新结点)
  3. 任意的非叶节点的左右子树交换后仍然还是哈夫曼树。
  4. N2=N0-1(N2:为度是2的结点,N0为度是0的结点)
  5. 时间复杂度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;
}

 

 

 

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值