数据结构学了一段时间,初次接触线段树,放一点代码上来。
用数组实现的线段树,可以尝试用链表实现,节约空间。
优先队列可以传入一个比较器,而线段树则需要一个融合器:
public interface Merger<E> {
public E merge(E left,E right);
}
线段树的调用过程:
public class Test {
//随手写的测试用例
public static void main(String[] args) {
Integer [] arr={0,1,2,3,4,5,6,7,8,9};
//用拉姆达表达式书写融合过程,融合过程为两者相加
SegmentTree<Integer> st=new SegmentTree<>(arr,(a,b)->a+b);
System.out.println(st);
//查某一段
System.out.println(st.qurey(3,5));
//更新某一个节点
st.set(4, 61);
System.out.println(st.qurey(3,5));
}
}
线段树主主题函数:
public class SegmentTree <E> {
//基于数组构造线段树
private E []tree;
//用于接收数组
private E []array;
//融合器
private Merger<E> merger;
//构造方法
//arr 传入一个数组;merger 传入一个融合器;
public SegmentTree(E[] arr ,Merger<E> merger) {
this.merger=merger;
array=(E [])new Object[arr.length];
for(int i=0;i<arr.length;i++)
array[i]=arr[i];
tree=(E [])new Object[4*arr.length];
//调用辅助函数构造线段树
tree[0]=buildTree(0,0,array.length-1);
}
//函数语义:返回构造完成后的线段树的根
//treeRoot 当前根对应的下标;
//left 根中线段的左边界;right 根中线段的右边界
private E buildTree(int TreeRoot,int left,int right) {
//递归到底的情况
if(left==right )
return array[left];
//对于非叶子节点,处理方式类似于后续遍历
//取当前线段的中点
int mid=left+(right-left)/2;
//左孩子和右孩子分别调用函数生成对应的线段树
tree[leftChild(TreeRoot)]=buildTree(leftChild(TreeRoot),left,mid);
tree[rightChild(TreeRoot)]=buildTree(rightChild(TreeRoot),mid+1,right);
//用户设计融合器,将左右节点融合。
tree[TreeRoot]=(E) merger.merge(tree[leftChild(TreeRoot)], tree[rightChild(TreeRoot)]);
//返回根节点
return tree[TreeRoot];
}
//查询某一段线段树
public E qurey(int left,int right) {
return qurey(0,left,right,0,array.length-1);
}
//辅助查询函数。语义:返回本段的查询结果
//root 当前根节点;qureyL查询的左边界;right查询的右边界;left当前的左边界;right当前的右边界
private E qurey(int root,int qureyL,int qureyR,int left,int right) {
//递归到底的情况
if(left==qureyL && right==qureyR)
return tree[root];
//当前区间左右边界的中间值
int mid=left+(right-left)/2;
//1.待查询的区间完全落在左子树
if(qureyR<=mid)
return qurey(leftChild(root),qureyL,qureyR,left,mid);
//2.待查询的区间完全落在右子树
if(qureyL>=mid+1)
return qurey(rightChild(root),qureyL,qureyR,mid+1,right);
//3.当待查询的区间在左右区间有交集的时候
E leftResult=qurey(leftChild(root),qureyL,mid,left,mid);
E rightResult=qurey(rightChild(root),mid+1,qureyR,mid+1,right);
return merger.merge(leftResult, rightResult);
}
//更新某个叶子节点。index:被更新的数组下标;e:更新为e
public void set(int index,E e) {
if(index>=array.length||index<0)
throw new IndexOutOfBoundsException();
//先更新数组中的
array[index]=e;
//再更新树中的
tree[0]=set(0,0,array.length-1,index,e);
}
//辅助更新函数。语义:返回本段更新的结果
// root:根节点;left:左边界;right:右边界;index:被更新的数组下标;e:更新为e
private E set(int root, int left,int right,int index,E e) {
//递归终止条件
if(left==right)
return e;
//取中点下标、根节点的左右孩子下标
int mid=left+(right-left)/2;
int leftChild=leftChild(root);
int rightChild=rightChild(root);
//叶子节点在左子树
if(index<=mid)
tree[leftChild]=set(leftChild,left,mid,index,e);
//叶子节点在右子树
else if(index>=mid+1)
tree[rightChild]=set(rightChild,mid+1,right,index,e);
//左右子树融合
return merger.merge(tree[leftChild], tree[rightChild]);
}
public int getSize() {
return array.length;
}
public E get(int index) {
return array[index];
}
public boolean isEmpty() {
return array.length==0;
}
private int leftChild(int index) {
return 2*index+1;
}
private int rightChild(int index) {
return 2*index+2;
}
//打印输出函数
@Override
public String toString() {
// TODO 自动生成的方法存根
StringBuilder strBd=new StringBuilder();
strBd.append("[");
for(E e:tree) {
strBd.append(e+",");
}
strBd.replace(strBd.length()-1, strBd.length()-1, "]");
return strBd.toString();
}
}