排序
1. 排序的概念及引⽤
1.1 排序的概念
排序:指将一组记录按照特定关键字的大小进行升序或降序排列的操作。
稳定性:在待排序序列中,若存在多个相同关键字的记录,排序后这些记录的相对顺序保持不变(即原序列中r[i]在r[j]之前且r[i]=r[j],排序后r[i]仍在前),则称该排序算法是稳定的;否则为不稳定排序。

内部排序 :数据元素全部放在内存中的排序。
外部排序:数据元素太多不能同时放在内存中,根据排序过程的要求不断的在内外存之间移动数据的排序。
1.2 排序运⽤

1.3 常⻅的排序算法

2. 常⻅排序算法的实现
2.1 插⼊排序
2.1.1 基本思想:
直接插入排序是一种简单的排序算法,其核心思想如下: 将待排序元素依次插入到一个已排好序的序列中,通过不断比较和移动元素,最终形成完整的有序序列。这个过程类似于整理扑克牌时,将新摸到的牌插入到手牌中合适位置的操作方式。
2.1.2 直接插⼊排序
在插入第i个元素(i≥1)时,前i-1个元素array[0]到array[i-1]已有序排列。此时将array[i]的排序码依次与array[i-1]、array[i-2]等元素进行比较,找到合适位置后插入,并将该位置之后的元素依次后移。

直接插⼊排序的特性总结:
- 元素集合越接近有序,直接插⼊排序算法的时间效率越⾼
- 时间复杂度:O(N^2)
- 空间复杂度:O(1),它是⼀种稳定的排序算法
- 稳定性:稳定
2.1.3 希尔排序( 缩⼩增量排序 )
希尔排序(又称缩小增量排序)是一种改进的插入排序算法。其核心思想是:首先选定一个初始间隔值,将待排序序列中的所有元素按该间隔分组,并对每组进行插入排序。随后逐步缩小间隔值,重复分组和排序过程。当间隔最终减至1时,整个序列便完成排序。

希尔排序特性总结:
- 该算法是对直接插入排序的优化改进
- 当gap>1时进行预排序,使数组逐渐接近有序状态;当gap=1时,数组已基本有序,排序效率显著提升
- 这种分阶段优化的方式能有效提高整体性能,可通过性能测试对比验证效果
2.1.3.1 希尔排序的时间复杂度计算
希尔排序的时间复杂度分析:
外层循环: 外层循环的时间复杂度为 O(log n)
内层循环:
假设共有n个数据,分为gap组,每组包含n/gap个数据。在最坏情况下,每组中的插入移动次数为1+2+3+...+(n/gap-1),因此总移动次数为:
gap × [1 + 2 + 3 + ... + (n/gap - 1)]
gap的取值序列为(以除以3为例): n/3 → n/9 → n/27 → ... → 2 → 1
具体计算示例:
- 当gap=n/3时,总移动次数为:(n/3)×(1+2) = n
- 当gap=n/9时,总移动次数为:(n/9)×(1+2+...+8) = (n/9)×[8×(1+8)/2] = 4n
- 最后一轮gap=1时,即为直接插入排序,时间复杂度为O(n²)
根据上述分析,可以绘制相应的曲线图:
因此,希尔排序在最初和最后的排序的次数都为n,即前⼀阶段排序次数是逐渐上升的状态,当到达某⼀顶点时,排序次数逐渐下降⾄n,⽽该顶点的计算暂时⽆法给出具体的计算过程
希尔排序的时间复杂度难以精确计算,这主要是由于 gap 取值的多样性。因此,不同文献中对希尔排序时间复杂度的描述往往存在差异。例如,在严蔚敏所著的《数据结构(C语言版)》中,给出的时间复杂度为:
《数据结构-⽤⾯向对象⽅法与C++描述》--- 殷⼈昆

《数据结构(C语⾔版)》--- 严蔚敏

2.2 选择排序
2.2.1 基本思想:
每次从待排序元素中选取最小(或最大)的元素,将其置于序列起始端,重复该过程直至所有元素完成排序。
2.2.2 直接选择排序
- 从数组元素array[i]到array[n-1]中选取关键码最大(或最小)的数据元素
- 如果该元素不是当前子数组的最后一个(或第一个)元素,则将其与子数组末尾(或开头)的元素交换位置
- 在缩小后的子数组array[i]到array[n-2](或array[i+1]到array[n-1])中重复上述操作,直至子数组仅剩一个元素

