遞歸算法之不用乘號的乘法——用位移實現乘法(dart語言實現)


  前兩天突發奇想,寫一個乘法的實現,但不用乘號*。並測試一下性能如何。因此就有了下面的代碼:(本文主要目的是為了玩遞歸和位移,因此僅限自然數)

首先,標准乘法:

1 int commonMultiplication(int a, int b) => a * b;

第二,從數學的角度,乘法其實就是加法,只是加法的簡寫而已,因此 a * b 可以理解為 b 個 a 相加;故得出用加法代替的乘法。為了減少加法的次數,取 a, b 兩數的最小值進行循環:

 1 int plusMultiplication(int a, int b) {
 2   var t = a > b ? b : a, r = 0;
 3   if (t == a) {
 4     a = b;
 5     b = t;
 6   }
 7 
 8   while (b-- > 0) r += a;
 9   return r;
10 }

最后,位移乘法。計算機進行位移操作的速度是非常快的,因此,如果能把乘法換算成位移操作的話,速度會不會更快?但是根據計算機二進制的特點,每次向左位移,相當於是乘了2的n次方,怎么辦?我們看看例子:

  31 * 18

怎么用位移來實現呢?首先我們取小的數:18,進行拆解。拆解的算法是,任何一個自然數,在計算機中都可以用二進制表示,那么,任何一個自然數,也可以拆解為2的冪的和。比如 18 的二進制表示 為  10010,拆解成十進制,即為 18 = 16 + 2;那么上面可以拆解為:

  31 * 18 = 31 * (16 + 2) = 31 * 16 + 31 * 2

此時即可換成位移的算法:

  31 * 18 = 31 * (16 + 2) = 31 * 16 + 31 * 2 = 31 * 2^4 + 31 * 2^1 = (31 << 4) + (31 << 1)

是不是轉成了位移?這里要注意對乘數 18 的處理,位移的關鍵是找到乘數 b 如何拆解為 2 的冪,這里要遞歸拆解,以18為例,第一次的關鍵是找到16這個2的冪,然后用18 - 16,結果等於 2,繼續找2等於多少2的冪,直到差小於2為止。同時,考慮到a*b,在實際計算中即使小的乘數也很大的情況,采用類似二分搜索的方式,給定階數,從大的階數向小的階數遞歸。階數的設定可以參考計算機的位數,即32位還是64位。因此,計算一個數可以拆分出來的最大2的冪的代碼如下:

1 int _bitShift(int d, int order) {
2   if (order == 0) return 0;
3   var i = 0;
4   for (var t = d; (t >>= order) > 0; d = t, t = d) i += order;
5   return i + _bitShift(d, order >> 1);
6 }

位移乘法實現的代碼

int bitMultiplication(int a, int b, int order) {
  var t = a > b ? b : a, r = 0;
  if (t == a) {
    a = b;
    b = t;
  }

  for (var i = _bitShift(b, order); i > 0; i = _bitShift(b, order)) {
    r += a << i;
    b -= 1 << i;
  }

  return b == 1 ? r + a : r;
}

經過采用隨機大數進行多次循環測試,結果顯示,用加法實現的乘法速度最慢,最快的仍然是 普通乘法,即用乘號的乘法;位移乘法居中。看來底層的計算機指令還是對*乘法做了優化。

 

最后說明一下,本文純屬原創,沒有使用dart 自帶math包,尤其是在求一個數可以拆出來的最大的2的冪的數字時。否則直接用math包中的log函數即可得出結果,但這樣就起不到自己練習位運算和遞歸的目的了。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM