- C++
算法:排序概念和稳定性
- 2025-5-1 15:40:17 @
算法教程:排序概念与稳定性(0基础详细讲解)
📘 前言:什么是排序?
在编程中,排序(Sorting)是指将一组数据按照某种顺序(如从小到大或从大到小)进行排列。这是计算机科学中最基本、最常用的操作之一。
本教程将从零开始讲解排序的基本概念,包括:
- 排序的定义和作用
- 排序算法的分类
- 稳定排序与不稳定排序
- 各种排序方式的直观理解
🧠 第一章:排序的基本概念
1.1 什么是排序?
👉 排序是将一个无序的数据序列变成有序的过程。
例如:
原始数组:{5, 2, 9, 1, 5, 6}
排序后:{1, 2, 5, 5, 6, 9} (升序)
1.2 为什么要排序?
- 查找更高效(如二分查找的前提是数据有序)
- 数据可视化更清晰
- 便于后续处理(如去重、合并等)
📊 第二章:排序算法的分类
排序算法有很多,常见的有以下几种(按时间复杂度分类):
分类 | 时间复杂度 | 常见算法 |
---|---|---|
O(n²) | 简单排序 | 冒泡排序、插入排序、选择排序 |
O(n log n) | 高效排序 | 快速排序、归并排序、堆排序 |
O(n) | 特殊排序 | 计数排序、桶排序、基数排序 |
我们不会在这篇教程里深入讲解这些算法的实现,而是先了解它们的一些重要属性。
🧩 第三章:排序的“稳定性”是什么意思?
3.1 定义
一个排序算法是稳定的(Stable),当它能够保持原本相同值元素之间的相对顺序不变。
通俗点说:
- 如果有两个相同的数字,在排序前 A 在 B 前面;
- 排序之后,如果 A 还在 B 前面,那么这个排序就是稳定的;
- 如果 A 跑到了 B 后面,那就是不稳定的。
3.2 举个例子
原始数据(按分数排序):
学生 | 分数 |
---|---|
小明 | 85 |
小红 | 90 |
小刚 | 85 |
排序后(目标:按分数从低到高排序):
✅ 稳定排序结果:
- 小明(85)
- 小刚(85)
- 小红(90)
❌ 不稳定排序结果:
- 小刚(85)
- 小明(85)
- 小红(90)
虽然分数排对了,但两个人的顺序变了 → 不稳定
🧱 第四章:哪些常见排序是稳定的?哪些不是?
下面是一些常见排序算法的稳定性总结:
排序算法 | 是否稳定 | 说明 |
---|---|---|
冒泡排序 | ✅ 是 | 相邻比较,只交换顺序错误的元素 |
插入排序 | 插入时不打乱原有顺序 | |
归并排序 | 使用分治策略,保留顺序 | |
快速排序 | ❌ 否 | 比较+交换可能打乱原顺序 |
选择排序 | 每次选最小值放到前面,可能改变顺序 | |
计数排序 | ✅ 是 | 不涉及比较,直接统计计数 |
基数排序 | 依赖稳定排序(通常是计数排序) | |
堆排序 | ❌ 否 | 建堆过程会破坏顺序 |
🧭 第五章:为什么稳定性很重要?
虽然大多数时候只需要数值正确排序即可,但在某些实际场景中,稳定性非常重要:
场景1️⃣:多字段排序
比如你要先按班级排序,再按成绩排序。
如果你使用的是不稳定排序算法,第二次排序可能会把第一次的结果打乱。
正确的做法是:
- 先按成绩排序(可以是非稳定)
- 再按班级排序(必须是稳定排序),这样班级内成绩仍然保持有序
场景2️⃣:带辅助信息的排序
比如你有一个商品列表,每个商品有价格和名称两个属性:
商品名 | 价格 |
---|---|
苹果 | 5 |
香蕉 | 3 |
梨子 | 5 |
你想按价格排序,但希望价格相同时仍保持原来的顺序(苹果在梨子前面),那么你需要使用稳定排序算法。
🔍 第六章:如何判断一个排序是否稳定?
你可以通过观察它的实现逻辑来判断:
- 如果排序过程中没有进行跨位置交换,或者总是从前向后移动元素,那可能是稳定的。
- 如果排序时大量交换远距离元素,特别是根据条件随意交换,那很可能是不稳定的。
🧪 第七章:动手实验(用代码看排序是否稳定)
我们以 C++ 中的 std::stable_sort
和 std::sort
来演示:
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
struct Student {
string name;
int score;
};
int main() {
vector<Student> students = {
{"小明", 85},
{"小刚", 85},
{"小红", 90}
};
// 使用 std::sort(默认不稳定)
sort(students.begin(), students.end(), [](Student a, Student b) {
return a.score < b.score;
});
cout << "使用 std::sort 排序后的顺序:" << endl;
for (auto s : students)
cout << s.name << " (" << s.score << ")" << endl;
// 重置数据
students = {{"小明", 85}, {"小刚", 85}, {"小红", 90}};
// 使用 std::stable_sort(保证稳定性)
stable_sort(students.begin(), students.end(), [](Student a, Student b) {
return a.score < b.score;
});
cout << "\n使用 std::stable_sort 排序后的顺序:" << endl;
for (auto s : students)
cout << s.name << " (" << s.score << ")" << endl;
return 0;
}
输出示例:
使用 std::sort 排序后的顺序:
小刚 (85)
小明 (85)
小红 (90)
使用 std::stable_sort 排序后的顺序:
小明 (85)
小刚 (85)
小红 (90)
可以看到,默认的 std::sort
可能会改变同分者的顺序,而 std::stable_sort
会保留原始输入顺序。
📋 第八章:总结对比表
排序类型 | 是否稳定 | 时间复杂度 | 适用场景 |
---|---|---|---|
冒泡排序 | ✅ | O(n²) | 教学、小数据排序 |
插入排序 | 类似摸牌,适合局部有序数据 | ||
归并排序 | O(n log n) | 大数据排序,要求稳定 | |
快速排序 | ❌ | 一般用途,速度快 | |
选择排序 | O(n²) | 简单直观,速度慢 | |
计数排序 | ✅ | O(n + k) | 整数排序,范围有限 |
堆排序 | ❌ | O(n log n) | 找最大/最小值 |
基数排序 | ✅ | O(d * n) | 字符串/整数排序 |
💡 第九章:学习建议 & 练习题推荐
✅ 动手练习建议
- 编写冒泡排序和选择排序程序,并测试它们对相同元素的稳定性。
- 用结构体保存学生信息(姓名、年龄、成绩),尝试用 C++ 的
std::sort
和std::stable_sort
对年龄排序,并观察稳定性差异。 - 思考:如果我需要先按性别排序,再按成绩排序,应该从哪个字段开始排?为什么?
- 查阅 STL 中
std::map
和std::multiset
的底层实现,看看它们是否使用了稳定排序特性。
📚 推荐学习资源
- C++ STL 官方文档
- LeetCode 题目:
-
- 最大数(排序应用)
-
- 插入排序链表
-
- 排序链表(归并排序应用)
-
- 洛谷题目:
- P1059 明明的随机数(简单排序去重)
- P1177 【模板】快速排序
📌 结语
排序是编程中最基础也是最重要的操作之一。掌握排序的稳定性概念,可以帮助你在处理现实问题时避免很多不必要的错误。
如果你对某个具体排序算法(如归并排序、快速排序)感兴趣,或者想了解如何自己写出一个稳定的排序函数,请告诉我!我可以为你定制进阶内容 😊
📌 下一步推荐学习:
- 快速排序详解与实现(含非递归版本)
- 归并排序详解与应用场景分析
- 自定义稳定排序函数编写技巧
- C++ STL 中排序函数的扩展使用(自定义比较器)
1 条评论
-
admin SU @ 2025-5-1 15:42:12
C++算法:排序概念和稳定性教程
1. 排序的基本概念
排序是计算机科学中一项基础且重要的操作,它的目的是将一组数据按照特定的顺序(通常是升序或降序)重新排列。例如,将一组整数
{5, 3, 8, 1, 2}
按照升序排列后变为{1, 2, 3, 5, 8}
。排序在很多场景中都有广泛应用,比如在数据库中对查询结果进行排序,在搜索引擎中对搜索结果进行排序等。2. 排序算法的稳定性
排序算法的稳定性是指在排序过程中,相等元素的相对顺序是否保持不变。如果一个排序算法能够保证相等元素的相对顺序不变,那么这个排序算法就是稳定的;反之,则是不稳定的。
例如,有一组学生数据,每个学生有姓名和成绩两个属性,现在要按照成绩对学生进行排序。如果有两个学生
A
和B
的成绩相同,在排序前A
在B
前面,经过排序后A
仍然在B
前面,那么这个排序算法就是稳定的;如果排序后B
到了A
前面,那么这个排序算法就是不稳定的。3. 常见排序算法的稳定性分析
3.1 冒泡排序(Bubble Sort)
冒泡排序是一种简单的排序算法,它重复地走访过要排序的数列,一次比较两个元素,如果它们的顺序错误就把它们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。
#include <iostream> #include <vector> // 冒泡排序函数 void bubbleSort(std::vector<int>& arr) { int n = arr.size(); for (int i = 0; i < n - 1; i++) { for (int j = 0; j < n - i - 1; j++) { if (arr[j] > arr[j + 1]) { // 交换 arr[j] 和 arr[j+1] int temp = arr[j]; arr[j] = arr[j + 1]; arr[j + 1] = temp; } } } } int main() { std::vector<int> arr = {64, 34, 25, 12, 22, 11, 90}; bubbleSort(arr); std::cout << "Sorted array: "; for (int num : arr) { std::cout << num << " "; } std::cout << std::endl; return 0; }
稳定性分析:冒泡排序是稳定的排序算法。因为在比较相邻元素时,如果它们相等,是不会进行交换的,所以相等元素的相对顺序不会改变。
3.2 选择排序(Selection Sort)
选择排序是一种简单直观的排序算法。它的工作原理是每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到全部待排序的数据元素排完。
#include <iostream> #include <vector> // 选择排序函数 void selectionSort(std::vector<int>& arr) { int n = arr.size(); for (int i = 0; i < n - 1; i++) { int minIndex = i; for (int j = i + 1; j < n; j++) { if (arr[j] < arr[minIndex]) { minIndex = j; } } // 交换 arr[i] 和 arr[minIndex] int temp = arr[i]; arr[i] = arr[minIndex]; arr[minIndex] = temp; } } int main() { std::vector<int> arr = {64, 25, 12, 22, 11}; selectionSort(arr); std::cout << "Sorted array: "; for (int num : arr) { std::cout << num << " "; } std::cout << std::endl; return 0; }
稳定性分析:选择排序是不稳定的排序算法。例如,有一组数据
{5, 8, 5, 2}
,在第一次选择最小元素时,会将第一个5
和2
交换位置,这样就改变了两个5
的相对顺序。3.3 插入排序(Insertion Sort)
插入排序是一种简单直观的排序算法。它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。
#include <iostream> #include <vector> // 插入排序函数 void insertionSort(std::vector<int>& arr) { int n = arr.size(); for (int i = 1; i < n; i++) { int key = arr[i]; int j = i - 1; while (j >= 0 && arr[j] > key) { arr[j + 1] = arr[j]; j--; } arr[j + 1] = key; } } int main() { std::vector<int> arr = {12, 11, 13, 5, 6}; insertionSort(arr); std::cout << "Sorted array: "; for (int num : arr) { std::cout << num << " "; } std::cout << std::endl; return 0; }
稳定性分析:插入排序是稳定的排序算法。在插入元素时,如果遇到相等元素,会将新元素插入到相等元素的后面,不会改变相等元素的相对顺序。
4. 总结
排序算法的稳定性在某些场景下非常重要,比如在多级排序中,先按照一个属性排序,再按照另一个属性排序时,如果第一个排序算法是稳定的,那么第二个排序可以在第一个排序的基础上进行,而不会破坏第一个排序的结果。在实际应用中,需要根据具体的需求选择合适的排序算法。
以上就是关于 C++ 中排序概念和稳定性的基础教程,希望对你有所帮助。你可以自己动手实现这些排序算法,并测试它们的稳定性。
- 1