二叉树的进一步拓展
线段树问题
线段树是属于二叉树的一种,具有二叉树的所有性质的同时还具备了进行区间处理的能力
线段树就如他的名字一样,是建立在线段上的一种二叉树,每个线段[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语句。