307·区域和检索-数组可修改

线段树是一种数据结构,用于高效处理动态数组的区间查询和修改操作。它以O(logN)的时间复杂度实现单点和区间修改,并能快速计算区间和。本文通过实例介绍了线段树的构建、查询和更新,以及两种不同的实现方式:树结构和数组结构。线段树在处理动态数组区间和问题时展现出优越的性能。
摘要由CSDN通过智能技术生成

http://t.csdn.cn/Yw8tK

题目

示例

思路

需要查询数组区间和,当数组为静态时,显然数组前缀和更方便,但是当数组需要动态更新的话,数组前缀和就显得乏力,所以我们需要引用线段树

线段树是一种二叉搜索树,与区间树相似,它将一个区间划分成一些单元区间,每个单元区间对应线段树中的一个叶子结点。使用线段树可以快速以O(logN)的时间复杂度实现单点、区间的修改和查询,综合性能较好。对于线段树中的每一个非叶子结点[i, j],它的左儿子表示的区间为[i, (i + j)/2],右儿子表示的区间为[(i + j)/2 + 1, j],因此线段树是平衡二叉树。

以数组arr = [1, 2, 3, 5, 7]为例,我们可以画出以下的线段树,每个结点的值表示对应区间的元素和,叶子结点表示单个元素的值,非叶子结点表示的区间和等于两个儿子结点的和,因此我们可以通过自底向上的递归来构建一颗线段树。由于线段树与完全二叉树十分相似,于是我们可以用一个数组来模拟二叉树,根节点为0的话,那么每个结点的左儿子结点就等于当前结点的下标*2+1,右儿子的下标为当前结点的下标 * 2 + 2。


如果要实现单点更新,我们不难想到,只要找到对应的叶子结点,修改后,自底向上返回即可,这与二叉搜索树类似,不过比较的是元素所属的区间,可以想到区间修改也是类似的操作,找到对应的元素修改即可

由于线段树的每个结点的值都表示一段区间的和,所以求解区间和十分高效方便,思路就是根据所求的区间,找到相应的结点,加起来即可,以上图为例,如果要查询区间[2, 3]的和,首先判断所求区间是否与当前结点所表示的区间完全重合,如果完全重合,返回结点的值即可,否则就需要根据所求区间与儿子结点所表示区间是否有交集进行递归,比如[2, 3]与左儿子[0, 2]有交集[2, 2],那么就以[2, 2]为所求区间继续向下递归,对于[0, 1],没有交集,直接返回0即可,对于[2, 2],完全重合,返回结点值3,两者的和作为结点[0, 2]的返回值向上传递;相似的,对于右儿子[3, 4],交集为[3, 3],递归后返回结点[3, 3]的值向上传递,最终返回结果8。

代码

        树实现

typedef struct NumArray{
    int sum_val;//左右下标元素之和
    int index_left;//对应数组左下标
    int index_right;//对应数组右下标
    struct NumArray * tree_left;//左子树节点
    struct NumArray * tree_right;//右子树节点
} NumArray;
/*
*NumArray * TreeCreate(int left, int right, int * nums, int mid)
NumArray * TreeCreate:递归将数组元素转换为对应线段树
int left:数组左节点
int right:数组右节点
int * nums:数组
int mid:中间值下标
返回值:线段树
*/
NumArray * TreeCreate(int left, int right, int * nums, int mid)
{
    if(mid == left && mid == right)//到达数组单个节点
    {
        NumArray * obj = malloc(sizeof(NumArray));
        obj->sum_val = nums[mid];
        obj->index_left = left;
        obj->index_right = mid;
        obj->tree_left = NULL;
        obj->tree_right = NULL;
        return obj;
    }
    else
    {
        NumArray * root = malloc(sizeof(NumArray));//创建树节点,将数组分割保存,左子树保存数组左边,右子树保存数组右边
        root->index_left = left;
        root->index_right = right;
        root->tree_left = TreeCreate(left, mid, nums, (left + mid) / 2);
        root->tree_right = TreeCreate(mid + 1, right, nums, (right + mid + 1) / 2);
        root->sum_val = root->tree_left->sum_val + root->tree_right->sum_val;
        return root;
    }
}
/*
*void Tree_printf(NumArray * obj)
void Tree_printf:先序遍历打印节点
NumArray * obj:树头结点
*/
void Tree_printf(NumArray * obj)
{
    printf("%d  ", obj->sum_val);
    if(obj->tree_left)
        Tree_printf(obj->tree_left);
    if(obj->tree_right)
        Tree_printf(obj->tree_right);
}
/*
*NumArray* numArrayCreate(int* nums, int numsSize)
NumArray* numArrayCreate:调用创建函数,创建一颗线段树
int* nums:需要转换为线段树的数组
int numsSize:数组长度
返回值:线段树的头结点
*/
NumArray* numArrayCreate(int* nums, int numsSize) {
    NumArray * root = TreeCreate(0, numsSize-1, nums, (0 + numsSize - 1)/2);
    Tree_printf(root);
    return root;
}
/*
*void numArrayUpdate(NumArray* obj, int index, int val)
void numArrayUpdate:修改数组元素
NumArray* obj:数组对应线段树头结点
int index:待修改元素下标
int val:带修改元素值
无返回值
*/
void numArrayUpdate(NumArray* obj, int index, int val) {
    if(obj->index_left == index && obj->index_right == index)//遍历到了叶子结点,并且当前节点就是需要修改的下标
    {
        obj->sum_val = val;
        return;
    }
    int mid = (obj->index_left + obj->index_right) / 2;
    if(mid >= index)//不是叶子结点,判断下标在左子树函数右子树
    {
        numArrayUpdate(obj->tree_left, index, val);
        obj->sum_val = obj->tree_left->sum_val + obj->tree_right->sum_val;//动态更新每一个树节点
    }
    else
    {
        numArrayUpdate(obj->tree_right, index, val);
        obj->sum_val = obj->tree_left->sum_val + obj->tree_right->sum_val;
    }
    return;
}
/*
*int numArraySumRange(NumArray* obj, int left, int right)
int numArraySumRange:查询数组对应下标和
NumArray* obj:数组对应线段树头结点
int left:右节点
int right:左节点
返回值:对应数组下标和
*/
int numArraySumRange(NumArray* obj, int left, int right) {
    if(obj->index_left == left && obj->index_right == right)
    {
        return obj->sum_val;
    }
    int sum_val = 0;
    int mid = (obj->index_left + obj->index_right) / 2;
    if(mid < left)
    {
        sum_val += numArraySumRange(obj->tree_right, left, right);
    }
    else if(mid >= right)
    {
        sum_val += numArraySumRange(obj->tree_left, left, right);
    }
    else
    {
        sum_val += numArraySumRange(obj->tree_left, left, mid);
        sum_val += numArraySumRange(obj->tree_right, mid+1, right);
    }
    return sum_val;
}
/*
*void numArrayFree(NumArray* obj)
void numArrayFree:递归销毁数组对应线段树线段树
NumArray* obj:数组对应线段树头结点
*/
void numArrayFree(NumArray* obj) {
    if(obj->tree_left)
        numArrayFree(obj->tree_left);
    if(obj->tree_right)
        numArrayFree(obj->tree_right);
    free(obj);
}

