經典算法題每日演練——第十題 樹狀數組

有一種數據結構是神奇的,神祕的,它展示了位運算與數組結合的神奇魅力,太牛逼的,它就是樹狀數組,這種數據結構不是神人是發現不了的。 數組

一:概序 數據結構

     假如我如今有個需求,就是要頻繁的求數組的前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=0110;能夠發現末尾連續的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
     }
 }
相關文章
相關標籤/搜索