同《QT Demo 之 threading(2) Spinner》一樣,這一章也是把《QT Demo 之 threading》中的一個和threading完全獨立的部分拿出來分析一下。
在中的WorkerScript.onMessage函數中有如下的調用,用來計算給定row和column下計算triangle的值:
WorkerScript.onMessage = function(message) {
//Calculate result (may take a while, using a naive algorithm)
var calculatedResult = triangle(message.row, message.column);
//Send result back to main thread
WorkerScript.sendMessage( { row: message.row,
column: message.column,
result: calculatedResult} );
}
triangle的意義是三角形,但是我們通過程序的運算結果知道在這裏它實際表達的是求排列組合中的組合數。
排列組合簡述
而排列組合的定義(來自百度百科)如下:
排列組合是組合學最基本的概念。所謂排列,就是指從給定個數的元素中取出指定個數的元素進行排序。組合則是指從給定個數的元素中僅僅取出指定個數的元素,不考慮排序。
其中組合的計算公式如下:
原有算法
示例中的實現代碼如下:
function triangle(row, column) {
if (cache[row][column])
return cache[row][column]
if (column < 0 || column > row)
return -1;
if (column == 0 || column == row)
return 1;
return triangle(row-1, column-1) + triangle(row-1, column);
}
這種計算方法的原理是採用了下述的公式:
即:triangle(row-1, column-1) + triangle(row-1, column) = triangle(row, column)
這種計算方法本身沒有問題,但是由於其採用加法進行遞歸處理,而且是一變二的遞歸調用,那麼這個調用次數就非常可觀了。
經過測試,從triangle(63,1)到triangle(63,5),triangle函數每次的遞歸調用次數和花費時間如下:
(63,1) | (63,2) | (63,3) | (63,4) | (63,5) | |
調用次數 | 125 | 3905 | 79421 | 1191329 | 14057693 |
運行時間(ms) | 1 | 15 | 346 | 4492 | 52352 |
吐槽2:作者在開始定義了一個64*64的二維數組,但是,在代碼中完全沒有使用到啊!!!真是做到了既浪費空間又不提高時間效率!
改進算法
通過上面我們提到的組合計算公式,我們可以推導出下面的公式:
按照上面的公式,我們對triangle函數進行了優化:
function triangle(row, column) {
if (cache[row][column])
return cache[row][column]
if (column < 0 || column > row)
return -1;
if (column == 0 || column == row)
return 1;
return triangle(row, column-1) / column * (row+1-column);
}
此處仍然是採用了遞歸的方法,故邊界條件和遞歸終止條件都沒有變,只是修改了每次遞歸的算法。
經過測試,從triangle(63,1)到triangle(63,5),triangle函數每次的遞歸調用次數和花費時間如下:
(63,1) | (63,2) | (63,3) | (63,4) | (63,5) | |
調用次數 | 1 | 2 | 3 | 4 | 5 |
運行時間(ms) | 0 | 0 | 0 | 0 | 0 |
和上面的算法結果一比較就知道原有算法是何其的效率低下。
總結
這一節從Qt方面來講沒有學習新知識,但是通過分析和優化triangle函數的實現,我們瞭解到如何通過在算法上進行優化從而極大的提高應用的計算效率和顯示效果。
在進行軟件開發中,做對只是最最基本的一步,做好纔是最優價值的。