线段树C++实现

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

        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;
}

四、运行结果

 

### 线段树实现与应用 线段树是一种常用的数据结构,尤其在处理区间查询和更新问题时表现出色。其基本思想是将一个区间划分成若干个子区间,并通过递归的方式构建一棵二叉树。每个节点代表一个区间,叶子节点表示单个元素,而内部节点则存储其子区间的合并结果。 #### 线段树的基本操作 线段树的核心操作包括**构建、查询和更新**。这些操作通常基于递归实现,适用于静态数组或动态数组的场景。 ##### 构建线段树 线段树的构建过程是一个递归过程。以求和为例,假设原始数组为`arr`,线段树的每个节点保存对应区间的总和。构建函数如下: ```cpp void build(int node, int start, int end) { if (start == end) { tree[node] = arr[start]; } else { int mid = (start + end) / 2; build(2 * node, start, mid); build(2 * node + 1, mid + 1, end); tree[node] = tree[2 * node] + tree[2 * node + 1]; } } ``` ##### 查询操作 查询操作用于获取某个区间`[l, r]`的总和或最大值等信息。该函数需要判断当前节点的区间是否完全包含在查询区间内,部分重叠时需要递归查询左右子树。 ```cpp int query(int node, int start, int end, int l, int r) { if (r < start || end < l) { return 0; // 区间不相交,返回0 } if (l <= start && end <= r) { return tree[node]; // 完全包含,直接返回 } int mid = (start + end) / 2; int left_sum = query(2 * node, start, mid, l, r); int right_sum = query(2 * node + 1, mid + 1, end, l, r); return left_sum + right_sum; } ``` ##### 更新操作 更新操作用于修改数组中的某个元素,并同步更新线段树中的相关节点。 ```cpp void update(int node, int start, int end, int idx, int value) { if (start == end) { arr[idx] = value; tree[node] = value; } else { int mid = (start + end) / 2; if (idx <= mid) { update(2 * node, start, mid, idx, value); } else { update(2 * node + 1, mid + 1, end, idx, value); } tree[node] = tree[2 * node] + tree[2 * node + 1]; } } ``` #### 延迟标记(Lazy Propagation) 对于区间更新操作,延迟标记技术可以显著优化性能。当对一个区间进行更新时,延迟标记会将更新操作暂时保存在父节点中,直到后续查询需要访问子节点时才下推标记。 ```cpp void update_range(int node, int start, int end, int l, int r, int value) { if (lazy[node] != 0) { tree[node] += (end - start + 1) * lazy[node]; if (start != end) { lazy[2 * node] += lazy[node]; lazy[2 * node + 1] += lazy[node]; } lazy[node] = 0; } if (r < start || end < l) { return; } if (l <= start && end <= r) { lazy[node] += value; tree[node] += (end - start + 1) * value; return; } int mid = (start + end) / 2; update_range(2 * node, start, mid, l, r, value); update_range(2 * node + 1, mid + 1, end, l, r, value); tree[node] = tree[2 * node] + tree[2 * node + 1]; } ``` #### 动态线段树 在某些情况下,线段树的节点可能需要动态创建。例如,在稀疏更新或大规模数据场景中,动态线段树可以节省内存。 ```cpp struct Node { int left_bound, right_bound; Node* left_child = nullptr; Node* right_child = nullptr; int sum = 0; Node(int l, int r) : left_bound(l), right_bound(r) {} }; class SegmentTree { private: Node* root; void update(Node* node, int idx, int value) { if (node->left_bound == node->right_bound) { node->sum = value; } else { int mid = (node->left_bound + node->right_bound) / 2; if (idx <= mid) { if (!node->left_child) { node->left_child = new Node(node->left_bound, mid); } update(node->left_child, idx, value); } else { if (!node->right_child) { node->right_child = new Node(mid + 1, node->right_bound); } update(node->right_child, idx, value); } node->sum = 0; if (node->left_child) node->sum += node->left_child->sum; if (node->right_child) node->sum += node->right_child->sum; } } public: SegmentTree(int n) { root = new Node(1, n); } void update(int idx, int value) { update(root, idx, value); } int query(Node* node, int l, int r) { if (node->right_bound < l || node->left_bound > r) { return 0; } if (l <= node->left_bound && node->right_bound <= r) { return node->sum; } int res = 0; if (node->left_child) { res += query(node->left_child, l, r); } if (node->right_child) { res += query(node->right_child, l, r); } return res; } int query(int l, int r) { return query(root, l, r); } }; ``` #### 进阶线段树 进阶线段树包括**乘法线段树**和**根号线段树**。这些变种线段树通过调整合并函数来支持不同的运算需求。例如,乘法线段树的合并函数返回两个子区间的乘积,而根号线段树则返回子区间的平方根。 ```cpp // 乘法线段树的合并函数示例 tree[node] = tree[2 * node] * tree[2 * node + 1]; // 根号线段树的合并函数示例 tree[node] = sqrt(tree[2 * node] + tree[2 * node + 1]); ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

百年bd

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值