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日

相关文章

  • 基于C++详解数据结构(附带例题)

    基于C++详解数据结构(附带例题)攻略 简介 该攻略是基于C++编程语言详解数据结构的,主要涉及数据结构中的相关概念、操作以及例题演练。C++语言作为一种高性能的编程语言,对于开发数据结构问题具有很大的优势。 数据结构概念 数据结构基本概念 数据结构是计算机存储、组织数据的方式。具体来说,数据结构可以理解为计算机存储数据的一种方式,也可以看作是一些组织数据的…

    数据结构 2023年5月17日
    00
  • 数据结构 栈的操作实例详解

    数据结构 栈的操作实例详解 什么是栈? 栈(stack)是一种具有特殊限制的线性数据结构。它只允许在表的一端进行插入和删除操作,另一端是固定的,称为栈底;相反的另一端称为栈顶。栈底用于存放最早进入的元素,栈顶用于存放最近进入的元素,所以栈又称为后进先出的数据结构。 栈的操作 栈的主要操作包括入栈(push)、出栈(pop)、获取栈顶元素(top)和判断栈是否…

    数据结构 2023年5月17日
    00
  • C++线性表深度解析之动态数组与单链表和栈及队列的实现

    C++线性表深度解析之动态数组与单链表和栈及队列的实现 动态数组的实现 动态数组是一种可以动态扩展的数组结构,它的容量可以随着需要而动态增加。在C++中,使用vector类可以实现动态数组的功能。vector类相当于动态分配了一块内存空间,在使用时可以根据需要进行动态扩展。下面是一个示例代码: #include <vector> #include…

    数据结构 2023年5月17日
    00
  • 比特币区块链的数据结构

    让我来为你详细讲解比特币区块链的数据结构。 1. 区块链的定义 比特币区块链是一个去中心化的、可追溯的、公共的、可验证的交易数据库。每一笔交易都通过哈希算法,与之前的交易连接成一个区块,形成了一个数据结构链,也就是“区块链”。 2. 区块链的数据结构 区块链的数据结构由区块、交易和哈希三部分组成: 区块 区块是区块链数据结构的基本单位,每一个区块代表着一段时…

    数据结构 2023年5月17日
    00
  • C++数据结构与算法的基础知识和经典算法汇总

    C++数据结构与算法的基础知识和经典算法汇总 1. 基础知识 1.1 数据结构 数据结构是计算机存储、组织数据的方式。这里列出常见的数据结构,包括但不限于: 数组 链表 栈 队列 树 哈希表 1.2 算法 算法是解决问题的步骤和方法。下列是常见的算法: 排序算法 查找算法 字符串算法 图算法 1.3 复杂度 复杂度是算法性能的度量。常见的复杂度表示法有O(n…

    数据结构 2023年5月17日
    00
  • nginx内存池源码解析

    Nginx内存池源码解析 Nginx是一个高性能、高并发的Web服务器。为了提高其性能和速度,Nginx采用了特殊的内存管理机制,即内存池。 什么是内存池? 内存池是一种高效的内存分配和管理机制。它将一块内存划分成多个大小相等的块,并按需分配给系统。当内存块不再使用时,它并不被立即释放,而是留在内存池中待重复利用。 Nginx内存池结构 Nginx内存池主要…

    数据结构 2023年5月17日
    00
  • C语言实现通用数据结构之通用链表

    C语言是一门广泛应用于低级别系统编程的语言,也是数据结构和算法学习的重要工具之一,而在C语言中实现通用数据结构的方法之一就是通用链表。 通用链表是一种使用节点来组织数据的通用数据结构,每个节点包含一定量的数据以及指向链表中下一个节点的指针,因此,它可以用来实现许多不同的数据结构,例如栈、队列、树、图、哈希表等等。 具体实现通用链表的方法如下: 步骤一:定义节…

    数据结构 2023年5月17日
    00
  • Go语言数据结构之单链表的实例详解

    Go语言数据结构之单链表的实例详解 简介 单链表是一个常见的数据结构,它由一系列节点组成,每个节点包含一个值和指向下一个节点的引用。单链表的插入和删除操作比较容易,但是访问操作的效率相对较低。 在Go语言中,可以使用结构体配合指针来实现单链表。 实现思路 为了实现单链表,需要先定义一个节点结构体Node,包含一个value值和一个next指针。通过next指…

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