STL sort函式的內部實現

2021-07-11 01:32:41 字數 4423 閱讀 9967

(1)在stl提供的各式各樣的演算法中,sort()是最複雜最龐大的乙個。這個演算法只接受randomaccessiterators

(隨機訪問迭代器),然後將區間內所有元素由小到大重新排列。第二個版本允許使用者自己指定乙個仿函式作為排序標準。

(2)對於關係型容器,底層自己採用有自動排序功能的rb-tree,不需要用到sort演算法,序列式容器中stack、queue和priority_queue是容器介面卡,都有特別的出入口,不允許使用者對其進行排序。剩下的vector、deque和list,前兩者的迭代器屬於randomaccessiterators,適合用sort演算法,list的迭代器則屬於bidirectioniterators,不在stl標準之列的slist,其迭代器更是屬於forwarditerators,都不適合於sort演算法。如果要對list或者slist排序,應該他們自己提供member function sort()。那麼為什麼泛型演算法sort()一定要求randomaccessiterators?

(3)stl的sort()演算法,資料量大時採用quick sort,分段遞迴排序。一旦分段後的資料量小於某個閾值,為避免quick sort的遞迴呼叫帶來過大的額外開銷,就改用insertion sort(插入排序)。如果遞迴層次過深,還會改用heap sort。

(4)如果我們拿insertion sort來處理大資料量,其o(n^2)的複雜度無法令人接受。大資料量有更好的排序演算法可供選擇。快排是目前已知的最快的排序方法,平均複雜度為o(n log n),最壞情況o(n^2)。不過introsort(極其類似 median-of - three quicksort的一種排序演算法)可將最壞情況推進到o(nlogn),早期的stl sort演算法都採用quicksort,sgi stl已經改用introsort。

quicksort的精神在於將大區間分割為小區間,分段排序,每乙個小區間排序完成後串接起來大區間也就完成了排序。最壞的情況發生在分割時產生了乙個空的子區間 - 那完全沒有達到分割的預期效果。

(5)median - of - three(三點中值)

任何乙個元素都可以被選來當做樞軸(pivot),但是其合適與否會影響快排的效率。為了避免元素當初輸入時不夠隨機所帶來的惡化效應,最理想最穩當的做法是去整個序列的頭、尾、**三個位置的元素,以其中值(median)作為數軸。這就叫median-of - three quicksort,為了能夠快速取出**位置的元素,顯然迭代器必須能夠隨機定位,亦即必須要是個randomaccessiterators。

(6)閾值threshold

面對只有十來個元素的小型序列,用像quicksort這樣複雜而可能需要大量運算的快速排序似乎顯得不划算。所以,在小資料量的情況下,insertionsort也可能快過quicksort,因為quick會為了極小的子串行而產生許多的函式遞迴呼叫。

基於這種情況,適度評估序列的大小,然後決定採用insertionsort還是quicksort。

堆排序比較和交換次數比快速排序多,所以平均而言比快速排序慢,也就是常數因子比快速排序大,如果你需要的是「排序」,那麼絕大多數場合都應該用快速排序而不是其它的o(nlogn)演算法。

但有時候你要的不是「排序」,而是另外一些與排序相關的東西,比如最大/小的元素,topk之類,這時候堆排序的優勢就出來了。用堆排序可以在n個元素中找到top k,時間複雜度是o(n log k),空間複雜的是o(k),而快速排序的空間複雜度是o(n),也就是說,如果你要在很多元素中找很少幾個top k的元素,或者在乙個巨大的資料流裡找到top k,快速排序是不合適的,堆排序更省地方。

另外乙個適合用heap的場合是優先佇列,需要在一組不停更新的資料中不停地找最大/小元素,快速排序也不合適。

此外merge sort之類演算法雖說也是o(nlogn),但一般都只在一些很特殊的場合才會用,比如n-way merge,可以把n個已經排好序的資料流合併成乙個排好序的資料流,當然這個演算法其實嚴格說並不能算是merge sort,只是用了其中的幾個步驟,不過思路是一樣的。

基於交換的排序常用的就這麼幾種(什麼冒泡選擇之類的你可以無視了),其它的不基於交換的排序比如radix sort、bucket sort之類由於應用場合比較特殊,一般很少用到。

std::sort的**如下:

template inline void sort(randomaccessiterator first, randomaccessiterator last) 

}template void __introsort_loop(randomaccessiterator first,

randomaccessiterator last, t*,

size depth_limit)

--depth_limit;

randomaccessiterator cut = __unguarded_partition

(first, last, t(__median(*first, *(first + (last - first)/2),

*(last - 1))));

__introsort_loop(cut, last, value_type(first), depth_limit);

last = cut;

}}

遞迴深度閾值

現在我們來關注迴圈條件和if語句。__introsort_loop的最後乙個引數depth_limit是前面所提到的判斷分割行為是否有惡化傾向的閾值,即允許遞迴的深度,呼叫者傳遞的值為2logn。注意看if語句,當遞迴次數超過閾值時,函式呼叫partial_sort,它便是堆排序:

template void __partial_sort(randomaccessiterator first, randomaccessiterator middle,

randomaccessiterator last, t*, compare comp)

template inline void partial_sort(randomaccessiterator first,

randomaccessiterator middle,

randomaccessiterator last, compare comp)

如前所述,此時採用堆排序可以將快速排序的效率從o(n2)提公升到o(n logn),杜絕了過度遞迴所帶來的開銷。堆排序結束之後直接結束當前遞迴。

template randomaccessiterator __unguarded_partition(randomaccessiterator first,

randomaccessiterator last,

t pivot)

}

insertion sort(插入排序)

那麼同樣都是插入排序,__insertion_sort和__unguarded_insertion_sort有何不同,為什麼叫unguarded?接下來看看stl的實現:(注:這裡取得都是採用預設比較函式的版本):

template void __unguarded_linear_insert(randomaccessiterator last, t value) 

*last = value;

}template inline void __linear_insert(randomaccessiterator first,

randomaccessiterator last, t*)

else

__unguarded_linear_insert(last, value);

}template void __insertion_sort(randomaccessiterator first, randomaccessiterator last)

template void __final_insertion_sort(randomaccessiterator first,

randomaccessiterator last)

else

__insertion_sort(first, last);

}template void __unguarded_insertion_sort_aux(randomaccessiterator first,

randomaccessiterator last, t*)

template inline void __unguarded_insertion_sort(randomaccessiterator first,

randomaccessiterator last)

STL sort函式的用法

sort在stl庫中是排序函式,有時冒泡 選擇等o n 2 演算法會超時時,我們可以使用stl中的快速排序o n log n 完成排序 sort在庫裡面,原型如下 template void sort randomaccessiterator first,randomaccessiterator l...

STL sort演算法中的比較函式

排序,既陌生又熟悉的名詞。排序,成為面試官中喜歡問的演算法問題。c stl中為我們提供了std sort,所以今天我們不是來描述各種排序演算法的實現,而是看看怎麼使用stl為我們提供的sort。先預熱,include include include int main person int age,s...

memcpy的函式內部實現

memcpy和memmove函式的實現,需要注意memmove的覆蓋問題,還有指標型別需要考慮。下面的例子中,先給出了錯誤的例子,而後給出了正確的例子,引以為戒!區別 兩個函式都是進行n位元組記憶體內容的拷貝,入口引數和返回引數也都一樣,可是這兩個函式在內部實現上是有一定區別的,這主要是因為dest...