堆与并查集在竞赛中很少直接涉及,但是我们可以利用这两种数据结构对算法进行优化。
堆
定义
堆是一棵完全二叉树,且对于任意结点满足一定的父子关系。
用数组表示堆时,若有一个结点
A
[
i
]
A[ i ]
A[i],那么它的父亲是
A
[
⌊
i
2
⌋
]
A[ \lfloor \frac{i}{2} \rfloor ]
A[⌊2i⌋]1,它的左儿子是
A
[
2
×
i
]
A[2\times i]
A[2×i],它的右儿子是
A
[
2
×
i
+
1
]
A[2\times i + 1]
A[2×i+1]
如果父结点永远小于等于儿子结点,那么这样的堆称作小根堆
如果父结点永远大于等于儿子结点,那么这样的堆称作大根堆
相关操作
堆有两种操作:
- 加入元素
- 取出堆顶
加入元素算法步骤:
- 在堆尾加入元素
- 比较它和它父结点的大小,如果不符合当前堆规定的父子关系,交换它的父结点,重复此步骤,直至它和它父结点符合当前堆的父子关系,或此元素已成为堆顶
代码如下(以小根堆做示范):
void putx(int x)
{
int now,next;
A[++size]=x;// 在堆尾加入元素
now=size;
while(now>1)// 重复此步骤,直至它和它父结点符合当前堆的父子关系,或此元素已成为堆顶
{
next=now/2;
if(A[now] <= A[next]) break;// 比较它和它父结点的大小
swap(A[now],A[next]);// 如果不符合当前堆的父子关系,交换它的父结点
now=next;
}
}
时间复杂度 O ( log s i z e ) O(\log size ) O(logsize)
取出堆顶算法步骤:
- 取出堆顶元素
- 维护堆
代码如下(以小根堆做示范):
int popx()
{
int now=1,next=0;
int ret=A[1];
A[1]=A[size};//将堆尾摆上堆顶
size--;
while(now*2<=size)
{
next=now*2;
if(next<size&&A[next+1]<A[next]) next++;//如果可以到达右儿子下一个位置就去右儿子
if(A[now]>=A[next]) break;//判断是否符合当前规定的父子关系
swap(A[now,A[next]);
now=next;
}
return ret;
}
时间复杂度 O ( log s i z e ) O(\log size) O(logsize)
STL
在C++的STL中有大根堆即优先对列
需要的头文件
#include<queue>
定义的方式
priority_queue<数据类型> 变量名;
转成小根堆的两种方式
- 存入的元素的相反数
- 在尖括号中搞些事情(注意:最后两个尖括号之间要有空格,否则编译器会认为这是一个流的符号)
priority_queue<数据类型 ,vector<数据类型 > ,greater<数据类型 > > 变量名; // 这个不能少 ↑
STL中提供的常用操作:
加入元素
heap.push(x);
获得堆顶元素
int x=heap.top();
删除堆顶
heap.pop();
并查集
定义
用于查询或合并
a
,
b
a,b
a,b两个元素所在的集合的数据结构
常用数组
f
a
t
h
e
r
[
i
]
father[i]
father[i]表示
i
i
i的父亲
相关操作
一共有四种操作:
- 初始化
- 寻找一个元素的根结点
- 合并两个集合
- 判断两个元素是否在同一集合
初始化
for(int i=1;i<=n;i++) father[i]=i;//i的根结点是它自己
寻找一个元素的根结点
int findx(int x)
{
if(father[x]!=x) return findx(father[x]);
return father[x];
}
合并两个集合
void unionx(int x,int y)
{
x=findx(x);y=findx(y);
father[y]=x;
}
判断两个元素是否在同一集合
bool judge(int x,int y)
{
x=findx(x);y=findx(y);
if(x==y) return 1;
return 0;
}
⌊ x ⌋ \lfloor x \rfloor ⌊x⌋对 x x x向下取整 ↩︎