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

来源:互联网 发布:linux新建文档命令 编辑:程序博客网 时间:2024/04/24 23:35

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

原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 顶开高贵母亲的两辨 第章母亲抬腰迎合 母亲开始抗拒慢慢变迎合我口诉 母亲开始抗拒慢慢迎合我 第章顶开高贵母亲两瓣 母亲从抗拒慢慢变的迎合我 母亲疯狂迎合我txt小说下载 来吧儿子妈要你 影视片 儿子进来今晚妈让你做个够电影 儿子今晚妈让你做人个够知音网 离婚后和儿子睡觉 儿子让妈给你留个种吧 儿子妈要你的大吉吧 没开灯把小偷当老公 女儿怀孕我给姑爷解决问题 有给儿子口的吗 儿子你的真大 儿子想怎么弄就怎么弄吧 坏儿子还能在深点吗 睡着儿子顶了进来 儿子别急妈是你的小说 儿子今晚妈让你做人个够的视频 母亲和我做完跟父亲做在线阅读 高粱地里野坑头 明明有老公却想跟儿子 玉米地理日刘审一 玉米地理日刘审全集电影 玉米理日刘审 玉米地理日刘审全集 玉米地和娘亲的故事风雨 在玉米地插娘亲第1章 满仓与娘亲怀孕全文阅读 你不要命了你爸还没睡着 玉米地和娘亲故事会文 娘的身子能满足你吗 玉米地上了小翠 在玉米地插娘亲小说下载 在玉米地插娘亲目录 厨房撒下姑妈的裙子txt 在棉花地插娘 穷山村的娘和儿子长篇全文阅读