线段树

线段树

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;   
}  




 


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值