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


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

一:概序

     假如我現在有個需求,就是要頻繁的求數組的前n項和,並且存在着數組中某些數字的頻繁修改,那么我們該如何實現這樣的需求?當然大家可以往

真實項目上靠一靠。

① 傳統方法:根據索引修改為O(1),但是求前n項和為O(n)。

②空間換時間方法:我開一個數組sum[],sum[i]=a[1]+....+a[i],那么有點意思,求n項和為O(1),但是修改卻成了O(N),這是因為我的Sum[i]中牽

                         涉的數據太多了,那么問題來了,我能不能在相應的sum[i]中只保存某些a[i]的值呢?好吧,下面我們看張圖。

從圖中我們可以看到S[]的分布變成了一顆樹,有意思吧,下面我們看看S[i]中到底存放着哪些a[i]的值。

S[1]=a[1];

S[2]=a[1]+a[2];

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]是在樹中的第二層,並且S[6]中有21項,隨后我們求出了起始項:

            a[6-21+1]=a[5],但是在編碼中求出k的值還是有點麻煩的,所以我們采用更靈巧的Lowbit技術,即:2k=i&-i 。

           則:S[6]=a[6-21+1]=a[6-(6&-6)+1]=a[5]+a[6]。

二:代碼

1:神奇的Lowbit函數

 1 #region 當前的sum數列的起始下標
 2         /// <summary>
 3         /// 當前的sum數列的起始下標
 4         /// </summary>
 5         /// <param name="i"></param>
 6         /// <returns></returns>
 7         public static int Lowbit(int i)
 8         {
 9             return i & -i;
10         }
11         #endregion

 

2:求前n項和

     比如上圖中,如何求Sum(6),很顯然Sum(6)=S4+S6,那么如何尋找S4呢?即找到6以前的所有最大子樹,很顯然這個求和的復雜度為logN。

 1         #region 求前n項和
 2         /// <summary>
 3         /// 求前n項和
 4         /// </summary>
 5         /// <param name="x"></param>
 6         /// <returns></returns>
 7         public static int Sum(int x)
 8         {
 9             int ans = 0;
10 
11             var i = x;
12 
13             while (i > 0)
14             {
15                 ans += sumArray[i - 1];
16 
17                 //當前項的最大子樹
18                 i -= Lowbit(i);
19             }
20 
21             return ans;
22         }
23         #endregion

3:修改

如上圖中,如果我修改了a[5]的值,那么包含a[5]的S[5],S[6],S[8]的區間值都需要同步修改,我們看到只要沿着S[5]一直回溯到根即可,

同樣它的時間復雜度也為logN。

 1         public static void Modify(int x, int newValue)
 2         {
 3             //拿出原數組的值
 4             var oldValue = arr[x];
 5 
 6             for (int i = x; i < arr.Length; i += Lowbit(i + 1))
 7             {
 8                 //減去老值,換一個新值
 9                 sumArray[i] = sumArray[i] - oldValue + newValue;
10             }
11         }

最后上總的代碼:

View Code
  1 using System;
  2 using System.Collections.Generic;
  3 using System.Linq;
  4 using System.Text;
  5 using System.Diagnostics;
  6 using System.Threading;
  7 using System.IO;
  8 
  9 namespace ConsoleApplication2
 10 {
 11     public class Program
 12     {
 13         static int[] sumArray = new int[8];
 14 
 15         static int[] arr = new int[8];
 16 
 17         public static void Main()
 18         {
 19             Init();
 20 
 21             Console.WriteLine("A數組的值:{0}", string.Join(",", arr));
 22             Console.WriteLine("S數組的值:{0}", string.Join(",", sumArray));
 23 
 24             Console.WriteLine("修改A[1]的值為3");
 25             Modify(1, 3);
 26 
 27             Console.WriteLine("A數組的值:{0}", string.Join(",", arr));
 28             Console.WriteLine("S數組的值:{0}", string.Join(",", sumArray));
 29 
 30             Console.Read();
 31         }
 32 
 33         #region 初始化兩個數組
 34         /// <summary>
 35         /// 初始化兩個數組
 36         /// </summary>
 37         public static void Init()
 38         {
 39             for (int i = 1; i <= 8; i++)
 40             {
 41                 arr[i - 1] = i;
 42 
 43                 //設置其實坐標:i=1開始
 44                 int start = (i - Lowbit(i));
 45 
 46                 var sum = 0;
 47 
 48                 while (start < i)
 49                 {
 50                     sum += arr[start];
 51 
 52                     start++;
 53                 }
 54 
 55                 sumArray[i - 1] = sum;
 56             }
 57         }
 58         #endregion
 59 
 60         public static void Modify(int x, int newValue)
 61         {
 62             //拿出原數組的值
 63             var oldValue = arr[x];
 64 
 65             arr[x] = newValue;
 66 
 67             for (int i = x; i < arr.Length; i += Lowbit(i + 1))
 68             {
 69                 //減去老值,換一個新值
 70                 sumArray[i] = sumArray[i] - oldValue + newValue;
 71             }
 72         }
 73 
 74         #region 求前n項和
 75         /// <summary>
 76         /// 求前n項和
 77         /// </summary>
 78         /// <param name="x"></param>
 79         /// <returns></returns>
 80         public static int Sum(int x)
 81         {
 82             int ans = 0;
 83 
 84             var i = x;
 85 
 86             while (i > 0)
 87             {
 88                 ans += sumArray[i - 1];
 89 
 90                 //當前項的最大子樹
 91                 i -= Lowbit(i);
 92             }
 93 
 94             return ans;
 95         }
 96         #endregion
 97 
 98         #region 當前的sum數列的起始下標
 99         /// <summary>
100         /// 當前的sum數列的起始下標
101         /// </summary>
102         /// <param name="i"></param>
103         /// <returns></returns>
104         public static int Lowbit(int i)
105         {
106             return i & -i;
107         }
108         #endregion
109     }
110 }

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM