Initial commit: C++ courseware

This commit is contained in:
xiaolinquan committed 2026-11-05 22:51:02 +08:00
commit 4a4ee7c287
1789 files changed
+373489

No files matched your search

@@ -0,0 +1,201 @@
# C++ STL排序 `sort()` 知识点总结
### 1. 头文件
`sort` 函数包含在 `<algorithm>` 头文件中。
```cpp
#include <algorithm>
```
### 2. 函数原型与基本语法
`sort` 对给定区间内的元素进行排序。
* **函数原型**:`void sort(RandomAccessIterator first, RandomAccessIterator last);`
* **参数**:
* `first`:指向要排序序列起始位置的迭代器(包含)。
* `last`:指向要排序序列结束位置的下一位的迭代器(不包含)。
* 即:排序区间为 **左闭右开 [first, last)**。
### 3. 默认行为
* **排序规则**:默认按照 **升序**(从小到大)排列。
* **比较依据**:使用元素类型自带的 `<` 运算符进行比较。
* **适用类型**:
* 基本数据类型(`int`, `double`, `char` 等)。
* C++内置的扩展类型(`string`,按字典序(ASCII码表的顺序)排序)。
* 自定义结构体类型(需要配合自定义比较函数)。
### 4. 算法复杂度
* **时间复杂度**:
* 平均情况:$O(N \log N)$
* 最坏情况:$O(N \log N)$
* **空间复杂度**:
* $O(\log N)$
### 5. 底层实现原理 (了解)
`sort` 并非简单的快速排序,而是一种混合排序算法:
1. **快速排序 **:作为主要排序手段,不断的将数据拆分分片。
2. **堆排序 **:当快速排序的递归深度过深(超过 $\log N$ 级别)时,自动切换为堆排序。
3. **插入排序**:当数据片段非常小(少于 16 个元素)时,切换为插入排序。因为对于极小数组,插入排序比快排和堆排更高效且常数更小。
### 6. 代码示例
**示例 1:数组排序**
```cpp
int arr[] = {4, 2, 5, 1, 3};
// 排序整个数组,arr 是首地址,arr+5 是尾地址 + 1
sort(arr, arr + 5);
// 结果: 1, 2, 3, 4, 5
```
**示例 2:Vector 排序**
```cpp
vector<int> v = {10, 30, 20};
sort(v.begin(), v.end());
// 结果: 10, 20, 30
```
**示例 3:局部排序**
```cpp
vector<int> v = {5, 1, 9, 3, 7};
// 只排序前3个元素 (下标0到2)
sort(v.begin(), v.begin() + 3);
// 结果: 1, 5, 9, 3, 7 (后两个元素位置未动)
```
### 7. 自定义比较函数
`sort` 的第三个参数接收一个比较器,其中存在一个内置比较器`greater<T>()`用于降序排序。
```cpp
sort(v.begin(), v.end(), greater<T>()); // T 为 vec 中储存的数据类型
```
而在这里如果我们需要自定义比较的方法的话,我们就需要传递一个**比较函数**。
* **函数参数**:必须接受两个跟你要排序的同类型的参数(建议使用 `const&`以提高效率,不加也可以,但是因为加上`&`是传递原件,能节省复印所需要的时间),并返回 `bool` 类型。
* **返回值含义**:
* 返回 `true`:表示第一个参数应该排在第二个参数**前面**。
* 返回 `false`:表示第一个参数应该排在第二个参数**后面**,或者两者相等。
### 8. 比较函数的写法模板
假设有一个结构体 `Student`:
```cpp
struct Student {
string name;
int score;
int id;
};
```
我们需要定义一个普通的函数(写在 `main` 函数外面):
```cpp
// 比较函数:cmp
bool cmp(const Student& a, const Student& b) {
// 排序逻辑写在这里
return a.score < b.score; // 当 a 应该排在前面时,需要返回 true,我们这里按照分数的升序排序
}
```
### 9. 常用排序逻辑
#### A. 升序排列 (从小到大)
逻辑:当 `a` 的值 **小于** `b` 的值时,`a` 在前,返回 `true`。
```cpp
bool cmp(const Student& a, const Student& b) {
return a.score < b.score;
}
```
#### B. 降序排列 (从大到小)
逻辑:当 `a` 的值 **大于** `b` 的值时,`a` 在前,返回 `true`。
```cpp
bool cmp(const Student& a, const Student& b) {
return a.score > b.score;
}
```
#### C. 多条件排序 (重点)
这是结构体排序最常见的场景。例如:**先按分数从高到低排序;如果分数相同,则按学号从小到大排序。**
```cpp
bool cmp(const Student& a, const Student& b) {
if (a.score > b.score) {
// 第一关键字:分数不同,按分数大的在前排序
return true;
} else if(a.score < b.score){
// 第一关键字:分数不同,按分数大的在前排序,a 更小所以应该排在后面,返回false
return false;
}else{
// 第二关键字:分数相同,按学号升序(小的在前)
return a.id < b.id;
}
}
```
### 10. 完整代码示例
```cpp
#include <bits/stdc++.h>
using namespace std;
struct Student {
string name;
int score;
int id;
};
// 自定义比较函数:先按分数降序,分数相同按ID升序
bool cmp(const Student& a, const Student& b) {
if (a.score > b.score) {
// 第一关键字:分数不同,按分数大的在前排序
return true;
} else if(a.score < b.score){
// 第一关键字:分数不同,按分数大的在前排序,a 更小所以应该排在后面,返回false
return false;
}else{ // 分数相同
// 第二关键字:分数相同,按学号升序(小的在前)
return a.id < b.id;
}
}
int main() {
vector<Student> students; // 为了方便,没有输入测试数据,你可以自行添加
// 调用 sort,传入函数名 cmp 作为第三个参数
sort(students.begin(), students.end(), cmp);
// 输出结果
for (const auto& s : students) {
cout << s.name; // 输出的顺序已经按分数从高到低排序;如果分数相同,则按学号从小到大排序
}
return 0;
}
```
### 11. 注意事项
在编写比较函数时,必须满足严格弱序原则。最常见的错误是使用 `<=` 或 `>=`。
* **错误写法**:
```cpp
bool cmp(const Student& a, const Student& b) {
return a.score >= b.score; // 错误!
}
```
**为什么?**:因为如果 `a.score` 等于 `b.score`,`cmp(a, b)` 返回 `true`,且 `cmp(b, a)` 也返回 `true`。这会让 `sort` 认为 a **必须**在 b 前面,同时 b **也必须**在 a 前面,导致逻辑矛盾,可能引发**死循环**或**程序崩溃**。
* **正确的方法**:
当两个元素相等时,比较函数**必须**返回 `false`。两个都返回 `false` 函数就会认为 **a 等于 b** (谁在前无所谓,视为相等)。
* **正确写法**:
```cpp
bool cmp(const Student& a, const Student& b) {
return a.score > b.score; // 正确
}
```