package arithmetic; class Segment { int left; int right; int count=0; int sum =0; } public class SegmentTree { public static int num[] = new int[5000]; public static void main(String args[]) { Segment tree[] = new Segment[5000]; for(int i=0;i<tree.length;i++) tree[i] = new Segment(); /*create(tree,0,7,1); for(int i=1;i<=15;i++) { System.out.println("["+tree[i].left+" "+tree[i].right+"]"); }*/ // insert(tree,4,4,1); for(int i=1;i<=7;i++) num[i]=i; build(tree,1,7,1); for(int i=1;i<=15;i++) { System.out.println("["+tree[i].left+" "+tree[i].right+"] "+tree[i].sum); } System.out.println(query(tree,1,2,1)); // 修改 modify(tree,1,10,1); for(int i=1;i<=15;i++) { System.out.println("["+tree[i].left+" "+tree[i].right+"] "+tree[i].sum); } } /* * 线段树是一个完全二叉树,故其叶子结点为满足:左孩子=2*n 右孩子=2*n+1 * */ public static void create(Segment[]tree,int l,int r,int n) //线段树是一个静态二叉树结构 { if(l==r) //如果线段的两个端点已经相等的话,把它插入到里面就可以停止了,这样线段树的叶子结点就是 [1-1][2-2]这种形式的了,因为在插入线段的时候,如果要插入的线段不能洽好在线段树的一侧的话,那么就要把该线段分成两半插入到线段树中,那么在分隔的原理是:根据当前结点的中点进行分隔,当前结点的中点为mid ,那么就将线段[a-b]分成 [a-mid] 插入线段的左孩子,[mid-b]插入线段的右孩子中,但在分隔的过程中,如果分开的一段洽好是a==mid ,那么就会出现两个值都一样的情况,所以我们在建立线段树的时候,要建立到底,直到把所有的点都建立出来 { tree[n].left=l; tree[n].right=r; return ; } tree[n].left=l; tree[n].right=r; int mid = (r+l)/2; create(tree,l,mid,n*2); create(tree,mid+1,r,n*2+1); } /* * 线段树的插入 * */ public static void insert(Segment[] tree,int l,int r,int n) { System.out.println(n); if(l == tree[n].left && r == tree[n].right ) { tree[n].count++; return ; } int mid = (tree[n].left + tree[n].right)/2; if(r<=mid) // 插入到左孩子中去 insert(tree,l,r,2*n); else //一定要加 else , r = = l 的情况出如果不判断会出现错误 if(l>=mid)// 插入到右孩子中去 insert(tree,l,r,2*n+1); else //将 l ,r 分成两个线段 { insert(tree,l,mid,2*n); insert(tree,mid+1,r,2*n+1); } } /* --------------------------------------------应用------------------------------------------------------- * 下面写一个区间求和的算法,需求:N个点,每个点的有一个值,求任意两个点之间的和 * * 1. 首先创建一个线段树,每个结点的线段保存是两人上结点之间所有结点的和 * 2. 下面查询任意两个点之间的和 如果 [2-5] 那么就要求出 2 3 4 5 上点的和 * */ /* * l - r 代表区间 * 返回 数组 num [l -r ]区间内的和 * */ public static int build(Segment tree[],int l,int r,int n) { tree[n].left=l; tree[n].right=r; if(l==r) { return tree[n].sum=num[l]; } int mid = (l+r)>>1; return tree[n].sum = build(tree,l,mid,2*n)+ build(tree,mid+1,r,2*n+1); } /* * # l-r 表示区间 * 返回区间内的和 * * */ public static int query(Segment tree[],int l,int r,int n) { if(l==tree[n].left && r==tree[n].right) return tree[n].sum; int mid = (tree[n].left+tree[n].right)>>1; if(r<=mid) return query(tree,l,r,n<<1); else if(l>=mid) return query(tree,l,r,n<<1|1); else return query(tree,l,mid,n<<1)+query(tree,mid+1,r,n<<1|1); } /* * id 要修改的结点号 * sum 要修改为多少人 * */ public static void modify(Segment tree[],int id,int sum,int n) //是其一个点的人数,只要把其本身和所有的孩子改了即可, { tree[n].sum+=sum; if(tree[n].left == tree[n].right) return ; int mid = (tree[n].left+tree[n].right)>>1; if(id<=mid) //根据修改的编号判断是修改左孩子还是右孩子,因为不可能现时修改两个孩子 modify(tree,id,sum,n<<1); else modify(tree,id,sum,n<<1|1); } /* * 些事例为求任意区间内的和,所以线段树维护的应该是区间内的和 * * */ }