欢迎访问悦橙教程(wld5.com),关注java教程。悦橙教程  java问答|  每日更新
页面导航 : > > 文章正文

Java基础,

来源: javaer 分享于  点击 20575 次 点评:92

Java基础,


下面的文字说明引用了  int64Ago 的一篇博文。

用什么语言来形容当时的感觉呢?……太神奇了!真的,无法表达出那种感觉,她是那么的优雅,10行不到的代码,却把事情干的如此出色!没有了解她原理的前提下即使把代码倒背如流也理解不了!下面我争取用自己的方式让更多人明白她,而不是背诵她。为了更方便的说明,文章里会自己强加一些概念,只是为了更好的理解,不是什么专业术语之类的。

一、树状数组是干什么的?

       平常我们会遇到一些对数组进行维护查询的操作,比较常见的如,修改某点的值、求某个区间的和,而这两种恰恰是树状数组的强项!当然,数据规模不大的时候,对于修改某点的值是非常容易的,复杂度是O(1),但是对于求一个区间的和就要扫一遍了,复杂度是O(N),如果实时的对数组进行M次修改或求和,最坏的情况下复杂度是O(M*N),当规模增大后这是划不来的!而树状数组干同样的事复杂度却是O(M*lgN),别小看这个lg,很大的数一lg就很小了,这个学过数学的都知道吧,不需要我说了。申明一下,看下面的文章一定不要急,只需要看懂每一步最后自然就懂了。

二、树状数组怎么干的?

        先看两幅图(网上找的,如果雷同,不要大惊小怪~),下面的说明都是基于这两幅图的,左边的叫A图吧,右边的叫B图:

是不是很像一颗树?对,这就是为什么叫树状数组了~先看A图,a数组就是我们要维护和查询的数组,但是其实我们整个过程中根本用不到a数组,你可以把它当作一个摆设!c数组才是我们全程关心和操纵的重心。先由图来看看c数组的规则,其中c8 = c4+c6+c7+a8,c6 = c5+a6……先不必纠结怎么做到的,我们只要知道c数组的大致规则即可,很容易知道c8表示a1~a8的和,但是c6却是表示a5~a6的和,为什么会产生这样的区别的呢?或者说发明她的人为什么这样区别对待呢?答案是,这样会使操作更简单!看到这相信有些人就有些感觉了,为什么复杂度被lg了呢?可以看到,c8可以看作a1~a8的左半边和+右半边和,而其中左半边和是确定的c4,右半边其实也是同样的规则把a5~a8一分为二……继续下去都是一分为二直到不能分,可以看看B图。怎么样 ?是不是有点二分的味道了?对,说白了树状数组就是巧妙的利用了二分,她并不神秘,关键是她的巧妙!

它又是怎样做到不断的一分为二呢?说这个之前我先说个叫lowbit的东西,lowbit(k)就是把k的二进制的高位1全部清空,只留下最低位的1,比如10的二进制是1010,则lowbit(k)=lowbit(1010)=0010(2进制),介于这个lowbit在下面会经常用到,这里给一个非常方便的实现方式,比较普遍的方法lowbit(k)=k&-k,这是位运算,我们知道一个数加一个负号是把这个数的二进制取反+1,如-10的二进制就是-1010=0101+1=0110,然后用1010&0110,答案就是0010了!明白了求解lowbit的方法就可以了,继续下面。介于下面讨论十进制已经没有意义(这个世界本来就是二进制的,人非要主观的构建一个十进制),下面所有的数没有特别说明都当作二进制。

上面那么多文字说lowbit,还没说它的用处呢,它就是为了联系a数组和c数组的!ck表示从ak开始往左连续求lowbit(k)个数的和,比如c[0110]=a[0110]+a[0101],就是从110开始计算了0010个数的和,因为lowbit(0110)=0010,可以看到其实只有低位的1起作用,因为很显然可以写出c[0010]=a[0010]+a[0001],这就为什么我们任何数都只关心它的lowbit,因为高位不起作用(基于我们的二分规则它必须如此!),除非除了高位其余位都是0,这时本身就是lowbit。既然关系建立好了,看看如何实现a某一个位置数据跟改的,她不会直接改的(开始就说了,a根本不存在),她每次改其实都要维护c数组应有的性质,因为后面求和要用到。而维护也很简单,比如更改了a[0011],我们接着要修改c[0011],c[0100],c[1000],这是很容易从图上看出来的,但是你可能会问,他们之间有申明必然联系吗?每次求解总不能总要拿图来看吧?其实从0011——>0100——>1000的变化都是进行“去尾”操作,又是自己造的词--'',我来解释下,就是把尾部应该去掉的1都去掉转而换到更高位的1,记住每次变换都要有一个高位的1产生,所以0100是不能变换到0101的,因为没有新的高位1产生,这个变换过程恰好是可以借助我们的lowbit进行的,k +=lowbit(k)。

