线段树C++实现

news2024/11/14 13:29:17

一、本题线段树数组数据和结构

        data[]={1,2,-3,5,6,-2,7,1,12,30,-10},11个元素。

 二、各个函数和结构

(一)线段树结构

        创建线段树的结构, l、r为左边界和右边界,maxV和minV为最大值和最小值,sum为和,tag为lazyTag。

typedef struct SegNode{
    int l,r,maxV,minV,sum,tag;
    struct SegNode *pL, *pR;
}SegNode, *SegTree;

#define OK 1
#define ERROR 0

(二)线段树创建

        创建一个线段树,要改变指针变量的值就要传指针变量SegTree的地址进去,由于双重指针代表的是指针的指针(地址的地址),故我们这里使用双重指针用*SegTree指向SegTree的地址

        递归创建,出口为递归到左边界=有边界时。

void BuildSegTree(SegTree* root, int l, int r, int data[]){
    //当l == r时,左右边界为相同位置,递归结束
    if(l == r){
        (*root) = new SegNode;
        (*root)->sum = data[l]; //只有一个数,所以和就是这个数
        (*root)->maxV = data[l]; //同上
        (*root)->minV = data[l]; //同上
        (*root)->tag = 0; //新结点没有“欠债”
        (*root)->l = l;
        (*root)->r = r;
        (*root)->pL = NULL; //左右子树为空
        (*root)->pR = NULL;
        return;
    }
    //左右边界不相同时,创建该结点后,分成左右两个子树继续递归
    else{
        (*root) = new SegNode;
        (*root)->l = l;
        (*root)->r = r;
        (*root)->tag = 0;
        int mid = l + (r-l)/2; //求中间位置
        //递归调用该函数创建左右子树
        BuildSegTree(&((*root)->pL), l, mid, data);
        BuildSegTree(&((*root)->pR),mid+1, r, data);
        (*root)->sum = (*root)->pL->sum + (*root)->pR->sum; //通过两个子树的和求该树的和
        (*root)->maxV = max((*root)->pL->maxV, (*root)->pR->maxV); //通过两个子树的最大值比较求该树的最大值
        (*root)->minV = min((*root)->pL->minV,(*root)->pR->minV); //通过两个子树的最小值比较求该树的最小值
    }
}

(三)清算懒标记

        清算懒标记“债务”的函数,懒标记是可以大幅度提升效率的操作,用法是在每次更改一定范围数据的值时,先只更改当前结点,并且将要改变的值存在当前节点的懒标记中,等以后调用其他方法需要递归到下层节点时,再将懒标记传到子树中并且更改子树的值。

//清算懒标记“债务”的函数
void LateUpdate(SegTree root){
    //没债务返回就行
    if(root->tag == 0)
        return;
    //左子树不为空,把债务传递给左子树,并且更改左子树的值
    if(root->pL != NULL){
        root->pL->tag += root->tag;
        root->pL->sum += (root->pL->r - root->pL->l+1)*root->tag;
        root->pL->maxV += (root->tag);
        root->pL->minV += (root->tag);
    }
    //右子树同上
    if(root->pR != NULL){
        root->pR->tag = root->tag;
        root->pR->sum = (root->pL->r - root->pL->l+1)*root->tag;
        root->pR->maxV += (root->tag);
        root->pR->minV += (root->tag);
    }
    root->tag = 0; //债务结算完清空
}

(三)查询操作

        查询范围内的最大值、最小值、和。递归操作,出口为查询范围刚好与当前root范围相等。递归过程右三种情况:1.在mid左侧。2.在mid右侧。3.两边都有。

//查询操作
int query(SegTree root, int l, int r, int *tsum, int *tmax, int *tmin){
    //如果要查询的左边界范围大于右边界或者相反,返回
    if(r < root->l || l > root->r)
        return ERROR;
    //如果刚好查询的范围与当前root的范围相等
    if(l == root->l && r == root->r){
        *tsum = root->sum;
        *tmax = root->maxV;
        *tmin = root->minV;
        return OK;
    }
    LateUpdate(root); //运行到这说明要向子树递归,所以先将债务传递给子树
    int mid = root->l + (root->r - root->l)/2;
    //整个在mid左边的情况
    if(r<=mid){
        query(root->pL, l, r, tsum, tmax, tmin);
        return OK;
    }
    //整个在mid右边的情况
    else if(l >= mid+1){
        query(root->pR, l, r, tsum, tmax,tmin);
        return OK;
    }
    //被mid从中间分开的情况
    else{
        int lsum, rsum, lmax,rmax, lmin, rmin;
        query(root->pL, l, mid, &lsum, &lmax, &lmin);
        query(root->pR, mid+1, r, &rsum, &rmax, &rmin);
        *tsum = lsum+rsum;      //计算左右子树和
        *tmax = max(lmax,rmax); //从左右子树中找最大值
        *tmin = min(lmin,rmin); //从左右子树中找最小值
        return OK;
    }
}

