區間樹
注意:區間樹和線段樹不一樣哦,線段樹是一種特殊的區間樹。
區間樹:
區間樹是在紅黑樹基礎上進行擴展得到的支持以區間為元素的動態集合的操作,其中每個節點的關鍵值是區間的左端點。通過建立這種特定的結構,可是使區間的元素的查找和插入都可以在O(lgn)的時間內完成。相比於基礎的紅黑樹數據結構,增加了一個max[x],即以x為根的子樹中所有區間的斷點的最大值。邏輯結構如下所示:
區間樹具有和紅黑樹一樣的性質,並且區間樹的基礎操作和紅黑樹的基礎操作一樣。
紅黑樹的性質如下:(一定要牢記哦!)
(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; //是否覆蓋注意:線段樹的節點信息根據具體的應用需求添加相應的數據字段。
};
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思想,在一些操作中先不對線段樹進行更新,而是推遲到查找的過程中,在查找的過程中進行更新。