這一篇我們來看樹狀陣列的加強版線段樹,樹狀陣列能玩的線段樹一樣可以玩,而且能玩的更好,他們在區間求和,最大,平均
等經典的rmq問題上有著對數時間的優越表現。
一:線段樹
線段樹又稱"區間樹」,在每個節點上儲存乙個區間,當然區間的劃分採用折半的思想,葉子節點只儲存乙個值,也叫單元節點,所
以最終的構造就是乙個平衡的二叉樹,擁有curd的o(lgn)的時間。
從圖中我們可以清楚的看到[0-10]被劃分成線段的在樹中的分布情況,針對區間[0-n],最多有2n個節點,由於是平衡二叉樹的形
式也可以像堆那樣用陣列來玩,不過更加耗費空間,為最多4n個節點,在針對rmq的問題上,我們常常在每個節點上增加一些sum,
max,min等變數來記錄求得的累加值,當然你可以理解成動態規劃的思想,由於擁有logn的時間,所以在rmq問題上比陣列更加優美。
二:**
1:在節點中定義一些附加值,方便我們處理rmq問題。
#region 線段樹的節點
/// /// 線段樹的節點
///
public class node
#endregion
2:構建(build)
前面我也說了,構建有兩種方法,陣列的形式或者鏈的形式,各有特點,我就採用後者,時間為o(n)。
#region 根據陣列構建「線段樹"
/// /// 根據陣列構建「線段樹"
///
///
public node build(int nums)
#endregion
#region 根據陣列構建「線段樹"
/// /// 根據陣列構建「線段樹"
///
///
///
public node build(node node, int left, int right);}
if (node == null)
node = new node();
node.left = left;
node.right = right;
node.leftchild = build(node.leftchild, left, (left + right) / 2);
node.rightchild = build(node.rightchild, (left + right) / 2 + 1, right);
//統計左右子樹的值(min,max,sum)
node.min = math.min(node.leftchild.min, node.rightchild.min);
node.max = math.max(node.leftchild.max, node.rightchild.max);
node.sum = node.leftchild.sum + node.rightchild.sum;
return node;
}#endregion
3:區間查詢
① 完全包含:也就是節點的線段範圍完全在查詢區間的範圍內,這說明我們要麼到了「單元節點",要麼到了乙個子區間,這種情況
就是我找到了查詢區間的某乙個子區間,直接累積該區間值就可以了。
② 左交集: 這種情況我們需要到左子樹去遍歷。
③右交集: 這種情況我們需要到右子樹去遍歷。
比如說:我要查詢sum[4-8]的值,最終會成為:sum總=sum[4-4]+sum[5-5]+sum[6-8],時間為log(n)。
#region 區間查詢
/// /// 區間查詢(分解)
///
///
public int query(int left, int right)
/// /// 區間查詢
///
/// 查詢左邊界
/// 查詢右邊界
/// 查詢的節點
///
public void query(node node, int left, int right, ref int sum)
else
//右孩子有交集
if (right >= middle)}}
#endregion
4:更新操作
這個操作跟樹狀陣列中的更新操作一樣,當遞迴的找到待修改的節點後,改完其值然後在當前節點一路回溯,並且在回溯的過程中一
路修改父節點的附加值直到根節點,至此我們的操作就完成了,複雜度同樣為logn。
#region 更新操作
/// /// 更新操作
///
///
///
public void update(int index, int key)
/// /// 更新操作
///
///
///
public void update(node node, int index, int key)
else}}
#endregion
最後我們做個例子,在2000000的陣列空間中,尋找200-3000區間段的sum值,看看他的表現如何。
using system;
using system.collections.generic;
using system.linq;
using system.text;
using system.diagnostics;
using system.threading;
using system.io;
tree tree = new tree();
//將當前陣列構建成 「線段樹」
tree.build(nums);
var watch = stopwatch.startnew();
var sum = tree.query(200, 3000);
watch.stop();
console.read();}}
public class tree
#endregion
node nodetree = new node();
int nums;
#region 根據陣列構建「線段樹"
/// /// 根據陣列構建「線段樹"
///
///
public node build(int nums)
#endregion
#region 根據陣列構建「線段樹"
/// /// 根據陣列構建「線段樹"
///
///
///
public node build(node node, int left, int right);}
if (node == null)
node = new node();
node.left = left;
node.right = right;
node.leftchild = build(node.leftchild, left, (left + right) / 2);
node.rightchild = build(node.rightchild, (left + right) / 2 + 1, right);
//統計左右子樹的值(min,max,sum)
node.min = math.min(node.leftchild.min, node.rightchild.min);
node.max = math.max(node.leftchild.max, node.rightchild.max);
node.sum = node.leftchild.sum + node.rightchild.sum;
return node;
}#endregion
#region 區間查詢
/// /// 區間查詢(分解)
///
///
public int query(int left, int right)
/// /// 區間查詢
///
/// 查詢左邊界
/// 查詢右邊界
/// 查詢的節點
///
public void query(node node, int left, int right, ref int sum)
else
//右孩子有交集
if (right >= middle)}}
#endregion
#region 更新操作
/// /// 更新操作
///
///
///
public void update(int index, int key)
/// /// 更新操作
經典演算法題每日演練 第十九題 雙端佇列
話說大學的時候老師說妹子比工作重要 工作可以再換,妹子這個。所以。這兩個月也就一直忙著fall in love,嗨,慢慢調整心態吧,這篇就選乙個簡單的資料結構聊一聊,話說有很多資料結構都在玩組合拳,比如說 塊狀鍊錶,塊狀陣列,當然還有本篇的雙端佇列,是的,它就是 棧和佇列的組合體。一 概念 我們知道...
經典演算法題每日演練 第二十二題 奇偶排序
原文 經典演算法題每日演練 第二十二題 奇偶排序 這個專題因為各種原因好久沒有繼續下去了,mm吧。嘿嘿,不過還得繼續寫下去,好長時間不寫,有些東西有點生疏了,這篇就從簡單一點的乙個 奇偶排序 說起吧,不過這個排序還是蠻有意思的,嚴格來說複雜度是o n2 不過在多核的情況下,可以做到 n2 m 2 的...
經典演算法題每日演練 第七題 KMP演算法
在大學的時候,應該在資料結構裡面都看過kmp演算法吧,不知道有多少老師對該演算法是一筆帶過的,至少我們以前是的,確實kmp演算法還是有點饒人的,如果說紅黑樹是 級的,那麼kmp演算法比紅黑樹還要 很抱歉,每次打kmp的時候,輸 入法總是提示 看毛片 三個字,嘿嘿,就叫 看毛片演算法 吧。一 bf演算...