线段树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;
}

四、运行结果

猜你喜欢

转载自blog.csdn.net/weixin_63484669/article/details/130783544