C++高级数据结构之线段树

C++高级数据结构之线段树攻略

什么是线段树

线段树是一种数据结构,它可以支持区间查询和单点修改,是处理静态区间问题的常用手段。它利用了 二分思想,将区间离散化成一些个体,然后考虑对个体进行处理,再结合区间合并的特性,更新区间信息。线段树主要由四个操作构成:建树、单点修改、区间查询和区间修改。

线段树的数据表示

在实现线段树时,我们需要考虑数据结构的几个要素:存储方式、数据的存储结构、具体实现方式等。在最常见的线段树实现中,通常将读入的区间离散化后,使用数组保存区间信息。线段树的构造是在递归过程中完成的。每次将当前区间分成两部分,构造左右子树,最后将区间的统计信息合并到父节点,然后返回。

下面是一个典型线段树节点的结构:

struct Node{
    int l; // 区间左端点
    int r; // 区间右端点
    int sum; // 区间和
}node[maxn<<2]; // 线段树数组,开4倍空间

其中 lr 分别记录节点的左右端点,sum 表示区间和。

线段树的操作

建树

下面是线段树的建树过程,基本思想是对于当前区间,取区间中间位置为节点,然后将当前区间分成左右两部分分别构造其左右子树。

void build(int p, int l, int r){
    node[p].l = l;
    node[p].r = r;
    if(l == r){
        scanf("%d", &node[p].sum);
        return;
    }
    int mid = (l + r) >> 1; 
    build(p<<1, l, mid); // 左子树
    build(p<<1|1, mid+1, r); // 右子树
    node[p].sum = node[p<<1].sum + node[p<<1|1].sum; // 合并信息
}

单点修改

单点修改操作即为更改线段树某个节点的区间信息。单点修改只会影响一个节点,因此是一种很轻量的操作。具体操作就是从根节点开始,依次进入左右子树,直至遇到叶节点,然后进行更新。

void update(int p, int x, int k){
    if(node[p].l == x && node[p].r == x){
        node[p].sum += k;
        return;
    }
    int mid = (node[p].l + node[p].r) >> 1;
    if(x <= mid) update(p<<1, x, k); // 左子树
    else update(p<<1|1, x, k); // 右子树
    node[p].sum = node[p<<1].sum + node[p<<1|1].sum; // 合并信息
}

区间查询

区间查询即为查询线段树某个区间的信息。具体操作同样是从根节点开始,依次进入左右子树,直至遇到区间完全包含目标区间或者与目标区间相离的区间,然后将得到的信息合并。

int query(int p, int l, int r){
    if(node[p].l == l && node[p].r == r)
        return node[p].sum;
    int mid = (node[p].l + node[p].r) >> 1;
    if(r <= mid) return query(p<<1, l, r); // 左子树
    else if(l > mid) return query(p<<1|1, l, r); // 右子树
    else return query(p<<1, l, mid) + query(p<<1|1, mid+1, r); // 遇到分歧,分别查询左右子树
}

区间修改

区间修改将修改线段树某个区间内的节点信息。具体操作同样是从根节点开始,递归执行左右子树的修改,最后将修改后的子树信息合并。

void modify(int p, int l, int r, int k){
    if(node[p].l >= l && node[p].r <= r){
        node[p].sum += k;
        return;
    }
    int mid = (node[p].l + node[p].r) >> 1;
    if(l <= mid) modify(p<<1, l, r, k); // 左子树
    if(r > mid) modify(p<<1|1, l, r, k); // 右子树
    node[p].sum = node[p<<1].sum + node[p<<1|1].sum; // 合并信息
}

示例

示例一

题目描述:https://www.acwing.com/problem/content/description/802/
题目解答:https://www.acwing.com/solution/content/50632/

该题要求维护区间元素的和,并对某个区间元素加上一个值。下面是该题的代码实现,其中 n 表示待维护区间长度。

void build(int p, int l, int r){
    node[p].l = l;
    node[p].r = r;
    if(l == r){
        scanf("%lld", &node[p].sum);
        return;
    }
    int mid = (l + r) >> 1;
    build(p<<1, l, mid);
    build(p<<1|1, mid+1, r);
    node[p].sum = node[p<<1].sum + node[p<<1|1].sum;
}

