线段树也称为区间树。
1.线段树介绍:
对于有一类问题,我们关心的是线段(或者区间)。
线段树的应用:
1.最典型的线段树问题:区间染色:
m次操作后,我们可以看见多少种颜色?
m次操作后,我们可以在[i,j]区间内看见多少种颜色?
有一面墙,长度为n,每次选择一段儿墙进行染色。
之前被染色的部分,还能继续被下一次染色所覆盖。
再两次覆盖:
可以使用数组来解决从[i,j]区间内的染色问题。
2.经典问题:区间查询
查询一个区间[i,j]的最大值,最小值,或者区间数字和。
例如一个实质的问题:基于区间的统计查询
2019年注册用户中消费最高的用户?消费最少的用户?学习时间最长的用户?
由于时间是在不断改变的,所以线段树也是一种动态的数据结构。更新与查询。
线段树也是一种二叉树的结构。
2.线段树的实现思想
在数组A中:
以求和为例,将数组A转换成线段树:
此时叶子节点只有一个元素,不过该节点也是一个区间,区间长度为1。
查询4到7区间范围的元素:
查询2到6区间范围的元素:
我们只需要找到我们关心的那个区间对应的一个或者多个节点,而不需要对该区间的每一个节点都进行遍历。
3.线段树基础表示
上面给出的线段树是基于数组元素个数为8的情况下,根节点表是从索引0到7,来表示整个数组区间。
左节点为前半段区间,右节点为右半段区间。由于8比较特殊,所以线段树是满的,如果线段树的区间有10个元素,则二叉树的结构不为满了:
如果区间是奇数位,该位置的左右子节点的区间范围就不一样了:
对于线段树,不一定是一棵满的二叉树,也不一定是一棵完全二叉树,线段树一棵平衡二叉树,依然可以用数组来表示。
平衡二叉树:最大的深度与最小的深度相差只有1。
堆也是平衡二叉树。
我们可以将上面给出的线段树看成是满的二叉树,也就是把最后一层中未满的节点位置看成为null,用数组来表示。
如果区间有n个元素,数组表示需要有多少个节点?
对于此问题,我们需要将线段树看成是一个满的二叉树。
这样每一层的节点个数就有一定的规律了。
h层的满二叉树节点个数为:2^h - 1个节点(大约为2^h个)
区间浪费问题:
如果区间只有五个元素,最后一层的3和4需要找到区间的位置,用数组的形式存储,就要有意的浪费前面6个位置。
不过该情况可以忽略或者避免。用二分搜索树的存储结构来实现线段树,可以解决此类问题。
public class SegmentTree<E> {
private E[] tree;
private E[] data;
public SegmentTree(E[] arr){
data = (E[])new Object[arr.length];
for(int i = 0 ; i < arr.length ; i ++){
data[i] = arr[i];
}
//初始化线段树
tree = (E[])new Object[4 * arr.length];
}
public int getSize(){
return data.length;
}
public E get(int index){
if(index < 0 || index >= data.length){
throw new IllegalArgumentException("Index is illegal.");
}
return data[index];
}
// 返回完全二叉树的数组表示中,一个索引所表示的元素的左孩子节点的索引
private int leftChild(int index){
return 2*index + 1;
}
// 返回完全二叉树的数组表示中,一个索引所表示的元素的右孩子节点的索引
private int rightChild(int index){
return 2*index + 2;
}
}