(四)将范围内的每个数增加value

//在一个范围内将每个数增加value
void Add(int tl, int tr, SegTree root, int Value){
    //判断是否越界
    if(tl > root->r || tr < root->l){
        return;
    }
    //如果包含在内了,就把所有的值修改,并给tag写上债务
    if(tl <= root->l && tr >= root->r){
        root->sum += (root->r - root->l+1)*Value;
        root->maxV += Value;
        root->minV += Value;
        root->tag += Value;
        return;
    }
    //如果没包含,那就先将债务传递到子树,然后递归
    LateUpdate(root);
    int mid = root->l+(root->r-root->l)/2;
    //都在左侧的情况
    if(tr <= mid)
        Add(tl, tr, root->pL, Value);
    //都在右侧的情况
    else if(tl >= mid+1)
        Add(tl, tr, root->pR, Value);
    //两边都有
    else{
        Add(tl, mid, root->pL, Value);
        Add(mid+1, tr, root->pR, Value);
    }
    //计算各值
    root->sum = root->pL->sum + root->pR->sum;
    root->maxV = max(root->pL->maxV, root->pR->maxV);
    root->minV = min(root->pL->minV, root->pR->minV);
}

(五)销毁线段树

void DestroySegTree(SegTree *root){
    if(*root == NULL){
        return;
    }
    if((*root)->pL == NULL && (*root)->pR == NULL){
        delete *root;
        *root = NULL;
    }
    else{
        if((*root)->pL != NULL){
            DestroySegTree(&((*root)->pL));
        }
        if((*root)->pR != NULL){
            DestroySegTree(&((*root)->pR));
        }
        delete(*root);
    }
}

三、全部代码

#include <iostream>
#include <map>
#include <math.h>
#include <cctype>
using namespace std;

//创建线段树的结构, l、r为左边界和右边界,maxV和minV为最大值和最小值,sum为和,tag为lazyTag
typedef struct SegNode{
    int l,r,maxV,minV,sum,tag;
    struct SegNode *pL, *pR;
}SegNode, *SegTree;

#define OK 1
#define ERROR 0
//创建一个线段树,segTreesh
void BuildSegTree(SegTree* root, int l, int r, int data[]){
    //当l == r时,左右边界为相同位置,递归结束
    if(l == r){
        (*root) = new SegNode;
        (*root)->sum = data[l]; //只有一个数,所以和就是这个数
        (*root)->maxV = data[l]; //同上
        (*root)->minV = data[l]; //同上
        (*root)->tag = 0; //新结点没有“欠债”
        (*root)->l = l;
        (*root)->r = r;
        (*root)->pL = NULL; //左右子树为空
        (*root)->pR = NULL;
        return;
    }
    //左右边界不相同时,创建该结点后,分成左右两个子树继续递归
    else{
        (*root) = new SegNode;
        (*root)->l = l;
        (*root)->r = r;
        (*root)->tag = 0;
        int mid = l + (r-l)/2; //求中间位置
        //递归调用该函数创建左右子树
        BuildSegTree(&((*root)->pL), l, mid, data);
        BuildSegTree(&((*root)->pR),mid+1, r, data);
        (*root)->sum = (*root)->pL->sum + (*root)->pR->sum; //通过两个子树的和求该树的和
        (*root)->maxV = max((*root)->pL->maxV, (*root)->pR->maxV); //通过两个子树的最大值比较求该树的最大值
        (*root)->minV = min((*root)->pL->minV,(*root)->pR->minV); //通过两个子树的最小值比较求该树的最小值
    }
}

