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日

相关文章

  • 数据结构之AVL树详解

    数据结构之AVL树详解 什么是AVL树? AVL树是一种自平衡的二叉搜索树,它的名称来自它的发明者Adelson-Velsky和Landis。在AVL树中,每个节点的左右子树的高度差(平衡因子)最多为1,否则需要通过旋转操作来重新平衡树。AVL树基于二叉搜索树,所以它包含了二叉搜索树的所有特性,同时也保证了树的高度始终处于对数级别,因此它的查找、插入、删除都…

    数据结构 2023年5月16日
    00
  • C语言实现学生信息管理系统(链表)

    C语言实现学生信息管理系统(链表) 简介 学生信息管理系统是管理学生的一种系统,可以实现添加、查找、删除和修改学生信息等功能。本文将使用C语言实现学生信息管理系统,并通过链表的方式进行实现。 前提条件 在开始之前,我们需要了解如下内容: C语言基础知识 链表的基本概念和使用 系统架构 学生信息管理系统主要包含以下几个模块: 学生信息结构体 添加学生信息 查找…

    数据结构 2023年5月17日
    00
  • C语言数据结构算法基础之循环队列示例

    标题:C语言数据结构算法基础之循环队列示例 1. 简介 循环队列是一种常见的数据结构,它采用固定大小的数组来模拟队列的数据结构,可以高效地处理队列的进出操作。本文将会讲解循环队列的实现原理和示例代码。 2. 循环队列基本原理 循环队列通过两个指针front和rear来实现队列的添加和删除操作。在初始化时,front和rear的初始值都为0。每当数据进入队列时…

    数据结构 2023年5月17日
    00
  • C语言超详细讲解数据结构中的线性表

    C语言超详细讲解数据结构中的线性表完整攻略 线性表的概念和基本操作 线性表是指由同类型的数据元素构成的有限序列。即每个数据元素只有一个前驱和一个后继。线性表通常用于表示一维数组、列表、队列等数据结构。 线性表的基本操作包括: 初始化操作:创建一个空的线性表。 插入操作:在线性表中插入一个元素。 删除操作:删除线性表中的一个元素。 查找操作:查找线性表中是否存…

    数据结构 2023年5月17日
    00
  • python数据结构之二叉树的统计与转换实例

    下面是针对“python数据结构之二叉树的统计与转换实例”的详细讲解攻略: 什么是二叉树 二叉树指的是一种树状结构,具有如下特点: 每个节点最多有两个子节点,分别为左子节点和右子节点 左子节点的值比父节点小,右子节点的值比父节点大 二叉树可以是空树,也可以是非空树。 二叉树的遍历 在对二叉树进行操作时,需要对其节点进行遍历。二叉树的遍历方式一般有以下三种: …

    数据结构 2023年5月17日
    00
  • C语言单链队列的表示与实现实例详解

    C语言单链队列的表示与实现实例详解 什么是队列? 在计算机科学中,队列(Queue)是一种特殊的数据结构,它只允许在一端进行插入操作,在另一端进行删除操作。将新元素插入队列的过程可以称之为入队,而将元素从队列中删除的过程则可以称之为出队。队列的核心思想是“先进先出”(First In First Out,FIFO),即先入队的元素先出队。 单链队列的表示方式…

    数据结构 2023年5月17日
    00
  • C语言编程简单却重要的数据结构顺序表全面讲解

    C语言编程简单却重要的数据结构顺序表全面讲解 什么是顺序表? 顺序表是一种线性表,指的是一组有限元素的有限序列,其元素的逻辑顺序与它们在分配到的内存地址上的物理顺序相同或者等价。也就是说,顺序表中的元素按照其在内存中的位置依次存放。 顺序表的实现方式 顺序表的实现方式一般是使用数组,数组中的每一个元素对应着顺序表中的一个元素,位置相对应。 顺序表的优点 支持…

    数据结构 2023年5月17日
    00
  • 详解Java实现数据结构之并查集

    详解Java实现数据结构之并查集 简介 并查集是一种基于树型结构的数据结构,主要用于解决一些不交集问题。它支持两个操作: 合并两个元素所在的集合 判断两个元素是否在同一个集合中 在并查集中,每个节点都有一个指向其父节点的指针。如果一个节点的指针指向它本身,说明它是一个集合的根节点。 实现 我们用一个int类型的数组parent来表示每个节点的父节点。初始时,…

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