起向高楼撞晓钟,尚多昏睡正懵懵
写在前言
解决一个工程哲学问题:如何在理想情况下的激进优化与最坏情况下的稳健防御之间取得平衡? 这其实与你选择考研策略时的风险决策完全同构。根据数据特征动态切换三种算法,而这就是sort的决策内核。
- 快速排序是冲刺阶段的创业团队(快速试错)
- 堆排序是危机时期的审计部门(稳定兜底)
- 插入排序是优化细节的品控小组(局部微调)
这是sort函数的内省(Introsort),而在我们的学习生活中我们也应该不断内省(introspection)
C++ sort函数知识体系
一、容器与排序能力
1. 连续内存容器
- 静态数组
- 基本类型静态数组(
int arr[5]
) - 结构体静态数组(
Student class[30]
)
- 基本类型静态数组(
- 动态数组
- 既可以分配基本类型数组也可以分配结构体数组
new
动态分配数组(int* arr = new int[10]
)malloc
数组(int* arr = (Student*)malloc(n*sizeof(Student)
)
- vector容器(
sort(vec.begin(), vec.end())
) - deque容器(双端队列,支持随机访问,但是访问性能比不上vector,在排序算法选择上优先性低)
2. 节点式容器
- list容器
- 必须使用成员函数
list.sort()
- 原因:链表结构不支持随机访问迭代器
- 必须使用成员函数
3. 关联式容器
- set/map
- 特性:元素自动排序,禁止外部修改顺序(该类型容器无法使用sort)
二、sort参数详解
1. 迭代器/指针区间
- vector容器
迭代器:sort(vec.begin(), vec.end())
- 数组
指针区间:sort(arr, arr + N)
2. 比较函数规则
- 默认比较
operator<
(默认升序) /std::greater<>()
(降序) - lambda表达式
-
- lmada表达式是较于显式函数的一种匿名函数语法糖写法
[](auto& a, auto& b){ ; }
- lmada表达式是较于显式函数的一种匿名函数语法糖写法
- 函数对象
-
- 即自定义比较函数
bool cmp(){}
- 即自定义比较函数
- 重载运算符
小记巧
⚠️ 升序/降序口诀
return a < b → 升序 | return a > b → 降序
一题多解巧识so先生
set的优势区间
例题:P1059 [NOIP 2006 普及组] 明明的随机数
单关键字且去重排序,这是选择set/map明显更加高效
代码如下:
#include <bits/stdc++.h>
using namespace std;
int main()
{
int n, temp;
cin >> n;
set<int> nums; // 用set去重
for (int i = 1; i <= n; ++i)
{
cin >> temp;
nums.insert(temp); // set的插入只会插入未有的数
}
cout << nums.size()<<endl;
//for (set<int>::iterator i = nums.begin(); i != nums.end(); i++)//使用迭代器遍历
// {
// cout << i << " ";//解引用
// }
for(auto& i:nums)//强化for遍历引用传递避免拷贝
//使用值传递的话是:for(auto i:nums)
{
cout << i << " ";
}
}
三种思路带你解决
- 性能相差不大
例题:P1093 [NOIP 2007 普及组] 奖学金
vector+lmada表达式(最推荐的做法)
#include <bits/stdc++.h>
using namespace std;
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
int n;
cin >> n;
vector<vector<int>> arr(n,vector<int>(5,0)); //学号、三科成绩、总分
for(int i=0;i<n;++i)
{
arr[i][0] = i+1;
for(int j=1;j<=3;++j) cin >> arr[i][j],arr[i][4]+=arr[i][j];
}
sort(arr.begin(),arr.end(),
[](auto &a,auto &b){//lmada语法糖编写compare函数
if(a[4]!=b[4]) return a[4]>b[4];
if(a[1]!=b[1]) return a[1]>b[1];
return a[0]<b[0];} //总成绩和语文成绩降序,学号升序
);
for(int i=0;i<5;++i) cout << arr[i][0] << " " << arr[i][4] << endl;
return 0;
}
原生动态数组+自定义函数
#include <bits/stdc++.h>
using namespace std;
// 自定义比较函数(注意参数类型要与malloc分配的数组匹配)
bool compare(const int* a, const int* b) {
// 总分降序 → 语文降序 → 学号升序
if (a[4] != b[4]) return a[4] > b[4];
if (a[1] != b[1]) return a[1] > b[1];
return a[0] < b[0];
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
int n;
cin >> n;
// 动态分配二维数组(5列:学号、语、数、英、总分)
int** arr = (int**)malloc(n * sizeof(int*));
for (int i = 0; i < n; ++i) {
arr[i] = (int*)malloc(5 * sizeof(int));
arr[i][0] = i + 1; // 学号
arr[i][4] = 0; // 总分初始化
// 输入三科成绩
for (int j = 1; j <= 3; ++j) {
cin >> arr[i][j];
arr[i][4] += arr[i][j];
}
}
// 使用标准库排序(参数适配动态数组)
sort(arr, arr + n, compare);
// 输出前五名
for (int i = 0; i < 5; ++i) {
cout << arr[i][0] << " " << arr[i][4] << endl;
}
// 释放内存
for (int i = 0; i < n; ++i) free(arr[i]);
free(arr);
return 0;
}
静态结构体数组+重载运算符
#include <iostream>
#include <algorithm>
using namespace std;
struct Scholar {
int id; // 学号(1-based)
int chi; // 语文成绩
int math; // 数学成绩
int eng; // 英语成绩
int total; // 总分
// 运算符重载体现排序规则的层次性
bool operator<(const Scholar& rhs) const {
// 第一优先级:总分降序(注意反向比较实现降序)
if (total != rhs.total) return total > rhs.total;
// 第二优先级:语文降序(总分相同时触发)
if (chi != rhs.chi) return chi > rhs.chi;
// 最终约束:学号升序(前两者相同时触发)
return id < rhs.id;
}
} students[310]; // 静态数组解决n≤300的约束
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
int n;
cin >> n;
// 数据输入与总分计算
for (int i = 0; i < n; ++i) {
students[i].id = i + 1; // 学号从1开始
cin >> students[i].chi >> students[i].math >> students[i].eng;
students[i].total = students[i].chi + students[i].math + students[i].eng;
}
// 利用运算符重载实现自然排序
sort(students, students + n);
// 输出前五名(隐含n≥5的题目假设)
for (int i = 0; i < 5; ++i)
cout << students[i].id << " " << students[i].total << "\n";
return 0;
}
结语
我的老师曾说学会了sort便是叩开了算法殿堂的大门。先生执粉笔立于斑驳黑板前,青衫下的指针总爱在讲义的褶皱处游走,那时我不懂,为何要将这西洋的代码与《九章》的算筹并置。直到某夜重读《文心雕龙》,忽见月影在排序链表中流转如环,墨香里的冒泡竟与古琴减字谱的指序暗合——原来所有精微之道,初学时皆是笨拙的悬梁刺股,待豁然时便成星垂平野的朗照。