一、快速排序
基本思想
选取一个基准值,将数组分成大于这个值的一部分和小于这个值的一部分,再用同样的方法处理这两部分的数据。
代码
java
package Sort;
import java.io.*;
/**
* 快速排序
* 最快时间复杂度:O(nlogn)
* 最差时间复杂度:O(n^2)
* 不稳定
* 想象成一个二叉树,根节点是数组的第一个元素,左右子树是数组的剩余元素。每层是n,层高是logn,所以时间复杂度是O(nlogn)。
*/
public class QuickSort {
public static void main(String[] args) throws IOException {
int[] arr = {64, 34, 25, 12, 22, 11, 90};
System.out.println("排序前的数组:");
for (int i : arr) {
System.out.print(i + " ");
}
System.out.println();
quickSort(arr, 0, arr.length - 1);
System.out.println("排序后的数组:");
for (int i : arr) {
System.out.print(i + " ");
}
}
private static void quickSort(int[] arr, int l, int r) {
if(l >= r) {
return;
}
int q = arr[l];
int i = l;
int j = r;
while (i < j) {
//从右往左找
while (i < j && arr[j] > q) {
j--;
}
if (i < j) {
arr[i++] = arr[j];
}
//从左往右找
while (i < j && arr[i] < q) {
i++;
}
if (i < j) {
arr[j--] = arr[i];
}
}
arr[i] = q;
quickSort(arr, l, i - 1);
quickSort(arr, i + 1, r);
}
}
二、归并排序
基本思想
将数组分成左右两部分,对这两部分分别进行排序然后将两个排好序的数组合并。
代码
java
package Sort;
import java.io.*;
import java.util.Arrays;
/**
* 归并排序
* 稳定的时间复杂度:O(nlogn)
* 空间复杂度:O(n)
* 稳定排序
*/
public class MergeSort {
public static void main(String[] args) throws IOException {
int[] arr = {64, 34, 25, 12, 22, 11, 90};
System.out.println("排序前的数组:");
System.out.println(Arrays.toString(arr));
mergeSort(arr, 0, arr.length - 1);
System.out.println("排序后的数组:");
System.out.println(Arrays.toString(arr));
}
private static void mergeSort(int[] arr, int l, int r) {
if (l >= r) {
return;
}
// 计算中间位置
int mid = l + (r - l) / 2;
// 递归排序左半部分
mergeSort(arr, l, mid);
// 递归排序右半部分
mergeSort(arr, mid + 1, r);
// 合并两个有序数组
merge(arr, l, mid, r);
}
private static void merge(int[] arr, int l, int mid, int r) {
// 创建临时数组
int[] temp = new int[r - l + 1];
int i = l; // 左半部分的指针
int j = mid + 1; // 右半部分的指针
int k = 0; // 临时数组的指针
// 比较两个部分的元素,将较小的放入临时数组
while (i <= mid && j <= r) {
if (arr[i] <= arr[j]) {
temp[k++] = arr[i++];
} else {
temp[k++] = arr[j++];
}
}
// 将左半部分剩余的元素复制到临时数组
while (i <= mid) {
temp[k++] = arr[i++];
}
// 将右半部分剩余的元素复制到临时数组
while (j <= r) {
temp[k++] = arr[j++];
}
// 将临时数组的元素复制回原数组
for (int m = 0; m < temp.length; m++) {
arr[l + m] = temp[m];
}
}
}
三、插入排序
基本思想
选取一个元素排好序,然后下一个元素归并到这个有序数组。
代码
java
package Sort;
/**
* 插入排序算法实现
* 基本思想:将未排序的元素逐个插入到已排序序列的正确位置
* 最差时间复杂度:O(n²)
* 最好时间复杂度: O(n),越接近有序的序列,时间复杂度越低
* 空间复杂度:O(1)
* 稳定排序
*/
public class InsertionSort {
public static void main(String[] args) {
int[] arr = {64, 34, 25, 12, 22, 11, 90};
System.out.println("排序前的数组:");
for (int i : arr) {
System.out.print(i + " ");
}
System.out.println();
insertionSort(arr);
System.out.println("排序后的数组:");
for (int i : arr) {
System.out.print(i + " ");
}
}
public static void insertionSort(int[] arr) {
int n = arr.length;
// 从第二个元素开始,逐个插入到前面的已排序序列中
for (int i = 1; i < n; i++) {
int cur = arr[i]; // 当前要插入的元素
int pre = i - 1;
// 将比cur大的元素向后移动
while(pre >= 0 && cur < arr[pre]){
arr[pre + 1] = arr[pre];
pre--;
}
// 将cur插入到正确位置
arr[pre + 1] = cur;
}
}
}
四、堆排序
基本思想
最大堆的堆顶是堆中所有元素中最大的一个,利用这个特性,每次从堆顶选出最大元素交换到数组的末尾,再对剩下的元素重新建堆(只用对堆顶元素heapify),如此往复。
ps:如何建立最大堆?父节点一定大于等于两个字节点,从下往上初始化一个最大堆,先找到最后一个非叶子节点(n/2 - 1),再(n/2-1)--依次建立父节点大于子节点的最大堆。
代码
java
package Sort;
/**
* 堆排序算法实现
* 基本思想:利用最大堆(或最小堆)的性质进行排序
* 最差时间复杂度:O(n log n)
* 最好时间复杂度:O(n log n)
* 空间复杂度:O(1)
* 不稳定排序
*/
public class HeapSort {
public static void main(String[] args) {
int[] arr = {64, 34, 25, 12, 22, 11, 90};
System.out.println("排序前的数组:");
for (int i : arr) {
System.out.print(i + " ");
}
System.out.println();
heapSort(arr);
System.out.println("排序后的数组:");
for (int i : arr) {
System.out.print(i + " ");
}
}
public static void heapSort(int[] arr) {
int n = arr.length;
// 构建最大堆(从最后一个非叶子节点开始)
for (int i = n / 2 - 1; i >= 0; i--) {
heapify(arr, n, i);
}
// 逐个提取元素
for (int i = n - 1; i > 0; i--) {
// 将当前最大值(根节点)移到数组末尾
swap(arr, 0, i);
// 对剩余元素重新调整堆
heapify(arr, i, 0);
}
}
private static void heapify(int[] arr, int heapSize, int rootIndex) {
int largest = rootIndex; // 初始化最大值为根节点
int left = 2 * rootIndex + 1; // 左子节点
int right = 2 * rootIndex + 2; // 右子节点
// 如果左子节点大于根节点
if (left < heapSize && arr[left] > arr[largest]) {
largest = left;
}
// 如果右子节点大于当前最大值
if (right < heapSize && arr[right] > arr[largest]) {
largest = right;
}
// 如果最大值不是根节点,则交换并继续调整
if (largest != rootIndex) {
swap(arr, rootIndex, largest);
heapify(arr, heapSize, largest);
}
}
private static void swap(int[] arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
五、冒泡、选择排序:只入门,项目彻底不用
基本思想
冒泡排序从左到右两两比较,得到最大的放末尾,然后对剩下的n-1个元素重复这个操作
选择排序遍历得到最大的一个元素然后放末尾,然后对剩下的n-1个元素重复这个操作
六、希尔排序:几乎彻底淘汰
基本思想
跳步分组,组内进行插入排序
如:数组:[8,2,5,1,7,3,6],长度 7
- 第一轮间隔
3分组:(0,3,6)、(1,4)、(2,5),每组内部插入排序 - 第二轮间隔
1间隔变成 1,等同于整个数组做普通插入排序,排完整体有序
七、选型口诀
- 常规排序选快排,最优nlogn最慢平方态
- 要求稳定用归并,所有场景都是nlogn
- 求取 TopK 堆排上,复杂度全程不波动
- 小数有序插排优,顺序越好越接近O(n)
- 冒泡选择学基础,平方复杂度不投产
- 希尔算法已淘汰,日常开发用不上