前言
平均时间复杂度为O(n2)的经典排序算法有以下三个
- 冒泡排序
- 选择排序
- 插入排序
冒泡排序
实现
public static void bubbleSort(int[] numbers) {
for(int i=0; i<numbers.length; i++) {
boolean flag = false;
for(int j=0; j<numbers.length-i-1; j++) {
if(numbers[j] > numbers[j+1]) {
int tmp = numbers[j];
numbers[j] = numbers[j+1];
numbers[j+1] = tmp;
flag = true;
}
}
// 有序提前结束
if(!flag) {
break;
}
}
}
时间复杂度
- 最好:O(n)
- 最坏:O(n2)
- 平均:O(n2)
空间复杂度
原地排序:O(1)
稳定性
冒泡排序是稳定的,排序之后相同元素的先后顺序保持不变。
选择排序
实现
public static void selectSort(int[] numbers) {
for(int i=0; i<numbers.length; i++){
int index = i;
for(int j=index; j<numbers.length; j++){
if(numbers[j] < numbers[index]) {
index = j;
}
}
int tmp = numbers[i];
numbers[i] = numbers[index];
numbers[index] = tmp;
}
}
时间复杂度
- 最好:O(n2)
- 最坏:O(n2)
- 平均:O(n2)
空间复杂度
原地排序:O(1)
稳定性
选择排序是不稳定的,排序之后相同元素的先后顺序有可能发生改变。
插入排序
实现
public static void insertSort(int[] numbers) {
for(int i=1; i<numbers.length; i++) {
int val = numbers[i];
int j = i-1;
for(; j>=0; j--) {
if(numbers[j] > val){
numbers[j+1] = numbers[j];
} else {
break;
}
}
numbers[j+1] = val;
}
}
时间复杂度
- 最好:O(n)
- 最坏:O(n2)
- 平均:O(n2)
空间复杂度
原地排序:O(1)
稳定性
选择排序是稳定的,排序之后相同元素的先后顺序保持不变。
性能测试比较
样本量为10,使用这3种算法分别对长度为10,100,1000,5000,10000的乱序数组进行排序
测试环境
> java -version
openjdk version "11.0.12" 2021-07-20 LTS
OpenJDK Runtime Environment Corretto-11.0.12.7.2 (build 11.0.12+7-LTS)
OpenJDK 64-Bit Server VM Corretto-11.0.12.7.2 (build 11.0.12+7-LTS, mixed mode)
Hardware:
Hardware Overview:
Processor Name: Quad-Core Intel Core i5
Processor Speed: 2 GHz
Number of Processors: 1
Total Number of Cores: 4
L2 Cache (per Core): 512 KB
L3 Cache: 6 MB
Hyper-Threading Technology: Enabled
Memory: 16 GB
报告
样本数据:10, 数组长度:10
冒泡排序-avg-cost:0.2 ms
选择排序-avg-cost:0.0 ms
插入排序-avg-cost:0.0 ms
样本数据:10, 数组长度:100
冒泡排序-avg-cost:0.2 ms
选择排序-avg-cost:0.1 ms
插入排序-avg-cost:0.1 ms
样本数据:10, 数组长度:1000
冒泡排序-avg-cost:1.3 ms
选择排序-avg-cost:0.9 ms
插入排序-avg-cost:0.8 ms
样本数据:10, 数组长度:5000
冒泡排序-avg-cost:15.9 ms
选择排序-avg-cost:6.4 ms
插入排序-avg-cost:2.0 ms
样本数据:10, 数组长度:10000
冒泡排序-avg-cost:90.5 ms
选择排序-avg-cost:24.7 ms
插入排序-avg-cost:7.2 ms
从测试结果可以看出,虽然三种排序算法的平均时间复杂度均为O(n2),但实际的执行效率是 插入排序 > 选择排序 > 冒泡排序,且随着数据规模的扩大,这种性能差异越大。
拓展问题
算法的复杂度分析是否可以替代性能测试?
复杂度分析提供了一个粗略的分析模型,能让我们对一个算法的效率有一个感性的认识;但是在不同的平台、不同的数据集、不同的数据量大小下,实际的性能不一定和复杂度分析的一致;因此,复杂度分析和性能测试是相辅相成的,复杂度分析可以指导性能测试用例编写,性能测试结果又反过来验证和优化复杂度分析。
为什么实际的性能测试结果插入排序要快于冒泡排序?
插入排序的交换指令少于冒泡排序
//插入排序
if(numbers[j] > val){
numbers[j+1] = numbers[j];
} else {
break;
}
//冒泡排序
if(numbers[j] > numbers[j+1]) {
int tmp = numbers[j];
numbers[j] = numbers[j+1];
numbers[j+1] = tmp;
flag = true;
}
与选择排序的性能差异比较也一样