不用判斷語句,求兩個數的最大值

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章