【直接选择排序的特性总结】
- 直接选择排序虽然思路简单易懂,但执行效率较低,实际应用场景较少
- 时间复杂度为O(N²)
- 空间复杂度为O(1)
- 该排序算法不具备稳定性
2.2.3 堆排序
堆排序(Heapsort)是指利⽤堆积树(堆)这种数据结构所设计的⼀种排序算法,它是选择排序的⼀
种。它是通过堆来进⾏选择数据。需要注意的是排升序要建⼤堆,排降序建⼩堆。

【堆排序的特性总结】
- 堆排序通过堆结构进行元素选择,显著提升了排序效率
- 时间复杂度为O(N*logN)
- 空间复杂度仅需O(1)
- 该算法不具有稳定性
2.3 交换排序
基本思想:所谓交换,就是根据序列中两个记录键值的⽐较结果来对换这两个记录在序列中的位置,交换排序的特点是:将键值较⼤的记录向序列的尾部移动,键值较⼩的记录向序列的前部移动。
2.3.1 冒泡排序
【冒泡排序的特性总结】
- 冒泡排序是一种直观易懂的排序算法
- 时间复杂度为O(N²)
- 仅需O(1)的额外空间
- 该算法具有稳定性
2.3.2 快速排序
快速排序是Hoare在1962年提出的基于二叉树结构的交换排序算法。其核心思想是:从待排序列中选取一个元素作为基准值,根据排序码将序列划分为两个子序列。左侧子序列的所有元素都小于基准值,右侧子序列的所有元素都大于基准值。然后递归地对左右子序列重复这一过程,直到所有元素都处于正确位置为止。
java
// 假设按照升序对array数组中[left, right)区间中的元素进⾏排序
void QuickSort(int[] array, int left, int right) {
if(right - left <= 1)
return;
// 按照基准值对array数组的 [left, right)区间中的元素进⾏划分
int div = partion(array, left, right);
// 划分成功后以div为边界形成了左右两部分 [left, div) 和 [div+1, right)
// 递归排[left, div)
QuickSort(array, left, div);
// 递归排[div+1, right)
QuickSort(array, div+1, right);
}
这是快速排序递归实现的核心框架,其结构与二叉树的前序遍历非常相似。在编写递归代码时,可以参考二叉树前序遍历的规则来快速构建框架。后续只需专注于如何根据基准值对区间数据进行划分即可。
将区间按照基准值划分为左右两半部分的常⻅⽅式有:
1. Hoare版
java
private static int partition(int[] array, int left, int right) {
int i = left;
int j = right;
int pivot = array[left];
while (i < j) {
while (i < j && array[j] >= pivot) {
j--;
}
while (i < j && array[i] <= pivot) {
i++;
}
swap(array, i, j);
}
swap(array, i, left);
return i;
}
2. 挖坑法
java
private static int partition(int[] array, int left, int right) {
int i = left;
int j = right;
int pivot = array[left];
while (i < j) {
while (i < j && array[j] >= pivot) {
j--;
}
array[i] = array[j];
while (i < j && array[i] <= pivot) {
i++;
}
array[j] = array[i];
}
array[i] = pivot;
return i;
}
3. 前后指针
java
private static int partition(int[] array, int left, int right) {
int prev = left ;
int cur = left+1;
while (cur <= right) {
if(array[cur] < array[left] && array[++prev] != array[cur]) {
swap(array,cur,prev);
}
cur++;
}
swap(array,prev,left);
return prev;
}
2.3.3 快速排序优化
-
三数取中法选key
-
递归到⼩的⼦区间时,可以考虑使⽤插⼊排序
2.3.4 快速排序⾮递归
java
void quickSortNonR(int[] a, int left, int right) {
Stack<Integer> st = new Stack<>();
st.push(left);
st.push(right);
while (!st.empty()) {
right = st.pop();
left = st.pop();
if(right - left <= 1)
continue;
int div = PartSort1(a, left, right);
// 以基准值为分割点,形成左右两部分:[left, div) 和 [div+1, right)
st.push(div+1);
st.push(right);
st.push(left);
st.push(div);
}
}
2.3.5 快速排序总结
- 快速排序整体的综合性能和使⽤场景都是⽐较好的,所以才敢叫快速排序
- 时间复杂度:O(N*logN)
- 空间复杂度:O(logN)
- 稳定性:不稳定