好吧。现在更新的次序都有了,可能又会产生新的疑问了:为什么它非要是这种关系啊?这就要追究到之前我们说c8可以看作a1~a8的左半边和+右半边和……的内容了,为什么c[0011]会影响到c[0100]而不会影响到c[0101],这就是之前说的c[0100]的求解实际上是这样分段的区间 c[0001]~c[0001] 和区间c[0011]~c[0011]的和,数字太小,可能这样不太理解,在比如c[0100]会影响c[1000],为什么呢?因为c[1000]可以看作0001~0100的和加上0101~1000的和,但是0101位置的数变化并会直接作用于c[1000],因为它的尾部1不能一下在跳两级在产生两次高位1,是通过c[0110]间接影响的,但是,c[0100]却可以跳一级产生一次高位1。

可能上面说的你比较绕了,那么此时你只需注意:c的构成性质(其实是分组性质)决定了c[0011]只会直接影响c[0100],而c[0100]只会直接影响[1000],而下表之间的关系恰好是也必须是k +=lowbit(k)。

lowbit(a)2^(a的二进制表示末尾0的个数)。可以用下面式子求出:

lowbit(a)=a&(a ^ (a-1))
//或
lowbit(a)=a&(~a+1)
//或根据补码性质
lowbit(a)=a&(-a)
修改方法如下:

void modify(int p,int delta)
    {
        while (p<=N)
        {
            c[p]+=delta;
            p+=lowbit(p);
        }
    }


求前缀和如下:

int sum(int p)
    {
        int rs=0;
        while (p)
        {
            rs+=C[p];
            p-=lowbit(p);
        }
        return rs;
    }


下面是Java实现代码:

package com.yc.tree;

import java.util.Arrays;

public class BinaryIndexTree {
	static int[] c;
	int n;
	public BinaryIndexTree(int n) {
		this.n = n;
		c = new int[n+1];
	}

	int lowbit(int x) {
		return x & (x ^ (x-1));
	}
	/**
	 * 更新一个元素,初始数组c都是0,所以可以用这个方法初始化c,这时候增加与更新是等价的
	 * @param p    更新第p个元素,索引从1开始
	 * @param d    增加的值,不是更新后的值
	 */
	void update(int p, int d) {
		while (p <= n) {
			c[p] += d;
			p += lowbit(p);
		}
	}

	/**
	 * 计算从第一个元素到第p个元素的和
	 * @param p
	 * @return
	 */
	int sum(int p) {
		int ret = 0;
		while (p > 0) {
			ret += c[p];
			p -= lowbit(p);
		}
		return ret;
	}

	/**
	 * 计算从第s个元素到第e个元素的和
	 * @param s
	 * @param e
	 * @return
	 */
	int sum(int s, int e){
		if(s > n || e < 1 || s > e || s < 1 || e > n){
			System.out.println("input error!");
			return -1;
		}
		return sum(e) - sum(s - 1);
	}
	public static void main(String[] args) {
		int[] numbers = {1,2,3,4,5,6,7,8,9};
		BinaryIndexTree bit = new BinaryIndexTree(numbers.length);
		for (int i=0; i<numbers.length; i++) {
			bit.update(i+1, numbers[i]);
		}
		System.out.println( Arrays.toString(c));
		System.out.println("1-6的和为:"+bit.sum(6));
		//第三个元素 +4后
		bit.update(3, 4);
		System.out.println( "第三个元素 +4后:"+Arrays.toString(c));
		System.out.println("第三个元素 +4后.2-6的和为:"+bit.sum(2,6));
	}
}

测试结果如下:

[0, 1, 3, 3, 10, 5, 11, 7, 36, 9]
1-6的和为:21
第三个元素 +4后:[0, 1, 3, 7, 14, 5, 11, 7, 40, 9]
第三个元素 +4后.2-6的和为:24



相关文章

    暂无相关文章
相关栏目:

用户点评