//先序遍历输出测试
void preOrder(SegTree root){
    if(root == NULL){
        return;
    }
    cout << "(" << root->l << "->" << root->r << ") sum: " << root->sum << " maxV: " << root->maxV << " minV: " << root->minV << ' ' <<endl;
    preOrder(root->pL);
    preOrder(root->pR);
}

//清算懒标记“债务”的函数
void LateUpdate(SegTree root){
    //没债务返回就行
    if(root->tag == 0)
        return;
    //左子树不为空,把债务传递给左子树,并且更改左子树的值
    if(root->pL != NULL){
        root->pL->tag += root->tag;
        root->pL->sum += (root->pL->r - root->pL->l+1)*root->tag;
        root->pL->maxV += (root->tag);
        root->pL->minV += (root->tag);
    }
    //右子树同上
    if(root->pR != NULL){
        root->pR->tag = root->tag;
        root->pR->sum = (root->pL->r - root->pL->l+1)*root->tag;
        root->pR->maxV += (root->tag);
        root->pR->minV += (root->tag);
    }
    root->tag = 0; //债务结算完清空
}

//查询操作
int query(SegTree root, int l, int r, int *tsum, int *tmax, int *tmin){
    //如果要查询的左边界范围大于右边界或者相反,返回
    if(r < root->l || l > root->r)
        return ERROR;
    //如果刚好查询的范围与当前root的范围相等
    if(l == root->l && r == root->r){
        *tsum = root->sum;
        *tmax = root->maxV;
        *tmin = root->minV;
        return OK;
    }
    LateUpdate(root); //运行到这说明要向子树递归,所以先将债务传递给子树
    int mid = root->l + (root->r - root->l)/2;
    //整个在mid左边的情况
    if(r<=mid){
        query(root->pL, l, r, tsum, tmax, tmin);
        return OK;
    }
    //整个在mid右边的情况
    else if(l >= mid+1){
        query(root->pR, l, r, tsum, tmax,tmin);
        return OK;
    }
    //被mid从中间分开的情况
    else{
        int lsum, rsum, lmax,rmax, lmin, rmin;
        query(root->pL, l, mid, &lsum, &lmax, &lmin);
        query(root->pR, mid+1, r, &rsum, &rmax, &rmin);
        *tsum = lsum+rsum;      //计算左右子树和
        *tmax = max(lmax,rmax); //从左右子树中找最大值
        *tmin = min(lmin,rmin); //从左右子树中找最小值
        return OK;
    }
}

//在一个范围内将每个数增加value
void Add(int tl, int tr, SegTree root, int Value){
    //判断是否越界
    if(tl > root->r || tr < root->l){
        return;
    }
    //如果包含在内了,就把所有的值修改,并给tag写上债务
    if(tl <= root->l && tr >= root->r){
        root->sum += (root->r - root->l+1)*Value;
        root->maxV += Value;
        root->minV += Value;
        root->tag += Value;
        return;
    }
    //如果没包含,那就先将债务传递到子树,然后递归
    LateUpdate(root);
    int mid = root->l+(root->r-root->l)/2;
    //都在左侧的情况
    if(tr <= mid)
        Add(tl, tr, root->pL, Value);
    //都在右侧的情况
    else if(tl >= mid+1)
        Add(tl, tr, root->pR, Value);
    //两边都有
    else{
        Add(tl, mid, root->pL, Value);
        Add(mid+1, tr, root->pR, Value);
    }
    //计算各值
    root->sum = root->pL->sum + root->pR->sum;
    root->maxV = max(root->pL->maxV, root->pR->maxV);
    root->minV = min(root->pL->minV, root->pR->minV);
}

//销毁线段树
void DestroySegTree(SegTree *root){
    if(*root == NULL){
        return;
    }
    if((*root)->pL == NULL && (*root)->pR == NULL){
        delete *root;
        *root = NULL;
    }
    else{
        if((*root)->pL != NULL){
            DestroySegTree(&((*root)->pL));
        }
        if((*root)->pR != NULL){
            DestroySegTree(&((*root)->pR));
        }
        delete(*root);
    }
}

