代码改变世界

不使用临时变量交换两个整数

2013-08-03 22:33  youxin  阅读(1282)  评论(1编辑  收藏  举报

最普通的方法用临时变量的方法就不说了,下面的没有用临时变量:

int main()
{
    int a,b;
    
    while(scanf("%d%d",&a,&b)!=EOF)
    {
    
        a=a+b;
        b=a-b;
        a=a-b;

        printf("%d\t%d\n",a,b);
    }
     
}

有些人说当a,b比较大时会溢出产生错误的结果。其实不正确,溢出虽然会溢出,但是结果任然正确。为什么?

我们假设

a,b都是short int(int类似)。

a 0x7fff 32767(2^15-1,有符号整形所能表示的最大范围)。

b 0x7fff 

a=a+b后为0xfffe;(如果看成有符号数为-2(0xffff为-1)。看出无符号数为65534)。因为为signed,现在溢出了。

b=a-b; -1-32767=-32768再次溢出变成最大值32767

b=a-b类似。

可以看到,会有2次溢出。溢出规律:

SHRT_MAX+1 == SHRT_MIN
SHRT_MIN-1 == SHRT_MAX

 参考:http://bbs.csdn.net/topics/390335866?page=1#post-393371903

但是结果正确。我们要劲量避免溢出。

采用异或运算:

a=a^b;
b=a^b;
a=a^b;

(可以写成一句:b ^= a ^= b ^= a;  )

首先来看第一句:a=a^b;执行该语句后a中保存了a与b的差异位,也就是说如果原来的a和b的某一位不同,那么就将a的该位置为1,因此a在内存中成了如下图的样子,它说明a与b的第2,3个bit有差异:

 

接着我们来看第二句:b=b^a;其意思是,将b中有差异的位翻转,如此一来b中保存的值其实就等于原来a中的值,记住当第二个语句执行完之后a仍然保存了原来的a,b的差异信息,而b则变成了原来的a!

 

最后我们来看第三句:a=a^b;由于异或运算满足交换律,因此这一句等价于:a=b^a;记住这个语句赋值号右边的b中已经保存了原始的a值,而a中保存了原始的a,b的差异,因此这一句的最终作用是将原始a中有差异的位翻转(变成b)然后赋值给a,如此一来a中就保存了原始的b值。

 

总结:上述三句中:第一句是记录差异,第2,3句是翻转,最终实现了不用任何中间变量就交换两个变量的值

可以看到右边的都是一样a^b;

举例:

a 0x7fff

b 0x7ffe

a=a^b 0x0001;

b=a^b ; 结果0x7fff

a=a^b; 结果0xfffe

交换成功。

说明一下,不要使用异或运算,增加了复杂度。

通过异或交换2个数的一个小陷阱;

可用通过异或运算交换两个数,而不需要任何的中间变量。 如下面:

  1. void exchange(int &a, int &b)
  2. {
  3.     a ^= b;
  4.     b ^= a;
  5.     a ^= b;
  6. }


然而,这里面却存在着一个非常隐蔽的陷阱。

通常我们在对数组进行操作的时候,会交换数组中的两个元素,如exchang(&a[i], &b[j]), 这儿如果i==j了(这种情况是很可能发生的),得到的结果就并非我们所期望的。

  1. void main() 
  2. {
  3.    int a[2] = {1, 2};
  4.    exchange(a[0], a[1]); //交换a[0]和a[1]的值
  5.    printf("1---a[0]=%d a[1]=%d\n", a[0], a[1]);
  6.    exchange(a[0], a[0]); //将a[0]与自己进行交换
  7.    printf("2---a[0]=%d a[1]=%d\n", a[0], a[1]);
  8. }

 上面那段测试代码的输出是:

  1. 1---a[0]=2 a[1]=1
  2. 2---a[0]=0 a[1]=1

很意外吧,第一次的交换正确的执行了,但是第二次调用exchange的时候却将a[0]置为了0. 仔细分析,不难发现,这正是我们在exchange里面用异或实现交换所造成的。如果输入a和b是同一个数,exchange里面代码相当于:

  1. a ^= a;
  2. a ^= a;
  3. a ^= a;

成了a做了3次(a^a^a^a^a)于自己的异或,其结果当然是0了。

既然这样,我们就不能够在任何使用交换的地方采用异或了,即使要用,也一定要在交换之前判断两个数是否已经相等了,如下:

    1.     void exchange(int &a, int &b)
    2.     {
    3.         if(a == b) return; //防止&a,&b指向同一个地址;那样结果会错误。
    4.         a ^= b;
    5.         b ^= a;
    6.         a ^= b;
    7.     }
    8.  

用异或来交换两个变量是错误的。参考这篇文章批判了。