陳鍾誠

Version 1.0

查表法 Table Lookup

翻譯系統 (簡易英翻中)

執行結果

$ node e2c a dog chase a cat
[ '一隻', '狗', '追', '一隻', '貓' ]

程式碼

var e2c = { dog:"狗", cat:"貓", a: "一隻", chase:"追", eat:"吃" };

function mt(e) {
  var c = [];
  for (i in e) {
    var eword = e[i];
    var cword = e2c[eword];
    c.push(cword);
  }
  return c;
}

var c = mt(process.argv.slice(2));
console.log(c);

執行結果:

$ node e2c.js a dog
[ '一隻', '狗' ]
$ node e2c.js a dog chase a cat
[ '一隻', '狗', '追', '一隻', '貓' ]
$ node e2c.js a dog chase a car
[ '一隻', '狗', '追', '一隻', undefined ]

補充說明:請注意上面的 e2c[eword] 這一行不能改用 e2c.eword, 原因是 e2c.eword 是在查詢 eword 這個詞,也就是 e2c[’eword’] 的意思,上面範例中e2c[’eword’] 會是 undefined。

e2c[eword] 才是在查詢像 e2c[‘dog’] 這樣的內容。

請看下列的示範:

> var e2c = { dog:"狗", cat:"貓", a: "一隻", chase:"追", eat:"吃" };
undefined
> e2c
{ dog: '狗', cat: '貓', a: '一隻', chase: '追', eat: '吃' }
> e2c.eword
undefined
> var eword='dog'
undefined
> eword
'dog'
> e2c.eword
undefined
> e2c['dog']
'狗'
> e2c[eword]
'狗'

用查表加速 – 以費氏數列為例

傳統用遞迴方式的費氏數列算法,會耗費很久的時間:

function fibonacci (n) {
  if (n < 0) throw Error('fibonacci:n < 0')
  if (n === 0) return 0
  if (n === 1) return 1
  return fibonacci(n - 1) + fibonacci(n - 2)
}

var startTime = Date.now()
console.log('fibonacci(43)=', fibonacci(43))
var endTime = Date.now()
var milliSeconds = endTime - startTime
console.log('time:%dms', milliSeconds)

執行結果:

$ node .\fiboanacci.js
fibonacci(43)= 433494437
time:25530ms

加入查表,讓已經算過的就不需要算第二次,第二次之後改用查的!

var fib = [0, 1]

function fibonacci (n) {
  if (n < 0) throw Error('fibonacci:n < 0')
  if (fib[n] != null) return fib[n]
  fib[n] = fibonacci(n - 1) + fibonacci(n - 2)
  return fib[n]
}

var startTime = Date.now()
console.log('fibonacci(43)=', fibonacci(43))
var endTime = Date.now()
var milliSeconds = endTime - startTime
console.log('time:%dms', milliSeconds)

執行結果

$ node .\fiboanacci_lookup.js
fibonacci(43)= 433494437
time:14ms

用查表加速 C(n,k) 函數

排列組合中的 C(n, k) 函數,其定義為 n!/(k!(n-k!)) ,我們可以直接套公式計算之。

function factorial(n) {
  var p = 1
  for (i=1; i<=n; i++) {
    p = p * i;
  }
  return p
}

function c(n, k) {
  return factorial(n) / (factorial(k)*factorial(n-k))
}

console.log("c(5,2)=", c(5,2))
console.log("c(7,3)=", c(7,3))
console.log("c(12,5)=", c(12,5))
console.log("c(60,30)=", c(60,30))

執行結果如下:

$ node .\Cnk
c(5,2)= 10
c(7,3)= 35
c(12,5)= 792
c(60,30)= 118264581564861470

其中前三個答案是對的,但是第四個 c(60, 30) 的答案很接近,但卻是錯的,因為計算過程中的 factorial(60) = 60! 太大,導致javascript 的雙精度浮點數無法精確表示,於是原本應該 c(60, 30) = 118264581564861420 ,卻計算出了 c(60, 30) = 118264581564861470 了!

對於 c(n,k) 函數,我們還可以採用《帕斯卡公式》(Pascal’s rule) 進行遞迴計算,其算式如下:

C(n,k) = C(n-1, k-1) + C(n-1,k) for (1 <=k<=n)

於是我們可以寫出下列遞迴式:

function c(n, k) {
  if (k < 0 || k > n) return 0
  if (k > n-k) k = n - k
  if (k==0 || n <= 1) return 1
  return c(n-1, k) + c(n-1, k-1)
}

console.log("c(5,2)=", c(5,2))
console.log("c(7,3)=", c(7,3))
console.log("c(12,5)=", c(12,5))
console.log("c(60,30)=", c(60,30))

但是這個遞迴重複計算了太多次,導致執行 c(60, 30) 時會很久出不來,感覺好像當掉了,以下是執行結果 …

PS D:\ccc\book\algjs\code\09-dynamicProgramming\combinatorial> node .\CnkR
c(5,2)= 10
c(7,3)= 35
c(12,5)= 792   // 後面就很久出不來了 ....

如果我們採用《查表法》的方式加速遞迴式,那麼很快就能計算出來了,以下是加了查表法的遞迴程式

var C = []

function c(n, k) {
  if (k < 0 || k > n) return 0
  if (k > n-k) k = n - k
  if (C[n] == null) C[n] = []
  if (C[n][k] != null) return C[n][k]
  if (k==0 || n <= 1)
    C[n][k] = 1
  else 
    C[n][k] = c(n-1,k) + c(n-1, k-1)
  return C[n][k]
}

console.log("c(5,2)=", c(5,2))
console.log("C=%j", C);
console.log("c(7,3)=", c(7,3))
console.log("c(12,5)=", c(12,5))
console.log("c(60,30)=", c(60,30))

執行結果:

PS D:\ccc\book\algjs\code\09-dynamicProgramming\combinatorial> node .\CnkRLookup
c(5,2)= 10
C=[null,[1],[1,2],[1,3],[null,4,6],[null,null,10]]
c(7,3)= 35
c(12,5)= 792
c(60,30)= 118264581564861420

結語

對於需要重複計算的事項,我們只要先記在表格裡面,下次需要時就不需要再次計算,只要從表格裡面查出來就行了!

如果該計算比查表時間多,那麼這樣的方法就可以加快速度!