• 从Swap函数谈加法溢出问题


    1.      初始题目

    面试题:不用额外的变量,实现一个Swap函数,交换两个参数的值(问题1

    这个题目太经典,也太简单,有很多人都会不假思索结出答案:

    //Code 1
    void Swap(int* a, int* b)
    {
        *a = *a + *b;
        *b = *a - *b;
        *a = *a - *b;
    }

    但真正的难点来了,接下来,就会有面试的第二问:指出code1算法的问题,并修正(问题2

    2.      错误思路

    一些毁人不倦的书,把其中的问题归结到了溢出(overflow)。修改方法是用异或替代加减,即:

    //Code 2
    void Swap(int* a, int* b)
    {
        *a = *a ^ *b;
        *b = *a ^ *b;
        *a = *a ^ *b;
    }

    诚然,在很多场合,大数的加法都会带来溢出的问题。但Code1 中的Swap函数真会构成溢出问题么?(问题3

    2.1 错误的想法

    大家都能清楚的描述加法溢出的产生原因:运算结果超出了变量的表示范围。

    发生溢出时,结果会是多少呢?有很多人是知道的,但可能表述得不太清晰:舍弃超出变量内存范围的高位(加法只会舍弃最高位),保留在范围内的低位。

    用这个概念,去分析问题3,恐怕是很难得到明确的答案的。很多人简单看看,认为执行两个很大的数的加法后,运算结果丢了最高位,所以会构成问题。

    和问题3的知识点相同的,有另外一个问题:

    判断两个int型变量相加,是否会溢出?不会溢出返回0否则返回非0整数。下面的方法是否可行?(问题4

    //Code 3
    int IsOverFlow(int x, int y)
    {
        int sum = x + y;
        return (sum - x == y) && (sum - y == x);
    }

    对问题3,回答“会构成问题”的同学。回答问题4时,应该会认为“方法可行”。理由是,认为溢出后,信息丢失,以后的运算都不再能得到正确结果。

    2.2  加法的溢出

    事实上,问题3的答案是:不会溢出;问题4的答案是:方法不可行。

    用一下数学符号表述,可以清晰的得到正确答案。

    假设一个整型变量有w位,那么,整型变量的表示范围是: -2w-1 ~ 2w-1-1。 当求和结果超出这个范围时,就发生溢出。即正数的绝对值大于2w-1-1时,发生正溢出,负数绝对值大于2w-1时,发生负溢出

    加法的溢出,只会发生在两个求和参数同正负符号的时候。

    第w-1位

    (符号位)

    第w-2位

    第w-3位

    …………

    第2位

    第1位

    第0位

    一正一负时,肯定不会发生溢出,因为这时:|x +y| < max{|x|, |y|}

    对于两个很大的正数相加,发生正溢出时,第w-1位肯定为1,假设除去最高位时,剩下w-1位(第0至w-2位)的内存可以表示为数:r。即 x + y = r + 2w-1

    由于是有符号数,第w-1位是符号位,由于符号位是1,说明结果是负数,采用补码,即运算结果为:sum =- (2w-1 – r) = r-2w-1。即于sum = r + 2w-1-2w=x + y - 2w

    同时,两个绝对值很大的负数相加,发生负溢出后的结果是: s = x + y + 2w

    当两个正数产生加法溢出时,运算结果s = x + y – 2w。 此时, s < 0。

    再做减法:s – x = x + y - 2w - x = y + 2w 由于|y| <2w-1,有 |y - 2w| >2w-1 ,发生负溢出,在结果上又加上了 2w。即,x + y - 2w – x + 2w ,这个结果等于y。

    因此问题3的答案是“不构成问题”,问题4的答案是:不可行。

    判断两个int型变量相加,是否会溢出?不会溢出返回0否则返回非0整数。这个函数需要怎么设计?(问题5

    发生正溢出后,结果将是负数;发生负溢出后,结果将是正数。

    因此可以这样做:

    //Code 4
    int IsOverFlow(int x, int y)
    {
        int sum = x + y;
        int posOver = (x > 0) && (y > 0) && (x + y < 0);
        int negOver = (x < 0) && (y < 0) && (x + y > 0);
        return posOver || negOver;
    }
     


    3.      解决问题

    回顾一个经典的算法,快排:

    //Code 5
    void QSort(int a[], int start, int end)
    {
        if (start >= end)
            return;
        int lastLessIdx = start;
        for (int traveler = start + 1; traveler <= end; ++traveler)
        {
            if (a[traveler] < a[start])
            {      
                Swap(&a[++lastLessIdx], &a[traveler]);
            }      
        }
        Swap(&a[lastLessIdx], &a[start]);
        QSort(a, start, lastLessIdx - 1);
        QSort(a, lastLessIdx + 1, end);
    }
    


    其中用到了Swap函数。但是,直接用code1中给出的Swap函数行吗?

    不行。Code1中的Swap函数,没有考虑自己与自己换的情况。当需要自己与自己交换时(快排中lastLessIdx + 1 == traveler 时),Code1直接把值变成了0。

    因此,修正方法很简单,判断是不是自己在和自己交换即可:

    //Code 6
    void Swap(int* a, int* b)
    {
        if (a == b)
            return;
        *a = *a + *b;
        *b = *a - *b;
        *a = *a - *b;
    }



  • 相关阅读:
    Thrift安装编译指南
    Linux磁盘与文件系统管理
    你懂得C,所以C++也不在话下
    加速NFV(网络功能虚拟化)数据面:SR-IOV和DPDK[原文意译]
    十张图看懂SDN与NFV的区别与联系?
    Lambda表达式用法
    论文写作+gnuplot制图
    私钥、公钥、数字签名和数字证书
    synchronized和Lock的异同
    介绍HTTP协议的传输过程
  • 原文地址:https://www.cnblogs.com/dyllove98/p/3230989.html
Copyright © 2020-2023  润新知