void modify(int p, int x, int k){
    if(node[p].l == x && node[p].r == x){
        node[p].sum += k;
        return;
    }
    int mid = (node[p].l + node[p].r) >> 1;
    if(x <= mid) modify(p<<1, x, k);
    else modify(p<<1|1, x, k);
    node[p].sum = node[p<<1].sum + node[p<<1|1].sum;
}

ll query(int p, int l, int r){
    if(node[p].l == l && node[p].r == r)
        return node[p].sum;
    int mid = (node[p].l + node[p].r) >> 1;
    if(r <= mid) return query(p<<1, l, r);
    else if(l > mid) return query(p<<1|1, l, r);
    else return query(p<<1, l, mid) + query(p<<1|1, mid+1, r);
}

int main(){
    int n, m;
    scanf("%d%d", &n, &m);
    build(1, 1, n);
    for(int i = 0; i < m; ++i){
        int opt, x, y;
        scanf("%d%d%d", &opt, &x, &y);
        if(opt == 1) modify(1, x, y);
        else printf("%lld\n", query(1, x, y));
    }
    return 0;
}

示例二

题目描述:https://www.acwing.com/problem/content/1073/
题目解答:https://www.acwing.com/solution/content/39389/

该题要求维护区间最大值,并对某个元素进行单点修改。代码实现如下,其中 n 表示待维护区间长度。

void build(int p, int l, int r){
    node[p].l = l;
    node[p].r = r;
    if(l == r){
        scanf("%d", &node[p].sum);
        return;
    }
    int mid = (l + r) >> 1;
    build(p<<1, l, mid);
    build(p<<1|1, mid+1, r);
    node[p].sum = max(node[p<<1].sum, node[p<<1|1].sum);
}

void update(int p, int x, int k){
    if(node[p].l == x && node[p].r == x){
        node[p].sum = k;
        return;
    }
    int mid = (node[p].l + node[p].r) >> 1;
    if(x <= mid) update(p<<1, x, k);
    else update(p<<1|1, x, k);
    node[p].sum = max(node[p<<1].sum, node[p<<1|1].sum);
}

int query(int p, int l, int r){
    if(node[p].l == l && node[p].r == r)
        return node[p].sum;
    int mid = (node[p].l + node[p].r) >> 1;
    if(r <= mid) return query(p<<1, l, r);
    else if(l > mid) return query(p<<1|1, l, r);
    else return max(query(p<<1, l, mid), query(p<<1|1, mid+1, r));
}

int main(){
    int n, m;
    scanf("%d%d", &n, &m);
    build(1, 1, n);
    for(int i = 0; i < m; ++i){
        int opt, x, y;
        scanf("%d%d%d", &opt, &x, &y);
        if(opt == 1) update(1, x, y);
        else printf("%d\n", query(1, x, y));
    }
    return 0;
}

总结

线段树是一种非常常用的数据结构,可以高效地解决静态区间查询问题。它具有以下特点:

  • 可以支持区间查询、单点修改、区间修改等操作;
  • 基于二分思想,将区间离散化,实现节点合并;
  • 可以高效地支持大规模数据处理,常被用于OI竞赛和工程实现。

同时线段树也存在一些注意事项,下面进行总结:

  • 在某些情况下,线段树需要懒标记来实现区间修改,以避免重复修改的问题;
  • 线段树的空间复杂度为 $O(n\log n)$,对于卡空间的情况需要注意;
  • 在线段树实现过程中,要注意数组越界的问题,保证程序的稳定运行。

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:C++高级数据结构之线段树 - Python技术站

(0)
上一篇 2023年5月17日
下一篇 2023年5月17日