int main(){
    SegTree root = NULL; //创建线段树
    int data[]={1,2,-3,5,6,-2,7,1,12,30,-10}; //输入一些数据
    int ssum, smax, smin;
    BuildSegTree(&root, 0, 10, data);  //构建线段树
    preOrder(root);  //先序遍历测试

    query(root, 0, 7, &ssum, &smax, &smin); //求0~7的和
    cout << endl << "0~7的和为:" << ssum << ' ' << smax << ' ' << smin << endl << endl;

    Add(0, 7, root, 3); //给0~7增加3
    query(root, 0, 7, &ssum, &smax, &smin); //求0~7的和
    preOrder(root);
    cout << endl << "加3后,0~7的和为:" << ssum << ' ' << smax << ' ' << smin << endl;

    return 0;
}

四、运行结果

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/548093.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

English Learning - L3 作业打卡 Lesson2 Day12 2023.5.16 周二

English Learning - L3 作业打卡 Lesson2 Day12 2023.5.16 周二 引言&#x1f349;句1: Dollars are called greenbacks because that is the color of the back side of the paper money.成分划分弱读连读爆破语调 &#x1f349;句2: The color black is used often in expres…

抽象 + 接口 + 内部类

抽象类和抽象方法 抽象类不能实例化抽象类不一定有抽象方法&#xff0c;有抽象方法的类一定是抽象方法可以有构造方法抽象类的子类 要么重写抽象类中的所有抽象方法要么是抽象类 案例 Animal类Dog类 Sheep类Test类 接口 接口抽象类针对事物&#xff0c;接口针对行为案…

使用Google浏览器开启New bing

简介 搭建 通过谷歌商店下载两个浏览器插件&#xff0c;一个用于修改请求头agent的插件和一个用于伪造来源的插件x-forwarded-for插件&#xff0c;当然类似的插件很多很多&#xff0c;我这里使用的两个插件是 User-Agent Switcher Header Editor 使用 User-Agent Switcher 插件…

云HIS住院业务模块常见问题及解决方案

一&#xff1a;住院业务 1.患者办理住院时分配了错误的病区怎么办&#xff1f; 操作员误操作将患者分配了错误的病区分为以下两种情况&#xff1a; &#xff08;1&#xff09;、患者刚刚入院&#xff0c;未分配床位、主治医师与管床护士&#xff1a;这种情况比较好处理&#xf…

文件转pdf

背景 项目中很多预览工具&#xff0c;文件转pdf预览&#xff0c;采用libreoffice6.1插件实现 环境说明 系统CentOS&#xff1a;CentOS7 libreoffice&#xff1a;6.1 下载 中文官网 https://zh-cn.libreoffice.org/download/libreoffice/ 下载其他老版本 Index of /lib…

不敢妄谈K12教育

做为大学生的父亲&#xff1a;不敢妄谈孩子教育 大约10年前&#xff0c;写了一本教育书稿 找到一个出版社的编辑&#xff0c;被训了一通 打消了出书以及K12教育的念想 趣讲大白话&#xff1a;娘生九子&#xff0c;各有不同 【趣讲信息科技171期】 ****************************…

Vs+Qt+C++电梯调度控制系统

程序示例精选 VsQtC电梯调度控制系统 如需安装运行环境或远程调试&#xff0c;见文章底部个人QQ名片&#xff0c;由专业技术人员远程协助&#xff01; 前言 这篇博客针对<<VsQtC电梯调度控制系统>>编写代码&#xff0c;代码整洁&#xff0c;规则&#xff0c;易读。…

PT100温度采集

1、信号采集的基本原理 PT100是将温度信号转换为电阻输出&#xff0c;其电阻值变化范围为0~200Ω。AD转换器只能对电压进行转换&#xff0c;无法采集直接采集温度&#xff0c;因此&#xff0c;需要一个1mA恒电流源给PT100供电&#xff0c;将电阻变化转换为电压变化。使用恒流源…

linux 安装 maven 3.8 版本

文章目录 1&#xff1a;maven 仓库官网 2、下载安装包 3、使用&#xff1a;Xftp 上传到你想放的目录 4、解压文件 ​编辑 5、配置环境变量 ​编辑 6、刷新 /etc/profile 文件 7、查看maven 版本 1&#xff1a;maven 仓库官网 Maven – Download Apache Mavenhttps://mave…

【C++】模板的一点简单介绍

