树状数组算法计算数组的元素之和
最近在研究怎样使算法效率提高的问题,涉及到一个数组的元素求和问题,假设一个数组A[ARRAY_SIZE],求数组里面所有元素的和。我以前所能想到的就是o(n)的算法,遍历数组,使用一个变量sum依次加上数组的元素。但是,假设这个数组的元素经常变化呢?假设我们要求这个数组的前X项的和呢?难道还是要一遍遍的遍历么?
答案是no,肯定有更好的办法来实现求一个数组的元素之和。一个比较好的想法就是使用数组的某些项来保存数组中某些元素的和。这就是传说中的树状数组,我觉得发明这个数组的人真是太有才了。废话不多说,下图就是树状数组:
这个数组的整体结构是一个树状的结构,
S[1]=a[1];
S[2]=a[2]+a[1]=a[2]+S[1];
S[3]=a[3];
S[4]=a[4]+a[3]+a[2]+a[1]=a[4]+S[3]+S[2];
S[5]=a[5];
S[6]=a[6]+a[5]=a[6]+S[5];
S[7]=a[7];
S[8]=a[8]+a[7]+a[6]+a[5]+a[4]+a[3]+a[2]+a[1]=a[8]+S[7]+A[6]+S[4];
之所以使用这样的分布,是因为我们遵循这样的公式,S[i]=a[i-2k+1]+…+a[i],其中这个2k中的k表示的是S[i]在树状结构中的层数,这个值就是i的二进制表示中末尾连续的0的个数。打个比方来讲,S[8]中,810=10002,末尾的连续的0的个数是3,则k=3,S[8]是位于第四层,则S[8]=a[8-23+1]+…+a[8]=a[1]+…+a[8]。
但是在编程序中这个k求起来有点麻烦,则我们可以利用一个巧方法不用求K,即:2k=i&-i。这样就很容易得到当前数组的起始下标:i-2k+1。在编程序中需要注意一点是上面这个图的下标都是从1开始的。
更新的时候就比较简单了,给出要更新值的下标和更新的值,从当前位置开始,加上当前位的2K,得到要更新的所有元素的下标,一直更新到数组最顶层即可。废话不多说,上代码:
1 // treeAlgorithm.cpp : Defines the entry point for the console application. 2 // author:zxh 3 // 使用visual studio编辑 4 5 #include "stdafx.h" 6 #include <stdio.h> 7 #include <stdlib.h> 8 #define ARRAY_SIZE 8 9 10 int original_array[ARRAY_SIZE]; 11 int tree_array[ARRAY_SIZE]; 12 13 //打印数组 14 void print_array( int array_pointer[] ) 15 { 16 for (int idx=0;idx<ARRAY_SIZE;idx++) 17 { 18 printf("%d ",array_pointer[idx]); 19 } 20 printf("\n"); 21 } 22 23 //得到idx(数组下标+1)位置的2^k 24 int low_bit(int idx) 25 { 26 return idx&(-idx); 27 } 28 //初始化array,把原始的array转换成tree_array 29 void init_tree_array() 30 { 31 int start,sum=0; 32 for (int idx=1;idx<=ARRAY_SIZE;idx++) 33 { 34 original_array[idx-1]=idx; 35 36 sum=0; 37 start=idx-low_bit(idx);//求出初始下标 38 while(start<idx) 39 { 40 sum+=original_array[start]; 41 start++; 42 } 43 tree_array[idx-1]=sum; 44 } 45 } 46 //求前idx项和,idx不是下标 47 int sum(int idx) 48 { 49 int sum=0,start=idx; 50 while(start>0) 51 { 52 sum+=tree_array[start-1]; 53 start-=low_bit(start); 54 } 55 return sum; 56 } 57 //更新数组内的元素,为新值idx为要更新元素在数组中的下标 58 //只需要从当前位置往树的上层遍历即可。 59 void modify(int pointer,int new_value) 60 { 61 int old_value=sum(pointer+1)-sum(pointer); 62 for (int p=pointer;p<ARRAY_SIZE;p+=low_bit(p+1)) 63 { 64 tree_array[p]=tree_array[p]-old_value+new_value; 65 } 66 } 67 68 69 int _tmain(int argc, _TCHAR* argv[]) 70 { 71 init_tree_array(); 72 73 printf("Original array:"); 74 print_array(original_array); 75 76 printf("Tree array:"); 77 print_array(tree_array); 78 79 printf("sum(5)=%d:\n",sum(5)); 80 81 printf("Set original_array[3]=5:\n"); 82 modify(3,5); 83 84 printf("After modified,the tree array:\n"); 85 print_array(tree_array); 86 87 printf("sum(5)=%d:\n",sum(5)); 88 return 0; 89 }
参考:一线码农
通过上述代码我们也可以看到,根据树状数组求原始数组对应单元的内容时,不像原来的线性数组复杂度O(1),这里获取原始数据就需要使用sum(i)-sum(i-1)的方法,复杂度会有所提高,这也是树状数组的弊端,不过有弊有利么,要有取舍。