如何分析排序算法
最好、最坏、平均时间复杂度
- 算法的时间复杂度,会随着排序集合的有序性而改变。我们需要分析不同算法在不同数据下的表现
- 最好时间复杂度:在完全有序的情况下的时间复杂度(满有序度)
- 最坏时间复杂度:在最坏情况下的时间复杂度(有序度为0)
内存消耗
- 就是排序算法的 空间复杂度
- 原地排序:原地排序算法,就是特指空间复杂度是 O(1) 的排序算法。
算法的稳定性
- 如果待排序的序列中存在值相等的元素,经过排序之后,相等元素之间原有的先后顺序不变。
有序度、逆序度
- 有序度是数组中具有有序关系的元素对的个数
- 逆序度 = 满有序度 - 有序度
- 排序的过程就是一种增加有序度,减少逆序度的过程,最后达到满有序度,就说明排序完成了。
实例
- 对于一个倒序排列的数组,比如 6,5,4,3,2,1,有序度是 0
- 对于一个完全有序的数组,比如 1,2,3,4,5,6,有序度就是 n*(n-1)/2,也就是 15
- 我们把完全有序的数组的有序度叫作满有序度。
分析
冒泡排序的原理和性能分析
原理
- 冒泡排序只会操作相邻的两个数据。
- 冒泡排序会将相邻的两个数据两两比较,如果不符合要求则进行位置交换
- 优化:当某次遍历没有数据交换的时候,则停止排序
代码
// 冒泡排序,a表示数组,n表示数组大小
public void bubbleSort(int[] a) {
if (a.length <= 1) return;
for (int i = 0; i < a.length; ++i) {
// 提前退出冒泡循环的标志位
boolean flag = false;
for (int j = 0; j < a.length - i - 1; ++j) {
if (a[j] > a[j+1]) { // 交换
int tmp = a[j];
a[j] = a[j+1];
a[j+1] = tmp;
flag = true; // 表示有数据交换
}
}
if (!flag) break; // 没有数据交换,提前退出
}
}
性能分析
- 是否是原地排序算法
- 只进行了比较和交换操作,没有借助额外的空间。
- 是否是稳定的
- 当有相邻的两个元素大小相等的时候,不会交换顺序
- 时间复杂度分析
- 最好:O(n) 完全有序。不需要重排序,只需要进行一次遍历
- 最坏:O(n²) 倒序。需要进行遍历 O(n);每次遍历都需要冒泡,共需要 N 次
- 平均:O(n²)
- 有序度
- 冒泡排序包含两个操作原子,比较和交换。
- 每交换一次,有序度就加 1。不管算法怎么改进,交换次数总是确定的,即为逆序度。
- 平均情况下,需要 n*(n-1)/4 次交换操作,比较操作肯定要比交换操作多,而复杂度的上限是 O(n2),所以平均情况下的时间复杂度就是 O(n2)
- 最坏情况,有序度为0。最好情况,有序度为 (n-1)n/2
插入排序的原理和性能分析
原理
- 将数据集分为了 已排序 和 待排序。每次的排序过程就是从待排序中取出一个,插入到已排序中的相应位置
代码
public int[] insertSort(int[] items) {
if (items.length < 1) return items;
for (int i = 1; i < items.length; i++) {
int cur = items[i];
//1. 从未排序的队列,拿出一个
int j = i - 1;
//2. 在已排序的队列,找到合适的位置
for (; j > 0; j--) {
if (items[j] > cur) {
items[j + 1] = items[j];
} else {
//找到位置
break;
}
}
//3. 插入到合适的位置
items[j] = cur;
}
return items;
}
性能分析
- 是否为原地排序
- 是。只是数据的比较和移动
- 是否稳定
- 是。对于值相同的元素,按照先来后到的顺序排列
- 时间复杂度
- 最好:O(n)。完全有序
- 最坏:O(n²)。每次都插入到已排序序列的队头
- 平均:O(n²)。每次插入操作都相当于在数组中插入一个数据,循环执行 n 次插入操作,所以平均时间复杂度为 O(n2)。
选择排序的原理和性能分析
原理
- 也是将数据及分为 已排序 和 待排序 两个区间。
- 排序过程:选取待排序区间中最小/最大的数据,放到已排序区间的队尾(与待排序区间队头交换位置)
代码
public int[] selectSort(int[] items) {
if (items.length < 1) return items;
for (int i = 0; i < items.length; i++) {
int minIndex = i;
for (int j = i; j < items.length - 1; j++) {
if (items[j + 1] < items[j]) {
minIndex = j + 1;
}
}
//swap
if (minIndex != i) {
int tmp = items[i];
items[i] = items[minIndex];
items[minIndex] = tmp;
}
}
return items;
}
性能分析
- 是否为原地排序
- 是
- 是否稳定
- 否,涉及到交换位置。每次都要找剩余未排序元素中的最小值,并和前面的元素交换位置。
- 时间复杂度
- 最好/最坏/平均:O(n²)。即使完全有序,也需要经过 ( n + 1 ) n / 2 次遍历