不用判断语句,求两个数的最大值

Hash 思想

1. 首先引进一种简单的思路:
  假设我们对两个无符号整型进行求最大值的操作:
复制代码
#define BIT_SIZE (sizeof(int) * 8)
int max_ui32(unsigned int a, unsigned int b)
{
    int hash[2] = {a, b} ;              
    int index = (a - b) >> (BIT_SIZE-1) & 1 ;   //   a <=> b : index
                              //     >=    :   0
                              //      <    :   1  
    return hash[index] ;
}
复制代码

  此方法的精髓在于, 两个无符号整型的减法会导致符号位的变化. 
  当 a >= b 时, 结果为正数, 因此符号位为 0.
  当 a < b 时, 结果为负数, 因此符号位为 1. 
  显然, 此方法只是用于无符号整型, 更准确的说, 应该是只适用于同号的运算. 因为存在溢出问题(例如当一个很大的正数减去一个很小的负数, 如2000000000 和 -2123456789相减, 会导致结果向符号位进位, 从而溢出), 所以不能适应异号的运算.

2. 以下办法我个人认为也是应用了 hash 的思想, 只不过此程序通过枚举形成了一个映射, 从而处理了异号之间的问题. 适用于任何符号.
复制代码
 
  
// 引用他人程序
#define signed_bit(x)      (( (x) & 0x80000000) >> 31)
#define value_stuff(x)     ( x & 0x7FFFFFFF)

#define value_diff(x, y) signed_bit( value_stuff(x) - value_stuff(y) )


int Max( int x, int y)
{
  int nums[2][2][2] = 
  {
                x,                //000
                y,                //001
                x,                //010
                x,                //011
                y,                //100
                y,                //101
                x,                //110
                y                 //111
  };


  int idx0 = signed_bit(x);
  int idx1 = signed_bit(y);
  int idx2 = value_diff(x, y);

  return nums[idx0][idx1][idx2];
}
复制代码

  

将一个int分成两部分,最高位为符号位,剩下的31位为负载部分。

符号位通过 signed_bit 得出,为 0(如果>=0) 或者为1(< 0)

负载部分通过 value_stuff 得出,介于0到0x7FFFFFFF之间,总为正数。

value_diff宏将两个数的负载部分进行减法运算(由于负载在0-0x7FFFFFFF之间且总为正数,因此不可能溢出),为0表示计算后的结果为正数,则x的负载大于或者等于y,否则x的负载小于y。

然后预先建立一个3维数组, 建立依据是逻辑运算:

对于的维分别代表:

signed_bit(x), signed_bit(y), value_diff(x, y)

然后给这个数组初始化:

x,                //000 x为正数,y为正数,x负载>=y, 那么x为最大值

y,                //001 x为正数,y为正数,x负载<y, 那么y为最大值

x,                //010 x为正数,y为负数,那么x为最大值 (无需考虑负载部分)

x,                //011 x为正数,y为负数,那么x为最大值(无需考虑负载部分)

y,                //100 x为负数,y为正数,那么y为最大值(无需考虑负载部分)

y,                //101 x为负数,y为正数,那么y为最大值(无需考虑负载部分)

x,                //110 x为负数,y为负数,x负载>=y, 那么x为最大值

y                 //111 x为负数,y为负数,x负载<y, 那么y为最大值

剩下就是分别得到3个维的index,并返回数组中的值。

3. 还是一个应用 hash 的变种, 只不过这次变化在思路上有所不同, 因为有对于异号运算的处理, 所以能够适用于所有符号.
复制代码
#include "stdafx.h"
#include <iostream>
using namespace std ;

#define BIT_SIZE (sizeof(int) * 8)

int max_same(int a, int b)
{
    int hash[2] = {a, b} ;              
    int index = (a - b) >> (BIT_SIZE-1) & 1 ;   // The 31-th bit is 0 if a >= b, 
                                                    // is 1 if a < b in 2'comlement.
    return hash[index] ;
}

