2.4 数据结构(二叉树、堆、优先队列、并查集)
2.4.1 树和二叉树
树:根、边、节点、叶子 - 父亲、兄弟、儿子
二叉树:树中所有节点的儿子个数都不超过2
2.4.2 优先队列和堆
优先队列:能完成【插入一个数值】、【获得最小值,并且删除】两个操作的数据结构
#include <queue>
priority_queue <int> pq;//默认大顶堆
priority_queue <int, vector<int>, greater<int> > pq2;//小顶堆
pq.push(3);
pq.push(4);
while(!pq.empty()){
pq.top();
pq.pop();
}
——用堆来解决
堆:是特别的二叉树(以小顶堆为例)
儿子的值一定不小于父亲,
节点从上到下、从左到右紧密排列。
堆中插入数值:在堆的末尾插入数值,不断向上提升直到大小没有颠倒。
堆中取出数值:取出堆顶元素,把堆的末尾数值提到顶部,并往下交换到大小没有颠倒。
复杂度:O(logn)
实现:数组存储。左儿子=自己 *2+1,右儿子=自己 *2+2
int heap[NMAX], sz = 0;
//插入数值x
void push(int x){
int i = sz++; //自己节点编号
while( i>0 ){
int p = (i-1)/2;
// i就是x位置,跳出循环
if(heap[p]<=x){
break;
}
// 下放父亲节点
heap[i]=heap[p];
i = p;
}
heap[i]=x;
}
//弹出数值x
void pop(){
int ret = heap[0];
int x = heap[--sz];
int i = 0;
while(i<sz){
int ls = i*2+1, rs = i*2+2;
//ls取为值更小的儿子节点id
if( rs<sz && heap[rs]<heap[ls] ) ls = rs;
//当前点已经满足小于两个儿子节点,跳出
if( x <= heap[ls] ) break;
//将小儿子提为当前点,自己去小儿子点
heap[i] = heap[ls];
i = ls;
}
heap[i] = x;
return ret;
}
例题1:加油问题 POJ2431
卡车行驶 L 单位距离,最开始有 P 单位汽油,每行驶1单位需要消耗1单位汽油。
图中有 N 个加油站,第 i 个加油站离终点 Ai 单位距离,最多可加 Bi 单位汽油。
卡车汽油容量无限大,问能否到达终点?至少要加几次油?
1<=N<=10000, 1<=L, P<=100000,
1<=Ai<L, 1<=Bi<=100
思路:到过的地方之后都可以加油,加油量都放在优先队列里。
当油量为0时,选择最多的进行加油,如果当前位置不可达就是-1。
#include <queue>
#include <cstdio>
#include <algorithm>
using namespace std;
const int NMAX = 10010;
int L,P,N;
pair<int,int> fuel[NMAX];
void solve(){
//终点设为最后一个加油站,妙啊
fuel[N].first = L;
fuel[N].second = 0;
N++;
priority_queue <int> pq;
int pos = 0, tank = P, cnt = 0; //pos当前位置,tank油箱油量,cnt加油次数
for(int i=0; i<N; i++){
int d = fuel[i].first-pos;
while( tank-d < 0 ){
if(pq.empty()){
printf("-1\n");
return;
}
tank+=pq.top();
pq.pop();
cnt++;
}
tank -= d;
pos = fuel[i].first;
pq.push(fuel[i].second);
}
printf("%d\n",cnt);
}
int main(){
while(~scanf("%d",&N)){
for(int i=0; i<N; i++){
scanf("%d%d",&fuel[i].first,&fuel[i].second);
}
scanf("%d%d",&L,&P);
for(int i=0; i<N; i++){
fuel[i].first = L-fuel[i].first;
}
sort(fuel,fuel+N);
solve();
}
return 0;
}
例2:贪心法中的修木板 POJ3253
2.4.3 二叉搜索树
二叉搜索树能够完成:【插入一个数值】、【查询是否包含一个数值】、【删除一个数值】
所有节点均满足:左子树节点比自己小,右子树节点比自己大。
【查询】:比当前节点小就往左,大就往右,要么正好相等要么无法行走。
【插入】:和查找步骤一样,找到对应的节点进行插入。
【删除】: 节点没有左儿子,把右儿子提上;
节点的左儿子没有右儿子,把左儿子提上;
节点左儿子子孙节点中最大点提上去。
操作复杂度O(logn)
标准库实现的二叉搜索树:set和map容器
#include <set>
set<int> s; //声明
s.insert(1); //插入
s.insert(4);
s.insert(2);
set<int>::iterator it; //迭代器
it = s.find(1); //查找,没有是s.end()
if(it == s.end()) puts("not found");
s.erase(3);//删除,数值或迭代器
s.erase(it);
if(!s.count(3)) puts("not found"); //查找
//遍历
for(it = s.begin(); it!=s.end();it++){
printf("%d",*it);
}
map:
#include <map>
map<int, const char*> m; //声明
m.insert(make_pair(1,"ONE")); //插入
m.insert(make_pair(2,"TWO"));
m[100]="HUNDRED";
map<int, const char*>::iterator it; //迭代器
it = m.find(1); //查找,没有是end()
if(it == m.end()) puts("not found");
m.erase(2); //删除
//遍历
for(it = m.begin(); it!=m.end(); it++){
printf("%d - %s\n", it->first, it->second);
}
注意:二叉树退化可能会变成1条直线,每次操作退化成O(n),因此最好使用旋转的平衡二叉树。
标准库中已经实现了平衡二叉树,所以STL解决一切。
2.4.4 并查集
并查集只能合并不好分割,能够完成:【查询a和b是否在同一组】【合并a和b所在的组】
实现结构:树形结构。
【初始化】:没有边。
【合并】:从一组根向另一组根连边
【查询】:根节点是否是同一个根。
避免退化成直线的trick:
1.记录树的高度rank。两棵树高度不同时,高度小的向大的连边。
2.路径压缩:把点到父亲的边改为直接连根节点。
复杂度 O ( α ( n ) ) O(\alpha(n)) O(α(n))。
板子:
int fa[NMAX]; //父亲点
int rank[NMAX]; //高度
void init(int n){
for(int i=0; i<n; i++){
fa[i]=i;
rank[i]=0;
}
}
//查询
int find(int x){
if(fa[x]==x)
return x;
return fa[x]=find(fa[x]);
}
//合并
void unite(int x, int y){
x = find(x);
y = find(y);
if( x == y )
return;
if( rank[x]<rank[y] ){
fa[x]=y;
} else{
fa[y]=x;
if(rank[x]==rank[y]) rank[x]++;
}
}
例题1:食物链 POJ1182
N只动物,编号为1到N。所有动物属于A\B\C其中一种。已知A吃B、B吃C、C吃A。
按顺序给出K条信息,信息有两类:
- x和y属于同一种类
- x吃y
这些信息可能会错,也可能x和y不在正确范围内。求k条信息里有几条不正确。
1<=N<=50000, 0<=K<=100000
思想:将这些动物分为三块 AX BX CX,
- AX - AY, BX-BY, CX-CY
- AX- BY, BX-CY, CX-AY
同时操作前检查是不是有矛盾。
//这题不好用循环读入
#include <cstdio>
using namespace std;
const int NMAX=150050;
int N,K;
int fa[NMAX],rk[NMAX];
void init(){
for(int i=1;i<=N*3;i++){
fa[i]=i;
rk[i]=0;
}
}
int findf(int x){
if(fa[x]==x)return x;
return fa[x] = findf(fa[x]);
}
void unit(int x, int y){
x = findf(x);
y = findf(y);
if(x==y) return;
if(rk[x]<rk[y]){
fa[x] = y;
}else{
fa[y] = x;
if(rk[x]==rk[y]) rk[x]++;
}
}
int main(){
int D,X,Y,cnt;
// while(~scanf("%d%d",&N,&K)){
scanf("%d%d",&N,&K);
init();
cnt=0;
while(K--){
scanf("%d%d%d",&D,&X,&Y);
if(X<=0||Y<=0||X>N||Y>N){
cnt++;
continue;
}
if(D==1){
if(findf(X) == findf(Y+N) || findf(X) == findf(Y+2*N))
cnt++;
else{
unit(X, Y);
unit(X+N, Y+N);
unit(X+2*N, Y+2*N);
}
}
else{
if(findf(X) == findf(Y) || findf(X) == findf(Y+2*N))
cnt++;
else{
unit(X, Y+N);
unit(X+N, Y+2*N);
unit(X+2*N, Y);
}
}
}
printf("%d\n",cnt);
// }
return 0;
}