为什么使用线段树
最经典的线段树问题:PMQ(区间最值问题)和区间求和
线段树能帮助我们快速的求出区间和
构建线段树
1、线段树不是完全二叉树,是平衡二叉树。可以使用数组表示树的结点
2、给定搞一个数组arr,获取生成线段树的高度
在这里插入图片描述
3、生成线段树的结点个数最多为:2^h - 1
4、生成线段树
private T[] source; // 源数组
private T[] data; // 线段树对应的数组
private Merger<T> merger;
public SegmentTree(T[] arr, Merger<T> merger) {
this.merger = merger;
if (arr != null) {
this.source = Arrays.copyOf(arr, arr.length);
int height = (int) Math.ceil(Math.log(this.source.length) / Math.log(2) + 1);
int dataLength = (int) Math.pow(2, height) - 1;
this.data = (T[]) (new Object[dataLength]);
buildingSegmentTree(0, this.source.length - 1, 0);
}
}
/**
* 构建线段树
*
* @param start 区间的开始
* @param end 区间的结尾
* @param index 线段树对应[start:end]区间的索引
*/
private void buildingSegmentTree(int start, int end, int index) {
//1、递归到底的情况
if (start == end) {
this.data[index] = this.source[start];
return;
}
//2、递归操作
int mid = start + (end - start) / 2;
int leftIndex = 2 * index + 1;
int rightIndex = leftIndex + 1;
buildingSegmentTree(start, mid, leftIndex);
buildingSegmentTree(mid + 1, end, rightIndex);
this.data[index] = this.merger.merge(this.data[leftIndex], this.data[rightIndex]);
}
注意:int mid = (start + end)/2;可能会超范围,所以使用 int mid = start +(end -start)/2 此构建线段树的方法只支持求和操作,为了具有通用性,可以设计一个融合接口Merge
public interface Merger<T> {
T merge(T a, T b);
}
查询操作
线段树的查询无非就是区间查询,我们只需要输入开始的区间和结束的区间,就可以获取这个区间你所需要的查询结果。
public T getRangValue(int from, int to) {
if (from < 0 || to >= this.source.length || from > to) {
throw new IllegalArgumentException("error");
}
return query(0, this.source.length - 1, 0, from, to);
}
/**
* 进行查询操作
*
* @param start 线段树结点所表示区间的start
* @param end 线段树结点所表示区间的end
* @param index 线段树上结点的索引
* @param from 查区间的开询始
* @param to 查询区间的结尾
* @return
*/
private T query(int start, int end, int index, int from, int to) {
//1、递归到底的情况
if (start == from && end == to){
return this.data[index];
}
//2、递归操作 1)完全不包含 2)包含部分 3)包含全部
int mid = start + (end - start) / 2;
int leftIndex = 2 * index + 1;
int rightIndex = leftIndex + 1;
if (mid<from){
return query(mid+1,end,rightIndex,from,to);
}else if (to<=mid){
return query(start,mid,leftIndex,from,to);
}else {
T leftVal = query(start,mid,leftIndex,from,mid);
T rightVal = query(mid+1,end,rightIndex,mid+1,to);
return this.merger.merge(leftVal,rightVal);
}
}
更新操作
给顶一个位置和元素,将位置中的元素替换成我们想要替换的元素,只需要在线段树中找到那个位置,然后替换即可,不过也需要将其他相关区间的值进行修改。
public void update(int site,T val){
if (site<0 || site > this.source.length){
throw new IllegalArgumentException("site is wrongful!");
}
update(0,this.source.length-1,0,site,val);
}
private void update(int start, int end, int index, int site, T val) {
//1、递归到底
if (start == end && start == site){
this.data[index] = this.source[site] = val;
return;
}
int mid = start+ (end-start)/2;
int leftIndex = index*2+1;
int rightIndex = leftIndex+1;
if (mid<site){
update(mid+1,end,rightIndex,site,val);
}else {
update(start,mid,leftIndex,site,val);
}
this.data[index] = this.merger.merge(this.data[leftIndex],this.data[rightIndex]);
}
练习
力扣303题
区域和检索-数组不可变