C#實(shí)現(xiàn)線段樹的示例代碼
一、線段樹
線段樹又稱"區(qū)間樹”,在每個(gè)節(jié)點(diǎn)上保存一個(gè)區(qū)間,當(dāng)然區(qū)間的劃分采用折半的思想,葉子節(jié)點(diǎn)只保存一個(gè)值,也叫單元節(jié)點(diǎn),所以最終的構(gòu)造就是一個(gè)平衡的二叉樹,擁有 CURD 的 O(lgN)的時(shí)間。

從圖中我們可以清楚的看到[0-10]被劃分成線段的在樹中的分布情況,針對(duì)區(qū)間[0-N],最多有 2N 個(gè)節(jié)點(diǎn),由于是平衡二叉樹的形式也可以像堆那樣用數(shù)組來玩,不過更加耗費(fèi)空間,為最多 4N 個(gè)節(jié)點(diǎn),在針對(duì) RMQ 的問題上,我們常常在每個(gè)節(jié)點(diǎn)上增加一些 sum,max,min 等變量來記錄求得的累加值,當(dāng)然你可以理解成動(dòng)態(tài)規(guī)劃的思想,由于擁有 logN 的時(shí)間,所以在 RMQ 問題上比數(shù)組更加優(yōu)美。
二、代碼
1、在節(jié)點(diǎn)中定義一些附加值,方便我們處理 RMQ 問題。
#region 線段樹的節(jié)點(diǎn)
/// <summary>
/// 線段樹的節(jié)點(diǎn)
/// </summary>
public class Node
{
/// <summary>
/// 區(qū)間左端點(diǎn)
/// </summary>
public int left;
/// <summary>
/// 區(qū)間右端點(diǎn)
/// </summary>
public int right;
/// <summary>
/// 左孩子
/// </summary>
public Node leftchild;
/// <summary>
/// 右孩子
/// </summary>
public Node rightchild;
/// <summary>
/// 節(jié)點(diǎn)的sum值
/// </summary>
public int Sum;
/// <summary>
/// 節(jié)點(diǎn)的Min值
/// </summary>
public int Min;
/// <summary>
/// 節(jié)點(diǎn)的Max值
/// </summary>
public int Max;
}
#endregion
2、構(gòu)建(Build)
前面我也說了,構(gòu)建有兩種方法,數(shù)組的形式或者鏈的形式,各有特點(diǎn),我就采用后者,時(shí)間為 O(N)。
#region 根據(jù)數(shù)組構(gòu)建“線段樹"
/// <summary>
/// 根據(jù)數(shù)組構(gòu)建“線段樹"
/// </summary>
/// <param name="length"></param>
public Node Build(int[] nums)
{
this.nums = nums;
return Build(nodeTree, 0, nums.Length - 1);
}
#endregion
#region 根據(jù)數(shù)組構(gòu)建“線段樹"
/// <summary>
/// 根據(jù)數(shù)組構(gòu)建“線段樹"
/// </summary>
/// <param name="left"></param>
/// <param name="right"></param>
public Node Build(Node node, int left, int right)
{
//說明已經(jīng)到根了,當(dāng)前當(dāng)前節(jié)點(diǎn)的max,sum,min值(回溯時(shí)統(tǒng)計(jì)上一層節(jié)點(diǎn)區(qū)間的值)
if (left == right)
{
return new Node
{
left = left,
right = right,
Max = nums[left],
Min = nums[left],
Sum = nums[left]
};
}
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);
//統(tǒng)計(jì)左右子樹的值(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、區(qū)間查詢
在線段樹中,區(qū)間查詢還是有點(diǎn)小麻煩的,存在三種情況。
① 完全包含:也就是節(jié)點(diǎn)的線段范圍完全在查詢區(qū)間的范圍內(nèi),這說明我們要么到了“單元節(jié)點(diǎn)",要么到了一個(gè)子區(qū)間,這種情況就是我找到了查詢區(qū)間的某一個(gè)子區(qū)間,直接累積該區(qū)間值就可以了。
② 左交集: 這種情況我們需要到左子樹去遍歷。
③ 右交集: 這種情況我們需要到右子樹去遍歷。
比如說:我要查詢 Sum[4-8]的值,最終會(huì)成為:Sum 總=Sum[4-4]+Sum[5-5]+Sum[6-8],時(shí)間為 log(N)。
#region 區(qū)間查詢
/// <summary>
/// 區(qū)間查詢(分解)
/// </summary>
/// <returns></returns>
public int Query(int left, int right)
{
int sum = 0;
Query(nodeTree, left, right, ref sum);
return sum;
}
/// <summary>
/// 區(qū)間查詢
/// </summary>
/// <param name="left">查詢左邊界</param>
/// <param name="right">查詢右邊界</param>
/// <param name="node">查詢的節(jié)點(diǎn)</param>
/// <returns></returns>
public void Query(Node node, int left, int right, ref int sum)
{
//說明當(dāng)前節(jié)點(diǎn)完全包含在查詢范圍內(nèi),兩點(diǎn):要么是單元節(jié)點(diǎn),要么是子區(qū)間
if (left <= node.left && right >= node.right)
{
//獲取當(dāng)前節(jié)點(diǎn)的sum值
sum += node.Sum;
return;
}
else
{
//如果當(dāng)前的left和right 和node的left和right無交集,此時(shí)可返回
if (node.left > right || node.right < left)
return;
//找到中間線
var middle = (node.left + node.right) / 2;
//左孩子有交集
if (left <= middle)
{
Query(node.leftchild, left, right, ref sum);
}
//右孩子有交集
if (right >= middle)
{
Query(node.rightchild, left, right, ref sum);
}
}
}
#endregion
4、更新操作
這個(gè)操作跟樹狀數(shù)組中的更新操作一樣,當(dāng)遞歸的找到待修改的節(jié)點(diǎn)后,改完其值然后在當(dāng)前節(jié)點(diǎn)一路回溯,并且在回溯的過程中一路修改父節(jié)點(diǎn)的附加值直到根節(jié)點(diǎn),至此我們的操作就完成了,復(fù)雜度同樣為 logN。
#region 更新操作
/// <summary>
/// 更新操作
/// </summary>
/// <param name="index"></param>
/// <param name="key"></param>
public void Update(int index, int key)
{
Update(nodeTree, index, key);
}
/// <summary>
/// 更新操作
/// </summary>
/// <param name="index"></param>
/// <param name="key"></param>
public void Update(Node node, int index, int key)
{
if (node == null)
return;
//取中間值
var middle = (node.left + node.right) / 2;
//遍歷左子樹
if (index >= node.left && index <= middle)
Update(node.leftchild, index, key);
//遍歷右子樹
if (index <= node.right && index >= middle + 1)
Update(node.rightchild, index, key);
//在回溯的路上一路更改,復(fù)雜度為lgN
if (index >= node.left && index <= node.right)
{
//說明找到了節(jié)點(diǎn)
if (node.left == node.right)
{
nums[index] = key;
node.Sum = node.Max = node.Min = key;
}
else
{
//回溯時(shí)統(tǒng)計(jì)左右子樹的值(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;
}
}
}
#endregion
最后我們做個(gè)例子,在 2000000 的數(shù)組空間中,尋找 200-3000 區(qū)間段的 sum 值,看看他的表現(xiàn)如何。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using System.Threading;
using System.IO;
namespace ConsoleApplication2
{
public class Program
{
public static void Main()
{
int[] nums = new int[200 * 10000];
for (int i = 0; i < 10000 * 200; i++)
{
nums[i] = i;
}
Tree tree = new Tree();
//將當(dāng)前數(shù)組構(gòu)建成 “線段樹”
tree.Build(nums);
var watch = Stopwatch.StartNew();
var sum = tree.Query(200, 3000);
watch.Stop();
Console.WriteLine("耗費(fèi)時(shí)間:{0}ms, 當(dāng)前數(shù)組有:{1}個(gè)數(shù)字, 求出Sum=:{2}", watch.ElapsedMilliseconds, nums.Length, sum);
Console.Read();
}
}
public class Tree
{
#region 線段樹的節(jié)點(diǎn)
/// <summary>
/// 線段樹的節(jié)點(diǎn)
/// </summary>
public class Node
{
/// <summary>
/// 區(qū)間左端點(diǎn)
/// </summary>
public int left;
/// <summary>
/// 區(qū)間右端點(diǎn)
/// </summary>
public int right;
/// <summary>
/// 左孩子
/// </summary>
public Node leftchild;
/// <summary>
/// 右孩子
/// </summary>
public Node rightchild;
/// <summary>
/// 節(jié)點(diǎn)的sum值
/// </summary>
public int Sum;
/// <summary>
/// 節(jié)點(diǎn)的Min值
/// </summary>
public int Min;
/// <summary>
/// 節(jié)點(diǎn)的Max值
/// </summary>
public int Max;
}
#endregion
Node nodeTree = new Node();
int[] nums;
#region 根據(jù)數(shù)組構(gòu)建“線段樹"
/// <summary>
/// 根據(jù)數(shù)組構(gòu)建“線段樹"
/// </summary>
/// <param name="length"></param>
public Node Build(int[] nums)
{
this.nums = nums;
return Build(nodeTree, 0, nums.Length - 1);
}
#endregion
#region 根據(jù)數(shù)組構(gòu)建“線段樹"
/// <summary>
/// 根據(jù)數(shù)組構(gòu)建“線段樹"
/// </summary>
/// <param name="left"></param>
/// <param name="right"></param>
public Node Build(Node node, int left, int right)
{
//說明已經(jīng)到根了,當(dāng)前當(dāng)前節(jié)點(diǎn)的max,sum,min值(回溯時(shí)統(tǒng)計(jì)上一層節(jié)點(diǎn)區(qū)間的值)
if (left == right)
{
return new Node
{
left = left,
right = right,
Max = nums[left],
Min = nums[left],
Sum = nums[left]
};
}
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);
//統(tǒng)計(jì)左右子樹的值(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 區(qū)間查詢
/// <summary>
/// 區(qū)間查詢(分解)
/// </summary>
/// <returns></returns>
public int Query(int left, int right)
{
int sum = 0;
Query(nodeTree, left, right, ref sum);
return sum;
}
/// <summary>
/// 區(qū)間查詢
/// </summary>
/// <param name="left">查詢左邊界</param>
/// <param name="right">查詢右邊界</param>
/// <param name="node">查詢的節(jié)點(diǎn)</param>
/// <returns></returns>
public void Query(Node node, int left, int right, ref int sum)
{
//說明當(dāng)前節(jié)點(diǎn)完全包含在查詢范圍內(nèi),兩點(diǎn):要么是單元節(jié)點(diǎn),要么是子區(qū)間
if (left <= node.left && right >= node.right)
{
//獲取當(dāng)前節(jié)點(diǎn)的sum值
sum += node.Sum;
return;
}
else
{
//如果當(dāng)前的left和right 和node的left和right無交集,此時(shí)可返回
if (node.left > right || node.right < left)
return;
//找到中間線
var middle = (node.left + node.right) / 2;
//左孩子有交集
if (left <= middle)
{
Query(node.leftchild, left, right, ref sum);
}
//右孩子有交集
if (right >= middle)
{
Query(node.rightchild, left, right, ref sum);
}
}
}
#endregion
#region 更新操作
/// <summary>
/// 更新操作
/// </summary>
/// <param name="index"></param>
/// <param name="key"></param>
public void Update(int index, int key)
{
Update(nodeTree, index, key);
}
/// <summary>
/// 更新操作
/// </summary>
/// <param name="index"></param>
/// <param name="key"></param>
public void Update(Node node, int index, int key)
{
if (node == null)
return;
//取中間值
var middle = (node.left + node.right) / 2;
//遍歷左子樹
if (index >= node.left && index <= middle)
Update(node.leftchild, index, key);
//遍歷右子樹
if (index <= node.right && index >= middle + 1)
Update(node.rightchild, index, key);
//在回溯的路上一路更改,復(fù)雜度為lgN
if (index >= node.left && index <= node.right)
{
//說明找到了節(jié)點(diǎn)
if (node.left == node.right)
{
nums[index] = key;
node.Sum = node.Max = node.Min = key;
}
else
{
//回溯時(shí)統(tǒng)計(jì)左右子樹的值(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;
}
}
}
#endregion
}
}

到此這篇關(guān)于C#實(shí)現(xiàn)線段樹的示例代碼的文章就介紹到這了,更多相關(guān)C# 線段樹內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
WPF利用RPC調(diào)用其他進(jìn)程的方法詳解
這篇文章主要給大家介紹了關(guān)于WPF利用RPC調(diào)用其他進(jìn)程的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2018-05-05
C#給picturebox控件加圖片選中狀態(tài)的2個(gè)方法
C#給picturebox控件加圖片選中狀態(tài)的2個(gè)方法,需要的朋友可以參考一下2013-03-03
c#實(shí)現(xiàn)把異常寫入日志示例(異常日志)
這篇文章主要介紹了c#實(shí)現(xiàn)把異常寫入日志示例(異常日志),需要的朋友可以參考下2014-04-04

