11月7日一周总结

二叉树的进一步拓展

线段树问题

线段树是属于二叉树的一种,具有二叉树的所有性质的同时还具备了进行区间处理的能力
在这里插入图片描述

线段树就如他的名字一样,是建立在线段上的一种二叉树,每个线段[L,R],L代表左子节点,R代表右子节点,L等于R说明只有一个点,L小于R的时候,[L,M]代表了左子树根节点,[M,R]代表了右子树根节点,而M等于(L+R)/2。
对于m次的数据处理,这个数据结构的复杂度O(mlog2n),线段树的节点代码如下:

#define MAXN 200
struct tree{
int l,r;
int LENGTH;
}tree[4*MAXN];

对于这个数组大小的问题,因为叶子结点是每个数据自己,所以数组至少开到2倍大。
对于线段树的问题,使用完全二叉树一般更为合适,这样在构建线段树的时候可以从叶节点开始,一层层向上构造二叉树,先排满前n个数,然后这一层剩下的放空值或者0(视情况而定,目的是不影响节点的判断)这个时候根据当前节点构造父节点,就要用到完全二叉树的基本性质,左结点:父节点编号乘2,右节点:父节点编号乘2再加1,根据这个反推,可以根据子节点找到父节点。

void t(int i, int value, int num) { // 把元素i修改为值value
    if (tree[num].l == tree[num].r) { // 到达目标叶子节点
        tree[num].sum = value;
        return;
    }
    int mid = (tree[num].l + tree[num].r) / 2;
    if (i <= mid) {
        t(i, value, num * 2); // 继续递归左儿子
    }
    else {
        t(i, value, num * 2 + 1); // 继续递归右儿子
    }
}

对于修改数据元素的问题,每一次我们都从根开始递归遍历。我们先判断要更改的元素属于当前节点的左儿子还是右儿子,并且递归到该节点。递归结束后更新当前节点的值。假如遍历到叶子节点,说明我们已经遍历到了想要修改的元素,那么我们直接把该节点的值修改为value就可以了。(由这一问题可以看出排序对于建造线段树数据的处理非常重要)

void tt(int l, int r, int value, int num) { // [l,r]每一项都增加value
    if (tree[num].l == l && tree[num].r == r) { // 找到当前区间
        tree[num].sum += ( r - l + 1 ) * value; // r-l+1是区间元素个数
        tree[num].lazy += value;
        return;
    }
    int mid = (tree[num].l + tree[num].r) / 2;
    if (r <= mid) { // 在左区间
       tt(l, r, value, num * 2);
    }
    else if (l > mid) { // 在右区间
        tt(l, r, value, num * 2 + 1);
    }
    else { // 分成2块
       tt(l, mid, value, num * 2);
       tt(mid + 1, r, value, num * 2 + 1);
    }
}

区间修改与节点修改基础思想差不多,但是要考虑修改的区间被拆开的情况,比如修改的区间是左子树的一部分和右子树的一部分,这时候就要再加一个条件,也就是最后一个else语句。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值