有一種數據結構是神奇的,神祕的,它展示了位運算與數組結合的神奇魅力,太牛逼的,它就是樹狀數組,這種數據結構不是神人是發現不了的。 數組
一:概序 數據結構
假如我如今有個需求,就是要頻繁的求數組的前n項和,而且存在着數組中某些數字的頻繁修改,那麼咱們該如何實現這樣的需求?固然你們能夠往 函數
真實項目上靠一靠。 編碼
① 傳統方法:根據索引修改成O(1),可是求前n項和爲O(n)。 spa
②空間換時間方法:我開一個數組sum[],sum[i]=a[1]+....+a[i],那麼有點意思,求n項和爲O(1),可是修改卻成了O(N),這是由於個人Sum[i]中牽 code
涉的數據太多了,那麼問題來了,我能不能在相應的sum[i]中只保存某些a[i]的值呢?好吧,下面咱們看張圖。 索引
從圖中咱們能夠看到S[]的分佈變成了一顆樹,有意思吧,下面咱們看看S[i]中到底存放着哪些a[i]的值。 同步
S[1]=a[1]; string
S[2]=a[1]+a[2]; it
S[3]=a[3];
S[4]=a[1]+a[2]+a[3]+a[4];
S[5]=a[5];
S[6]=a[5]+a[6];
S[7]=a[7];
S[8]=a[1]+a[2]+a[3]+a[4]+a[5]+a[6]+a[7]+a[8];
之因此採用這樣的分佈方式,是由於咱們使用的是這樣的一個公式:S[i]=a[i-2k+1]+....+a[i]。
其中:2k 中的k表示當前S[i]在樹中的層數,它的值就是i的二進制中末尾連續0的個數,2k也就是表示S[i]中包含了哪些a[],
舉個例子: i=610=01102 ;能夠發現末尾連續的0有一個,即k=1,則說明S[6]是在樹中的第二層,而且a[]有21項,隨後咱們求出了起始項:
a[6-21+1]=a[5],可是在編碼中求出k的值仍是有點麻煩的,因此咱們採用更靈巧的Lowbit技術,即:2k=i&-i 。
則: a[6-21+1]=a[6-(6&-6)+1]=a[5]。
二:代碼
1:神奇的Lowbit函數
#region 當前的sum數列的起始下標 /// <summary> /// 當前的sum數列的起始下標 /// </summary> /// <param name="i"></param> /// <returns></returns> public static int Lowbit(int i) { return i & -i; } #endregion
2:求前n項和
好比上圖中,如何求Sum(6),很顯然Sum(6)=S4+S6,那麼如何尋找S4呢?即找到6之前的全部最大子樹,很顯然這個求和的複雜度爲logN。
#region 求前n項和 /// <summary> /// 求前n項和 /// </summary> /// <param name="x"></param> /// <returns></returns> public static int Sum(int x) { int ans = 0; var i = x; while (i > 0) { ans += sumArray[i - 1]; //當前項的最大子樹 i -= Lowbit(i); } return ans; } #endregion
3:修改
如上圖中,若是我修改了a[5]的值,那麼包含a[5]的S[5],S[6],S[8]的區間值都須要同步修改,咱們看到只要沿着S[5]一直回溯到根便可,
一樣它的時間複雜度也爲logN。
public static void Modify(int x, int newValue) { //拿出原數組的值 var oldValue = arr[x]; for (int i = x; i < arr.Length; i += Lowbit(i + 1)) { //減去老值,換一個新值 sumArray[i] = sumArray[i] - oldValue + newValue; } }最後上總的代碼:
View Code 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 { static int[] sumArray = new int[8]; static int[] arr = new int[8]; public static void Main() { Init(); Console.WriteLine("A數組的值:{0}", string.Join(",", arr)); Console.WriteLine("S數組的值:{0}", string.Join(",", sumArray)); Console.WriteLine("修改A[1]的值爲3"); Modify(1, 3); Console.WriteLine("A數組的值:{0}", string.Join(",", arr)); Console.WriteLine("S數組的值:{0}", string.Join(",", sumArray)); Console.Read(); } #region 初始化兩個數組 /// <summary> /// 初始化兩個數組 /// </summary> public static void Init() { for (int i = 1; i <= 8; i++) { arr[i - 1] = i; //設置其實座標:i=1開始 int start = (i - Lowbit(i)); var sum = 0; while (start < i) { sum += arr[start]; start++; } sumArray[i - 1] = sum; } } #endregion public static void Modify(int x, int newValue) { //拿出原數組的值 var oldValue = arr[x]; arr[x] = newValue; for (int i = x; i < arr.Length; i += Lowbit(i + 1)) { //減去老值,換一個新值 sumArray[i] = sumArray[i] - oldValue + newValue; } } #region 求前n項和 /// <summary> /// 求前n項和 /// </summary> /// <param name="x"></param> /// <returns></returns> public static int Sum(int x) { int ans = 0; var i = x; while (i > 0) { ans += sumArray[i - 1]; //當前項的最大子樹 i -= Lowbit(i); } return ans; } #endregion #region 當前的sum數列的起始下標 /// <summary> /// 當前的sum數列的起始下標 /// </summary> /// <param name="i"></param> /// <returns></returns> public static int Lowbit(int i) { return i & -i; } #endregion } }