1.学习内容#
主要学习内容为树形数据结构。知识点有:
- 最小生成树:Prim算法,Kruskal算法
- 二叉堆,堆排序,优先队列
- 哈夫曼树,哈夫曼编码
- 树状数组,线段树
- 并查集
2.知识小结#
(1)最小生成树:
求最小生成树的常规算法有两种:Prim和Kruskal,Prim以点为主,Krukal以边为主。Prim以点开展遍历,寻求权值最小的点,并将之加入到集合中(接在树上)。核心代码:
for(int i=1; i<=n; i++)
dis[i]=w[1][i]; //w[i][j]表示i,j直接距离
dis[1]=0; //从节点1开始遍历
for(int i=1; i<n; i++){
min=INF; //min为边权最小值
for(int j=1; j<=n; j++)
if(dis[j]!=0 && dis[j]<min){ //不在树上并且边权更小
min=dis[j];
k=j; //k为边权最小点
}
dis[k]=0; //将k加入集合,接在树上
ans+=min; //ans为最小生成树的边权和
for(int j=1; j<=n; j++)
if(w[k][j]<dis[j])
dis[j]=w[k][j]; //更新节点边权
}
Kruskal先将边权排序,将边权最小并且所连接节点未在树上的边依次接在树上。核心代码:
//Kruskal算法需要用并查集维护,下文会有对并查集的介绍
sort(edge+1, edge+m+1, CMP); //将边权排序,CMP函数比较边权大小
for(int i=1; i<=m; i++)
if(find(edge[i].x)!=find(edge[i].y)){ //并查集,find函数用来寻找父节点.
ans+=edge[i].w; //ans为最小生成树的边权和
f[find(edge[i].x)]=edge[i].y; //并查集,将该边两个节点合并
}
(2)二叉堆,堆排序,优先队列:
我们平时所说的“堆”即为二叉堆,二叉堆是完全二叉树。二叉堆分为小根堆和大根堆,小根堆为父节点的值总是小于等于任意一个子节点的值,大根堆则恰好相反。关于二叉堆主要有三种操作:添加,删除,修改元素,具体实现就不多说了。二叉堆的主要作用为,以logN(N为数组元素个数)的复杂度求动态数组(数组中元素可以修改)中的最值。堆排序也是利用了堆的思想,用NlogN的复杂度将数组排序。STL中关于堆的函数包含在algorithm库中,有:make_heap()、pop_heap()、push_heap()、sort_heap()四个函数。
优先队列是将队列中每个元素赋一个优先级,每次优先级最高元素出队的一种队列。不难看出,二叉堆是一种特殊的优先队列,大根堆是元素大的优先级高的优先队列,小根堆则相反。优先队列的实现比较复杂,但STL中有关于优先队列的函数,包含在queue库中,定义如下:
// 声明一个叫q的int型优先队列 priority_queue <int, vector<int>, greater<int> > q; //小根堆 priority_queue <int, vector<int>, less<int> > q; //大根堆 priority_queue <int > q; //大根堆,与上一种等价
有 empty( ),pop( ) ,push( ) ,size( ) ,top( )四种操作,用法与普通队列类似。
介绍一种黑科技!有一个叫pb_ds(平板电视)的库,当中有优先队列,平衡二叉树,红黑树,Splay,哈希表等内容。重要的是它们的复杂度出奇的低,优先队列远快于STL中的,甚至比手打的还要快!而且在OI的比赛中允许使用,需要以下定义:
#include <ext/pb_ds/priority_queue.hpp>
__gnu_pbds::priority_queue <T>
例如,小根堆的声明为:
__gnu_pbds::priority_queue <int, std::greater<int>, __gnu_pbds::pairing_heap_tag> minheap;
用法与STL中的类似,使用起来很方便。
(3)哈夫曼树,哈夫曼编码:
哈夫曼树也叫最优二叉树,是一种带权路径(叶子节点权值乘该节点到根节点路径长度)之和最短的二叉树。构造哈夫曼树一种基于贪心的算法,通过哈夫曼树可以构造出哈夫曼编码,可以用于数据压缩和数据加密。
(4)树状数组,线段树:
树状数组是主要用于查询数组中任意两位之间所有元素之和的数据结构,查询和修改的复杂度都是logN。树状数组可以拓展出区间修改,单点查询等功能。树状数组能解决的问题都可以使用线段树解决,但是树状数组的效率会高一些。树状数组的主要思想为二进制,它的操作尤为神奇。
如前文所说,线段树可以代替树状数组,同时也可以解决一些树状数组无法解决的问题。线段树运用了二分的思想,将一个大区间通过二分,分为若干个小区间,通过维护这些小区间,利用小区间合并出查询的结果。一般有查询,修改删除的操作,它们的复杂度都为log2N。线段树是一种极其重要的数据结构,内容也有很多,需要好好掌握。
(5)并查集:
并查集是一种维护集合合并和查询的数据结构。一般有合并,查询等操作,可以通过路径压缩来优化时间复杂度,实现的代码如下:
//初始化
for(int i=1; i<=n; i++)
f[i]=i; //f[x]表示x的父节点,意思即为每个节点均无父节点(父节点为自己)
//查找父亲节点(路径压缩)
int find(int x)
{
if(f[x]!=x) f[x]=find(f[x]); //如果f[x]==x则没有父节点,跳过此句,如果f[x]!=x,则继续寻找父节点
return f[x]; //返回父节点值
}
//合并
void Union(int x, int y)
{
int fx=find(x),fy=find(y); //寻找父节点
if(fx!=fy) f[fx]=fy; //若fx==fy则在同一集合,fx!=fy则合并
return;
}
//查询(询问x,y是否在同一集合)
if(find(x)==find(y)) printf("YES
");
else printf("NO
");
3.题目归纳
- 最小生成树:BZOJ1050 旅行
- 二叉堆:lg1801 黑匣子
- 优先队列:BZOJ1216 操作系统
- 哈夫曼树,哈夫曼编码: BZOJ4198 荷马史诗
- 树状数组,线段树: lg3372 3372 【模板】线段树 1
- 并查集:BZOJ4195 程序自动分析机