C++剑指offer:高级数据结构之线段树入门详解

本文详细介绍了线段树这一高级数据结构,从理解其本质到实现建树、插入和查询操作。通过实例分析,解释了线段树在处理区间问题上的优势,特别讨论了在处理负数端点和区间重叠情况下的策略。

目录

前言

理解

操作

后记


前言

早在刚学C++编程之际,就已经听说了线段树的大名今天的我也已经学会线段树了虽然是半懂半懵。不过有趣的是我在学线段树时并没有感觉到太困难,可能是我比较擅长树和递归这一块的知识吧。总之我很快就初步掌握了线段树,虽然只是初步掌握,但还是来写博客了。笔者初学线段树,博客中可能有纰漏,如果有错误的,欢迎指出以供纠正。

理解

先说说我对线段树的理解吧。

相信大家都已经了解了二叉树。线段树本质上也是一种二叉树,但它的特殊之处在于它的每个节点都可以表示为一个大的区间,而二叉树末端的叶节点则只有一个单位。如图就可以理解为 一个用来表示区间 1-6 的线段树

如图,1-6便是这棵线段树的根节点。

接着,我们把根节点的编号设为1,按照二叉树的规则,我们把编号为i的节点的左儿子设为i*2,右儿子设为i*2+1。如图,橙色的字对应的便是每个节点的编号。

于是我们就可以用结构体表示每个节点,并且用二叉树的规则来建树了。

每个节点的结构体,l为这个节点(线段)的左端点,r为这个节点(线段)的右端点。

struct node {
    int l,r;
} tree[N];

需要注意的是,如果题面要求有N个数,那么我们建立数组时就应该设为它的5倍(具体为什么大家可以下来好好思考一下)

然后是建树的操作:

void build(int l,int r,int i) {
    tree[i].l=l;
    tree[i].r=r;
    if(l==r)
        return ;
    int mid=(l+r)/2;
    build(l,mid,i*2);
    build(mid+1,r,i*2+1);
}

初学者理解之后可以自己动手打一下。

操作

线段树的操作中无非就是三个,建树,插入(更新),查询。

我们已经实现了其中的其中一步建树,下面我将用这个例题来实现接下来的两步。

问题 D(1911): 【高级数据结构】线段的条数

时间限制: 1 Sec  内存限制: 64 MB

题目描述

无限长的X轴上从下向上依次叠放一定长度某种线段。问在某个单位区间上一共叠放了多少条线段?

 

输入

第1行:1个整数N。1<=N<=100000,表示线段的条数
接下来N行,每行2个整数L,R,-100000 <=L < R<= 100000,表示一线段的左、右端点(左闭右开区间);
最后1行:1个整数P,表示单位区间的起点。-100000 <=P<100000

输出

第1行:1个整数M,表示[P, P+1) ]区间叠放了多少条线段

样例输入

5
-1 5
3 10
-7 7
3 4
-9 2
-4

样例输出

先初步分析一下。

首先此题的左右端点有负数的情况,所以可以在输入时把每个节点的 左右端点都加一个100000确保不会数组溢出导致运行错误。

然后,此题说过区间是左闭右开,所以在输入时我们还要把右端点-1。

为了判断这个区间里重叠的线段条数,可以再结构体里再添加一个变量cnt来统计重叠的线段条数。

我们可以从根节点出发开始递归查找,如果当前节点的左端点tree[i].l比我们要插入的线段l-r的r还要大,说明这步查找是没有意义的。我们最终的目标是要找到一个节点tree[k].l>=l && tree[k].r<=r来保证它是完全被我们要插入的线段完全覆盖的,然后就可以cnt++。这条线段是相交却不相互包含的(如图)

就要把这个节点一分为二,继续查找它的左儿子和右儿子直到能被l-r完全覆盖。

为了方便理解,这里演示一下在线段树中插入一条线段3-5的递归过程

代码如下:

void insert(int i) {
    if(r<tree[i].l || l>tree[i].r) //区间之外,推出
        return ;
    if(tree[i].l>=l && tree[i].r <= r) {//找到区间,插入后退出
        tree[i].cnt++;
        return ;
    }
    insert(i*2);//继续查找
    insert(i*2+1);
}
 

现在我们已经完成了插入这一步。之后就是查找了。查找也很简单,我们只要统计区间x,x+1就行了(因为是左闭右开,所以可以将它看做一个点)

代码如下:

void find(int i) {//ans统计答案
    if(tree[i].l>X || tree[i].r<X)
        return ;
    ans+=tree[i].cnt;//累加
    find(i*2);//继续找
    find(i*2+1);
}

所以此题的最终代码便是:

#include <iostream>
#include <cstdio>
#include <cstring>
    
using namespace std;
    
#define N 401020
#define T 100000
#define LL long long
#define inf 0x7f7f7f7f
#define mem(a,n) memset(a,n,sizeof(a))
    
struct node {
    int l,r,cnt;
} tree[4*N];
    
int read() {
    int f=1,s=0;char a=getchar();
    while(!(a>='0'&&a<='9')) {if(a=='-') f=-1 ; a=getchar(); }
    while(a>='0'&&a<='9') {s=s*10+a-'0'; a=getchar();}
    return f*s;
}
    
int L,R,n,tot,book[4*N],ans,l,r,C,X;
    
void build(int l,int r,int i) {
    tree[i].l=l;
    tree[i].r=r;
    if(l==r)
        return ;
    int mid=(l+r)/2;
    build(l,mid,i*2);
    build(mid+1,r,i*2+1);
}
    
void insert(int i) {
    if(r<tree[i].l || l>tree[i].r)
        return ;
    if(tree[i].l>=l && tree[i].r <= r) {
        tree[i].cnt++;
        return ;
    }
    insert(i*2);
    insert(i*2+1);
}
 
void find(int i) {
    if(tree[i].l>X || tree[i].r<X)
        return ;
    ans+=tree[i].cnt;
    find(i*2);
    find(i*2+1);
}
 
int main() {
    n=read();
    build(0,2*T+2,1);
    for(int i=1;i<=n;i++) {
        int a=read(),b=read()-1;
        a+=T;b+=T;
        l=a;r=b;
        insert(1);
    }
    X=read();
    X+=T;
	find(1);
    cout<<ans;
}

后记

笔者在线段树的学习上还正处于初学者,请大家提出我的缺点以供改正

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值