C++中的sort與自定義排序詳解
基本使用與原理
std::sort 是一個(gè)模板函數(shù),常見簽名如下:
template<class RandomIt> void sort(RandomIt first, RandomIt last); template<class RandomIt, class Compare> void sort(RandomIt first, RandomIt last, Compare comp);
- first,last:指定排序范圍的隨機(jī)訪問迭代器。
- comp:可選的比較器,定義元素順序,默認(rèn)為 std::less(基于 operator< 的升序排序)。
- 時(shí)間復(fù)雜度:平均和最壞情況均為 O(n log n)。
- 空間復(fù)雜度:通常為 O(log n),用于遞歸棧或臨時(shí)緩沖區(qū)。
- 要求:比較器必須滿足嚴(yán)格弱序(strict weak ordering),否則可能導(dǎo)致未定義行為。
核心算法:內(nèi)省排序(Introsort)
C++ 標(biāo)準(zhǔn)未強(qiáng)制指定 std::sort 的實(shí)現(xiàn)算法,但大多數(shù)現(xiàn)代標(biāo)準(zhǔn)庫(kù)(如 libstdc++、libc++)采用 內(nèi)省排序(Introsort)。
Introsort 由 David Musser 于 1997 年提出,是一種混合排序算法,結(jié)合了快速排序(Quicksort)、堆排序(Heapsort)和插入排序(Insertion Sort)的優(yōu)點(diǎn),以兼顧效率和魯棒性。
1. 快速排序
快速排序是 Introsort 的主要算法,步驟如下:
- 選擇基準(zhǔn)(pivot):通常使用三數(shù)取中(median-of-three,選取首、尾、中間元素的中位數(shù))或隨機(jī)選擇。
- 分區(qū)(partition):將元素分為小于等于基準(zhǔn)和大于基準(zhǔn)的兩部分。
- 遞歸:對(duì)兩個(gè)子區(qū)間遞歸排序。
特點(diǎn):
- 平均時(shí)間復(fù)雜度:O(n log n)。
- 最壞情況(如已排序或逆序數(shù)組):O(n²)。
- 優(yōu)化:三數(shù)取中或隨機(jī)化基準(zhǔn)減少最壞情況概率。
實(shí)現(xiàn)細(xì)節(jié):
- 分區(qū)方案:常用 Lomuto 或 Hoare 分區(qū)算法。
- 基準(zhǔn)選擇:三數(shù)取中避免極端情況(如已排序輸入)。
2. 堆排序
當(dāng)快速排序的遞歸深度超過閾值(通常為 2 * log n),Introsort 切換到堆排序,以避免快速排序的最壞情況。
步驟:
- 構(gòu)建最大堆(或最小堆,取決于排序順序)。
- 反復(fù)將堆頂元素移到末尾并調(diào)整堆。
特點(diǎn):
- 時(shí)間復(fù)雜度:始終為 O(n log n)。
- 空間復(fù)雜度:O(1)(原地排序)。
- 適合處理快速排序退化的場(chǎng)景。
實(shí)現(xiàn)細(xì)節(jié):
- 使用數(shù)組表示堆,通過下標(biāo)計(jì)算父子節(jié)點(diǎn)關(guān)系。
- 堆調(diào)整(sift-down)確保堆性質(zhì)。
3. 插入排序
當(dāng)子區(qū)間大小較小時(shí)(通常 < 16 或 32 個(gè)元素,具體閾值依實(shí)現(xiàn)而定),Introsort 切換到插入排序。
步驟:
- 逐個(gè)將元素插入到已排序的子序列中。
特點(diǎn):
- 時(shí)間復(fù)雜度:O(n²),但在小數(shù)組上常數(shù)開銷低。
- 適合部分有序或小規(guī)模數(shù)據(jù)。
實(shí)現(xiàn)細(xì)節(jié):
- 通常通過循環(huán)實(shí)現(xiàn),避免遞歸。
- 常用于優(yōu)化小規(guī)模子區(qū)間的排序。
原因:
- 快速排序:平均性能優(yōu)異,但在最壞情況下(如已排序或重復(fù)元素)退化為 O(n²)。
- 堆排序:保證最壞情況 O(n log n),但平均性能稍遜,且堆調(diào)整開銷較高。
- 插入排序:在小規(guī)模數(shù)據(jù)上高效,減少遞歸開銷。 Introsort 動(dòng)態(tài)切換算法,確保:
- 高效性:利用快速排序的平均性能。
- 魯棒性:通過堆排序避免最壞情況。
- 優(yōu)化性:插入排序處理小數(shù)組。
自定義排序
1. 函數(shù)指針
定義一個(gè)獨(dú)立的比較函數(shù),簽名需為bool(T, T):
bool compare(int a, int b) {
return a > b; // 降序排序
}
std::vector<int> vec = {5, 2, 9, 1, 5};
std::sort(vec.begin(), vec.end(), compare); // 結(jié)果為 {9, 5, 5, 2, 1}
2. 靜態(tài)成員函數(shù)
在類中定義靜態(tài)比較函數(shù),避免依賴對(duì)象實(shí)例:
class Sorter {
public:
static bool compare(int a, int b) {
return a > b; // 降序排序
}
};
std::vector<int> vec = {5, 2, 9, 1, 5};
std::sort(vec.begin(), vec.end(), Sorter::compare);
注意:非靜態(tài)成員函數(shù)因隱含this指針,簽名不匹配std::sort的要求,因此無法直接使用。
3. 使用函數(shù)對(duì)象
通過定義一個(gè)類并重載operator(),可以在比較器中存儲(chǔ)狀態(tài):
class Comparator {
int threshold;
public:
Comparator(int t) : threshold(t) {}
bool operator()(int a, int b) const {
return a > threshold && a < b; // 僅對(duì)大于threshold的元素降序排序
}
};
std::vector<int> vec = {5, 2, 9, 1, 5};
std::sort(vec.begin(), vec.end(), Comparator(3));
4. 使用Lambda表達(dá)式(C++11及以上)
Lambda表達(dá)式提供了一種簡(jiǎn)潔的方式定義比較器,并可捕獲外部變量:
int threshold = 3;
std::vector<int> vec = {5, 2, 9, 1, 5};
std::sort(vec.begin(), vec.end(), [threshold](int a, int b) {
return a > threshold && a < b;
});
5.使用std::function
C++11引入的std::function可以包裝任何可調(diào)用對(duì)象(包括函數(shù)指針、Lambda、函數(shù)對(duì)象等),提供更靈活的方式:
#include <functional>
std::function<bool(int, int)> comp = [](int a, int b) {
return a > b; // 降序排序
};
std::vector<int> vec = {5, 2, 9, 1, 5};
std::sort(vec.begin(), vec.end(), comp);
適用場(chǎng)景:當(dāng)需要在運(yùn)行時(shí)動(dòng)態(tài)選擇比較器時(shí),std::function非常有用,但會(huì)引入少量性能開銷。
6.使用標(biāo)準(zhǔn)比較器(如std::greater、std::less)
C++標(biāo)準(zhǔn)庫(kù)提供了預(yù)定義的比較器(如<functional>中的std::greater和std::less),可直接用于簡(jiǎn)單排序需求:
#include <functional>
std::vector<int> vec = {5, 2, 9, 1, 5};
std::sort(vec.begin(), vec.end(), std::greater<int>()); // 降序排序
std::sort(vec.begin(), vec.end(), std::less<int>()); // 升序排序
優(yōu)點(diǎn):無需手動(dòng)定義比較器,代碼簡(jiǎn)潔,適合常見升序或降序需求。
7.重載operator<
對(duì)于自定義結(jié)構(gòu)體或類,可以通過重載operator<來定義默認(rèn)排序規(guī)則,省去顯式比較器:
struct Person {
std::string name;
int age;
bool operator<(const Person& other) const {
return age < other.age; // 按年齡升序
}
};
std::vector<Person> people = {{"Alice", 25}, {"Bob", 30}, {"Charlie", 20}};
std::sort(people.begin(), people.end()); // 使用operator<,按年齡升序
適用場(chǎng)景:當(dāng)類型有自然的排序規(guī)則且不需要多種排序方式時(shí),重載operator<是最簡(jiǎn)潔的方案。
自定義結(jié)構(gòu)體或類的排序
當(dāng)排序?qū)ο笫亲远x結(jié)構(gòu)體或類時(shí),可以結(jié)合上述方法。
例如,使用Lambda:
std::vector<Person> people = {{"Alice", 25}, {"Bob", 30}, {"Charlie", 20}};
// 按名字字典序排序
std::sort(people.begin(), people.end(), [](const Person& a, const Person& b) {
return a.name < b.name;
});
總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
C++調(diào)用C#的DLL實(shí)現(xiàn)方法
這篇文章主要介紹了C++調(diào)用C#的DLL實(shí)現(xiàn)方法,很有實(shí)用價(jià)值,需要的朋友可以參考下2014-07-07
C語言實(shí)現(xiàn)簡(jiǎn)單的五子棋小游戲
這篇文章主要為大家詳細(xì)介紹了C語言實(shí)現(xiàn)簡(jiǎn)單的五子棋小游戲,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-05-05
C語言三種函數(shù)調(diào)用約定_cdecl與_stdcall及_fastcall詳細(xì)講解
本篇文章使用的工具是vs2010,內(nèi)容可能涉及到匯編的知識(shí),建議有一些匯編基礎(chǔ)的再來看,不過沒有匯編基礎(chǔ)也沒有關(guān)系,了解一下這三種調(diào)用約定即可2022-10-10
C語言fgetc和fputc函數(shù)用法詳解(以字符形式讀寫文件)
這篇文章主要介紹了C語言fgetc和fputc函數(shù)用法詳解(以字符形式讀寫文件),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-01-01
C++實(shí)現(xiàn)ETW進(jìn)行進(jìn)程變動(dòng)監(jiān)控詳解
ETW提供了一種對(duì)用戶層應(yīng)用程序和內(nèi)核層驅(qū)動(dòng)創(chuàng)建的事件對(duì)象的跟蹤記錄機(jī)制。為開發(fā)者提供了一套快速、可靠、通用的一系列事件跟蹤特性。本文將利用ETW進(jìn)行進(jìn)程變動(dòng)監(jiān)控,需要的可以參考一下2022-07-07