int max_diff(int a, int b)
{
    // One is non-gegative while other is negative.
        // Of course non-negative is bigger than negative one.
    int hash[2] = {a, b} ;              
    int index = a >> (BIT_SIZE - 1) & 1 ;       // sign bit of a : index
                                                //       0       :   0
                                                //       1       :   1
    return hash[index] ;
}

int max(int a, int b)
{
    int hash[2] = {a, b} ;
    int (*fun[2])(int a, int b) = {max_same, max_diff} ;

    int index = (a ^ b) >> (BIT_SIZE - 1) & 1 ; // Is 1 if 'a' & 'b' are same sign,
                                                    // otherwise is 0.
    
    return fun[index](a, b) ;
}

int _tmain(int argc, _TCHAR* argv[])
{
    cout <<"max_same: " <<max_same(2000000000, -2000000000) <<endl ;    // 溢出
    cout <<"max_diff: " <<max_diff(2000000000, -2000000000) <<endl ;
    cout <<"max: " <<max(2000000000, -2000000000) <<endl ;              // 根据不同符号情况, 调用正确处理过程
    cout <<"max: " <<max(2000000000, 0) <<endl ;                        // 根据相同符号情况, 调用正确处理过程

    return 0;
}

复制代码
  对于函数 max_same 不做过多解释, 请看第一个简单的示例.
  对于函数 max_diff, 在逻辑上也是十分简单的: 一个负数和一个非负数比大小, 结果显然是非负数一定大于负数. 因此, 只要两个变量异号, 则凡是符号位是 1 的变量, 必定小于另一个. 因为没有减法操作, 从而避免了溢出的问题.
  对于最终的函数 max, 则是通过异或运算判断两个变量是同号还是异号. 根据不同的情况调用相对应的 max_XXXX 函数, 得到正确的结果. 


数学插值

接下来看一个, 这个方法我目前没明白是如何实现的, 待高手指点.
复制代码
 
  
// 引用他人程序
new_max (int x, int y)
{
        int xy, yx;

        xy = ((x - y) >> 31) & 1;
        yx = ((y - x) >> 31) & 1;
        return xy * y + yx * x + (1 - xy - yx) * x;
}
复制代码

这里还有一种更精妙的方法, 正所谓 "棋妙子无多". 
复制代码
 
  
// 引用他人程序
int max_same(int x,int y)
{
    unsigned int z;

    z=((x-y)>>31)&1;

    return (1-z)*x+z*y;        // 各人认为, 这种数学方法与 hash 是殊途同归. 但是在使用方便程度上更胜一筹.
}

int max_diff(int x, int y)
{
    unsigned int z;

    z=(x>>31)&1;
    return (1-z)*x + z*y;
}

int max(int x, int y)
{
    unsigned int z;

    z=((x^y)>>31)&1;
    return (1-z)*max_same(x,y) + z*max_diff(x,y);   // 这里极大的体现出了使用数学公式而非 hash 映射的简洁性. 
                                  // 使用 hash 则必须要使用函数指针.
} 
复制代码
   对于函数 max_same: 在不考虑溢出的情况下,unsigned int z=((x-y)>>31)&1的值有两种可能,x>=y时为0,x<y时为1,所以当x>=y时(1-z)*x+z*y的值为x,当x<y时其值为y,所以(1-z)*x+z*y就是x,y的最大值。函数如下:
   对于函数 max_diff: 在考虑溢出的情况下,unsigned int z=((x^y)>>31)&1的值有两种可能,x、y同号时为0,x、y异号时为1。当x、y同号时x-y不会溢出,可参考1,得出最大值;当x、y异号时,取正的那个就是最大值。函数如下:


其它

此算法核心思想是通过异或运算, 找到最高位的 1 的位置, 利用此位置做除法, 将结果映射到 hash 中. 思想略显复杂, 此程序作者数学功底扎实.
复制代码
// 引用他人程序
#define SHIFT (sizeof(int) * 8 - 1)

