|
template< class T, class I >
void solution_5( T BigArr[], T ResArr[] )
{
//同solution_4,略
//这个后续元素比ResArr中最小的元素大,则替换。
if( BigArr[i] > ResArr[MinElemIdx] )
{
ResArr[MinElemIdx] = BigArr[i];
if( MinElemIdx == ZoneBeginIdx )
--ZoneBeginIdx;
//太多杂乱元素的时候排序
if( ZoneBeginIdx < 9400 )
{
std::sort( ResArr, ResArr + RES_ARR_SIZE, std::greater() );
ZoneBeginIdx = MinElemIdx = RES_ARR_SIZE - 1;
continue;
}
//同solution_4,略
}
代码中的9400是经过试验得出的最好数值,即在有600个元素无序的时候进行一次排序。测试的结果令人惊喜,用时仅400毫秒左右,约为solution_4的五分之一,这也证明了上述思想是正确的。
殚思极虑
脚步永远向前,在取得solution_5这样的成果之后,仍然有必要分析和优化它。对这一看似已经完美的算法进行下一次优化要从哪里着手?这时候要借助于性能剖分工具了,常用的有Intel的VTune以及Microsoft Visual C++自带的profile等。使用MS profile对solution_5分析产生的报告如下(略去一些无关数据):
Func Func+Child Hit
Time % Time % Count Function
---------------------------------------------------------
37.718 1.0 3835.317 99.5 1 _main (algo.obj)
111.900 2.9 3220.082 83.6 1 solution_5(int * ...
0.000 0.0 3074.063 79.8 112 _STL::sort(int *,...
……
可以发现sort函数的调用用去了将近80%的时间,这表明sort函数是问题所在,优化应该从这里着手。但正如前文所说,STL的sort已经高度优化速度很快了,再对他作优化是极难的;而且sort函数里又调用了其它STL内部函数,如蛛丝般牵来绕去,读得懂已经不是一般人可完成的了,优化从何谈起?
我们不能左右天气,但我们可以左右心情;我们不能修改sort函数,但我们可以控制sort的调用。再看看solution_5里对sort的调用有没有什么蛛丝马迹可寻:
std::sort( ResArr, ResArr + RES_ARR_SIZE, std::greater() );
这个调用是把结果数组ResArr重新排序一遍。需要把整个ResArr完全重新排序吗?答案是需要的,但可以不使用这个方法。因为ResArr里的元素绝大部分是有序的(结合上文可知前面94%的元素都有序),待排序的只是6%。只要把这600个数据重新排序然后将前后两个有序数组归并为一个有序数组即可(归并算法的时间复杂度为O(n+m)),将因为排序的数据量较少而大大节约时间。写代码如下:
template< class T, class I >
void solution_6( T BigArr[], T ResArr[] )
{
//同solution_5,略
//太多杂乱元素的时候排序
if( ZoneBeginIdx < 9400 )
{
std::sort( ResArr + 9400, ResArr + RES_ARR_SIZE, std::greater() );
std::merge(ResArr, ResArr + 9400, ResArr + 9400, ResArr + RES_ARR_SIZE, BigArr, std::greater() );
memcpy( ResArr, BigArr, sizeof(T) * RES_ARR_SIZE );
//同solution_5,略
}
经测试,solutio_6的运行时间为250毫秒左右,比solution_5快了将近一半,通过profile分析报告计算sort函数和merge函数的占用时间总计约为执行时间的19.6%,远小于solution_5的占用时间。
结束语
一番努力之后,终于将一个原来需要近一个小时才能解决的问题用250毫秒完成,文章到这里要完结,不过上述算法仍有可优化的余地,这就要读者朋友自己去挖掘了。我希望看到这篇文章的人不仅仅是赞叹算法的奇妙,更希望能够学会算法优化的方法和技巧。对于算法优化的方法,我总结如下(仅供参考及抛砖引玉之用):
不断地否定自己的方法[全文]
减少重复计算[solution_3];
不要做没要求你做的事[solution_3];
深化对需求的理解[solution_4];
温故而知新,多重读自己的算法代码[solution_4];
从程序的输出(或者中间结果)里找突破[solution_5];
时刻保持头脑清醒,常常跳出习惯的框框[solution_5];
善于使用工具[solution_6];
养成解决一个问题思考多个方案的习惯[全文]。
最后要讲的一点就是STL里提供了一个可以直接完成这一问题的算法——nth_element。经测试,nth_element在大数组比较小的时候速度比以上算法都要快,但在大数组尺寸为1亿的时候所用的时间为1.3秒左右,是solution_6运行时间的5倍。原因在于nth_elenemt的实现方法跟本文介绍的算法大不相同,有兴趣的朋友可以去阅读其源码。建议大家在一般情况下使用STL的nth_element,它在数量为十万级的时候仍有极好的性能。
参考资料:
[1] 侯捷 《STL源码剖析》 华中科技大学出版社 2002年6月
[2] Anany Levitin 潘彦[译] 《算法设计与分析基础》 清华大学出版社 2004年6月
[3] http://job.csdn.net/n/20051216/31105.html
注:
[1] 此题目版权归出题人或者其单位所有
[2] 本文所有的优化都针对于平均情况,即大数组由随机数构成且无序
[3] 所有测试均设BIG_ARR_SIZE = 1亿,RES_ARR_SIZE = 1万,测试的机器配置为:CPU P4EE 3.0G + 512 M memory,HyperThreading Enabled,操作系统:Windows 2000 pro,编译器: MS VC++ 6.0 + sp6,STL库: STLport 4.6.2;可从我的博客http://lanphaday.bokee.com下载本文所有算法源码和测试程序。
[4] 如果要求有序,可以通过先找出结果,再对结果排序完成要求
|
|