首页 > 代码库 > 线段树(一)
线段树(一)
问题:
先抛出一个问题,坐标轴上有若干线段,现在给定若干个点,对于每个点,求出包含点的线段的数量
如果用常规的解法,时间复杂度是O(mn),空间复杂度是O(m + n)
能不能降低一下时间复杂度呢?答案是肯定的,这些线段里有大量相交或者覆盖的线段,而上面的解法显然没有利用这些信息,导致时间复杂度较高,现在我们引入
一个数据结构,线段树(Segment Tree),从Wikipedia抄过来定义如下:
In computer science, a segment tree is a tree data structure for storing intervals, or segments. It allows querying which of the
stored segments contain a given point. It can be implemented as a dynamic structure. A similar data structure is the interval tree.
第一次看我也是一头雾水,什么也没说嘛,下面用一张图解释一下:
A. 建树
给出8条线段,S[1-8],区间分别为[1,3],[2,4],[3,5],[4,6],[7,8],[7,9],[9,10],[8,10],总区间为[1,10],一棵线段树要覆盖区间,因此用区间[1,10]
建立一棵线段树,如上图,可以看出,线段树是一棵二叉平衡树,简单性质如下:
1. 对于非叶子节点:r[a,b](a < b) r的左儿子节点的区间为[a,(a + b) / 2];r的右儿子节点的区间为[(a + b) / 2 + 1, b]
2. 叶子节点对应一个点,也即r[a,b](a = b)
A. 插入线段
那么建立起这样一棵树后,怎么插入一条线段呢?方法很简单,以插入S8为例,看图:
可以看出,方法很简单:
1. 从根节点r(a,b)开始, 待插线段s(c,d)
1.0 如果a==b,转到2
1.1 如果d <= (a + b) / 2,以r的左孩子为根,转到1
1.2 如果c > (a + b) / 2,以r的右孩子为根,转到1
1.3 否则搜索r的左孩子和右孩子
2. 节点的值+1
C. 查询
最后查询某个点的包含次数,就非常简单了,沿着根节点搜索到叶子节点,对应的叶子节点的值即为包含次数
给出代码:
1 /*建树*/ 2 void build_ST(int i, int left, int right) { 3 st[i].left = left; 4 st[i].right = right; 5 if(left == right) { 6 st[i].count = 0; 7 return; 8 } 9 build_ST(i * 2, left, (left + right) / 2); 10 build_ST(i * 2 + 1, (left + right) / 2 + 1, right);11 }
1 /*插入线段*/ 2 void insert_ST(int i, int left, int right) { 3 int l = st[i].left; 4 int r = st[i].right; 5 if(l == r) { 6 st[i].count++ 7 } 8 else if(right <= (r + l) / 2) { 9 insert_ST(i * 2, left, right);10 }11 else if(left > (r + l) / 2) {12 insert_ST(i * 2 + 1, left, right);13 }14 else {15 insert_ST(i * 2, left, right);16 insert_ST(i * 2 + 1, left, right);17 }18 }
1 int query_ST(int i, int value) { 2 int l = st[i].left; 3 int r = st[i].right; 4 if(l == r) 5 return st[i].count; 6 else if(value <= (r + l) / 2) 7 return query_ST(i * 2, value); 8 else 9 return query_ST(i * 2 + 1, value);10 }
最后看一下复杂度:
操作 | 时间复杂度 | 空间复杂度 |
建树 | O(points) | O(points) |
插入 | O(points) | |
查询 | O(logpoints) |
由于本人水平有限,文中难免有不当和错误指出,欢迎大家批评指正,愿共同进步!!!
线段树(一)