2.4 归并排序
2.4.1 基本思想
归并排序(MERGE-SORT)是一种基于归并操作的高效排序算法,采用典型的分治策略(Divide and Conquer)。其核心思想是通过将有序子序列合并来实现整体有序:首先使每个子序列有序,再确保子序列间有序。当两个有序表合并为一个有序表时,称为二路归并。
归并排序的核心步骤:

2.4.2 归并排序总结
-
归并排序的主要缺点是空间复杂度较高,需要O(N)的额外存储空间。这种算法特别适合处理磁盘上的外排序问题。
-
时间复杂度:O(N*logN)
-
空间复杂度:O(N)
-
稳定性:稳定
2.4.3 海量数据的排序问题
外部排序:当数据量超过内存容量时,需要在磁盘等外部存储设备上进行的排序操作
应用场景:内存容量为1G,需排序数据量为100G
由于内存无法容纳全部数据,必须采用外部排序,其中归并排序是最常用的外部排序算法
具体实现步骤:
- 将数据文件分割为200份,每份512MB
- 对每个512MB文件进行内部排序(此时内存足以处理,可采用任意排序算法)
- 执行2路归并操作,逐步合并200个有序子文件,最终得到完整有序结果
3. 排序算法复杂度及稳定性分析

| 排序方法 | 最好时间复杂度 | 最坏时间复杂度 | 空间复杂度 | 稳定性 |
|---|---|---|---|---|
| 冒泡排序 | O(n²) | O(n²) | O(1) | 稳定 |
| 插入排序 | O(n) | O(n²) | O(1) | 稳定 |
| 选择排序 | O(n²) | O(n²) | O(1) | 不稳定 |
| 希尔排序 | O(n) | O(n²) | O(1) | 不稳定 |
| 堆排序 | O(n·log n) | O(n·log n) | O(1) | 不稳定 |
| 快速排序 | O(n·log n) | O(n²) | O(log n) ~ O(n) | 不稳定 |
| 归并排序 | O(n·log n) | O(n·log n) | O(n) | 稳定 |
4. 其他⾮基于⽐较排序(了解)
1.计数排序
核心思想 :计数排序基于鸽巢原理,是哈希直接定址法的变形应用。
实现步骤 :
a. 统计各元素出现频次
b. 根据频次统计结果将元素回填至原序列
算法特性 :
a. 在数据范围集中的场景下效率突出,但应用场景受限
b. 时间复杂度:O(MAX(N, 数据范围))
c. 空间复杂度:O(数据范围
d. 稳定性:稳定
5. 选择题
1.快速排序算法是基于哪种方法实现的排序算法?
A:分治法 B:贪心法 C:递归法 D:动态规划法
2.对记录序列(54,38,96,23,15,72,60,45,83)进行从小到大的直接插入排序时,当插入第8个记录45时,采用从后往前比较的方式,需要比较多少次才能找到插入位置?
A: 3 B: 4 C: 5 D: 6
3.以下排序方法中需要占用O(n)辅助存储空间的是:
A: 简单排序 B: 快速排序 C: 堆排序 D: 归并排序
4.下列排序算法中既稳定且时间复杂度为O(n²)的是:
A: 快速排序 B: 冒泡排序 C: 直接选择排序 D: 归并排序
5.关于排序算法,以下说法不正确的是:
A: 快排平均时间复杂度为O(N*logN),空间复杂度为O(logN)
B: 归并排序是稳定的,堆排序和快排都不稳定
C: 当序列基本有序时,快排效率会降低,直接插入排序效率最高
D: 归并排序空间复杂度为O(N),堆排序空间复杂度为O(logN)
6.给定初始关键字序列(65,56,72,99,86,25,34,66),以第一个关键字65为基准进行一趟快速排序后,正确的结果是:
A: 34,56,25,65,86,99,72,66
B: 25,34,56,65,99,86,72,66
C: 34,56,25,65,66,99,86,72
D: 34,56,25,65,99,86,72,66
【参考答案】 1.A 2.C 3.D 4.B 5.D 6.A
感谢您的观看!

