这一节,我们对这一章所学习的排序算法做一个总结。
| 平均时间复杂度 | 最坏时间复杂度 | 最好时间复杂度 | 空间复杂度 | 是否原地排序 | 是否稳定 | |
|---|---|---|---|---|---|---|
| 1、选择排序 | 是 | |||||
| 2、堆排序 | 是 | |||||
| 3、冒泡排序 | 是 | 稳定 | ||||
| 4、插入排序 | 是 | 稳定 | ||||
| 5、希尔排序 | (暂不讨论) | (暂不讨论) | (暂不讨论) | 是 | ||
| 6、归并排序 | 稳定 | |||||
| 7、快速排序 | 是 | |||||
| 8、桶排序 | 稳定 | |||||
| 9、计数排序 | 稳定 | |||||
| 10、基数排序 | 稳定 |
Partition 测试
package cn.leetcode.sorting;
import cn.leetcode.sorting.utils.SortingUtil;
import java.util.Arrays;
public class Partition {
public static void main(String[] args) {
int[] arr = new int[]{4, 5, 1, 6, 7, 3, 2};
int pivot = arr[0];
// [1, j - 1] < pivot
// [j, i - 1] >= pivot
int j = 1;
for (int i = 1; i < arr.length; i++) {
if (arr[i] < pivot) {
SortingUtil.swap(arr, i, j);
j++;
}
System.out.println(Arrays.toString(arr));
}
SortingUtil.swap(arr, 0, j - 1);
System.out.println(Arrays.toString(arr));
}
}
测试选择排序
Java 代码:
package cn.leetcode.sorting;
import cn.leetcode.sorting.utils.SortingUtil;
public class SelectionSort implements ISortingAlgorithm {
@Override
public void sort(int[] arr) {
int len = arr.length;
for (int i = 0; i < len - 1; i++) {
// 在区间 [i, len - 1] 中找到最小元素的索引
int minIndex = i;
for (int j = i + 1; j < len; j++) {
if (arr[j] < arr[minIndex]) {
minIndex = j;
}
}
SortingUtil.swap(arr, i, minIndex);
}
}
public static void main(String[] args) {
int[] randomArray = SortingUtil.generateRandomArray(10000, 1, 10000);
SortingUtil.testSortingAlgorithm(new SelectionSort(), randomArray);
int[] randomArray2 = SortingUtil.generateRandomArray(20000, 1, 20000);
SortingUtil.testSortingAlgorithm(new SelectionSort(), randomArray2);
int[] randomArray3 = SortingUtil.generateRandomArray(40000, 1, 10000);
SortingUtil.testSortingAlgorithm(new SelectionSort(), randomArray3);
}
}
Java 代码:
package cn.leetcode.sorting;
public interface ISortingAlgorithm {
void sort(int[] arr);
}
选择排序:
import java.util.Arrays;
public class HelloAlgorithms {
/**
* 时间复杂度
* 空间复杂度
*
* @param args
*/
public static void main(String[] args) {
int[] arr = {3, 4, 5, 4, 2, 1};
int len = arr.length;
// 循环不变量:在未排定区间区间 [0, i) 中包含原始数组已经排序的 i 个最小元素
// 初始时:[0, 0) 为空
// 遍历中:[0, i) 我们总是把未排定区域中的最小元素放在索引 i 这个位置,在 i 逐渐增大的过程中保持这个性质,
// 结束:[0, len - 2] ,最后一个元素一定最大,因此 [0, len - 1] 有序
// 贪心算法:循环不变量保证了这道问题使用贪心算法是有效的
for (int i = 0; i < len - 1; i++) {
System.out.println(Arrays.toString(arr));
// 在区间 [i, len - 1] 中找到最小元素的索引
int minIndex = i;
for (int j = i + 1; j < len; j++) {
if (arr[j] < arr[minIndex]) {
minIndex = j;
}
}
swap(arr, i, minIndex);
}
System.out.println(Arrays.toString(arr));
}
private static void swap(int[] arr, int index1, int index2) {
int temp = arr[index1];
arr[index1] = arr[index2];
arr[index2] = temp;
}
}
排序工具类:
package cn.leetcode.sorting.utils;
import cn.leetcode.sorting.ISortingAlgorithm;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.Duration;
import java.time.Instant;
import java.util.Arrays;
import java.util.Random;
public class SortingUtil {
/**
* 随机数种子
*/
public static final Random RANDOM = new Random(System.currentTimeMillis());
/**
* 测试方法执行的次数
*/
private static final int TEST_TIMES = 3;
/**
* 小数点后保留几位有效数字
*/
private static final int SCALE = 8;
/**
* 将毫秒转化为秒的分母常量
*/
private static final BigDecimal DIVISOR = new BigDecimal("1000");
/**
* 打印数组
*
* @param nums 待打印数组
*/
public static void printArray(int[] nums) {
int len = nums.length;
System.out.print("[");
for (int i = 0; i < len; i++) {
System.out.print(nums[i]);
if (i == len - 1) {
System.out.println("]");
break;
}
System.out.print(", ");
}
// 使用 Java,可以用下面这一行代码代替
// System.out.println(Arrays.toString(nums));
}
/**
* 交换数组中两个索引位置的元素
*
* @param nums 数组
* @param index1 索引 1
* @param index2 索引 2
*/
public static void swap(int[] nums, int index1, int index2) {
// 严格意义上说,这里需要对 index1 和 index2 的有效性做校验
int temp = nums[index1];
nums[index1] = nums[index2];
nums[index2] = temp;
}
/**
* 复制数组:根据已经有的数组得到一个新的数组(用于比较排序算法的效率)
*
* @param nums 待复制数组
* @return 与 nums 相等的数值
*/
public static int[] copyFromArray(int[] nums) {
int len = nums.length;
int[] newArr = new int[len];
// Java 可以使用下面一行代码
// System.arraycopy(nums, 0, newArr, 0, len);
for (int i = 0; i < len; i++) {
newArr[i] = nums[i];
}
return newArr;
}
/**
* 判断两个数组是否相等,通过逐个比对的方式比较两个数组是否相等
*
* @param arr1 待比较的数组 1
* @param arr2 待比较的数组 2
*/
public static void judgeArrayEquals(int[] arr1, int[] arr2) {
int len1 = arr1.length;
int len2 = arr2.length;
if (len1 != len2) {
throw new RuntimeException("两个数组长度不相等,请检查!");
}
for (int i = 0; i < len1; i++) {
if (arr1[i] != arr2[i]) {
throw new RuntimeException("您编写的排序算法错误!");
}
}
}
/**
* 生成随机整型数组
*
* @param len 生成随机整型数组的长度
* @param min 生成随机整型中元素的最小值(可以取到)
* @param max 生成随机整型中元素的最大值(可以取到)
* @return 一个随机数组
*/
public static int[] generateRandomArray(int len, int min, int max) {
// 参数校验
assert len > 0;
if (min > max) {
int temp = max;
max = min;
min = temp;
}
int[] randomArray = new int[len];
for (int i = 0; i < len; i++) {
// nextInt(n) 生成 [0, n) 的随机整数
randomArray[i] = min + RANDOM.nextInt(max - min + 1);
}
return randomArray;
}
/**
* 测试排序算法正确性、计算排序算法执行时间
*
* @param sortingAlgorithm 我们自己编写的排序算法实现对象
*/
public static void testSortingAlgorithm(ISortingAlgorithm sortingAlgorithm, int[] arr) {
// 多运行几次,避免我们编写的算法恰好"蒙混过关"
for (int i = 0; i < TEST_TIMES; i++) {
System.out.print(String.format("生成第 %d 个数组,", i + 1));
// 根据一定的策略生成测试用例数组
// 生成测试用例数组的拷贝
int[] randomArrayCopy = SortingUtil.copyFromArray(arr);
// 将计时逻辑封装到一个函数中,更好的做法是使用动态代理或者过滤器
timingSortingAlgorithm(sortingAlgorithm, arr);
// 使用系统库函数对 randomArrayCopy 进行排序
Arrays.sort(randomArrayCopy);
// 逐个比较两个排序以后的数组元素,以验证我们编写的排序算法的正确性
SortingUtil.judgeArrayEquals(arr, randomArrayCopy);
}
System.out.println("您编写的排序算法正确!\n");
}
/**
* 统计排序算法耗时
*
* @param sortingAlgorithm 排序算法,传入我们自己编写的排序算法实现对象
* @param nums 待排序数组
*/
private static void timingSortingAlgorithm(ISortingAlgorithm sortingAlgorithm, int[] nums) {
// 使用我们的算法对 nums 进行排序
Instant startTime = Instant.now();
sortingAlgorithm.sort(nums);
Instant endTime = Instant.now();
// 以毫秒为单位
long millis = Duration.between(startTime, endTime).toMillis();
// 向上取整
BigDecimal spendBigDecimal = new BigDecimal(String.valueOf(millis)).divide(DIVISOR, SCALE, RoundingMode.CEILING);
System.out.print(String.format("耗时 %s 秒。\n", spendBigDecimal.toString()));
}
}