一、本题线段树数组数据和结构
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;
}
四、运行结果