1. sort() - 快速排序/内省排序
- 快速排序(Quick Sort):这是sort()函数底层使用的主要算法
- 内省排序(Introsort):实际上是C++中sort()的完整实现,结合了快速排序、堆排序和插入排序的优点
2. stable_sort() - 归并排序
- 归并排序(Merge Sort):stable_sort()通常基于归并排序实现,保证相等元素的相对顺序不变
3. partial_sort() - 堆排序
- 堆排序(Heap Sort):partial_sort()函数底层使用堆排序算法,只对前m个元素进行排序
4. nth_element() - 快速选择算法
- 快速选择算法(Quickselect):这是nth_element()函数使用的算法,用于找到第n小的元素
文章中还提到的其他相关算法:
5. 插入排序(Insertion Sort)
- 在小规模数据时,内省排序会切换到插入排序
6. 冒泡排序(Bubble Sort)
- 作为基础排序算法在文章中提及
7. 选择排序(Selection Sort)
- 作为基础排序算法在文章中提及
8. 计数排序(Counting Sort)
- 在进阶学习方向中提到的特定数据高效排序算法
9. 桶排序(Bucket Sort)
- 在进阶学习方向中提到的特定数据高效排序算法
10. 堆排序(Heap Sort)
- 除了partial_sort()使用外,还在进阶学习方向中作为独立算法提及
11. 归并排序(Merge Sort)
- 除了stable_sort()使用外,还在进阶学习方向中作为独立算法提及
一、引言:为什么排序和排列如此重要?
想象一下你正在整理书架:
- 排序就像按字母顺序排列书籍,让查找变得轻松
- 排列则像尝试所有可能的书籍摆放方式,找到最合适的布局
在编程世界中,排序和排列是两大基础操作:
- 排序:将数据按特定规则(升序、降序等)重新排列
- 排列:生成数据所有可能的排列组合
C++标准库提供了强大的排序和排列工具,让我们能高效处理这些操作。本文将带你从零开始,彻底掌握C++中的排序与排列技术。
二、排序函数:C++中的排序神器
2.1 排序函数全家福
C++标准库提供了多种排序函数,各有特色:
| 函数名 | 用途 | 时间复杂度 | 稳定性 | 适用场景 |
|---|---|---|---|---|
| sort() | 完全排序 | O(n log n) | 不稳定 | 通用排序 |
| stable_sort() | 稳定排序 | O(n log n) | 稳定 | 需要保持相等元素顺序 |
| partial_sort() | 部分排序 | O(n log m) | 不稳定 | 只需前m个有序 |
| nth_element() | 第n小元素 | O(n) | 不稳定 | 找中位数或分位数 |
2.2 sort():最常用的排序函数
基本用法
#include <algorithm>
#include <vector>
int main() {
std::vector<int> nums = {5, 2, 9, 1, 5, 6};
// 默认升序排序
std::sort(nums.begin(), nums.end());
// 排序后:1, 2, 5, 5, 6, 9
}
工作原理
sort()使用**内省排序(Introsort)**算法:
- 开始使用快速排序(平均O(n log n))
- 当递归深度过大时切换为堆排序(避免最坏情况O(n²))
- 对小规模数据使用插入排序(优化小数组性能)
降序排序
// 方法1:使用greater
std::sort(nums.begin(), nums.end(), std::greater<int>());
// 方法2:使用lambda表达式
std::sort(nums.begin(), nums.end(), [](int a, int b) {
return a > b;
});
2.3 stable_sort():保持相对顺序的稳定排序
为什么需要稳定排序?
想象你有一个学生名单,先按成绩排序,再按姓名排序:
- 使用sort():成绩相同的学生顺序可能被打乱
- 使用stable_sort():成绩相同的学生保持原有顺序
示例
struct Student {
std::string name;
int score;
};
std::vector<Student> students = {
{"Alice", 90},
{"Bob", 85},
{"Charlie", 90}
};
// 先按成绩排序(稳定)
std::stable_sort(students.begin(), students.end(),
[](const Student& a, const Student& b) {
return a.score > b.score;
});
// 再按姓名排序(稳定)
std::stable_sort(students.begin(), students.end(),
[](const Student& a, const Student& b) {
return a.name < b.name;
});
// 结果:Alice(90), Charlie(90), Bob(85)
// 注意:Alice和Charlie的顺序保持不变
2.4 partial_sort():部分排序专家
应用场景
当你只需要前几名有序时,partial_sort()是最佳选择:
- 找出成绩前10名的学生
- 获取销售额最高的5个产品
示例
std::vector<int> nums = {9, 6, 1, 3, 8, 2, 5, 7, 4};
// 只需要前3个元素有序
std::partial_sort(nums.begin(), nums.begin() + 3, nums.end());
// 结果:1, 2, 3, 9, 8, 6, 5, 7, 4
// 前三个是最小的三个,且有序,后面顺序不定
2.5 nth_element():找第n小元素的神器
应用场景
- 找中位数
- 找第90百分位数
- 快速选择算法
示例
std::vector<int> nums = {5, 2, 9, 1, 5, 6};
// 找第4小的元素(索引3)
std::nth_element(nums.begin(), nums.begin() + 3, nums.end());
// 结果:1, 2, 5, 5, 9, 6
// 第4个位置(5)是正确的,前面都<=5,后面都>=5
三、排列算法:生成所有可能组合
3.1 next_permutation():生成下一个排列
基本概念
排列是指对一组元素进行重新排列的所有可能方式。例如:
- {1, 2, 3}的排列有:
1, 2, 3
1, 3, 2
2, 1, 3
2, 3, 1
3, 1, 2
3, 2, 1
基本用法
#include <algorithm>
#include <vector>
int main() {
std::vector<int> nums = {1, 2, 3};
do {
// 处理当前排列
for (int num : nums) std::cout << num << " ";
std::cout << std::endl;
} while (std::next_permutation(nums.begin(), nums.end()));
}
工作原理
next_permutation()按字典序生成下一个排列:
- 从后向前找第一个相邻升序对(i, j),满足a[i] < a[j]
- 从后向前找第一个大于a[i]的元素a[k]
- 交换a[i]和a[k]
- 反转i之后的所有元素
示例解析
以{1, 2, 3}为例:
- 初始:1, 2, 3
- next_permutation() → 1, 3, 2
- next_permutation() → 2, 1, 3
- next_permutation() → 2, 3, 1
- next_permutation() → 3, 1, 2
- next_permutation() → 3, 2, 1
- next_permutation() → false(回到1,2,3)
3.2 prev_permutation():生成前一个排列
与next_permutation()相反,prev_permutation()生成字典序的前一个排列:
std::vector<int> nums = {3, 2, 1};
do {
// 处理当前排列
for (int num : nums) std::cout << num << " ";
std::cout << std::endl;
} while (std::prev_permutation(nums.begin(), nums.end()));
3.3 排列生成注意事项
- 初始顺序很重要:要生成所有排列,必须从升序开始
std::vector<int> nums = {3, 1, 2};
std::sort(nums.begin(), nums.end()); // 先排序
do { ... } while (next_permutation(...));
- 处理重复元素:当有重复元素时,会生成重复排列
std::vector<int> nums = {1, 1, 2};
// 会生成:1,1,2; 1,2,1; 2,1,1
- 避免无限循环:当已经是最后一个排列时,next_permutation返回false
四、自定义比较:打造专属排序规则
4.1 比较函数的三种形式
1. 函数指针
bool compareDesc(int a, int b) {
return a > b;
}
std::sort(nums.begin(), nums.end(), compareDesc);
2. 函数对象(仿函数)
struct Compare {
bool operator()(int a, int b) const {
return a > b;
}
};
std::sort(nums.begin(), nums.end(), Compare());
3. Lambda表达式(C++11)
std::sort(nums.begin(), nums.end(), [](int a, int b) {
return a > b;
});
4.2 复杂对象排序
按多个字段排序
struct Person {
std::string name;
int age;
double height;
};
std::vector<Person> people = {{"Alice", 25, 1.65}, {"Bob", 30, 1.80}, {"Charlie", 25, 1.75}};
// 先按年龄升序,年龄相同按身高降序
std::sort(people.begin(), people.end(), [](const Person& a, const Person& b) {
if (a.age != b.age) {
return a.age < b.age;
}
return a.height > b.height;
});
自定义排列规则
// 自定义比较函数,生成特定顺序的排列
bool customCompare(int a, int b) {
// 偶数排在奇数前面
if (a % 2 == 0 && b % 2 != 0) return true;
if (a % 2 != 0 && b % 2 == 0) return false;
// 同奇偶则按升序
return a < b;
}
std::vector<int> nums = {1, 2, 3, 4};
std::sort(nums.begin(), nums.end(), customCompare);
// 结果:2, 4, 1, 3
五、实例演示:排序与排列的实际应用
5.1 实例1:学生成绩管理系统
#include <iostream>
#include <algorithm>
#include <vector>
#include <string>
struct Student {
std::string name;
int id;
int score;
};
int main() {
std::vector<Student> students = {
{"Alice", 101, 85},
{"Bob", 102, 92},
{"Charlie", 103, 78},
{"David", 104, 92},
{"Eve", 105, 88}
};
// 1. 按成绩降序排序
std::sort(students.begin(), students.end(), [](const Student& a, const Student& b) {
return a.score > b.score;
});
std::cout << "按成绩排名:\n";
for (const auto& s : students) {
std::cout << s.name << ": " << s.score << std::endl;
}
// 2. 找出成绩前3名的学生
std::partial_sort(students.begin(), students.begin() + 3, students.end(),
[](const Student& a, const Student& b) {
return a.score > b.score;
});
std::cout << "\n前三名:\n";
for (int i = 0; i < 3; ++i) {
std::cout << students[i].name << ": " << students[i].score << std::endl;
}
// 3. 找出成绩中位数
std::vector<int> scores;
for (const auto& s : students) {
scores.push_back(s.score);
}
std::nth_element(scores.begin(), scores.begin() + scores.size()/2, scores.end());
std::cout << "\n成绩中位数: " << scores[scores.size()/2] << std::endl;
return 0;
}
5.2 实例2:旅行路线规划
#include <iostream>
#include <algorithm>
#include <vector>
#include <string>
int main() {
std::vector<std::string> cities = {"北京", "上海", "广州", "深圳"};
std::cout << "所有可能的旅行路线:\n";
int count = 0;
// 生成所有可能的旅行路线
do {
std::cout << ++count << ": ";
for (const auto& city : cities) {
std::cout << city << " -> ";
}
std::cout << "起点\n";
} while (std::next_permutation(cities.begin(), cities.end()));
std::cout << "\n共有 " << count << " 种不同的旅行路线\n";
return 0;
}
5.3 实例3:密码破解模拟
#include <iostream>
#include <algorithm>
#include <vector>
#include <string>
int main() {
std::string password = "1234";
std::string guess = "1234";
std::vector<char> chars = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'};
std::cout << "尝试破解4位数字密码...\n";
int attempts = 0;
// 生成所有可能的4位数字组合
do {
attempts++;
std::string current(chars.begin(), chars.begin() + 4);
if (current == password) {
std::cout << "破解成功!密码是: " << current << std::endl;
std::cout << "尝试次数: " << attempts << std::endl;
break;
}
} while (std::next_permutation(chars.begin(), chars.end()));
return 0;
}
六、性能考虑:选择合适的算法
6.1 排序算法选择指南
| 场景 | 推荐算法 | 原因 |
|---|---|---|
| 通用排序 | sort() | 平均性能最好,O(n log n) |
| 需要保持相等元素顺序 | stable_sort() | 稳定排序,但稍慢 |
| 只需前m个有序 | partial_sort() | O(n log m)比完全排序快 |
| 找第n小元素 | nth_element() | O(n)线性时间 |
| 小规模数据(<10) | 插入排序 | 虽然sort()内部已优化,但手动插入排序可能更快 |
| 链表排序 | list::sort() | 专门为链表优化 |
6.2 排列生成效率
- 时间复杂度:生成n个元素的所有排列需要O(n!)时间
- 空间复杂度:O(1)(原地操作)
- 实际考虑:
- 当n>10时,排列数量巨大(10! = 3,628,800)
- 对于大n,考虑使用回溯算法+剪枝
- 如果只需要部分排列,考虑使用组合数学
6.3 优化技巧
- 预分配内存:对于vector,提前reserve()减少重新分配
std::vector<int> largeData;
largeData.reserve(1000000); // 预分配空间
- 移动语义:对于大对象,使用移动语义减少拷贝
std::vector<std::string> strings;
std::string temp = "hello";
strings.push_back(std::move(temp)); // 移动而非拷贝
- 并行排序:C++17支持并行排序
std::sort(std::execution::par, vec.begin(), vec.end());
- 避免不必要的排序:如果只需要最小/最大值,使用min_element/max_element
auto minIt = std::min_element(vec.begin(), vec.end());
七、常见陷阱与解决方案
7.1 排序常见问题
问题1:排序后元素"消失"
std::vector<int> v = {5, 3, 1, 2, 4};
std::sort(v.begin(), v.end() - 1); // 错误:排除了最后一个元素
解决方案:确保范围正确
std::sort(v.begin(), v.end()); // 正确
问题2:自定义比较函数不一致
// 错误的比较函数
bool badCompare(int a, int b) {
return a <= b; // 应该是严格小于
}
解决方案:比较函数必须满足严格弱序
bool goodCompare(int a, int b) {
return a < b; // 正确
}
问题3:排序后迭代器失效
std::vector<int> v = {3, 1, 2};
auto it = v.begin() + 1; // 指向1
std::sort(v.begin(), v.end()); // 排序后it可能不再指向1
解决方案:排序后不要依赖旧的迭代器
7.2 排列常见问题
问题1:没有生成所有排列
std::vector<int> v = {3, 1, 2};
do { ... } while (std::next_permutation(v.begin(), v.end())); // 可能遗漏排列
解决方案:先排序
std::sort(v.begin(), v.end());
do { ... } while (std::next_permutation(v.begin(), v.end()));
问题2:重复元素导致重复排列
std::vector<int> v = {1, 1, 2};
// 会生成重复排列:1,1,2; 1,2,1; 2,1,1
解决方案:使用set去重
std::set<std::vector<int>> uniquePerms;
do {
uniquePerms.insert(v);
} while (std::next_permutation(v.begin(), v.end()));
问题3:排列顺序不符合预期
std::vector<int> v = {1, 2, 3};
std::next_permutation(v.begin(), v.end());
// 期望得到1,3,2但实际可能不同
解决方案:理解字典序,确保初始状态正确
八、总结与进阶学习
8.1 核心要点回顾
-
排序函数:
- sort():通用排序,O(n log n)
- stable_sort():稳定排序
- partial_sort():部分排序
- nth_element():找第n小元素
-
排列算法:
- next_permutation():生成下一个排列
- prev_permutation():生成前一个排列
- 需要从升序开始才能生成所有排列
-
自定义比较:
- 函数指针、函数对象、lambda表达式
- 必须满足严格弱序
-
性能考虑:
- 根据场景选择合适算法
- 注意排列生成的指数级复杂度
8.2 进阶学习方向
-
其他排序算法:
- 归并排序(merge)
- 堆排序(make_heap, push_heap, pop_heap)
- 计数排序、桶排序(特定数据高效)
-
高级排列组合:
- 组合生成(next_combination)
- 子集生成
- 可重复排列
-
并行算法:
- C++17并行排序
- 多线程排列生成
-
实际应用:
- 数据库索引
- 搜索算法
- 密码学
- 统计分析
8.3 实践建议
-
动手实践:
- 实现自己的排序算法(快速排序、归并排序等)
- 手动模拟排列生成过程
- 解决LeetCode相关题目(排序、排列组合类)
-
性能测试:
- 比较不同排序算法的性能
- 测试不同数据规模下的表现
- 分析缓存、分支预测等影响
-
项目应用:
- 在实际项目中使用排序和排列
- 优化现有代码中的排序逻辑
- 设计需要排列组合的功能
6794

被折叠的 条评论
为什么被折叠?



