交换两个变量方法

将两个变量的值互换,相当简单的问题。假设有变量a、b: 

    int a;
    int b;

方法1:将a、b的值互换,代码为:      

    int tmp = a;

    a = b;
    b = tmp;

然而,如果要求不用中间变量,就交换变量的值,该怎么做呢?一个比较有效率且众所周知的方法就是:


方法2:三次异或操作: 

int a=10,b=12; //a=1010^b=1100;
a=a^b; //a=0110^b=1100;
b=a^b; //a=0110^b=1010;
a=a^b; //a=1100=12;b=1010;

这应该是最有效率的一种方法了吧,全部都是位操作。但你可能要花一点时间,才能明白为什么这样可以交换两个变量的值。  

现在再看另外的方法,你就会比较容易明白其中的道理了。 

方法3:利用加减法来实现

    a = a + b;
    b = a - b;
    a = a - b;

它的原理是:把a、b看做数轴上的点,围绕两点间的距离来进行计算。
具体过程:第一句“a=b-a”求出ab两点的距离,并且将其保存在a中;第二句“b=b-a”求出a到原点的距离(b到原点的距离与ab两点距离之差),并且将其保存在b中;第三句“a=b+a”求出b到原点的距离(a到原点距离与ab两点距离之和),并且将其保存在a中。完成交换。
此算法与标准算法相比,多了三个计算的过程,但是没有借助临时变量。(以下称为算术算法)
缺点:是只能用于数字类型,字符串之类的就不可以了。a+b有可能溢出(超出int的范围),溢出是相对的, +了溢出了,-回来不就好了,所以溢出不溢出没关系,就是不安全。

原理总结:

其实它的原理就是加法,请看下面的式子:      z = x + y;
    y = z - x;
    x = z - y;
根据加法的规则,我们只要知道其中任意两个变量的值,就可以求出第三个变量的值。因此,这三个变量中,舍弃其中任何一个,都不会导致信息的丢失。你看到的上面一例,就是通过将两个数先累加到其中一个变量中,然后再分别求得另一个变量,并同时互换了值。  

那么,你想到了吗?还有什么方法来互换两个变量的值?
对了,就是用乘法: 

    a = a * b;
    b = a / b;
    a = a / b;
通过后面两个例子的比较,你可以看出一些规律来了吧。
只要有两种操作,它们的关系类似于*/或+-,那么就可以用来交换两个变量的值。我们来尝试一下形式化的总结:
设有函数F,满足
    z = F( x, y );
如果存在函数G,使得
    x = G( z, y );
    y = G( z, x );
那么就可以用以下的步骤来交换两个变量的值
    a = F( a, b );
    b = G( a, b );
    a = G( a, b );
现在一切都明白了吧。
但是,难道你没有疑问吗:为什么只用异或操作就可以实现?呵呵,那是因为和“异或”相对的那个操作就是“异或”本身,也即      若F = ^,则G = ^。

现在,你可以定义你自己的函数F和G。


另外补充两种方法

1) 指针地址操作
因为对地址的操作实际上进行的是整数运算,比如:两个地址相减得到一个整数,表示两个变量在内存中的储存位置隔了多少个字节;地址和一个整数相加即“a+10”表示以a为基地址的在a后10个a类数据单元的地址。所以理论上可以通过和算术算法类似的运算来完成地址的交换,从而达到交换变量的目的。即:

int *a,*b; //假设
*a=new int(10);
*b=new int(20); //&a=0x00001000h,&b=0x00001200h
a=(int*)(b-a); //&a=0x00000200h,&b=0x00001200h
b=(int*)(b-a); //&a=0x00000200h,&b=0x00001000h
a=(int*)(b+int(a)); //&a=0x00001200h,&b=0x00001000h


通过以上运算a、b的地址真的已经完成了交换,且a指向了原先b指向的值,b指向原先a指向的值了吗?上面的代码可以通过编译,但是执行结果却令人匪夷所思!原因何在?
首先必须了解,操作系统把内存分为几个区域:系统代码/数据区、应用程序代码/数据区、堆栈区、全局数据区等等。在编译源程序时,常量、全局变量等都放入全局数据区,局部变量、动态变量则放入堆栈区。这样当算法执行到“a=(int*)(b-a)”时,a的值并不是0x00000200h,而是要加上变量a所在内存区的基地址,实际的结果是:0x008f0200h,其中0x008f即为基地址,0200即为a在该内存区的位移。它是由编译器自动添加的。因此导致以后的地址计算均不正确,使得a,b指向所在区的其他内存单元。再次,地址运算不能出现负数,即当a的地址大于b的地址时,b-a<0,系统自动采用补码的形式表示负的位移,由此会产生错误,导致与前面同样的结果。
有办法解决吗?当然!以下是改进的算法:
if(a<b){
      a=(int*)(b-a);
      b=(int*)(b-(int(a)&0x0000ffff));
      a=(int*)(b+(int(a)&0x0000ffff));
}else{
      b=(int*)(a-b);
      a=(int*)(a-(int(b)&0x0000ffff));
      b=(int*)(a+(int(b)&0x0000ffff));
}


算法做的最大改进就是采用位运算中的与运算“int(a)&0x0000ffff”,因为地址中高16位为段地址,后16位为位移地址,将它和0x0000ffff进行与运算后,段地址被屏蔽,只保留位移地址。这样就原始算法吻合,从而得到正确的结果。
此算法同样没有使用第三变量就完成了值的交换,与算术算法比较它显得不好理解,但是它有它的优点即在交换很大的数据类型时,它的执行速度比算术算法快。因为它交换的时地址,而变量值在内存中是没有移动过的。(以下称为地址算法)

2)栈实现。不多解释了,栈和相关函数定义省去。

int exchange(int x,int y) 
{ 
 	stack S; 
 	push(S,x); 
	 push(S,y); 
 	x=pop(S); 
 	y=pop(S); 
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值
>