本文是对《算法 第四版》中排序章节的总结,包括 选择排序,插入排序,希尔排序,归并排序,快速排序,堆排序和冒泡排序
各种排序算法的性能特点
有多种排序算法存在,就是因为各种算法拥有不同的性能特点,各有所长,适用于不同场合,下面是书中对各种排序算法的性能特点的总结:
算法 | 时间复杂度 | 空间复杂度 | 是否稳定 |
---|---|---|---|
选择排序 | 最差:N^2,平均:N^2,最优:N^2 | 1 | 不稳定 |
插入排序 | 最差:N^2,平均:N^2,最优:N | 1 | 稳定 |
希尔排序 | 最差:N*logN,平均:N*logN,最优:与递增序列有关 | 1 | 不稳定 |
快速排序 | 最差:N^2,平均:N*logN,最优:N*logN | lgN | 不稳定 |
归并排序 | 最差:N*logN,平均:N*logN,最优:N*logN | N | 稳定 |
堆排序 | 最差:N*logN,平均:N*logN,最优:N*logN | 1 | 不稳定 |
冒泡排序 | 最差:N^2,平均:N^2,最优:N | 1 | 稳定 |
本文使用 Java 实现以上几种排序算法,并对《算法 第四版》书中的代码稍有修改,作为演示,只针对 int[] 类型进行排序,因此文中排序算法的输入源都是 int[] 类型,并且将一些公共方法抽离出来,比如比较两个数大小的 less() 和交换两个数的 exchange() 方法,公共方法放在抽象类 SortModel.java 中,其他具体的排序方法只需要继承它,并实现自己特有的 sort() 方法即可
模版
将排序算法的公共方法放在一个抽象类中,具体的排序算法类只需要继承自这个抽象类,并实现自己的 sort() 方法即可,具体代码如下:
public abstract class SortModel {
//记录排序消耗的时间
protected long usedTime = 0;
//具体的排序方法,由子类实现
protected abstract void sort(int[] a);
//比较 a 和 b 的大小,如果 a 小于 b,则返回 true
protected boolean less(int a, int b) {
return a < b;
}
//交换数组中的两个数
protected void exchange(int[] arr, int a, int b) {
int temp = arr[a];
arr[a] = arr[b];
arr[b] = temp;
}
//打印数组
protected void show(int[] arr, int count) {
System.out.println("\n\n使用 " + getSortMethod() + " 对 " + count + " 个数排序用时: " + usedTime + "ms");
for (int i : arr) {
System.out.print(" " + i);
}
}
//获取当前使用的排序方法名称
protected abstract String getSortMethod();
}
接下来开始总结这些排序算法的具体实现
选择排序
选择排序是比较基础的排序算法,也是一种很容易想到的排序算法,具体描述是这样的:首先找到数组中最小的元素(这是一个遍历比较的过程),然后将它和数组中的第一个元素交换位置,接着在剩下的元素中找到最小的元素,将它与第二个元素交换位置,如此循环,直到将整个数组排序完成
在选择排序中一个主要的操作就是在数组中找到最小的元素,如何在一个给定的数组中找到值最小的那个元素呢?这个过程分为两步:
遍历:
遍历最简单的形式就是使用一个 for 循环,从开始索引,到结束索引,依次访问数组中的元素比较:
比较至少需要两个元素,在遍历的时候,每次只访问数组中的一个元素,因此为了能够比较,需要有一个临时索引指向的元素来和当前访问的元素进行比较,如果当前元素小于临时索引指向的元素,就把当前元素的索引赋值给临时索引
通过以上这两步,在遍历中比较,在满足当前元素小于临时索引指向的元素的条件时,就将当前元素的索引赋值给临时索引,如此循环,遍历结束后,临时索引指向的元素值便是最小元素值
假设有一组数:16,13,18,11,14,12
找最小元素的过程是:
默认临时索引为数组第一个元素,即索引为 0,开始遍历数组
当前元素是索引为 0 的元素 16,和临时索引为 0 的元素比较,相等,不赋值
当前元素是索引为 1 的元素 13,和临时索引为 0 的元素比较,小于,当前索引赋值给临时索引
当前元素是索引为 2 的元素 18,和临时索引为 1 的元素比较,大于,不赋值
当前元素是索引为 3 的元素 11,和临时索引为 1 的元素比较,小于,当前索引赋值给临时索引
当前元素是索引为 4 的元素 14,和临时索引为 3 的元素比较,大于,不赋值
当前元素是索引为 5 的元素 12,和临时索引为 3 的元素比较,大于,不赋值
遍历结束,临时索引为 3,因此索引为 3 的元素就是这个数组中的最小元素
以上是一次寻找最小元素的过程,需要执行的比较次数与遍历的数组长度成正比,如果遍历的数组长为 N,则查找最小元素需要 N 次比较
选择排序的排序过程是:
遍历索引[0-5],找到最小元素 11,对应的索引为 3,将它与索引为 0 的元素交换,交换后如下
遍历索引[1-5],找到最小元素 12,对应的索引为 5,将它与索引为 1 的元素交换,交换后如下
遍历索引[2-5],找到最小元素 13,对应的索引为 5,将它与索引为 2 的元素交换,交换后如下
遍历索引[3-5],找到最小元素 14,对应的索引为 4,将它与索引为 3 的元素交换,交换后如下
遍历索引[4-5],找到最小元素 16,对应的索引为 4,将它与索引为 4 的元素交换,交换后如下
遍历索引[5-5],找到最小元素 18,对应的索引为 5,将它与索引为 5 的元素交换,交换后如下
以上是选择排序的过程,从这个过程中可以分析到,在一次选择排序过程中需要* N+(N-1)+(N-2)+…+3+2+1 = N(N-1)/2 ~ N^2/2* 次比较和 N 次交换,需要的比较次数属于 N^2 级别。并且,在选择排序中不存在最优与最坏情况,无论输入的数据情况怎样,选择排序都需要固定次数的比较和交换,
选择排序有两个特点:
运行时间和输入无关
从上面的分析可以知道,选择排序中不存在最优情况和最坏情况,即使输入数据已经整体有序,选择排序所需要的比较和交换次数依然是固定的,只和输入数组的大小有关数据移动次数最少
选择排序所需的交换次数和输入数组的大小有关,如果输入数组大小为 N,则选择排序所需交换次数为 N