相关文章

  • 「分治」黑白棋子的移动

    本题为3月23日23上半学期集训每日一题中A题的题解 题面 题目描述 有2n个棋子(n≥4)排成一行,开始位置为白子全部在左边,黑子全部在右边,如下图为n=5的情形: ○○○○○●●●●● 移动棋子的规则是:每次必须同时移动相邻的两个棋子,颜色不限,可以左移也可以右移到空位上去,但不能调换两个棋子的左右位置。每次移动必须跳过若干个棋子(不能平移),要求最后能…

    算法与数据结构 2023年4月18日
    00
  • 【ACM算法竞赛日常训练】DAY16【奇♂妙拆分】【区区区间间间】【小AA的数列】数学 | 位运算 | 前缀和

    DAY16共3题: 奇♂妙拆分(简单数学) 区区区间间间(单调栈) 小AA的数列(位运算dp) ? 作者:Eriktse? 简介:19岁,211计算机在读,现役ACM银牌选手?力争以通俗易懂的方式讲解算法!❤️欢迎关注我,一起交流C++/Python算法。(优质好文持续更新中……)?? 阅读原文获得更好阅读体验:https://www.eriktse.com…

    算法与数据结构 2023年4月20日
    00
  • 从零学JSON之JSON数据结构

    从零学JSON之JSON数据结构 什么是JSON? JSON全称为JavaScript Object Notation,即JavaScript对象表示法。它是一种轻量级的数据交换格式,具有可读性高、易于开发和解析的特点。JSON格式通常用于客户端和服务器之间的数据传输,可以支持多种编程语言。如下是一个简单的JSON格式示例: { "name&quo…

    数据结构 2023年5月17日
    00
  • C、C++线性表基本操作的详细介绍

    我来详细讲解“C、C++线性表基本操作的详细介绍”。 一、线性表的定义 线性表是一种数据结构,它是由n个数据元素组成的有限序列,记为(a1,a2,…,an),其中a1是线性表的第一个元素,an是线性表的最后一个元素。除第一个元素之外,每一个元素有且仅有一个直接前驱元素,除了最后一个元素之外,每一个元素有且仅有一个直接后继元素。 线性表可以理解为一个一维数…

    数据结构 2023年5月17日
    00
  • Java数据结构之基于比较的排序算法基本原理及具体实现

    Java数据结构之基于比较的排序算法基本原理及具体实现 前言 排序算法是计算机科学中最基本的算法之一,其广泛应用于各领域中。基于比较的排序算法是一种流行的排序算法类型,本篇文章将阐述其基本原理及具体实现,以帮助读者深入了解该算法。 算法介绍 基于比较的排序算法是根据元素之间的比较操作来完成排序的一种算法类型,它可以对各种数据类型进行排序,如整数、浮点数、字符…

    数据结构 2023年5月17日
    00
  • Java数据结构之HashMap和HashSet

    Java数据结构之HashMap和HashSet HashMap 介绍 HashMap是一种基于哈希表实现的Map集合,它提供了快速的插入、查询、删除操作。HashMap中存储的元素是以键值对(Key-Value)的形式存储的,其中Key是用来从Map中查找值的索引,Value是存储在Map中的值。HashMap中的Key和Value都可以为null,但是在…

    数据结构 2023年5月17日
    00
  • C语言植物大战数据结构快速排序图文示例

    C语言植物大战数据结构的快速排序可以分为以下步骤: 准备工作 首先需要定义一个关于植物大战中植物的结构体,例如: struct Plant { int hp; int atk; int cost; }; 然后准备一个装载植物信息的数组: struct Plant plants[] = { {75, 36, 100}, {100, 20, 50}, {125,…

    数据结构 2023年5月17日
    00
  • 带头节点的单链表的思路及代码实现

    带头节点的单链表的思路及代码实现(JAVA) 一、什么是的单链表 ①标准定义 单链表是一种链式存取的数据结构,用一组地址任意的存储单元存放线性表中的数据元素。链表中的数据是以结点来表示的,每个结点的构成:元素(数据元素的映象) +指针(指示后继元素存储位置,元素就是存储数据的存储单元,指针就是连接每个结点的地址数据。) 以上是标准定义不太好让人对单链表有直观…

    算法与数据结构 2023年4月17日
    00
合作推广
合作推广
分享本页
返回顶部