线段树

来源:互联网 发布:win10修改网卡mac地址 编辑:程序博客网 时间:2024/06/07 16:37

线段树

1  建树  

以求区间最大值为例


a数组为构造线段树的数组

root 为线段树的树根

star 为数组起始下表

end 为数组终点下表

开始 root = 1,star = 1,end = n; 

void build(int root,int star,int end)  {       if(star==end)      {       int k;    scanf("%d",&k);        stu[root] = k;return ;       }      int mid = (star+end)/2;      build(root*2,star,mid);      build(root*2+1,mid+1,end);        stu[root]=max(stu[2*root],stu[2*root+1]);  }  




2 单节点更新

root 为线段树的树根

star 为数组起始下表

end为数组终点下表

i 为数组中要更新的位置

add 为这个节点 要更新的值;

void updet(int root,int star,int end,int i,int add)   {      if(star==end&&star==i)     {stu[root]=add;return ;}             int mid=(star+end)/2;      int t1;      if(i<=mid)  //和二分查找差不多;          updet(2*root,star,mid,i,add);      else          updet(2*root+1,mid+1,end,i,add);      stu[root] = max(stu[2*root],stu[2*root+1]);     }


3 查询线段树

root 为线段树树根

star 为数组起始下表

end 为数组终点下表

i  为要查询的区间起始下表

j 为要查询的区间终点下表


这个另一道题,查询区间和的,记住只是查询,不能改变原建树的内容

而下面这个写的也不好,可能会出错,runtime erorr(栈溢出)

int Query(int root,int star,int end,int i,int j) // 只查询,查询区间值; {      if(i>end||j<star)      {          return 0;      }      if(i<=star&&end<=j)          return stu[root].sum;      int mid = (star+end)/2;            int t1 = Query(root*2,star,mid,i,j);      int t2 = Query(root*2+1,mid+1,end,i,j);      //stu[root].sum=t1+t2; //千万不要再重复赋值,本来在更新值时都算好了,                              //你在更新有啥意义,还把原来的值给覆盖了;                              // 直接return 找到的两者的和就行了;       return t1+t2;  } 

这个是查询区间最大值的,避免了runtime erorr

int Query(int root,int star,int end,int i,int j) {      if(i<=star&&end<=j)          return stu[root];    int mid = (star+end)/2; int ret=0;if(i<=mid)   // 减少递归; ret=Query(root*2,star,mid,i,j); if(j>mid)// 减少递归;(避免 runtime erorr 栈溢出) ret=max(ret,Query(root*2+1,mid+1,end,i,j));  //这用的挺好;      return ret;        }

4 线段树区间更新 (这个以给出一个区间,给出一个数,区间中所有值,都变为了这个数)


这个要引出新的东西,“延迟标记”,,这个标记非常懒,更新节点时能停则停,即这个区间包含在需要更新的区间内,就不会更新下面的子节点了。除非需要访问或修改它的子节点,它才会往下更新。不得不说,这样效率非常高。

struct node{int abadd;//延迟标记 int sum;}stu[3*Max];


(1) 建树

初始数组里面都为1,前两个数代表的数区间,第三个数是这个区间里的所有数变为这个数;

和上面建树过程一样,唯一的区别就是把节点的所有的延迟标记都初始为0

void build(int root,int star,int end) //建线段树   {      stu[root].abadd = 0;     //延迟标记初始为0;       if(star==end)      {          stu[root].sum = 1;          return ;      }      int mid = (star+end)/2;      build(2*root,star,mid);      build(2*root+1,mid+1,end);      stu[root].sum = stu[root*2].sum+stu[root*2+1].sum;  }

(2) 延迟标记传给左右子节点,自己的清零

void down_updat(int root,int star,int end) //延迟标记传给左右子节点   {      if(stu[root].abadd !=0)               {          int k=stu[root].abadd;          int mid = (star+end)/2;          stu[2*root].abadd  = k;          stu[2*root+1].abadd = k;          stu[2*root].sum = (mid-star+1)*k;  // 左右孩子的值 也得更新;         stu[2*root+1].sum = (end-mid)*k;          stu[root].abadd = 0;            //自己的延迟标记清0       }  }



(3) 查询 区间

int Query(int root,int star,int end,int i,int j) // 只查询,查询区间值; {      if(i>end||j<star)      {          return 0;      }      if(i<=star&&end<=j)          return stu[root].sum;    down_updat(root,star,end);  //传延迟标记    int mid = (star+end)/2;      int t1 = Query(root*2+1,star,mid,i,j);      int t2 = Query(root*2+2,mid+1,end,i,j);      //stu[root].sum=t1+t2; //千万不要再重复赋值,本来在更新值时都算好了,                              //你在更新有啥意义,还把原来的值给覆盖了;                              // 直接return 找到的两者的和就行了;       return t1+t2;  }

(4)  更新区间值    

root 线段树树根

star 数组的起始下表

end 数组的终点下表

s1 要更新的区间起始下表

e1 要更新的区间终点下表

add 区间中全部要更新为的值

下面程序 只是找到当前区间的结点,更新这个节点值,这个节点上加个延迟标记,它的子孙节点只有访问了才更新,不访问就不更新(这也是这个程序的高效性,不清楚的,看上面 “延迟标记” 定义)


int updat(int root,int star,int end,int s1,int e1,int add) //只更新找的区间结点值,再加上个延迟标记   {      if(s1>end||e1<star)          return stu[root].sum;      if(star>=s1&&end<=e1)      {          stu[root].abadd = add;          stu[root].sum = (end-star+1)*add;          return stu[root].sum;         }            down_updat(root,star,end);  //传给左右子树延迟标记              int mid = (star+end)/2;      int t1 = updat(2*root,star,mid,s1,e1,add);      int t2 = updat(2*root+1,mid+1,end,s1,e1,add);  //这不和查询一样,要更新区间值的;    return stu[root].sum = t1 + t2;   }  




 


原创粉丝点击