int max(int x, int y)
{
        unsigned int m, n;
        int ary[2] = {x, y};

        m = x ^ y;
        m = m & ~(m / 2) & ~(m / 4);
        n = m | 0x01;

        return ary[ ((x & n) + n / 2) / n ^ !(m >> SHIFT)];
}
复制代码

  说明一下吧:

这个算法是企图对x、y按位比较,简单地说,如果不考虑+、-号,则在第一次出现两者的位不相同时,该位是 1 的值比较大,如

00001111111100001111000011110000

00001110111100111111110011111010

左数第8位上,前者是1,后者是0,所以前者比后者大。

对于有符号的情况我们在后面再分析。

1、首先,x^y把x、y中不相同的位置1了,假设x^y=0...01X...X,后面的X表示任意的0或1,这表示在1所在的位置之前的位x、y是相等的,在这一位上或者x为1、y为0;或者x为0、y为1;我们主要是要确定哪一个为1。可惜的是我们无法得到0...010...0这个数以及1所在的位号(不然就要用循环去确定,这不符合题目的要求)

2、设m=x^y,则当x > y时,x在该位上为1,所以x  & m = 0...01X...X,所以(x & m) / m 大约为1(注意“大约”,因为有等于0的可能存在,我们等会儿把它排除掉);当x < y时,x & m = 0...00X...X,所以(x & m) / m = 0,现在我们的程序好象可以工作了。。。

3、但是,有一个BUG,如果m == 0呢?(x & m) / m会溢出!m = 0说明x=y,现在的处理是m = m | 0x01,即把最后一位置1,使m >= 1!

这样当m == 0时,我们不管(x & m) / m算出来的是个啥结果,反正哪个都一样(x == y嘛!),而m > 0时,最后一位是不是1不影响我们的计算结果了!

4、前面说过这里还有个BUG,即当x在该位上为1时,我们不能确定(x & m) / m 一定 = 1,因为尽管x & m在该位上也为1,但x & m还是有可能 < m的(还是感叹如果我们能得到0..010...0这个数就好了!)。现在的处理是m = m & ~( m / 2 ) & ~( m / 4),把m这个数变成0...0100X...X,算式也修正为((x & m) + m / 2) / m,这个可以满足我们的要求了

5、最后再来考虑一下有符号的情况

设t=((x & m) + m / 2) / m,f = m的第一位=m >> SHIFT

则当m = 0时,表示x、y同号,这时按上面的方式确定最大值

当m = 1时,x、y一正一负,t表示x首位,为1则x < 0,为0则x >= 0

总的说来:

(1)当f=0,t=0时最大值为y -----1

(2)当f=0,t=1时最大值为x -----0

(3)当f=1,t=0时最大值为x -----0

(4)当f=1,t=1时最大值为y -----1

显然是个同或运算, 可以表示为: t ^ !f

如果不让用!那就是t ^ (~f )

关于 m = m & ~(m / 2) & ~(m / 4);

是把m变成:0...0100X...X这样的形式,即1后面有两个0. 至于为什么有两个 0, 是为了让 (x & m) + m/2 的时候, 不产生进位而避免溢出. (如果溢出将导致 ((x & m) + m/2) / m 的结果为 0).

于是当x在该位上为1时:

(x & m) + m / 2 >=0...0100...0 + 0...0010...0 = 0...0110...0 >=m

(x & m) + m / 2) <= 0...01001...1 + 0...001001...1 <= 0...011...10 < 2m

所以((x & m) + m / 2) / m = 1

而当x在该位上为0时:

(x & m) + m / 2 <= 0...00001...1 + 0....001001...1 = 0...0011...1 < m

所以((x & m) + m / 2) / m = 0 

一点提升:
a ^ b 的最高位 ---- 判断符号是否相等, 对于异或操作的理解应为: 将不同的位置为 1.
a - b  的最高位 ---- 在符号相同的情况下判断大小
a       的最高位 ---- a 的符号
b       的最高位 ---- b 的符号

原文地址:http://www.cnblogs.com/walfud/articles/2176238.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值