1、直接插入排序
插入排序的基本操作就是将一个数据插入到已经排好序的有序数据中,从而得到一个新的、个数加一的有序数据,算法适用于少量数据的排序,时间复杂度为O(n^2)。是稳定的排序方法。
public static void insertionSort(int[] arr) {
if (arr == null || arr.length <= 1) {
return;
}
// 从第二个元素开始(第一个元素默认已排序)
for (int i = 1; i < arr.length; i++) {
int current = arr[i]; // 当前要插入的元素
int j = i - 1;
// 将比 current 大的元素向后移动
while (j >= 0 && arr[j] > current) {
arr[j + 1] = arr[j];
j--;
}
// 插入 current 到正确位置
arr[j + 1] = current;
}
}
2、冒泡排序
传统的冒泡排序需要依次比较相邻的两个元素,按照升序或者降序的规则进行交换,如要让 3 2 1三个数进行升序排序,首先从3开始跟2比较大于2进行交换,然后在与1进行比较,进行交换,第一趟排序结果就是2 1 3;然后 2与1比较大于1交换,2与3比较小于3不变。这就是冒泡排序的原理。然而,如果是让 3 2 1进行降序排序呢,还要再从头到尾比较一边岂不是很浪费空间,此时我们就可以使用一个 flag标记来对冒泡排序进行排序。具体方法我们来看一下代码。
public static void bubbleSort(int[] arr) {
if (arr == null || arr.length <= 1) {
return;
}
int n = arr.length;
// 进行 n-1 轮比较
for (int i = 0; i < n - 1; i++) {
// 每轮将最大的元素"冒泡"到末尾
for (int j = 0; j < n - 1 - i; j++) {
// 如果前面的元素比后面大,就交换
if (arr[j] > arr[j + 1]) {
swap(arr, j, j + 1);
}
}
}
}
3、选择排序
第一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,然后再从剩余的未排序元素中寻找到最小(大)元素,然后放到已排序的序列的末尾。以此类推,直到全部待排序的数据元素的个数为零。选择排序是不稳定的排序方法。
假设序列第一个位置为最小,然后依次和后面的元素进行比较,比第一个元素小的元素就设个标记再往后依次比较,直到找到最小值然后与第一个位置元素进行交换。
public static void selectionSort(int[] arr) {
if (arr == null || arr.length <= 1) {
return;
}
int n = arr.length;
// 进行 n-1 轮选择
for (int i = 0; i < n - 1; i++) {
// 假设当前轮次的第一个元素是最小值
int minIndex = i;
// 在未排序部分寻找真正的最小值
for (int j = i + 1; j < n; j++) {
if (arr[j] < arr[minIndex]) {
minIndex = j;
}
}
// 将找到的最小值与当前位置交换
if (minIndex != i) {
swap(arr, i, minIndex);
}
}
}
4、快速排序
通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。大致意思就是在一个数组中取中间元素比它小的方左边比它大的则放右边 两边元素再按照快排要求,最终变成有序序列
public static void quickSort(int[] arr, int left, int right) {
int l = left;// 左下标
int r = right;// 右下标
int pivot = arr[(left + right) / 2];// 找到中间的值
// 将比pivot小的值放在其左边,比pivot大的值放在其右边
while (l < r) {
// 在pivot左边寻找,直至找到大于等于pivot的值才退出
while (arr[l] < pivot) {
l += 1;// 将l右移一位
}
// 在pivot右边寻找,直至找到小于等于pivot的值才退出
while (arr[r] > pivot) {
r -= 1;// 将r左移一位
}
if (l >= r) {
// 左右下标重合,寻找完毕,退出循环
break;
}
// 交换元素
int temp = arr[l];
arr[l] = arr[r];
arr[r] = temp;
//倘若发现值相等的情况,则没有比较的必要,直接移动下标即可
// 如果交换完后,发现arr[l]==pivot,此时应将r左移一位
if (arr[l] == pivot) {
r -= 1;
}
// 如果交换完后,发现arr[r]==pivot,此时应将l右移一位
if (arr[r] == pivot) {
l += 1;
}
}
// 如果l==r,要把这两个下标错开,否则会出现无限递归,导致栈溢出的情况
if (l == r) {
l += 1;
r -= 1;
}
// 向左递归
if (left < r) {
quickSort(arr, left, r);
}
// 向右递归
if (right > l) {
quickSort(arr, l, right);
}
}
| 排序算法 | 平均时间复杂度 | 最坏时间复杂度 | 最好时间复杂度 | 空间复杂度 | 稳定性 |
|---|---|---|---|---|---|
| 直接插入排序 | O(n²) | O(n²) | O(n) | O(1) | ✅ 稳定 |
| 冒泡排序 | O(n²) | O(n²) | O(n) | O(1) | ✅ 稳定 |
| 选择排序 | O(n²) | O(n²) | O(n²) | O(1) | ❌ 不稳定 |
| 快速排序 | O(n log n) | O(n²) | O(n log n) | O(log n) | ❌ 不稳定 |