文章目录
1 冒泡排序
1.1 原理
1.2 图解
1.3 代码
public static void bubbleSort(int[] nums) {
int n = nums.length;
// 有序区为i到n-1,每次把0到i之间的最值冒泡交换到有序区的前面,使得有序区扩大一位。
for (int i = n-1; i >= 0; i--) {
// 冒泡排序优化,如果在当前趟无序区的比较中,没有发生过交换,说明当前无序区已经有序,可以直接结束排序过程
boolean swapFlag = false;
for (int j = 0; j < i ; j++) {
// 将最大值冒泡交换到有序区
if (nums[j] > nums[j+1]) {
swapFlag = true;
int t = nums[j];
nums[j] = nums[j + 1];
nums[j + 1] = t;
}
}
if (!swapFlag)
break;
}
}
2 选择排序
2.1 原理
2.2 图解
2.3 代码
public static void selectSort(int[] nums) {
for (int i = 0; i < nums.length; i++) {
int min = Integer.MAX_VALUE;
int minIdx = -1;
// 遍历无序区,找到最小(优先级最低)的数,及其索引
for (int j = i; j < nums.length; j++) {
if (nums[j] < min) {
min = nums[j];
minIdx = j;
}
}
// 将最小数与无序区的首位元素进行交换,从而扩大前面的无序区
if (i != minIdx) {
nums[minIdx] = nums[i];
nums[i] = min;
}
}
}
3 直接插入排序
3.1 原理
3.2 图解
3.3 代码
public static void insertSort(int[] nums) {
int n = nums.length;
for (int i = 1; i < n ; i++) {
// 将j = i位置,即无序区的开始,囊括到有序区,并将该位置的数据,插入到合适的位置
// 反向遍历有序区,如果当前元素不小于(优先级不低于)待插入数据),则将当前元素后移一位(当前位置不用管)
// 找到了第一个优先级低于 待插入数据 的位置, 将待插入数据 放到这个位置的后面即可。
int insertValue = nums[i];
int insertIdx = 0;
for (int j = i - 1; j >= 0 ; j--) {
if (nums[j] >= insertValue) {
nums[j + 1] = nums[j];
}
else {
insertIdx = j + 1;
break;
}
}
// 判断是否需要插入
if (insertIdx != i)
nums[insertIdx] = insertValue;
}
}
4 希尔排序 (缩小增量排序)
4.1 原理
4.1.1 交换法和移位法
4.2 图解
4.3 代码
4.3.1 交换法
// 希尔排序 交换法 (多次交换导致速度很慢)
public static void shellSortSwap(int[] nums) {
int n = nums.length;
// 初始增量为长度的一半,每次将增量缩小一半
int t = 0;
int count = 0;
for (int gap = n / 2; gap > 0 ; gap /= 2) {
for (int i = gap; i < n ; i++) {
// 从每个分组的最后一个元素开始向前遍历,进行插入排序
for (int j = i - gap; j >= 0 ; j -= gap) {
// 交换法
if (nums[j] >= nums[j + gap]) {
t = nums[j];
nums[j] = nums[j + gap];
nums[j + gap] = t;
}
}
}
//System.out.println("希尔排序第"+(++count)+"轮后: " + Arrays.toString(nums));
}
}
4.3.2 移位法
// 希尔排序 移位法
public static void shellSortShift(int[] nums) {
int n = nums.length;
// 初始增量为长度的一半,每次将增量缩小一半
int t = 0;
int count = 0;
for (int gap = n / 2; gap > 0 ; gap /= 2) {
for (int i = gap; i < n ; i++) {
// 从每个分组的最后一个元素开始向前遍历,进行插入排序
int insertValue = nums[i];
int insertIdx = i;
for (int j = i - gap; j >= 0 ; j -= gap) {
// 移位法:类似插入排序中的处理
if (nums[j] >= insertValue) {
nums[j + gap] = nums[j];
insertIdx -= gap;
}
else break;
}
if (insertIdx != i)
nums[insertIdx] = insertValue;
}
//System.out.println("希尔排序第"+(++count)+"轮后: " + Arrays.toString(nums));
}
}
5 快速排序
5.1 原理
5.2 图解
5.3 代码
选择基准值的方式有多种:例如左边界、中间位置、右边界以及随机位置,这里采用左边界。
public static void quickSort(int[] nums, int left, int right) {
if (left < right) {
// 治
int pivot_loc = partition(nums, left, right);
// 分
quickSort(nums, left, pivot_loc - 1);
quickSort(nums, pivot_loc + 1, right);
}
}
public static int partition(int[] nums, int left, int right) {
/* // 随机一个位置作为基准值(轴值)
int pivotLoc = left + new Random().nextInt(right - left + 1);
int pivot = nums[pivotLoc];
*/
int pivot = nums[left]; // 选择第一个元素作为轴值
// 以最左边为轴值时,在交换值时应该先右后左
while (left != right) {
// 从右往左找到比轴值小的元素
while (nums[right] >= pivot && left < right) {
right--;
}
// 将这个元素的值赋到left位置
if (left < right) {
nums[left] = nums[right];
left++;
}
// 从左往右找到比轴值大的元素
while (nums[left] < pivot && left < right) {
left++;
}
// 将这个元素的值赋到right位置
if (left < right) {
nums[right] = nums[left];
right--;
}
}
nums[left] = pivot;
return left;
}
6 归并排序
6.1 原理
6.2 图解
6.3 代码
少量空间换时间
public static void mergeSort(int[] nums) {
int[] temp = new int[nums.length];
divideAndConquer(nums, 0 , nums.length - 1, temp);
}
public static void divideAndConquer(int[] nums, int left, int right, int[] temp) {
if (left < right) {
int mid = left + (right - left) / 2;
// System.out.printf("正在分解数组[%d-%d]为[%d-%d][%d-%d]\n", left, right, left, mid, mid + 1, right);
//向左递归不断分解
divideAndConquer(nums, left, mid, temp);
//向右递归不断分解
divideAndConquer(nums, mid + 1, right, temp);
// 第一次到这里,数组应该被分为n份,每份长度为1,即l=r
// System.out.printf("正在合并数组[%d-%d][%d-%d]为[%d-%d]\n", left, mid, mid + 1, right, left, right);
merge(nums, left, mid, right, temp);
}
}
public static void merge(int[] nums, int left, int mid, int right, int[] temp) {
// 归并排序“治”的过程,实际上就是 合并两个有序序列的过程。
int i = left;
int j = mid + 1;
int t = 0;
while (i <= mid && j <= right) {
if (nums[i] <= nums[j]) {
temp[t] = nums[i];
t++;
i++;
}
else {
temp[t] = nums[j];
t++;
j++;
}
}
while (i <= mid) {
temp[t] = nums[i];
i++;
t++;
}
while (j <= right) {
temp[t] = nums[j];
j++;
t++;
}
// 将temp数组的当此有效元素 拷贝到原数组nums中
// 注意这里temp数组和nums数组映射关系不同
// temp 0 -> right - left - 1 nums left -> right
t = 0;
int tempLeft = left;
while (tempLeft <= right) {
nums[tempLeft] = temp[t];
t++;
tempLeft++;
}
}
7 基数排序
7.1 原理
7.2 图解
7.3 代码
public static void radixSort(int[] nums) {
int n = nums.length;
// 创建10个桶,第i个桶存储当前位为i的数据,为了防止溢出,每个桶的大小应该和原数组大小相同
int[][] buckets = new int[10][n];
// 记录每轮排序后,每个桶实际放了多少个数据
int[] bucketsTop = new int[10];
// 排序前,先遍历依次:1、找到数组最小值; 2、找到数组最大绝对值,计算最大位数
int min = 0;
for (int num : nums) {
min = Math.min(num, min);
}
// 如果min < 0,说明存在负数,将所有数据加上 -min 使得全部为正数
int max = 0;
if (min < 0) {
for (int i = 0; i < nums.length; i++) {
nums[i] += -min;
max = Math.max(max, nums[i]);
}
}
// 计算最大位长度
int maxBitLength = (max + "").length();
for (int b = 0; b <= maxBitLength; b++) {
// 遍历原数组,按照当前位每个数据的位值,将其放到对应的桶
int bit = (int) Math.pow(10, b);
for (int i = 0; i < n; i++) {
int digit = (nums[i] / bit) % 10;
buckets[digit][bucketsTop[digit]] = nums[i];
bucketsTop[digit]++;
}
// 遍历所有桶,将桶中数据按顺序放回原数组
int index = 0;
for (int i = 0; i < 10; i++) {
for (int j = 0; j < bucketsTop[i]; j++) {
nums[index] = buckets[i][j];
index++;
}
// 每一轮处理后,应该把桶的数据下标置0
bucketsTop[i] = 0;
}
}
// 如果min < 0,说明存在负数,恢复排序好的数据,所有数据加上 min
if (min < 0) {
for (int i = 0; i < nums.length; i++) {
nums[i] += min;
}
}
}
8 堆排序
9 比较
9.1 时空复杂度
搬一张尚硅谷的图
9.2 实际测试
9.2.1 代码
public class SortTest {
public static void main(String[] args) {
int length = 100000000;
int[] nums = new int[length];
for (int i = 0; i < length; i++) {
nums[i] = (int) (Math.random() * length);
}
System.out.printf("测试 %d 长度随机数组进行升序排序的耗时:\n", length);
System.out.println("冒泡排序: " + bubbleSortTest(nums) + "ms");
System.out.println("选择排序: " + selectSortTest(nums) + "ms");
System.out.println("插入排序: " + insertSortTest(nums) + "ms");
System.out.println("希尔排序交换法: " + shellSortSwap(nums) + "ms");
System.out.println("希尔排序移位法: " + shellSortShift(nums) + "ms");
System.out.println("快速排序左边基准值法: " + quickSortLeftPivotTest(nums) + "ms");
System.out.println("快速排序随机基准值法: " + quickSortRandomPivotTest(nums) + "ms");
System.out.println("归并排序:" + mergeSortTest(nums) + "ms");
System.out.println("基数排序:" + radixSortTest(nums) + "ms");
}
public static long bubbleSortTest(int[] nums) {
int[] arr = Arrays.copyOf(nums, nums.length);
long start = System.currentTimeMillis();
BubbleSort.bubbleSort(arr);
return System.currentTimeMillis() - start;
}
public static long selectSortTest(int[] nums) {
int[] arr = Arrays.copyOf(nums, nums.length);
long start = System.currentTimeMillis();
SelectSort.selectSort(arr);
return System.currentTimeMillis() - start;
}
public static long insertSortTest(int[] nums) {
int[] arr = Arrays.copyOf(nums, nums.length);
long start = System.currentTimeMillis();
InsertSort.insertSort(arr);
return System.currentTimeMillis() - start;
}
public static long shellSortSwap(int[] nums) {
int[] arr = Arrays.copyOf(nums, nums.length);
long start = System.currentTimeMillis();
ShellSort.shellSortSwap(arr);
return System.currentTimeMillis() - start;
}
public static long shellSortShift(int[] nums) {
int[] arr = Arrays.copyOf(nums, nums.length);
long start = System.currentTimeMillis();
ShellSort.shellSortShift(arr);
return System.currentTimeMillis() - start;
}
public static long quickSortLeftPivotTest(int[] nums) {
int[] arr = Arrays.copyOf(nums, nums.length);
long start = System.currentTimeMillis();
QuickSort.quickSort(arr, 0, arr.length - 1);
return System.currentTimeMillis() - start;
}
public static long quickSortRandomPivotTest(int[] nums) {
int[] arr = Arrays.copyOf(nums, nums.length);
long start = System.currentTimeMillis();
QuickSort.randomQuickSort(arr, 0, arr.length - 1);
return System.currentTimeMillis() - start;
}
public static long mergeSortTest(int[] nums) {
int[] arr = Arrays.copyOf(nums, nums.length);
long start = System.currentTimeMillis();
MergeSort.mergeSort(arr);
return System.currentTimeMillis() - start;
}
public static long radixSortTest(int[] nums) {
int[] arr = Arrays.copyOf(nums, nums.length);
long start = System.currentTimeMillis();
RadixSort.radixSort(arr);
return System.currentTimeMillis() - start;
}
}