开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」第12天,点击查看活动详情 上节讲了线段树应该开的空间及它的基础原理,今天我们就来讲讲它的建立过程。
像它的树族小伙伴一样,讲到线段树最简单易懂的建立方式就绕不开递归式建立。
在第一节解释里讲到,线段树的叶子结点是用来存储单点数据的
除了叶子结点外的其中结点就都是由多点甚至多段的数据进行区间加法得到
下面举个例子🎉
PS:黑体框出的是区间(线段)的范围 无框数字为线段的值
容易看出:
1~4 的sum属性是由底下两个子区间合并而来,
底下两个子区间是由它们下属的两两单点合并而来,
只有最底层单点 ,它们的属性由自己提供,无子区间合并
因此我们知道,整棵线段树里,只有叶子节点存有最原始的值,其他线段的属性都是由其他区间或点合并而来
那要怎么把这个性质跟我们的递归建树联系到一起捏?
很简单,我们一开始从根节点开始建树,也就是整段区间。
每建立一个结点,就把它的左边界和右边界设置好
tree[root] = { l,r };
然后每次建树先算出中间值,由此将当前线段一分为2,建立左右孩子
int mid = (l + r) >> 1;
build(root<<1, l, mid), build(root<<1|1, mid + 1, r) ;
PS:,此过程自带正整数向下取整
在此过程中,我们建立结点的线段长度会不断减小,直到线段的,
即叶子节点,这时候我们就将原始数据赋给它
if (l == r) {
cin >> x;
tree[root] = { l,l,x,0 };
return;
}
完成了最底层叶子结点的赋值
建树完成过程:
void build(ll root, ll l, ll r) {
tree[root] = { l,r };
if (l == r) { cin >> x; tree[root] = { l,l,x,0 }; return; }
int mid = (l + r) >> 1;
build(LL(root), l, mid), build(RR(root), mid + 1, r) ;
pushup(root);
}
那我们要怎么完成叶子结点对上一层的结点的数据“反哺”呢,
这就要请到我们的函数了,
字面意思就是 上传
将底层的数据属性合并到上一层,即将孩子结点的属性合并给父亲
void pushup(ll root) {
tree[root].val = tree[LL(root)].val + tree[RR(root)].val;
}
这个函数简单易懂,大家看一会就理解啦
PS:
即
细想一下:叶子结点的建立就是由它上一层的结点线段一分为二的来的,
那么,结束完叶子节点的建立函数之后,不就到了父亲的建立完毕吗
由此:当最底层的叶子节点建立完后,就会一层一层向上合并数据,来完成更上一层的父亲结点的建立