同《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 |
可以看出这种运算方法的效率是何其底下,可能原作者在这里是为了演示后台线程的长时间运行不会阻塞UI线程的效果。但是我不得不吐槽的是,在这里使用一个纯粹的sleep也比这种很低效的运算方法强啊。
吐槽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函数的实现,我们了解到如何通过在算法上进行优化从而极大的提高应用的计算效率和显示效果。
在进行软件开发中,做对只是最最基本的一步,做好才是最优价值的。