/**
 * Your NumArray struct will be instantiated and called as such:
 * NumArray* obj = numArrayCreate(nums, numsSize);
 * numArrayUpdate(obj, index, val);
 
 * int param_2 = numArraySumRange(obj, left, right);
 
 * numArrayFree(obj);
*/

        数组实现 

#define MaxSize 40000

typedef struct {
    int* Tree;
    int* arr;
    int arrSize;
} NumArray;

void BuildTree(NumArray* obj, int start, int end, int pos){
    if(start == end){
        obj->Tree[pos] = obj->arr[start];
        }
    else{
        int mid = (start + end) / 2;
        int left_node = pos * 2 + 1;
        int right_node = pos * 2 + 2;
        BuildTree(obj, start, mid, left_node);
        BuildTree(obj, mid + 1, end, right_node);
        obj->Tree[pos] = obj->Tree[left_node] + obj->Tree[right_node];
        }
}

void Update(NumArray* obj, int start, int end, int idx, int val, int pos){
    if(start == end){
        obj->arr[idx] = val;
        obj->Tree[pos] = val;
        }
    else{
        int mid = (start + end) / 2;
        int left_node = pos * 2 + 1;
        int right_node = pos * 2 + 2;
        if(start <= idx && idx <= mid){ //走左分支
            Update(obj, start, mid, idx, val, left_node);
            }
        else{   //走右分支
            Update(obj, mid + 1, end, idx, val, right_node);
            }
        obj->Tree[pos] = obj->Tree[left_node] + obj->Tree[right_node];  //更新节点的值
        //printf("%d %d\n", obj->Tree[left_node], obj->Tree[right_node]);
        }
}

int Query(NumArray* obj, int start, int end, int left, int right, int pos){
    if(start == left && end == right){  //区间完全重合,返回节点值
        return obj->Tree[pos];
        }
    else{
        int mid = (start + end) / 2;
        int left_node = pos * 2 + 1;
        int right_node = pos * 2 + 2;
        int left_sum = 0, right_sum = 0;
        if(left <= mid && right >= start){  //说明[start, mid]和[left, right]有交集
            int l = fmax(left, start);
            int r = fmin(right, mid);
            left_sum = Query(obj, start, mid, l, r, left_node);
            }
        if(left <= end && right >= mid + 1){
            int l = fmax(left, mid + 1);
            int r = fmin(right, end);
            right_sum = Query(obj, mid + 1, end, l, r, right_node);
            }
        return left_sum + right_sum;
    }
}


NumArray* numArrayCreate(int* nums, int numsSize) {
    if(numsSize == 0) return NULL;
    NumArray* obj = (NumArray*)malloc(sizeof(NumArray));
    obj->Tree = (int*)calloc(MaxSize, sizeof(int));
    obj->arr = (int*)calloc(numsSize, sizeof(int));
    memcpy(obj->arr, nums, sizeof(int) * numsSize);
    obj->arrSize = numsSize;
    BuildTree(obj, 0, obj->arrSize - 1, 0);
    return obj;
}

void numArrayUpdate(NumArray* obj, int i, int val) {
    Update(obj, 0, obj->arrSize - 1, i, val, 0);
}

int numArraySumRange(NumArray* obj, int i, int j) {
    return Query(obj, 0, obj->arrSize - 1, i, j, 0);
}

void numArrayFree(NumArray* obj) {
    free(obj);
}

时间空间复杂度

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值