线段树

转载自:https://www.cnblogs.com/TenosDoIt/p/3453089.html

 

  1 const int INFINITE = INT_MAX;
  2 const int MAXNUM = 1000;
  3 struct SegTreeNode
  4 {
  5     int val;
  6     int addMark;//延迟标记
  7 } segTree[MAXNUM]; //定义线段树
  8 
  9 /*
 10 功能:构建线段树
 11 root:当前线段树的根节点下标
 12 arr: 用来构造线段树的数组
 13 istart:数组的起始位置
 14 iend:数组的结束位置
 15 */
 16 void build(int root, int arr[], int istart, int iend)
 17 {
 18     segTree[root].addMark = 0;//----设置标延迟记域
 19     if(istart == iend)//叶子节点
 20         segTree[root].val = arr[istart];
 21     else
 22     {
 23         int mid = (istart + iend) / 2;
 24         build(root * 2 + 1, arr, istart, mid); //递归构造左子树
 25         build(root * 2 + 2, arr, mid + 1, iend); //递归构造右子树
 26         //根据左右子树根节点的值,更新当前根节点的值
 27         segTree[root].val = min(segTree[root * 2 + 1].val, segTree[root * 2 + 2].val);
 28     }
 29 }
 30 
 31 /*
 32 功能:当前节点的标志域向孩子节点传递
 33 root: 当前线段树的根节点下标
 34 */
 35 void pushDown(int root)
 36 {
 37     if(segTree[root].addMark != 0)
 38     {
 39         //设置左右孩子节点的标志域,因为孩子节点可能被多次延迟标记又没有向下传递
 40         //所以是 “+=”
 41         segTree[root * 2 + 1].addMark += segTree[root].addMark;
 42         segTree[root * 2 + 2].addMark += segTree[root].addMark;
 43         //根据标志域设置孩子节点的值。因为我们是求区间最小值,因此当区间内每个元
 44         //素加上一个值时,区间的最小值也加上这个值
 45         segTree[root * 2 + 1].val += segTree[root].addMark;
 46         segTree[root * 2 + 2].val += segTree[root].addMark;
 47         //传递后,当前节点标记域清空
 48         segTree[root].addMark = 0;
 49     }
 50 }
 51 
 52 /*
 53 功能:线段树的区间查询
 54 root:当前线段树的根节点下标
 55 [nstart, nend]: 当前节点所表示的区间
 56 [qstart, qend]: 此次查询的区间
 57 */
 58 int query(int root, int nstart, int nend, int qstart, int qend)
 59 {
 60     //查询区间和当前节点区间没有交集
 61     if(qstart > nend || qend < nstart)
 62         return INFINITE;
 63     //当前节点区间包含在查询区间内
 64     if(qstart <= nstart && qend >= nend)
 65         return segTree[root].val;
 66     //分别从左右子树查询,返回两者查询结果的较小值
 67     pushDown(root); //----延迟标志域向下传递
 68     int mid = (nstart + nend) / 2;
 69     return min(query(root * 2 + 1, nstart, mid, qstart, qend),
 70                query(root * 2 + 2, mid + 1, nend, qstart, qend));
 71 
 72 }
 73 
 74 /*
 75 功能:更新线段树中某个区间内叶子节点的值
 76 root:当前线段树的根节点下标
 77 [nstart, nend]: 当前节点所表示的区间
 78 [ustart, uend]: 待更新的区间
 79 addVal: 更新的值(原来的值加上addVal)
 80 */
 81 void update(int root, int nstart, int nend, int ustart, int uend, int addVal)
 82 {
 83     //更新区间和当前节点区间没有交集
 84     if(ustart > nend || uend < nstart)
 85         return ;
 86     //当前节点区间包含在更新区间内
 87     if(ustart <= nstart && uend >= nend)
 88     {
 89         segTree[root].addMark += addVal;
 90         segTree[root].val += addVal;
 91         return ;
 92     }
 93     pushDown(root); //延迟标记向下传递
 94     //更新左右孩子节点
 95     int mid = (nstart + nend) / 2;
 96     update(root * 2 + 1, nstart, mid, ustart, uend, addVal);
 97     update(root * 2 + 2, mid + 1, nend, ustart, uend, addVal);
 98     //根据左右子树的值回溯更新当前节点的值
 99     segTree[root].val = min(segTree[root * 2 + 1].val, segTree[root * 2 + 2].val);
100 }

 

个人理解:

  线段树的高效性在于它避免了树的遍历,因此延迟标记在线段树中尤为重要,树的遍历时间复杂度为O(n),而通过延迟标记这个行为可降到O(logn),能高效解决连续区间的动态查询问题。

对于延迟标记这一行为的理解:

  就比如说第一次更新是一个区间增加2,他可能不用到叶子节点就已经实现了这个更新。但是第二次比如是这个区间的一个子区间增加3,那么这次更新还要往下遍历,那么上一次的延迟标记值还要加上这次的延迟标记值,因为上次的那个延迟标记可能没有往下传到它的孩子就实现了,但是实际上他的孩子的值也是要改变的。并非一定是第二次更新,只要你的操作需要继续往下遍历,那么延迟标记就需要往下传递,这也就避免了子节点的不必要的遍历。

线段树貌似还有很多其他的应用,比如线段树的离散化,线段树+扫描线等等,以后接触到再来学习补充

 

posted @ 2018-07-25 12:15  Piccolo_Devil  阅读(217)  评论(0编辑  收藏  举报