排序算法
待排序的元素需要实现 Java 的 Comparable 接口,该接口有 compareTo() 方法,可以用它来判断两个元素的大小关系。
使用辅助函数 less() 和 swap() 来进行比较和交换的操作,使得代码的可读性和可移植性更好。
敲黑板:排序算法的成本模型是比较和交换的次数,也是衡量排序算法的好坏的方式。
-
选择排序(Selection Sort)
从数组中选择最小元素,将它与数组的第一个元素交换位置。再从数组剩下的元素中选择出最小的元素,将它与数组的第二个元素交换位置。不断进行这样的操作,直到将整个数组排序。
选择排序需要 ~N2/2 次比较和 ~N 次交换,它的运行时间与输入无关,这个特点使得它对一个已经排序的数组也需要这么多的比较和交换操作。
看一张动图(动图网站链接在下面大家没事可以去看看,那个网站有各种算法可以模拟过程让你更直观的理解算法)
-
冒泡排序(Bubble Sort)
从左到右不断交换相邻逆序的元素,在一轮的循环之后,可以让未排序的最大元素上浮到右侧。
在一轮循环中,如果没有发生交换,那么说明数组已经是有序的,此时可以直接退出。
-
插入排序(Insertion Sort)
每次都将当前元素插入到左侧已经排序的数组中,使得插入之后左侧数组依然有序。
对于数组 {3, 5, 2, 4, 1},它具有以下逆序:(3, 2), (3, 1), (5, 2), (5, 4), (5, 1), (2, 1), (4, 1),插入排序每次只能交换相邻元素,令逆序数量减少 1,因此插入排序需要交换的次数为逆序数量。
插入排序的时间复杂度取决于数组的初始顺序,如果数组已经部分有序了,那么逆序较少,需要的交换次数也就较少,时间复杂度较低。
- 平均情况下插入排序需要 ~N2/4 比较以及 ~N2/4 次交换;
- 最坏的情况下需要 ~N2/2 比较以及 ~N2/2 次交换,最坏的情况是数组是倒序的;
- 最好的情况下需要 N-1 次比较和 0 次交换,最好的情况就是数组已经有序了
-
希尔排序(Shell Sort)
对于大规模的数组,插入排序很慢,因为它只能交换相邻的元素,每次只能将逆序数量减少 1。希尔排序的出现就是为了解决插入排序的这种局限性,它通过交换不相邻的元素,每次可以将逆序数量减少大于 1。
希尔排序使用插入排序对间隔 h 的序列进行排序。通过不断减小 h,最后令 h=1,就可以使得整个数组是有序的。
-
归并排序(Merge Sort)
归并排序的思想是将数组分成两部分,分别进行排序,然后归并起来。
1. 归并方法
归并方法将数组中两个已经排序的部分归并成一个。
-
public
abstract
class MergeSort<T extends Comparable<T>> extends Sort<T> {
-
-
protected T[] aux;
-
-
-
protected void merge(T[] nums, int l, int m, int h) {
-
-
int i = l, j = m +
1;
-
-
for (
int k = l; k <= h; k++) {
-
aux[k] = nums[k];
// 将数据复制到辅助数组
-
}
-
-
for (
int k = l; k <= h; k++) {
-
if (i > m) {
-
nums[k] = aux[j++];
-
-
}
else
if (j > h) {
-
nums[k] = aux[i++];
-
-
}
else
if (aux[i].compareTo(aux[j]) <=
0) {
-
nums[k] = aux[i++];
// 先进行这一步,保证稳定性
-
-
}
else {
-
nums[k] = aux[j++];
-
}
-
}
-
}
-
}
-
2. 自顶向下归并排序
将一个大数组分成两个小数组去求解。
因为每次都将问题对半分成两个子问题,这种对半分的算法复杂度一般为 O(NlogN)。
-
public
class Up2DownMergeSort<T extends Comparable<T>> extends MergeSort<T> {
-
-
@Override
-
public void sort(T[] nums) {
-
aux = (T[])
new Comparable[nums.length];
-
sort(nums,
0, nums.length -
1);
-
}
-
-
private void sort(T[] nums, int l, int h) {
-
if (h <= l) {
-
return;
-
}
-
int mid = l + (h - l) /
2;
-
sort(nums, l, mid);
-
sort(nums, mid +
1, h);
-
merge(nums, l, mid, h);
-
}
-
}
3. 自底向上归并排序
先归并那些微型数组,然后成对归并得到的微型数组。
-
public
class Down2UpMergeSort<T extends Comparable<T>> extends MergeSort<T> {
-
-
@Override
-
public void sort(T[] nums) {
-
-
int N = nums.length;
-
aux = (T[])
new Comparable[N];
-
-
for (
int sz =
1; sz < N; sz += sz) {
-
for (
int lo =
0; lo < N - sz; lo += sz + sz) {
-
merge(nums, lo, lo + sz -
1, Math.min(lo + sz + sz -
1, N -
1));
-
}
-
}
-
}
-
}
-
快速排序(Quick Sort)
快速排序的基本思想:通过一趟排序将待排记录分隔成独立的两部分,其中一部分记录的关键字均比另一部分的关键字小,则可分别对这两部分记录继续进行排序,以达到整个序列有序。
快速排序使用分治法来把一个串(list)分为两个子串(sub-lists)。具体算法描述如下:
- 从数列中挑出一个元素,称为 “基准”(pivot);
- 重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作;
- 递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序。
排序算法比较
Java 的排序算法实现
Java 主要排序方法为 java.util.Arrays.sort(),对于原始数据类型使用三向切分的快速排序,对于引用类型使用归并排序。
ps:动图网址:https://visualgo.net