在建立正確性的迴歸測試之後,繼續前進。 首先用效能工具分析下, 發現有點悲劇: 效率又倒退了。去除不必要的系統調用後, Profile分析結果如下:
七、 一些小改進
產生一億個隨機數也比較耗時, 可以看到rand()耗費時間並不多,但creatListInternal 耗費時間卻很多, 可以推斷, 模運算上耗費了很多時間。可以消除模運算。使用(1+rand()+rand()) * (1+rand()) 來產生隨機數, (1-65535)*(1-32768) ,可以隨機產生1- 65535*32768 之間的任何數。當然,這隻是個簡單的演算法,會有重複元素。 此外,還可以啟用編譯器最佳化選項。
八、 聚焦作用區, 減少比較次數
不去最佳化次要的地方,再次聚焦作用區。 可以發現,fastFindkthMax 的主要時間幾乎都花在 fastMaxHeapify 上。 只要改進 fastMaxHeapify 的比較次數即可。 對於結點有左右孩子結點的大多數情形,原來的實現中,總要進行兩次與heapsize的比較; 但事實上只需要進行一次比較, 對相應代碼做一些改動, 即可獲得一定的提速。代碼如下:
if (rch <= heapsize) { if ((*(list+lch)) > temp) { curr_largest = lch; } if ((*(list+rch)) > (*(list+curr_largest))) { curr_largest = rch; } } else { if (lch <=heapsize && (*(list+lch)) > temp) { curr_largest = lch; } }
九、 快取的影響
在(上篇)中,一位博友提醒說快取也起著重要的影響。 感謝他的提醒! 鑒於自己在這方面掌握不夠紮實,暫時留空。
十、 回到演算法, 思路比較
要提速,還是要尋找更好的演算法改進。 有沒有更好的演算法呢? 本文的演算法有點“笨拙”, 先分配N個數,然後對這N個數建最大堆, 最後依次找出K個最大數。另有兩種思路如下:
1. 最小堆。 首先在N個數中選擇K個數建立K個元素的最小堆。 接著, for i = K+1 to N : 如果 i 小於最小堆的根項目, 那麼直接不做理會; 如果 i 大於最小堆的根項目,那麼, 將其替代堆的根項目,並重構最小堆。 其正確性如下: A。 初始狀態下, 堆中所有元素都比空元素大; B。 對於每次重構最小堆之後, 堆中的元素總是比被替代出來的所有元素要大;C。 當迴圈結束後,堆中的元素就比所有不在堆中的元素要大。其效率為 O(K + NlogK) ;
2. 分治。 分而治之總是一種有效策略。 先將N個數分成b 堆, 每堆 N/b 個數。 對於每個堆找出前K個從大到小排序的最大數 b*O(N/b+Klog(N/b)) ;最後, 在這b個堆的已排序的K個最大數(bK)找出前K個最大數(O(b+(K-1)logb))。這種演算法對於多處理器、並存執行機器更為有效,其時間為O(N/b+Klog(N/b)+b+(K-1)logb) + C(N), C是通訊時間。對於大資料量處理來說, 並行演算法是一種非常值得研究的領域。