歸併排序是一種相當穩健的排序演算法,無論何種輸入序列,其期望時間複雜度和最壞時間複雜度都是θ(nlogn),這已經達到了基於比較排序演算法的漸進下界。
因此歸併排序時常會用於對可能導致quicksort退化的序列排序。
歸併排序是典型的分治演算法,乙個最常見的實現如下:
void mergesort(int a, const
int low, const
int high)
return ;
}void merge(int a, const
int lowfirst, const
int highfirst, const
int highlast)
while (l < highfirst)
while (r <= highlast)
p = pbuffer;
for (int i = lowfirst; i <= highlast;)
delete pbuffer;
}
但是在實踐中,歸併排序花費的時間往往超過預期,對於普通的序列而言,所花費的時間甚至遠遠超過quicksort。
究其原因,和歸併排序的記憶體策略有關(不斷地分配new與釋放free記憶體)。
歸併排序不是原地排序,需要額外的儲存空間。並且在每次merge過程中,需要動態分配一塊記憶體以完成對兩個資料堆的排序合併。並且排序完畢之後,我們需要將儲存空間中的資料複製並覆蓋原序列。
最後一步操作是由歸併排序自身性質決定,無法優化,所以我們只能針對merge操作。
經過分析很容易知道,對於長度為n的序列,要執行logn次的merge操作,這意味著需要進行logn次的記憶體分配和**。記憶體操作開銷較大。
如果能夠一次性分配長度為n的儲存空間,那麼就省掉了大量的分配操作,可以極大提高效率。
由於歸併的分治特性,我們需要在原來的函式基礎之上,包裝一層驅動函式(driver function)
// driver function
void _mergesort(int a, const
int count)
// devide the sequence recuresively
void _msort(int a, int tmpbuffer/*extra space*/, const
int left, const
int right)
return ;
}// merge two parts
void _merge(int a, int tmpbuffer, const
int lbegin, const
int rbegin, const
int rend)
while (l < rbegin)
while (r <= rend)
for (bufpos = lbegin; bufpos <= rend; ++bufpos)
return ;
}
為了檢驗效能提公升,筆者對100,1000,10000,100000的資料規模分別進行測試,每次測試排序執行100次,得到如下資料表和圖示。
ps:為了減少干擾,以上測試均在release下進行。
經過測試發現,改進後的演算法基本上比原始的要快30~50倍。至於**中1000測試那行,猜測和快取命中有關。多次測試後,基本也快出30-40倍。
以上改進可以得到乙個結論:對於需要頻繁分配記憶體的演算法而言,一次性分配或者採用lazy-deletion以提高復用的策略可以大幅提高演算法效率。
歸併排序 分治策略
歸併排序 merge sort 是利用歸併的思想實現的排序方法,該演算法採用經典的分治 divide and conquer 策略 分治法將問題分 divide 成一些小的問題然後遞迴求解,而治 conquer 的階段則將分的階段得到的各答案 修補 在一起,即分而治之 private var arr...
Memcache 記憶體分配策略
memcached預設採用了名為slab allocator的機制分配和管理記憶體。在該機制出現以前,記憶體的分配是通過對所有記錄簡單的進行malloc和free來進行了。但是這種方式會導致記憶體碎片化嚴重,加重作業系統記憶體管理器的負擔。slab allocator就是位了解決該問題而誕生的。sl...
(八)記憶體分配策略
在虛擬機器中,我們知道物件的記憶體是分配在堆中的。但是堆又可以劃分為更小的區域以便垃圾 那麼,物件到底是怎麼在分配在堆中的呢?大多數情況下,物件都在新生代的eden區中分配記憶體。而因為大部分的物件都是 朝生夕死 的,所以新生代又會頻繁進行垃圾 需要大量連續空間的物件,如 長字串 陣列等,會直接在老...