线段树是一种比较高级的数据结构,在现实生活中也有比较广泛的应用,比如:实现对整个宇宙中某个星系的天体数的统计等.线段树是一种自底向上更新数据的数据结构,每个节点的值为其两个孩子节点进行某种操作(相加/取最大值/取最小值)得到的结果.
线段树在实现的时候可以使用数组作为底层,线段树并不是一个完全二叉树,但是是一个平衡二叉树,创建一个线段树需要先假设其是一个满二叉树,在其中存储全部的值,对于用不到的节点不做处理(赋值为null)就可以.
对于线段树中两个孩子节点进行某种合并这个操作,可以构造一个接口Merger,在其中定义一个merge方法,用于把两个值通过某种操作合并成一个值.而后建立一个SegmentTree类,完成一系列操作.
具体代码如下:
Merger接口类:
/**
* 接口类:用于让实现这个接口的类能够把两个类型的值合并成一个(可以是获取最大最小值等)
* @author ChenZhuJi
*
* @param <E>
*/
public interface Merger<E> {
E merge(E a,E b);
}
SegmentTree类:
/**
* 线段树的实现
* @author ChenZhuJi
*
* @param <E>
*/
public class SegmentTree<E> {
private E[] data;
private E[] tree;
private Merger<E> merger;
@SuppressWarnings("unchecked")
public SegmentTree(E[] arr,Merger<E> merger) {
this.merger = merger;
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];
buildSegmentTree(0,0,arr.length - 1);
}
/**
* 在用数组表示的线段树的指定索引处创建表示从begin到end的线段树
* @param treeIndex 数组表示的线段树的指定索引
* @param begin 要创建的线段树的起始索引
* @param end 要创建的线段树的结束索引
*/
private void buildSegmentTree(int treeIndex,int begin,int end) {
if(begin == end) { //递归终止条件:要创建的线段树中只有一个元素了
tree[treeIndex] = data[begin];
return;
}
//若要创建的线段树的长度不为1,则先创建其左子树和右子树
int leftChild = getLeftChild(treeIndex); //获取线段树的左子树的索引
int rightChild = getRightChild(treeIndex); //获取线段树的右子树的索引
int mid = begin + ( end - begin ) / 2; //线段树分为两段,即为左子树和右子树
buildSegmentTree(leftChild, begin, mid); //创建线段树的左子树,从起始到中点
buildSegmentTree(rightChild, mid + 1, end); //创建线段树的右子树,从中点到终点
tree[treeIndex] = merger.merge(tree[leftChild], tree[rightChild]);
}
/**
* 返回区间[queryL,queryR]的值
* @param queryL
* @param queryR
* @return 区间[queryL,queryR]的值
*/
public E query(int queryL,int queryR) {
if(queryL < 0 || queryL > data.length || queryL >queryR || queryR > data.length || queryR < 0) {
throw new IllegalArgumentException("Error!!");
}
return query(0,0,data.length - 1,queryL,queryR);
}
/**
* 表示在以treeIndex为根的线段树中[begin,...,end]范围内,搜索区间[queryL,...,queryR]的值
* @param treeIndex 根节点
* @param begin 线段树的起始索引
* @param end 线段树的终止索引
* @param queryL 待查找的区间的起始索引
* @param queryR 待查找的区间的终止索引
* @return 搜索后的结果
*/
private E query(int treeIndex,int begin,int end,int queryL,int queryR) {
if(begin == queryL && end == queryR) {
//递归终止条件:线段树范围恰好等于要搜索的区间的范围,此时直接返回线段树节点对应的索引值
return tree[treeIndex];
}
int mid = (begin + end) / 2; //中间段索引
int leftChild = getLeftChild(treeIndex); //左子树的索引
int rightChild = getRightChild(treeIndex); //右子树的索引
if(queryL >= mid + 1) { //若要搜索的区间完全位于右子树一端,就直接在右子树上查找
return query(rightChild,mid+1,end,queryL,queryR);
} else if(queryR <= mid) { //若要搜索的区间 完全位于左子树一端,就直接在左子树上查找
return query(leftChild,begin,mid,queryL,queryR);
} else { //若要搜索的区间在左子树和右子树上都有,则把要搜索的区间根据中间索引分成左右两段,分别在左子树和右子树上查找,最后合并
E leftResult = query(leftChild,begin,mid,queryL,mid);
E rightResult = query(rightChild,mid+1,end,mid+1,queryR);
return merger.merge(leftResult, rightResult);
}
}
/**
* 将线段树中指定索引的值进行修改(动态更新)
* @param index 索引值
* @param e 修改的值
*/
public void set(int index,E e) {
if(index >= data.length || index < 0) {
throw new IllegalArgumentException("index is error!!");
}
data[index] = e;
set(0,0,data.length - 1,index,e);
}
/**
* 在以treeIndex为根节点的线段树中,从begin到end范围内寻找索引为index的节点并修改其值
* @param treeIndex 根节点的索引值
* @param begin 搜索的起始范围
* @param end 搜索的终止范围
* @param index 待搜索的索引值
* @param e 修改的值
*/
private void set(int treeIndex,int begin,int end,int index,E e ) {
if(begin == end ) { //循环终止条件:起点等于终点,说明已经找到了这个索引
tree[treeIndex] = e;
return;
}
int mid =begin + ( end - begin ) / 2; //中间索引
int leftChild = getLeftChild(treeIndex); //左孩子的索引值
int rightChild = getRightChild(treeIndex); //右孩子的索引值
if(index >= mid + 1) { //若index不比中间索引值+1小,说明在右孩子这边,去右孩子那边找
set(rightChild, mid + 1, end, index, e );
} else { //若index比中间索引值小,说明在左孩子这边,去左孩子那边找
set(leftChild, begin, mid, index, e);
}
tree[treeIndex] = merger.merge(tree[leftChild], tree[rightChild]);//更新节点值:每个线段树等于左孩子和右孩子通过某种操作得到
}
public int getSize() {
return data.length;
}
public E get(int index) {
if(index >= data.length || index < 0) {
throw new IllegalArgumentException("索引越界");
}
return data[index];
}
private int getLeftChild(int index) {
return 2 * index + 1;
}
private int getRightChild(int index) {
return 2 * index + 2;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder("[");
for(int i = 0 ; i < tree.length ; i++) {
if(tree[ i ] != null) {
sb.append(tree[ i ]);
} else {
sb.append("null");
}
if( i != tree.length - 1) {
sb.append(", ");
}
}
sb.append(" ]");
return sb.toString();
}
}