一、引言
线段树又名区间树(segment tree),线段树也是一种树结构,该数据结构主要解决区间计算问题,叶子节点保存不可分割的最小区间的值,而非叶子节点保存的是当前区间的状态,例如求区间最大值、最小值、求和等等;线段树不是完全二叉树,但线段树是一颗平衡二叉树。
1、什么是线段树
- 假设有如下数组
- 用线段树表示以上数组
说明:以上线段树构建于具有8个元素的数组,目的在于求任意区间的元素之和。根节点保存的是整个数组区间的元素之和,其左节点表示[0,3]子区间的元素之和,其右节点表示[4,7]子区间的元素之和,依次类推,直到叶子节点表示的是单个元素的值。这个示例方便与计算区间之和,计算区间的值不需要遍历整个数组,沿着根节点往叶子节点方向寻找合适的区间。时间复杂度在O(logn)级别。
二、实现
1、基于静态数组实现线段树
- 线段树融合接口
/**
* 融合接口
* @param <E>
*/
public interface Merger<E> {
/**
* 融合操作
* @param a 元素
* @param b 元素
* @return
*/
E merge(E a, E b);
}
- 线段树实现
/**
* 线段树,基于静态数组实现
* @param <E>
*/
public class SegmentTree<E> {
/**
* 线段树容器,静态数组
*/
private E[] tree;
private E[] data;
/**
* 融合器,处理元素融合逻辑
*/
private Merger<E> merger;
public SegmentTree(E[] arr,Merger<E> merger) {
data = (E[]) new Object[arr.length];
this.merger = merger;
for (int i = 0; i < arr.length; i++) {
data[i] = arr[i];
}
//初始化静态数组容器为4n,足够容纳线段树结构
tree = (E[]) new Object[4 * arr.length];
//构建线段树
buildSegmentTree(0, 0, arr.length - 1);
}
/**
* 在treeIndex的位置创建表示区间[l,r]的线段树
* @param treeIndex 静态数组的索引位置
* @param l 左边界
* @param r 右边界
*/
private void buildSegmentTree(int treeIndex, int l, int r) {
//叶子节点,终止递归
if (l == r) {
tree[treeIndex] = data[l];
return;
}
//左右孩子索引
int leftTreeIndex = leftChild(treeIndex);
int rightTreeIndex = rightChild(treeIndex);
//区间中点
int mid = l + (r - l) / 2;
//构建左线段树
buildSegmentTree(leftTreeIndex, l, mid);
//构建右线段树
buildSegmentTree(rightTreeIndex, mid + 1, r);
tree[treeIndex] = merger.merge(tree[leftTreeIndex], tree[rightTreeIndex]);
}
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];
}
/**
* 返回完全二叉树的数组表示中,一个索引所表示的元素的左孩子节点的索引
* @param index 当前元素索引
* @return
*/
private int leftChild(int index) {
return 2 * index + 1;
}
/**
* 返回完全二叉树的数组表示中,一个索引所表示的元素的右孩子节点的索引
* @param index 当前元素索引
* @return
*/
private int rightChild(int index) {
return 2 * index + 2;
}
/**
* 返回区间[queryL,queryR]的值
* @param queryL 区间左边界
* @param queryR 区间右边界
* @return
*/
public E query(int queryL, int queryR) {
if (queryL < 0 || queryL >= data.length || queryR < 0
|| queryR >= data.length || queryL > queryR) {
throw new IllegalArgumentException("index is illegal");
}
return query(0, 0, data.length - 1, queryL, queryR);
}
/**
* 在以treeIndex为根的线段树[l,r]的范围里,搜索区间[queryL,queryR]的值
* @param treeIndex
* @param l
* @param r
* @param queryL
* @param queryR
* @return
*/
private E query(int treeIndex, int l, int r, int queryL, int queryR) {
//递归结束,待查找的区间与当前节点的区间完全吻合
if (l == queryL && r == queryR) {
return tree[treeIndex];
}
int mid = l + (r - l) / 2;
int leftTreeIndex = leftChild(treeIndex);
int rightTreeIndex = rightChild(treeIndex);
//待查找的区间完全落在右孩子节点上
if (queryL >= mid + 1) {
return query(rightTreeIndex, mid + 1, r, queryL, queryR);
} else if (queryR <= mid) {
return query(leftTreeIndex, l, mid, queryL, queryR);
}
//待查询区域部分落在左孩子和右孩子
E leftResult = query(leftTreeIndex, l, mid, queryL, mid);
E rightResult = query(rightTreeIndex, mid + 1, r, mid + 1, queryR);
return merger.merge(leftResult, rightResult);
}
/**
* 将index位置的值,更新为e
* @param index
* @param e
*/
public void set(int index, E e) {
if (index < 0 || index >= data.length) {
throw new IllegalArgumentException("index is illegal");
}
data[index] = e;
set(0, 0, data.length - 1, index, e);
}
/**
* 在以treeIndex为根的线段树中更新index的值为e
* @param treeIndex
* @param l
* @param r
* @param index
* @param e
*/
private void set(int treeIndex, int l, int r, int index, E e) {
//递归终止条件,更新叶子节点值
if (l == r) {
tree[treeIndex] = e;
return;
}
int mid = l + (r - l) / 2;
int leftTreeIndex = leftChild(treeIndex);
int rightTreeIndex = rightChild(treeIndex);
if (index >= mid + 1) {
set(rightTreeIndex, mid + 1, r, index, e);
} else {
set(leftTreeIndex,l,mid,index,e);
}
//更新线段树非叶子节点值
tree[treeIndex] = merger.merge(tree[leftTreeIndex],tree[rightTreeIndex]);
}
@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();
}
}
三、复杂度分析
1、基于静态数组的线段树实现
- 创建线段树:根据静态数组创建线段树,额外用了4n的静态数组空间,然后递归构建线段树填充4n空间的静态数组,时间复杂度是O(4n)。
- 更新线段树:更新线段树某个元素值,需要递归到叶子节点修改叶子节点的值,然后还需要重新计算各个父节点的值。时间复杂度依然之和树的深度相关,为O(logn)。
- 查询线段树:线段树查询合适的区间,是从根节点出发,往叶子节点方向递归,时间复杂度依然之和树的深度相关,为O(logn)。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。