树:
堆:
最大堆:每个节点的值都大于等于它的孩子节点。
最小堆:每个节点的值都小于等于它的孩子节点。
堆的存储:
可以理解为二叉树的一种,是节点间有序关系的完全二叉树,所以可以用数组来表示。
对于下标为i的节点,它的子树的左节点的下标为2i,右节点为2i+1,父亲的节点下标为i/2(向下取整)。
在程序设计中,使用位运算来代替直接*2可以提高运行速度。-
某些编译器中会把一些特定的乘法运算改写为位运算。
哈夫曼树与哈夫曼编码:
哈夫曼树又称最优树,是一类带权路径长度最短的树。
路径:从树中一个结点到另一个结点之间的分支构成这两个结点之间的路径。
路径长度:路径上的分支数目称作路径长度。
树的路径长度:从树根到每一结点的路径之和。
权:赋予某个实体的一个量,是对实体的某个或某些属性的数值描述。在数据结构中,实体有结点(元素)和边(关系)两大类,所以对应有结点权和边权。
结点的带权路径长度:从该结点到树根之间的路径长度与结点上权的乘积。
树的带权路径长度:树中所有叶子结点的带权路径长只和。
哈夫曼树:假设有m个权值{w1,w2,w3,…,wn},可以构造一棵含有n个叶子结点的二叉树,每个叶子结点的权为wi,则其中带权路径长度WPL最小的二叉树称做最优二叉树或哈夫曼树。
哈夫曼树的实现:
由于哈夫曼树中没有度为1的结点,则一棵有n个叶子结点的哈夫曼树共有2n -1个结点,可以存储在一个大小为2n-1的一维数组中。
typedef struct{
int weight; //结点的权重
int parent,lchild,rchild;//结点的双亲、左孩子、右孩子的下标
}HTNode,*HuffmanTree
初始化:首先动态申请2n个单元,然后循环2n-1次,从1号单元开始,依次将1至2n-1所有单元中的双亲、左孩子、右孩子的下标初始化为0,最后再循环n次,输入前n个单元中叶子的权值。
创建树:循环n-1次,通过n-1次选择、删除与合并来创建哈夫曼树。选择是从当前森林中选择双亲为0且权值最小的两个树根结点s1和s2。删除是指将结点s1和s2的双亲改为非0。合并就是将s1和s2的权值和作为一个新结点的权值依次存入到数组的第n+1之后的单元中,同时纪录这个新结点左孩子的下标s1,右孩子的下标s2。
void SelectMin(HuffmanTree hT, int n, int &s1, int &s2)
{
s1 = s2 = 0;
int i;
for(i = 1; i < n; ++ i){
if(0 == hT[i].parent){
if(0 == s1){
s1 = i;
}
else{
s2 = i;
break;
}
}
}
if(hT[s1].weight > hT[s2].weight){
int t = s1;
s1 = s2;
s2 = t;
}
for(i += 1; i < n; ++ i){
if(0 == hT[i].parent){
if(hT[i].weight < hT[s1].weight){
s2 = s1;
s1 = i;
}else if(hT[i].weight < hT[s2].weight){
s2 = i;
}
}
}
}
// 构造有n个权值(叶子节点)的哈夫曼树
void CreateHufmanTree(HuffmanTree &hT)
{
int n, m;
cin >> n;
m = 2*n - 1;
hT = new HTNode[m + 1]; // 0号节点不使用
for(int i = 1; i <= m; ++ i){
hT[i].parent = hT[i].lChild = hT[i].rChild = 0;
}
for(int i = 1; i <= n; ++ i){
cin >> hT[i].weight; // 输入前n个单元中叶子结点的权值
}
hT[0].weight = m; // 用0号节点保存节点数量
/****** 初始化完毕, 创建哈夫曼树 ******/
for(int i = n + 1; i <= m; ++ i){
//通过n-1次的选择、删除、合并来创建二叉树
int s1, s2;
SelectMin(hT, i, s1, s2);
hT[s1].parent = hT[s2].parent = i;
hT[i].lChild = s1; hT[i].rChild = s2; // 作为新节点的孩子
hT[i].weight = hT[s1].weight + hT[s2].weight; // 新节点为左右孩子节点权值之和
}
}
并查集:
在一些有N个元素的集合应用问题中,我们通常是在开始时让每个元素构成一个单元素的集合,然后按一定顺序将属于同一组的元素所在的集合合并,其间要反复查找一个元素在哪个集合中。这一类问题近几年来反复出现在信息学的国际国内赛题中,其特点是看似并不复杂,但数据量极大,若用正常的数据结构来描述的话,往往在空间上过大,计算机无法承受;即使在空间上勉强通过,运行的时间复杂度也极高,根本就不可能在比赛规定的运行时间(1~3秒)内计算出试题需要的结果,只能用并查集来描述。
并查集是一种树型的数据结构,用于处理一些不相交集合(Disjoint Sets)的合并及查询问题。常常在使用中以tree来表示。
用途:
维护一个无向图的连通性,判断n个点m条边时最少加多少边可以连通所有点
判断在一个无向图中,两点间加边是否会产生环(最小生成树克鲁斯卡尔中有用到)
维护集合等操作。
操作:
(1)Union(Root1, Root2):把子集合Root2并入集合Root1中。要求这两个集合互不相交,否则不执行合并。
(2)Find(x):搜索单元素x所在的集合,并返回该集合的名字。
(3)UnionFindSets(s):构造函数,将并查集中s个元素初始化为s个只有一个单元素的子集合。
主要代码:
long long int chazhao(long long int root)
{
long long int son,tmp;
son=root;
while(root!=a[root])
{
root=a[root];
}
while(son != root)
{
tmp = a[son];
a[son] = root;
son = tmp;
}
return root;
}
里面还有一段代码用来缩短路径,方便直接找到最高领导人。
放个例题:
描述
有n个人,编号1-n。
现在有一个舞会,在舞会上,大家会相互介绍自己的朋友。
即: 如果a认识b,b认识c。那么在舞会上,a就会通过b认识到c。
现在,给出m个关系
每个关系描述:
a b
表示 编号为a和编号为b的人是朋友关系。
格式
输入格式
输入n和m
接下来m行,每行为a b
输出格式
最后问,会有多少个朋友圈。
样例
样例输入 Copy
5 3
1 2
2 3
4 5
样例输出 Copy
2
这个就是简单的并查集很快就能做出来的:
#include<stdio.h>
long long int a[100000],b[100000]={0},t=0;
long long int chazhao(long long int root)
{
long long int son,tmp;
son=root;
while(root!=a[root])
{
root=a[root];
}
while(son != root)
{
tmp = a[son];
a[son] = root;
son = tmp;
}
return root;
}
int main()
{
long long int n,m,i,total,x,y,p,q;
scanf("%lld %lld",&n,&m);
total=n-1;
for(i=1;i<=n;i++)
a[i]=i;
for(i=0;i<m;i++)
{
scanf("%lld %lld",&q,&p);
x=chazhao(q);
y=chazhao(p);
if(x!=y)
{
a[y]=x;
total--;
}
}
for(i=1;i<=n;i++)
{
chazhao(i);
}
for(i=1;i<=n;i++)
printf("%d ",a[i]);
printf("\n");
for(i=1;i<=n;i++)
{
b[a[i]]++;
}
for(i=1;i<=n;i++)
if(b[i]!=0)
t++;
printf("%lld",t);
return 0;
}