本文将介绍六种基本排序算法思想及Java代码实现,它们分别是:
- 插入排序
- 选择排序
- 冒泡排序
- 希尔排序
- 归并排序
- 双向切分的快速排序
本文所有排序算法都将使用泛型
<T extends Comparable>
这六个基本排序算法都放在了一个类里面,每个方法都是static
的。
为了代码更清晰,在本类里面写了两个私有的辅助方法,分别是比较和交换:
/**
* 交换两个元素
* @param arr 数组
* @param a 第一个位置
* @param b 第二个位置
* @param <T>
*/
private static <T> void exchange(T[] arr, int a, int b) {
T temp = arr[a];
arr[a] = arr[b];
arr[b] = temp;
}
/**
* 比较两个元素
* @param arr 数组
* @param a 第一个位置
* @param b 第二个位置
* @param <T>
* @return arr[a]是否小于arr[b]
*/
private static <T extends Comparable> boolean less(T[] arr, int a, int b) {
return arr[a].compareTo(arr[b]) < 0;
}
测试类代码:
public class SortTest {
private Integer[] arr;
private Integer[] sortedArr;
/**
* 初始化数组
*/
@Before
public void initArray() {
arr = new Integer[200];
Random random = new Random();
for (int i = 0; i < arr.length; i++) {
arr[i] = random.nextInt(10000);
}
sortedArr = new Integer[200];
System.arraycopy(arr, 0, sortedArr, 0, arr.length);
Arrays.sort(sortedArr);
}
/**
* 检查数组是否排好序
*/
@After
public void checkArray() {
assertArrayEquals(sortedArr, arr);
}
@Test
public void insertSort() {
Sort.insertSort(arr);
}
// 其它排序方法类似...
}
1. 插入排序
插入排序类似于每次从桌上的扑克牌中摸起一张,插入到手中的牌的合适位置,使手中的牌总是有序。可利用Java的System.arraycopy
做适当优化。
/**
* 插入排序
* 1. 维护arr[0..i)为有序数组
* 2. 取得arr[i], 把它插入到前面的有序数组中的合适的位置
* 3. i从1遍历到n - 1
* @param arr 要排序的数组
* @param <T> 类型
*/
public static <T extends Comparable> void insertSort(T[] arr) {
T temp;
for (int i = 1; i < arr.length; i++) {
int j = i;
// 这里做了一个优化,先找到位置,再一次性交换.而不是一个个交换过去.
while (j >0 && less(arr, i, j - 1))
j--;
temp = arr[i];
System.arraycopy(arr, j, arr, j + 1, i - j);
arr[j] = temp;
}
}
2. 选择排序
选择排序类似于每次从桌上的牌堆中找出最小的那张牌,放到手中的牌的最后位置。
/**
* 选择排序
* 1. 维护arr[0..i)有序
* 2. 每次从arr[i..n)中取得最小值arr[j]
* 3. 交换arr[i]和arr[j]
* 4. i从0到n - 1 遍历
* @param arr 要排序的数组
* @param <T> 类型
*/
public static <T extends Comparable> void selectSort(T[] arr) {
for (int i = 0; i < arr.length; i++) {
int min = i; // 最小值的索引
for (int j = i; j < arr.length; j++) {
if (less(arr, j, min)) {
min = j;
}
}
// 交换i和min位置的元素
if (i != min) {
exchange(arr, i, min);
}
}
}
3. 冒泡排序
冒泡排序是一种交换排序,从数组底部往顶部冒泡,如果一个元素比前一个元素小,就交换这两个元素。这里使用了flag
变量来做简单的优化。
进一步的优化思路是:不必都要两两交换,可直接通过比较,找到需要发生交换的位置m,将arr[j]插入到m,然后利用System.arraycopy
移动数组。这种思路与上述的插入排序优化思路一致。
/**
* 冒泡排序
* 1. 维持[0..i)有序
* 2. 从数组末尾n - 1 位置到 i 位置, 每个元素与上一个元素比较,若小于,则两个元素交换.
* 3. 经过一趟2之后,最后到了i出, arr[i]就是[i..n - 1]最小的元素
* 优化:设置一个标记变量flag,当循环中没有交换数据时,算法将停止循环。
* 冒泡排序是一种交换排序.
* @param arr 要排序的数组
* @param <T> 类型
*/
public static <T extends Comparable> void bubbleSort(T[] arr) {
boolean flag = true; // 本趟冒泡是否发生了交换
for (int i = 0; i < arr.length && flag; i++) {
flag = false;
for (int j = arr.length - 1; j > i; j--) {
if (less(arr, j, j - 1)) {
flag = true;
exchange(arr, j, j - 1);
}
}
}
}
4. 希尔排序
希尔排序是快速排序的改进。希尔排序的增量序列的选取比较关键。本文只是简单的使用的是2的幂的增量序列。读者可以换用其它增量序列。
/**
* 希尔排序
* 1. 修改插入排序算法,使数组每隔k个数的子数列有序
* 2. 逐步缩小k,最后缩小到1,整个数组有序.
* 3. 需要注意的是这时不能使用插入排序的优化方法,因为不能直接简单地arraycopy
* @param arr 要排序的数组
* @param <T> 类型
*/
public static <T extends Comparable> void shellSort(T[] arr) {
for(int k = arr.length / 2; k > 0; k /= 2) {
for (int i = k; i < arr.length; i += k) {
for (int j = i; j >= k && less(arr, j, j - k); j -= k) {
exchange(arr, j, j - k);
}
}
}
}
5. 归并排序
归并排序利用了递归的思想。若左右两个子数组都排好序,那可以使用一次归并过程,将这两个有序子数组合并成一个有序数组。
/**
* 归并排序
* 1. 假设[lo..mid]与[mid+1..hi]均排好序,
* 2. 合并上述排好序的数组,组成一个新的有序数组
* 3. 当子数组长度为1时,自然是已经排好序的.
* 4. 使用递归算法重复上述过程
* @param arr 要排序的数组
* @param <T> 类型
*/
public static <T extends Comparable> void mergeSort(T[] arr) {
mergeSort(arr, 0, arr.length - 1);
}
/**
* 归并排序的递归过程
* @param arr 整个数组
* @param lo 低位
* @param hi 高位
* @param <T> 类型
*/
private static <T extends Comparable> void mergeSort(T[] arr, int lo, int hi) {
// 设置递归临界条件
if (hi <= lo) return;
int mid = lo + (hi - lo) / 2; // 防止边界溢出
mergeSort(arr, lo, mid);
mergeSort(arr, mid + 1, hi);
merge(arr, lo, mid, hi); // 合并
}
/**
* 归并排序的一趟合并过程.其中arr[lo..mid]和arr[mid + 1..hi]已经排好序
* @param arr 数组
* @param lo 低位
* @param mid 中位
* @param hi 高位
* @param <T> 类型
*/
private static <T extends Comparable> void merge(T[] arr, int lo, int mid, int hi) {
// 计算两边的长度和总长度
int lLen = mid - lo + 1;
int rLen = hi - mid;
int n = lLen + rLen;
Comparable[] temp = new Comparable[n];
System.arraycopy(arr, lo, temp, 0, n);
// 三个指针,i为左边数组当前位置,j为右边数组当前位置,k为最终数组当前位置
for (int i = 0, j = lLen, k = lo; k <= hi; k++) {
if (i >= lLen) arr[k] = (T) temp[j++];
else if (j >= n ) arr[k] = (T) temp[i++];
else if (less(temp, i, j)) arr[k] = (T) temp[i++];
else arr[k] = (T) temp[j++];
}
}
6. 快速排序
快速排序也是利用了递归的思想。它对一个数组arr[]排序时,选取某一个元素为支点,小于支点的放左边,大于支点的放右边,最后把支点放到中间合适的位置,返回这个位置。
/**
* 快速排序
* 1. 选取某一个元素为flag, 比flag小的放到左边,比flag大的放到右边.可以选取flag为第一个元素.
* 2. 利用递归思想,对左边的数组和右边的数组分别重复上述过程,逐渐缩小待排序数组的范围,直到为1,此时整个数组已经排好序
* @param arr 要排序的数组
* @param <T> 类型
*/
public static <T extends Comparable> void quickSort(T[] arr) {
quickSort(arr, 0, arr.length - 1);
}
/**
* 快速排序归并方法
* @param arr 要排序的数组
* @param lo 低位
* @param hi 高位
* @param <T> 类型
*/
private static <T extends Comparable> void quickSort(T[] arr, int lo, int hi) {
if (hi <= lo) return;
int p = partition(arr, lo, hi); // 一趟切分
quickSort(arr, lo, p - 1);
quickSort(arr,p + 1, hi);
}
/**
* 一趟快速排序的切分
* 1. 设置支点为arr[lo]
* 2. 设置两个指针i, j, 从两端到中间扫描
* 3. 先从左边向右扫描,如果发现arr[i]比支点大,停下来.
* 4. 再从右边向左边扫描,如果发现arr[j]比支点小,停下来.
* 5. 交换arr[i]与arr[j],继续向后扫描.直到 i >= j
* @param arr 整个数组
* @param lo 低位
* @param hi 高位
* @return 支点的位置
*/
private static <T extends Comparable> int partition(T[] arr, int lo, int hi) {
//左右扫描指针,注意初始化的条件,是因为下面的扫描是先移动再比较.比如++i --j
int i = lo, j = hi + 1;
while (true) {
while (less(arr, ++i, lo))
if (i == hi) break;
while (less(arr, lo, --j))
if (j == lo) break;
if (i >= j) break;
exchange(arr, i, j);
}
exchange(arr, lo, j); // 这里是j而不是i,因为到最后总是j <= i 的
return j;
}
几种基本排序算法的对比(为美观,表头省略了'复杂度') | 排序算法 | 最差时间 | 平均时间 | 空间 | 稳定 |
---|---|---|---|---|---|
插入 | O(n^2^) | O(n^2^) | O(1) | 是 | |
选择 | O(n^2^) | O(n^2^) | O(1) | 是 | |
冒泡 | O(n^2^) | O(n^2^) | O(1) | 是 | |
希尔 | - | - | O(1) | 否 | |
归并 | O(nlog~2~n) | O(nlog~2~n) | O(n) | 是 | |
快速 | O(n^2^) | O(nlog~2~n) | O(1) | 否 |
其它排序希尔排序的时间复杂度取决于增量序列的选取,一般位于O(nlog~2~n)到O(n^2^)之间。
另外常见的排序算法包括:
- 堆排序:将在介绍堆数据结构时介绍;
- 基数排序:数组的每个元素在一个较小的取值范围时,很快。
- TimSort:归并排序的改进,Python和Java默认的数组排序算法。有兴趣可以查看Java的
Arrays.sort(T[] a, Comparator<? super T> c)
源码。 - ...
全部代码: