线段树
线段树的作用
线段树是一种二叉搜索树,它能以较低时间复杂度O(logN)得解决大规模求和、gcd等
空间复杂度为2N,但实际应用时还需4N的空间
查询和单点更改的代码实现
class NumArray
{
public:
int *tree;
int size;
NumArray(vector<int> nums)
{
size = nums.size();
//size储存原始数据的数量
tree = new int[size * 4];
if (size != 0)
Build(1, size, 1, nums);
//以1为根节点,为了防止计算问题而废除0号位置
}
线段树大小为原始数组大小的4倍
==>Q:树叶节点和树节点一共2n个数据,为什么需要4倍?
==>A:在递归时候仍然需要访问叶子节点下一层子树,下一层子节点一共有2n个,所以共计4n的空间需求
void Build(int left, int right, int index, vector<int> &nums)
{
if (left == right) //如果到达叶子节点不再向下访问
{
tree[index] = nums[left - 1];
//==>Q:为什么需要访问nums[left-1]而不是nums[left]?
//==>A:tree数组以1位开始为第一个数据,而传入的数组是以0位为第一个数据
return;
}
int mid = (left + right) / 2;
Build(left, mid, 2 * index, nums);
Build(mid + 1, right, 2 * index + 1, nums);
PushUp(index);
//建完左右子树后更新自己的数据
}
==>Q:为什么左右节点的序号分别是2n和2n+1?
==>A:见下图
1 (1) 2 3 (2*1) (2*1+1) 4 5 6 7 (2*2) (2*2+1)(2*3) (2*3+1)
…continue
这种编号顺序无浪费而且易知根节点为index/2
void PushUp(int index)
{
tree[index] = tree[2 * index] + tree[2 * index + 1];
//根数据=左数据+右数据
}
void Update(int position, int val, int left, int right, int index)
{
if (left == right) //如果到达叶子节点不再向下访问
{
tree[index] = val;
//叶子节点值更改
return;
}
int mid = (left + right) / 2;
if (position <= mid) //算是一种二分查找,需要更改的位置在二分后是中值的左或者右边
Update(position, val, left, mid, index * 2);
//如果更改在中值左侧就访问左子树
else
Update(position, val, mid + 1, right, index * 2 + 1);
//如果更改在中值右侧就访问右子树
PushUp(index);
//更新自己的数据,使更改一层层加起来
}
int Query(int Left, int Right, int left, int right, int index)
{
if (Left <= left && right <= Right)
//如果数据值在所求区域内....[Left..[left.......right]......Right]....
{
return tree[index];
//返回我的数据,不再递归
}
int mid = (left + right) / 2;
int ans = 0;
if (Left <= mid) //如果中值>=所求区域左临界
ans += Query(Left, Right, left, mid, index * 2);
if (Right >= mid + 1) //如果中值+1<=所求区域右临界
ans += Query(Left, Right, mid + 1, right, index * 2 + 1);
return ans;
}
[1 2 3 4 5 6 7 8 9 10 11 12 13] => 求[4,12]
[1 2 3 4 5 6 7][8 9 10 11 12 13]
[1 2 3 4][5 6 7][8 9 10][11 12 13]
[3 4] XXX XXX [11 12]
[4] XXX
XXX
XXX表示返回上一个节点的数据不再递归
[4,12]==>[4] + [5,7] + [8,10] + [11,12]
在[1 2 3 4]被拆分为[1 2][3 4]时由于mid=2<3故不再访问左子树[1 2]
[13],[3]同理
在[1 2 3 4 5 6 7]被拆分为[5 6 7]时由于 5>3 && 8<12 则直接返回其数据不再递归
[8 9 10 11]同理
void update(int i, int val)
{
Update(i + 1, val, 1, size, 1);
//程序查询时候从0开始计位,故需要加一,从[1,size]中递归更改值
}
int sumRange(int i, int j)
{
return Query(i + 1, j + 1, 1, size, 1);
//程序查询时候从0开始计位,故需要加一,从[1,size]中递归查找结果值
}
};
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 GZTime's Blog!
评论