线段树在逻辑上的语义
-
堆对数组的树化,是对数组中每一个元素的树化,是逻辑上对数组中每一个元素拓扑结构的改变,由一维变二维,从而将遍历的时间复杂度从O(n)降到O(logn);
-
线段树对数组的树化,是对数组长度的意义的树化,而对数组长度的切割规则是二分法,所以线段树是在二叉树的结构下组织的;
-
线段树从逻辑上看由1个数组和一棵线段树组成,从物理上看由2个数组组成:
- 逻辑上:
- 1个数组是线段树存在的基础,是线段树这种高级的抽象表达的原始存在;
- 以二分法将数组的长度层层分封下去,直至长度的单位变成1为止,以二分法层层分封下去的过程中产生的所有长度在线段树中都有一个节点表示;
- 线段树中的每个节点,首先天然的对应数组中的一段长度,其次包含这段长度上被赋予的意义(这个长度区间数组元素的和,积,最大值,最小值);
- 物理上:
- 第1个数组是最原始的数组,是线段树赖以生存的本元,是需要被线段树抽象化表示的原始数据;
- 第2个数组是用来承载线段树的,其长度是第1个数组的4倍;
线段树的相关性质
- 线段树不一定是满二叉树,也不一定是完全二叉树;
- 线段树是一棵平衡二叉树(所有的叶子节点的层高最多相差1);
- 堆也是平衡二叉树,堆首先是完全二叉树;
- 二分搜索树就不一定是平衡二叉树,因为极端情况下可能退化成链表;
- 线段树重要结论1:把n个元素的区间构建成一棵线段树,用以存储这棵线段树的数组的长度是4n;
- 线段树重要结论2:把n个元素的区间构建成一棵线段树,对于所有n个元素而言,作为构建成的线段树的叶子节点,分布在线段树的最后2层;
满二叉树相关性质:
- 0层1个节点,1层2个节点,2层4个节点,3层8个节点,... ,h - 1层2^(h - 1)个节点;
- 满二叉树所有的h层一共有2^h - 1个节点(大约是2^h);
- h - 1层(最后一层),有2^(h - 1)个节点;
-
重要结论:对于满二叉树而言,最后一层的节点数大致等于前面所有层节点之和;
SegmentTree基础代码
- E[] data用来表示需要被构建成线段树的n个元素;
- E[] tree用来存储构建出的线段树;
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;
}
@Override
public String toString(){
StringBuilder res = new StringBuilder();
res.append('[');
for(int i = 0 ; i < tree.length ; i ++){
if(tree[i] != null)
res.append(tree[i]);
else
res.append("null");
if(i != tree.length - 1)
res.append(", ");
}
res.append(']');
return res.toString();
}
}