注意:区间树和线段树不一样哦,线段树是一种特殊的区间树。
区间树:
区间树是在红黑树基础上进行扩展得到的支持以区间为元素的动态集合的操作,其中每个节点的关键值是区间的左端点。通过建立这种特定的结构,可是使区间的元素的查找和插入都可以在O(lgn)的时间内完成。相比于基础的红黑树数据结构,增加了一个max[x],即以x为根的子树中所有区间的断点的最大值。逻辑结构如下所示:
区间树具有和红黑树一样的性质,并且区间树的基础操作和红黑树的基础操作一样。(具体红黑树的操作,参见http://blog.csdn.net/v_JULY_v/article/details/6105630)
红黑树的性质如下:(一定要牢记哦!)
(1)节点要么是红色的,要么是黑色的
(2)根节点是黑色的
(3)每个叶节点(即空节点)是黑色的
(4)若节点是红色的,则它的孩子节点必为黑色
(5)对每个节点,从该节点到它的子孙叶子节点的所有路径上包含相同的黑色节点。
线段树:线段树是一种平衡二叉查找树,它将一个区间划分成一些单元区间,每个单元区间对应线段树中的一个叶结点。主要的处理思想是基于分治的思想。它的逻辑结构如下:
设根节点的区间为[a,b),区间长度为L = b - a,线段树的性质:
(1)线段树是一个平衡树,树的高度为log(L)
(2)线段树把区间上的任意长度为L的线段都分成不超过2log(L)线段的并
线段树基础存储结构如下:(这里使用数组模拟指针,类似堆的存储结构)
struct tag_LineSegNode{ int left; //区间左单点 int right; //区间右单点 int mid; //区间中间值
int cover;<span style="white-space:pre"> </span> //是否覆盖(<span style="font-size: 14px; text-indent: 28px; background-color: rgb(255, 0, 0);">注意:线段树的节点信息根据具体的应用需求添加相应的数据字段。)</span> }; typedef struct tag_LineSegNode LSNode;
根据线段树的性质可知,线段树的节点个数大于2*L,这里设置线段树的节点个数为LSNode node[3 * L];
线段树的操作主要有:
(1)创建线段树
void BuildLineSegTree(int left,int right,int nodeNum) { node[nodeNum].left = left; node[nodeNum].right = right; node[nodeNum].mid = left + (right - left) / 2; node[nodeNum].cover = 0; //判断是否是叶子节点 if(left != right - 1) { BuildLineSegTree(left,node[nodeNum].mid,2 * nodeNum); BuildLineSegTree(node[nodeNum].mid,right,2 * nodeNum + 1); } }
(2)插入线段树
void InsertLineSegTree(int left,int right,int nodeNum) { //判断区间是否完全覆盖 if(node[nodeNum].left == left && node[nodeNum].right == right) { node[nodeNum].cover += 1; return ; } if(right <= node[nodeNum].mid) { //线段在左子树上 return InsertLineSegTree(left,right,2 * nodeNum); } else if(left >= node[nodeNum].mid) { //线段在右子树上 return InsertLineSegTree(left,right,2 * nodeNum + 1); } else { //线段一部分在左子树上,一部分在右子树上 return InsertLineSegTree(left,node[nodeNum].mid,2 * nodeNum) || InsertLineSegTree(node[nodeNum].mid,right,2 * nodeNum + 1); } }
(3)查询线段树
int SearchLineSegTree(int left,int right,int nodeNum) { if(node[nodeNum].left == left && node[nodeNum].right == right) { //线段完全覆盖,若该线段存在则返回1,否则返回0 return node[nodeNum].cover; } if(right <= node[nodeNum].mid) { //线段在左子树 return SearchLineSegTree(left,right,2 * nodeNum); } else if(left >= node[nodeNum].mid) { //线段在右子树 return SearchLineSegTree(left,right,2 * nodeNum + 1); } else { //线段一部分在左子树,一部分在右子树 return SearchLineSegTree(left,node[nodeNum].mid,2 * nodeNum) && SearchLineSegTree(node[nodeNum].mid,right,2 * nodeNum + 1); } }
(4)删除线段树
int DeleteLineSegTree(int left,int right,int nodeNum) { if(node[nodeNum].left == left && node[nodeNum].right == right) { //线段完全覆盖,若该线段存在则返回1,否则返回0 int ret = node[nodeNum].cover; node[nodeNum].cover = node[nodeNum].cover > 0 ? node[nodeNum].cover - 1 : 0; return ret; } if(right <= node[nodeNum].mid) { //线段在左子树 return DeleteLineSegTree(left,right,2 * nodeNum); } else if(left >= node[nodeNum].mid) { //线段在右子树 return DeleteLineSegTree(left,right,2 * nodeNum + 1); } else { //线段一部分在左子树,一部分在右子树 return DeleteLineSegTree(left,node[nodeNum].mid,2 * nodeNum) && DeleteLineSegTree(node[nodeNum].mid,right,2 * nodeNum + 1); } }
线段树在一些具体的应用中,需要对输入的数据进行离散化,以减小线段树的大小,此时需要注意离散前和离散后的数据对应。此外,在某些应用中,需要使用lazy思想,在一些操作中先不对线段树进行更新,而是推迟到查找的过程中,在查找的过程中进行更新。
具体的应用见本博客的后续部分。。。。敬请关注哦!
区间树和线段树