(1) 不用中间变量的交换
不用中间变量,这个起初觉得不太现实,但是经过一些技巧,却可以很轻松的实现。同样是3行代码,可以代替老套的
temp = a; a = b; b = temp; //swap a and b
这三行。
新的解决方案嘛,可以相当巧妙的,使用了位运算中的异或运算符。
代码也是只有三行:
a ^= b; b ^= a; a ^= b;
如果你是第一次看到这种代码,可能有点晕。不过没关系,稍微解释相信聪明的你可以很快就明白的。
因为是位运算,我们就不得不从位的角度(bitwise)去理解这个问题。
首先,我们要知道异或的作用。
异或嘛,个人觉得,有点像汉语的“抑或”。通常我们说这个词的时候,是表示两种情况中的一种。
如果从逻辑学的角度去分析,那么,就是只有两个逻辑子句,有且只有一个为true的时候,整条语句才为true,否则均为false。
那么,我们可以很容易得到下面的真值表:
条件a | 条件b | a异或b(a^b) |
true | true | false |
true | false | true |
false | true | true |
false | false | false |
从二进制的角度去理解,true代表1,false代表0。那么我们可以很容易地得到下面的结论:
任何位与0进行异或运算,均得到自身,与1进行异或运算,均得到自己的补位。
那么用x表示一个二进制位(bit),那么上面的结论就是 x^0=x且x^1=~x。
现在,我们继续稍稍研究下其他几个结论,也是很容易推出的。
任何数与自身进行异或,均为0 。即 a^a=0。
而且,异或运算具有交换性,即a^b = b^a。
异或运算具有结合性,即a^b^c=b^c^a=a^c^b。
(其实可以得到六个等式,不过其他的已包含在交换性内,不在列出)
呵呵,是不是很简单?
上面的既然都明白了,现在就很轻松地可以理解那种奇异的swap了。
我们不妨把最初的a,b分别命名为a1,b1。把交换后的a,b命名为a2,b2。
那么我们的目的就是让a2=b1,b2=a1。
首先是a^=b。
它代表a=a^b,那么这时候得到的a,既不是我们想要的a2,更不是b2,我们姑且叫它a3吧。
然后b^=a,就是b=b^a。
这里的a是刚才的a3,那么就是计算b1^a3。而a3又是a1^b1,那么我们就得到了b^a其实是b1^a1^b1。
嘿……刚才的一些性质发挥作用了,先改变下结合顺序,得到b1^b1^a1,然后b1^b1是0,那么就是0^a1,还是a1,所以刚才b^=a,已经得到b2。
第三句,a^=b,即a=a^b,这里的a是a3,b是b2。
那么,我们展开,b2可以认为是a1的值,那么等式右边的a^b其实就是a1^b1^a1,同上,很容易得到最后a被赋值为b1。
嘿嘿,同样达到了我们的目的。
接下来,稍微值得思考一下的是,是不是所有交换都可以这么实现呢?
呃,其实呢,起初,按我的想法呢,只要是值类型(Value Type),就都可以的。我特意在J2SE的环境下写了相关代码进行测试,发现小数类型(double以及float)不可以。编译错误提示为:
The operator ^= is undefined for the argument type(s) double, double
其他的,无论是short,long,int,boolean以及char都作过测试,均没问题。
进一步修改,做测试,发现不同类型也可以实现交换。比如如下代码:
int a=-5;
long b=43;
System.out.println(a+"/t"+b);
a^=b;
b^=a;
a^=b;
System.out.println(a+"/t"+b);
依旧没问题,可见Java编译器已经很成熟地做了对不同长度数据类型(包括有符号数的自动位扩充)的合适处理。
最后想说下, 个人觉得有以下好处:
第一嘛,可以炫耀你的代码,哈哈,因为不少人如果首次看到这三行,估计要想半天是怎么回事。
第二嘛,没有中间变量,当然节省了那么一点栈空间。
第三嘛,因为可以令不同类型的Value Type 实现交换(小数除外),那么……总比为了想一个合适的temp类型而煞费苦心要好的多。
……
不过也有不少局限,其中速度上比中间变量的交换稍微错了点。(可以使用大量循环累积时间来计时断定)
到此为止吧,呵呵,这个是第一个关于swap的发散思维,后面还有更多呢!谢谢你的支持!