【挑战程序设计竞赛】- 2.4 数据结构(二叉树、堆、优先队列、并查集)

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条信息,信息有两类:

  1. x和y属于同一种类
  2. x吃y

这些信息可能会错,也可能x和y不在正确范围内。求k条信息里有几条不正确。
1<=N<=50000, 0<=K<=100000

思想:将这些动物分为三块 AX BX CX,

  1. AX - AY, BX-BY, CX-CY
  2. 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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值