看了一宿的 Trachtenberg 速算法,終于弄明白了。不得不承認,腦力不夠??
簡單地說,這是一種低空間復雜度的線性算法。也就是說,占用的心理內存很少。看破了其實很簡單。我們以一個三位數×兩位數為例:
三位數×兩位數
對于 m 位 × n 位:
這個公式其實暗含了整個算法的原理。但現在還不容易看出來。
驗證一下:
m = 987
n = 65
as = m.digits
bs = n.digits
padding_as = as + Array.new(bs.size - 1, 0) # => [7, 8, 9, 0]
padding_bs = bs + Array.new(as.size - 1, 0) # => [5, 6, 0, 0]
d = 0
(0..(as.size + bs.size - 2)).each do |k|
memo = 0
(0..k).each do |i|
# puts "i: #{i}, j: #{k - i} --> #{padding_as[i]} * #{padding_bs[k - i]}"
memo += padding_as[i] * padding_bs[k - i]
end
memo *= (10 ** k)
d += memo
end
d # => 64155
這里的 a 也好,b 也好,都不超過 9 。所以,其乘積也不會過百。于是,我們定義兩個函數:
-
t(a×b)
: the tens digit of a×b; -
u(a×b)
: the units digit of a×b;
這樣,我們只需要從低位往高位依次計算,并把進位傳遞到高位就可以得到一個線性算法了。
記得加上「進位 c_i 」
以百位 (10^2) 為例,我們需要做的就是把所有下標加起來等于 2 的「個」位,以及所有下標加起來等于 2 - 1 的「十」位,還有上一位上傳來的進位,加起來。
987×65
現在,我們的任務只剩下找到一種好記的記法,記住哪些取個位、哪些取十位,就大功告成了。
而 Trachtenberg 的天才就在于,他把算式橫過來寫了!還可以有這種騷操作?? 豎線表示取個位,斜線表示取十位,像一個滑動窗口一樣滑過被乘數:
987×65
現在,當我們回過頭去看那個通用公式,會發現:其實它已經揭示了所有的秘密。我們要做的是兩件事:
- 找對一種方法,能夠方便地記住「下標加起來等于 k 」的所有項乘積的「個」位之和;
- 找對一種方法,能夠方便地記住「下標加起來等于 k - 1 」的所有項乘積的「十」位之和;
好吧,這其實就一件事:
豎線規則也好,斜線規則也好,都干的這一件事。
也就是說,不存在什么「豎線規則」「斜線規則」,我們只需要在頭腦中想象好一組配對,先計算「個位和」,再計算「十位和」(作為下一組的進位)。
class Array
def l; self[0] end
def r; self[1] end
end
class Trachtenberg
def initialize(m, n)
m, n = n, m if m < n
as = m.digits
bs = n.digits
@as = Array.new(bs.size - 1, 0) + as + Array.new(bs.size, 0) # => [0, 7, 8, 9, 0, 0]
@bs = bs # => [5, 6]
@base = bs.size - 1
end
def tens(l, r = 1) l * r / 10 end
def units(l, r = 1) l * r % 10 end
def calculate
d = []
carry = 0
@as[@base..-1].each_with_index do |a, ia|
pairs = @as[ia...ia + @bs.size].zip(@bs.reverse)
sum = pairs.reduce(0) { |memo, tuple| memo + units(tuple.l, tuple.r) } + carry
d << units(sum)
carry = pairs.reduce(0) { |memo, tuple| memo + tens(tuple.l, tuple.r) } + tens(sum)
end
# d => [5, 5, 1, 4, 6]
d.reverse.reduce('') { |memo, digit| memo + digit.to_s }.to_i
end
end
t = Trachtenberg.new(987, 65)
t.calculate # => 64155
Trachtenberg 實在太聰明了,并不僅僅是因為他竟然能夠找到這種圖形化表示方法,而在于把這套速算系統分解開來,每個部分都很簡單(包括「調整計算順序以簡化計算」其實也是一種很常見的思路),但是你就是想不到。也許就像豎式乘法之于橫式乘法,他的心理寄存器遠大于我們吧,所以才能把這些部件組合出如此精妙的算法來。
大家要想知道更多細節,去看 wiki 吧。