算法研究之不使用临时变量实现两个值的交换-阿里云开发者社区

开发者社区> eclipse_xu> 正文

算法研究之不使用临时变量实现两个值的交换

简介: 变量值的交换经常在程序中使用,一般方法是使用一个临时变量,交换两个数的值,其实,不使用临时变量,依然可以实现这一功能。 1、常规交换方法 int a,b,temp; temp=a; a=b; b=temp; 2、通过指针 1: inline void Swap(int *a,i...
+关注继续查看

变量值的交换经常在程序中使用,一般方法是使用一个临时变量,交换两个数的值,其实,不使用临时变量,依然可以实现这一功能。

1、常规交换方法

int a,b,temp;
temp=a;
a=b;
b=temp;
2、通过指针

1: inline void Swap(int *a,int *b)

2: {

3: *a=*a+*b;

4: *b=*a-*b;

5: *a=*a-*b;

6: }

两个人交换苹果和桔子但是每人只有一个手,也不借助其它的容器,那如何进行呢,有办法,就只是用一个手拿两样东西,也就是说先将苹果(桔子)给另一个人用一只手拿着,再从他手上拿桔子(苹果)。基本也就是代码的意思了。注意这种方法可能会溢出,因为使用的加法,很可能使结果超出范围,也就是说一只手要是拿不了一个苹果和桔子怎样办?

3、数学方法

        int i = 6;
            int j = 12;
            i = (j - i) + (j = i);

            Console.WriteLine(i);
            Console.WriteLine(j);

实际上里面包含了一个隐含的变量 (j-i)。

4、算术运算方法

int a,b; 
    a=10;b=12; 
    a=b-a; //a=2;b=12 
    b=b-a; //a=2;b=10 
    a=b+a; //a=12;b=10 

通过以上运算,a和b中的值就进行了交换。表面上看起来很简单,但是不容易想到,尤其是在习惯标准算法之后。
它的原理是:把a、b看做数轴上的点,围绕两点间的距离来进行计算。 
具体过程:第一句“a=b-a”求出ab两点的距离,并且将其保存在a中;第二句“b=b-a”求出a到原点的距离(b到原点的距离与ab两点距离之差),并且将其保存在b中;第三句“a=b+a”求出b到原点的距离(a到原点距离与ab两点距离之和),并且将其保存在a中。完成交换。 
5、指针地址操作方法

因为对地址的操作实际上进行的是整数运算,比如:两个地址相减得到一个整数,表示两个变量在内存中的储存位置隔了多少个字节;地址和一个整数相加即“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进行与运算后,段地址被屏蔽,只保留位移地址。这样就原始算法吻合,从而得到正确的结果。 
此算法同样没有使用第三变量就完成了值的交换,与算术算法比较它显得不好理解,但是它有它的优点即在交换很大的数据类型时,它的执行速度比算术算法快。因为它交换的时地址,而变量值在内存中是没有移动过的。

6、位运算方法

通过异或运算也能实现变量的交换,这也许是最为神奇的,请看以下代码: 
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; 
此算法能够实现是由异或运算的特点决定的,通过异或运算能够使数据中的某些位翻转,其他位不变。这就意味着任意一个数与任意一个给定的值连续异或两次,值不变。 
即:a^b^b=a。将a=a^b代入b=a^b则得b=a^b^b=a;同理可以得到a=b^a^a=b;轻松完成交换。
以上三个算法均实现了不借助其他变量来完成两个变量值的交换,相比较而言算术算法和位算法计算量相当,地址算法中计算较复杂,却可以很轻松的实现大类型(比如自定义的类或结构)的交换,而前两种只能进行整形数据的交换(理论上重载“^”运算符,也可以实现任意结构的交换)。

最后我们来总结下:

当我们来交换两个数时,我们为什么要用临时变量。

比方说我手上有一个苹果,你手上有一个桔子,我想和你交换,是如何进行的,用到媒介没有呢?我们好像是直接交换的,这其中似乎没有用到什么媒介,但是仔细想想过程是用到了媒介的。交换可以归纳为两种方法,1:将物品直接从一个人的左手放到一个人的右手2:借助其它的容器,如果盘,先将物品放在果盘中,再去拿自己要的。其实这两个也只是一个方法而已,第一个借助的“容器”是我们的身上的另一只空闲的手,很容易忽略掉这个问题,以为我们没有用到其它媒介,要是想想我们只有一只手如何交换呢。

物质是有载体的,不管这物质是实体的,还是虚体。一个载体在同一时间上只能承载一种物质,于是在交换两个载体承载的物质时,是不可能同时进行的,估都要用到其它的媒介载体。回到计算机的问题上,存放变量也有载体的,它是内存,而变量可以说是一种虚体物质。这从直观上解释一般为什么我们写交换变量函数时要用到临时变量。

虽然载体承载物质是必需的,我们不能以改变,却可以改变载体承载的性质。载体的容量大小不能改变,但可以去改变承载物体的大小,照样可以达到想要的目的。



版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
蚂蚁金服核心技术:百亿特征实时推荐算法揭秘
阿里妹导读:本文来自蚂蚁金服人工智能部认知计算组的基础算法团队,文章提出一整套创新算法与架构,通过对TensorFlow底层的弹性改造,解决了在线学习的弹性特征伸缩和稳定性问题,并以GroupLasso和特征在线频次过滤等自研算法优化了模型稀疏性,在支付宝核心推荐业务获得了uvctr的显著提升,并较大地提升了链路效率。
3082 0
【Java】java使用反射访问对象方法和成员变量
虽然java是一门静态语言,但是java的反射机制却给java提供了很强大的动态特性,其特点是能让java支持在运行时才能得知名称与内部结构的类,并能访问其所有的方法和成员变量,包括私有方法和私有成员变量。下面我写了一个比较简洁的测试代码,供参考和使用。 测试类 //OBClass.java package com.obo.javaassistdemo; public class
1476 0
任务调度:时间轮算法经典案例解析及应用实现
平时大家的工作中应该会遇到较多需要在某个时间点执行某个任务,比如对运维来说,定时数据库的备份,日志和监控信息的抓取;比如业务系统,某个时间点给某个人群用户发放优惠券,甚至从操作系统角度,人机交互进程、视频播放的实时进程、批处理的后台进程等进程间的调度。。。 所以如何将这些任务高效、精准的调度?是任务调度系统中最重要的命题,当然在业务系统中一个完善的任务调度系统是很复杂的,需要具备能调度、可视化管理、过程可追溯、结果可分析、持久化、高可用等特性,这篇文章主要讨论任务调度逻辑,其余的内容我们后面文章探讨。
108 0
C#.Net组件开发 - 使用Attach to Process实时调试设计器代码(转)
本文转载于:CS框架网http://www.csframework.com/archive/2/arc-2-20110829-1811.htm C#.Net组件开发 - 使用Attach to Process实时调试设计器代码 组件设计的调试对象为两个:一是我们关注的组件本身(Component/Control),二是组件的设计器(Component Designer/Control Designer)。
798 0
使用DTS从RDS PG实时同步数据到AnalyticDB for PostgreSQL
DTS支持从RDS PG将数据实时同步到AnalyticDB for PG,用户可以很方便的搭建起RDS PG到AnalyticDB for PG的数据同步,轻松实现数据的流转和复杂查询的优化。 使用DTS的前提条件 要求同步的数据表,必须建有主键(通过主键来保证源端和目标端表记录一致性)。
2233 0
+关注
eclipse_xu
《Android群英传》作者
262
文章
0
问答
文章排行榜
最热
最新
相关电子书
更多
《2021云上架构与运维峰会演讲合集》
立即下载
《零基础CSS入门教程》
立即下载
《零基础HTML入门教程》
立即下载