模板 前言泛型编程函数模板概念格式函数模板的原理函数模板的实例化 类模板类模板的定义格式类模板的实例化 前言 这篇博客讲的是模板的一些基本知识&#xff0c;并没有那么深入&#xff0c;但是如果你是为了过期末考试而搜的这篇博客&#xff0c;我觉得下面讲的是够了的。 之…

简单分享线程池的设计

温故而知新&#xff0c;可以为师矣。 线程池是什么 线程池&#xff08;Thread Pool&#xff09;是一种基于池化思想管理线程的工具&#xff0c;经常出现在多线程服务器中&#xff0c;如MySQL。 池化思想&#xff0c;就是为了提高对资源的利用率&#xff0c;减少对资源的管理&a…

MySQL---空间索引、验证索引、索引特点、索引原理

1. 空间索引 MySQL在5.7之后的版本支持了空间索引&#xff0c;而且支持OpenGIS几何数据模型 空间索引是对空间数据类型的字段建立的索引&#xff0c;MYSQL中的空间数据类型有4种&#xff0c;分别是&#xff1a; 类型 含义 说明 Geometry 空间数据 任何一种空间类型 Poi…

HCIA-VRP系统

目录 一&#xff0c;什么是VRP VRP提供的功能&#xff1a; VRP文件系统&#xff1a; VRP存储设备&#xff1a; 设备初始化过程&#xff1a; 设备管理方式&#xff1a; 1&#xff0c;Web界面&#xff1a;可视化操作&#xff0c;通过http和https登录&#xff08;192.168.1.…

信息安全工程复习

目录 第二章 从口令系统说起 2.1 身份鉴别常见手段及例子 2.2 多因子认证 2.3 计时攻击 2.4 口令机制 2.5 假托和钓鱼 第三章 安全协议 3.1 认证协议 3.2 安全协议攻击 3.3 密钥分配协议 3.4 课后作业 第四章 访问控制 4.1 概念 4.2 控制访问三要素 4.3 控制访问…

Windows服务

参考地址&#xff1a;https://www.cnblogs.com/2828sea/p/13445738.html 1. 新建服务 2. 在 service 下 添加安装程序 会自动添加 修改这两个文件属性&#xff1a; serviceInstaller1&#xff1a; DelayedAutoStart:是否自动启动Descrition:介绍服务&#xff08;自定义&…

chatgpt赋能Python-python3_图片处理

Python3图片处理&#xff1a;简单高效的图像处理工具 Python3作为一种高级编程语言&#xff0c;在科学、金融、工程等领域中广受欢迎。它具有简洁的语法、快速的开发速度、多样化的应用场景等特点。其中&#xff0c;Python3在图像处理方面也非常出色&#xff0c;本文将介绍Pyt…

pg事务:事务的处理

事务的处理 事务块 从事务形态划分可分为隐式事务和显示事务。隐式事务是一个独立的SQL语句&#xff0c;执行完成后默认提交。显示事务需要显示声明一个事务&#xff0c;多个sql语句组合到一起称为一个事务块。 事务块通过begin&#xff0c;begin transaction&#xff0c;st…

【学习日记2023.5.20】 之 菜品模块完善

文章目录 3. 功能模块完善之菜品模块3.1 公共字段自动填充3.1.1 问题分析3.1.2 实现思路3.1.3 代码开发1.3.1 步骤一1.3.2 步骤二1.3.3 步骤三 3.1.4 功能测试3.1.5 提交代码 3.2 新增菜品3.2.1 需求分析与设计3.2.2 代码开发3.2.2.1 文件上传实现3.2.2.2 新增菜品实现 3.2.3 功…

pg事务:快照

pg中的快照 快照&#xff08;snapshot&#xff09;是记录数据库当前瞬时状态的一个数据结构。pg数据库的快照保存当前所有活动事务的最小事务ID、最大事务ID、当前活跃事务列表、当前事务的command id等 快照数据保存在SnapshotData结构体类型中&#xff0c;源码src/include/u…

PyQt5桌面应用开发(16):定制化控件-QPainter绘图

本文目录 PyQt5桌面应用系列画画图&#xff0c;喝喝茶QPainter和QPixmapQPixmapQPainter绘制事件 一个魔改的QLabelCanvas类主窗口主程序&#xff1a; 总结 PyQt5桌面应用系列 PyQt5桌面应用开发&#xff08;1&#xff09;&#xff1a;需求分析 PyQt5桌面应用开发&#xff08;2…