線分ツリーの C++ 実装

1. この問題の線分木配列データと構造

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

 第二に、それぞれの機能と構造

(1) 線分ツリー構造

        線分ツリーの構造を作成します。lとrは左右の境界、maxVとminVは最大値と最小値、sumは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

(2) 線分ツリーの作成

        線分ツリーを作成する際、ポインタ変数の値を変更したい場合は、ポインタ変数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); //通过两个子树的最小值比较求该树的最小值
    }
}

(3) 遅延マークのクリア

        遅延マーキングの「負債」を解消する機能です。遅延マーキングは効率を大幅に向上させる操作です。使用方法は、一定範囲のデータの値が変更されるたびに、最初に現在のノードのみが変更され、現在のノードの遅延に変更対象の値が存在しており、タグ内では、今後他のメソッドを下位ノードに再帰する必要がある場合、サブツリーに遅延タグが渡され、サブツリーの値が変更されます。

//清算懒标记“债务”的函数
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; //债务结算完清空
}

(3) クエリ動作

        最大値、最小値、および をクエリします。再帰的な操作では、クエリ範囲が現在のルート範囲とまったく同じになることが終了します。再帰的プロセスには 3 つの状況があります。 1. 中央の左側。2. ミッドの右側。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;
    }
}

(4) 範囲内の各数値を数値で増加します。

//在一个范围内将每个数增加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);
}

(5) セグメントツリーを破壊する

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

3. すべてのコード

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

4. 走行結果

 

おすすめ

転載: blog.csdn.net/weixin_63484669/article/details/130783544
おすすめ