当前位置: 首页 > news >正文

Java 八大经典排序算法全解析

1. 冒泡排序(Bubble Sort)

1.1 Java 代码实现

public static void bubble(int[] arr) {// 外层循环控制总共需要进行多少轮冒泡(n-1轮即可)for (int j = 0; j < arr.length - 1; j++) {// 内层循环负责每轮两两比较,将当前未排序部分的最大元素逐渐“冒泡”到末尾// 每完成一轮,末尾一个元素归位,所以内层循环长度逐渐减少for (int i = 0; i < arr.length - 1 - j; i++) {// 比较相邻两个元素,如果前一个比后一个大,则交换,保证较大元素后移if (arr[i] > arr[i + 1]) {int temp = arr[i];arr[i] = arr[i + 1];arr[i + 1] = temp;}}}
}

1.2 解释说明

冒泡排序是一种最简单直观的排序算法。它通过多次遍历数组,比较相邻元素的大小,若顺序不正确则交换,每轮都能将最大值“冒泡”到末尾。

通俗理解:重的数像气泡一样“浮”到数组后面。

示例:

对数组 [5, 3, 4, 1, 2] 执行冒泡排序,过程如下:

  • 第 1 轮:[3, 4, 1, 2, 5] —— 最大的 5 冒到最后

  • 第 2 轮:[3, 1, 2, 4, 5] —— 次大值 4 冒到倒数第二

  • ...

  • 共执行 n-1 轮,数组最终有序。

1.3复杂度分析

性质分析
时间复杂度最坏:O(n²)(完全逆序)
最好:O(n)(已经有序)
平均:O(n²)
空间复杂度O(1),原地排序
稳定性✅ 稳定排序(相等元素不会交换位置)

优化建议:可增加“标志位”,当某轮无交换时提前退出循环,避免无意义遍历。

1.4 优劣比较

✅ 优点❌ 缺点
实现简单、逻辑直观效率低,最坏和平均都为 O(n²)
稳定性好,不会打乱相同值顺序不适合大规模数据
可优化为“冒泡 + 提前退出”形式

1.5 适用场景

  • 排序算法的入门学习

  • 数据量小、性能要求不高的场景

  • 对排序结果稳定性有要求(如:多个字段排序)


2. 选择排序(Selection Sort)

2.1 Java 代码实现

public static void select(int[] arr1) {// 外层循环控制每轮选择的起始位置,最终每轮确定一个最小元素的位置for (int i = 0; i < arr1.length - 1; i++) {int min = arr1[i];       // 记录当前轮的最小值,初始为当前位置元素int position = i;        // 记录最小值的位置索引// 内层循环从 i+1 开始遍历,寻找比 min 更小的元素for (int j = i + 1; j < arr1.length; j++) {if (arr1[j] < min) {min = arr1[j];   // 更新最小值position = j;    // 更新最小值索引}}// 将找到的最小值与当前位置元素交换,完成本轮排序arr1[position] = arr1[i];arr1[i] = min;}
}

2.2 解释说明

选择排序是一种直观的排序方法,其核心思想是:

每一轮从“未排序部分”中选出最小值,放到“已排序部分”的末尾。

以数组 [6, 3, 1, 4, 2] 为例,选择排序过程如下:

  • 第 1 轮:选择最小的 1,放到第一个位置 → [1, 3, 6, 4, 2]

  • 第 2 轮:选择次小的 2,放到第二个位置 → [1, 2, 6, 4, 3]

  • 共执行 n-1 轮,最终数组有序。

注意:选择排序在寻找最小值时不会立即交换,而是等一轮找完再换,减少了交换次数,但不是稳定排序。

2.3 复杂度分析

性质分析
时间复杂度最好、最坏、平均情况都为 O(n²),与数据初始状态无关
空间复杂度O(1),原地排序
稳定性不稳定排序(可能交换不相邻的相等元素)

⚠️ 注意:选择排序无论是否已经排好序,仍会进行全量扫描找最小值,因此无法优化为 O(n)。

2.4 优劣比较

✅ 优点❌ 缺点
实现简单,逻辑清晰时间复杂度固定为 O(n²)
比冒泡排序交换次数更少不稳定排序,可能打乱相同值顺序
适合交换代价高、读取代价低的场景不适合处理大规模数据

2.5 适用场景

  • 数据量小,且对稳定性无要求

  • 交换次数比冒泡排序更少,适用于交换成本高的场景

  • 作为教学用途了解“选择最小值”的策略


3. 插入排序(Insertion Sort)

3.1 Java 代码实现

public static void insert(int[] arr2) {// 从第二个元素开始,逐个将当前元素插入到前面已排序序列中合适的位置for (int i = 1; i < arr2.length; i++) {// 从当前元素的前一个位置开始向前比较for (int j = i - 1; j >= 0; j--) {// 如果当前元素比前一个元素小,交换位置if (arr2[j + 1] < arr2[j]) {int temp2 = arr2[j];arr2[j] = arr2[j + 1];arr2[j + 1] = temp2;} else {// 当前元素已找到合适位置,提前退出内层循环break;}}}
}

3.2 解释说明

插入排序就像“抓扑克牌”排序的过程:

  • 假设前面部分是已经排好序的;

  • 每次从无序区取一个元素,与前面有序区从后向前比较;

  • 找到合适位置插入,保持有序。

通俗理解:

就像打扑克牌时从牌堆里抓牌,一张张插入到已有的有序手牌中。

示例(数组 [5, 3, 4, 1, 2])排序过程:

  • 第 1 次:[3, 5, 4, 1, 2]

  • 第 2 次:[3, 4, 5, 1, 2]

  • 第 3 次:[1, 3, 4, 5, 2]

  • 第 4 次:[1, 2, 3, 4, 5]

3.3 复杂度分析

性质分析说明
时间复杂度最坏:O(n²)(完全逆序)
最好:O(n)(已排序)
平均:O(n²)
空间复杂度O(1),原地排序
稳定性✅ 稳定排序(相等元素不交换)

3.4 优劣比较

优点缺点
实现简单、稳定性好时间复杂度较高 O(n²)
对小规模或近乎有序数据性能较好不适合大规模无序数据
每次插入可直接定位最合适位置需大量元素移动操作

3.5适用场景

  • 数据基本有序;

  • 元素数量较少;

  • 希望排序过程尽量稳定,避免相等元素乱序;

  • 适用于链表(插入效率高)。


4. 希尔排序(Shell Sort)

4.1 Java 代码实现

public static void shel(int[] arr3) {// 从数组长度一半开始分组,逐渐缩小分组间隔直到为1for (int grp = arr3.length / 2; grp >= 1; grp /= 2) {// 遍历每组中从第grp个元素开始的元素,进行插入排序for (int i = grp; i < arr3.length; i++) {// 对当前元素与组内前面元素比较,步长为grp,实现分组插入排序for (int j = i - grp; j >= 0; j -= grp) {// 若后元素比前元素小,交换,保证组内局部有序if (arr3[j + grp] < arr3[j]) {int temp = arr3[j];arr3[j] = arr3[j + grp];arr3[j + grp] = temp;} else {// 已找到合适位置,提前退出内层循环break;}}}}
}

4.2 解释说明

希尔排序是 插入排序 的一种优化版本,得名于它的发明者 Donald Shell。它通过 将数组按一定间隔分组,对各组分别进行插入排序,从而加快整体排序过程。

核心思想:

  1. 将整个数组按一定步长(gap)分成若干个子序列;

  2. 对每个子序列进行插入排序;

  3. 不断缩小 gap,最终 gap = 1 时再进行一次普通插入排序。

最初 gap 可以设为数组长度的一半,然后每轮缩小为原来的一半,直到 gap 为 1。

以数组 [9, 8, 3, 7, 5, 6, 4, 1] 为例,gap 初始为 4:

  • 第 1 轮 gap=4 → 对 [9, 5], [8, 6], [3, 4], [7, 1] 插入排序

                               →     [5, 9], [6, 8], [3, 4], [1, 7]

            →     [5, 6, 3, 1, 9, 8, 4, 7]

  • 第 2 轮 gap=2 → 对 [5, 3, 9, 4], [6, 1, 8, 7] 插入排序

                              →      [3, 4, 5, 9], [1, 6, 7, 8]

                              →      [3, 1, 4, 6, 5, 7, 9, 8]

  • 第 3 轮 gap=1 → 普通插入排序完成收尾。

通俗理解:

原始的插入排序在大规模数据乱序时移动慢;而希尔排序先让远距离元素到差不多的位置,再让相邻元素细排,大大提高了效率。

4.3 复杂度分析

性质分析说明
最好时间复杂度O(n log n)(gap 设计合理时)
最坏时间复杂度O(n²)(gap 退化不理想时)
平均时间复杂度约 O(n^1.3 ~ n^1.5),依 gap 而异,可看成O(n log n)
空间复杂度O(1),原地排序
稳定性不稳定,可能打乱相等元素顺序

4.4 优劣比较

优点缺点
改进插入排序效率,适合较大数据量算法较复杂,不易理解
空间复杂度低(O(1))不稳定排序
可用于性能比冒泡/插入更高的排序场合gap 选择会影响排序性能

4.5 适用场景

  • 中等规模的数据排序;

  • 对空间要求较高(需原地排序);

  • 在插入排序慢的情况下使用它能显著提高性能。


5. 堆排序(Heap Sort)

5.1 Java 代码实现

// 堆排序
public static void heap(int[] arr) {// 第一步:从最后一个非叶子节点开始,构建大顶堆for (int p = arr.length / 2 - 1; p >= 0; p--) {adjust(arr, p, arr.length);}// 第二步:排序逻辑for (int i = arr.length - 1; i > 0; i--) {// 将堆顶元素(最大值)交换到数组末尾int temp = arr[0];arr[0] = arr[i];arr[i] = temp;// 对剩下的元素重新构造大顶堆adjust(arr, 0, i);}
}// 构建堆的辅助方法
public static void adjust(int[] arr, int parent, int length) {int child = 2 * parent + 1; // 左子节点while (child < length) {int rchild = child + 1; // 右子节点// 找出较大的子节点if (rchild < length && arr[rchild] > arr[child]) {child = rchild;}// 如果子节点大于父节点,则交换if (arr[child] > arr[parent]) {int temp = arr[parent];arr[parent] = arr[child];arr[child] = temp;// 继续向下调整parent = child;child = 2 * parent + 1;} else {break;}}
}

5.2 解释说明

堆排序是一种利用堆这种数据结构(通常是大顶堆)进行排序的方法。

  • 大顶堆(Max Heap):每个父节点都大于等于子节点,堆顶是当前最大的元素。

  • 每次将堆顶最大值取出放到数组末尾,然后重新调整剩余元素为堆结构

核心思想:

以数组 [4, 6, 8, 5, 9] 为例进行堆排序:

第一步:构建大顶堆

  • 数组下标为:[0, 1, 2, 3, 4]

  • 第一个非叶子节点:(n-1)/2 = 1

  • 从节点 1 开始向上构造堆:

    1. 比较节点 1(值6)和它的子节点 3 和 4(值5 和 9),9 最大 → 交换 6 和 9

    2. 继续比较新的节点 4(值6)没有子节点,结束

    3. 回到节点 0(值4)和子节点 1(现在是9)和2(8)比较 → 9 最大 → 交换 4 和 9

    4. 再比较节点 1(值4)和它的子节点(原来的6和5) → 6 最大 → 交换

➡️ 此时堆:[9, 6, 8, 5, 4]

第二步:排序

重复以下操作:

  1. 交换堆顶和最后一个元素(最大值放到末尾)

  2. 缩小堆的大小(排好序的部分不再动)

  3. 重新调整剩下部分为大顶堆

例如:

  • 交换 9 和 4 → [4, 6, 8, 5, 9]

  • 调整为堆 → [8, 6, 4, 5, 9]

  • 交换 8 和 5 → [5, 6, 4, 8, 9]

  • 调整 → [6, 5, 4, 8, 9]

  • ...

直到只剩一个元素,排序完成。

5.3 复杂度分析

属性
最好时间复杂度O(n log n)
最坏时间复杂度O(n log n)
空间复杂度O(1)
稳定性❌ 不稳定(元素可能交换顺序)

5.4 优劣比较

优点缺点
时间复杂度为 O(n log n),性能稳定实现略复杂,理解需要掌握堆结构
不受输入数据的影响,最坏情况也为 O(n log n)不是稳定排序(相等元素可能交换顺序)
空间复杂度低(O(1)),无需额外空间对部分场景不如快速排序表现优秀(例如小数据量)
适合数据量较大或对最坏性能有要求的排序建堆过程和维护堆有一定计算开销

5.5 适用场景

  • 大规模数据排序,尤其在对最坏情况性能有保障要求时;

  • 需要原地排序(in-place),不能使用额外内存;

  • 数据分布不均、已知快速排序表现不佳时,可以作为稳定替代;

  • 对稳定性要求不高的工程场景(如内核排序、数据结构底层实现);


6. 基数排序(Radix Sort)

6.1 Java 代码实现

// 基数排序(LSD:从低位到高位)
public static void radix(int[] arr) {int max = arr[0];for (int i = 1; i < arr.length; i++) {if (arr[i] > max) max = arr[i]; // 找最大值}int maxDigit = (max + "").length(); // 最大数字的位数int n = 1; // 每次处理的位数(1, 10, 100...)// 辅助空间:10个桶int[][] bucket = new int[10][arr.length];int[] count = new int[10]; // 每个桶中元素的计数for (int d = 0; d < maxDigit; d++) {// 分配:按当前位放入桶for (int i = 0; i < arr.length; i++) {int digit = (arr[i] / n) % 10;bucket[digit][count[digit]++] = arr[i];}// 收集:从桶中取出到原数组int idx = 0;for (int i = 0; i < 10; i++) {for (int j = 0; j < count[i]; j++) {arr[idx++] = bucket[i][j];}count[i] = 0; // 清空桶计数器}n *= 10; // 处理下一位}
}

6.2 解释说明

基数排序是一种非比较型整数排序算法,适用于位数有限的整数排序。

核心思想:将整数按位切割成不同的数字,然后按每个位数分别进行排序(通常从低位到高位),每次排序保持之前的相对顺序。

例如,对数组 [170, 45, 75, 90, 802, 24, 2, 66] 进行基数排序:

  • 第一次按个位排序:[170, 90, 802, 2, 24, 45, 75, 66]

  • 第二次按十位排序:[802, 2, 24, 45, 66, 170, 75, 90]

  • 第三次按百位排序:[2, 24, 45, 66, 75, 90, 170, 802]

最终得到有序数组。

注意:基数排序依赖“稳定的子排序算法”如计数排序。

6.3 复杂度分析

属性
最好时间复杂度O(n × k)(k 是位数)
最坏时间复杂度O(n × k)
空间复杂度O(n + k)
稳定性✅ 稳定排序

6.4 优劣比较

优点缺点
非比较型排序,时间复杂度低,适合大数据排序仅适用于整数或可分割为整数的排序(如字符串)
稳定排序,适合对有重复元素的排序需要额外空间(桶和辅助数组)
对数据分布不敏感,排序性能稳定实现逻辑较复杂,理解难度比冒泡、插入等略高

6.5 适用场景

  • 需要对 大量整数等长字符串 进行排序;

  • 希望使用 稳定排序算法

  • 对排序速度有较高要求,且能接受额外的空间开销;

  • 数据值范围较小但数据量很大,例如银行账单、身份证号排序等。


7. 归并排序(Merge Sort)

7.1 Java 代码实现

// 归并排序的递归拆分函数,将数组分成两部分分别排序后合并
public static void spilt(int[] arr, int left, int right) {// 递归终止条件:当左右索引相等,表示只有一个元素,已经有序,直接返回if (left == right) {return;}// 计算中间索引,避免溢出一般写法是 left + (right - left)/2,这里简单写法也可int mid = (left + right) / 2;// 递归拆分左半部分spilt(arr, left, mid);// 递归拆分右半部分spilt(arr, mid + 1, right);// 合并左右两个有序部分,得到整体有序merge(arr, left, mid, right);
}// 合并两个有序子数组 arr[left..mid] 和 arr[mid+1..right],使其整体有序
public static void merge(int[] arr, int left, int mid, int right) {int s1 = left;      // 左半部分起始索引int s2 = mid + 1;   // 右半部分起始索引int[] temp = new int[right - left + 1]; // 临时数组,用于存放合并结果int index = 0;      // 临时数组下标// 当左右两部分都还有元素时,选择较小元素放入临时数组while (s1 <= mid && s2 <= right) {if (arr[s1] < arr[s2]) {temp[index++] = arr[s1++];} else {temp[index++] = arr[s2++];}}// 左半部分剩余元素全部放入临时数组while (s1 <= mid) {temp[index++] = arr[s1++];}// 右半部分剩余元素全部放入临时数组while (s2 <= right) {temp[index++] = arr[s2++];}// 将临时数组的数据写回原数组对应区间,实现排序合并for (int i = 0; i < temp.length; i++) {arr[left + i] = temp[i];}
}

7.2 解释说明

归并排序是一种分治思想的典型应用,通过**“分+合”**的方式实现排序。

核心思想
将原数组不断地对半拆分为左右两个子数组,直到每个子数组仅包含一个元素,然后再两两合并为有序数组。

例如,对数组 [8, 4, 5, 7, 1, 3, 6, 2] 进行归并排序:

  1. 拆分成 [8, 4, 5, 7][1, 3, 6, 2]

  2. 再拆成 [8,4] [5,7] ... 直到单个元素

  3. 然后归并成 [4,8] [5,7][4,5,7,8] 等等

  4. 最终输出:[1,2,3,4,5,6,7,8]

7.3 复杂度分析

属性
最好时间复杂度O(n log n)
最坏时间复杂度O(n log n)
空间复杂度O(n)
稳定性✅ 稳定排序

7.4 优劣比较

优点缺点
时间复杂度稳定为 O(n log n)空间复杂度为 O(n),需额外空间
稳定排序,适用于对象排序(保留顺序)实现复杂度较高
性能优于简单排序,适合链表、外部排序等场景递归方式较深时存在栈溢出风险

7.5 适用场景

  • 稳定性有要求的排序任务;

  • 排序链表大规模数据(可用于外部排序);

  • 内存充足,能够接受额外空间开销

  • 数据量大,但排序性能要求稳定。


8. 快速排序(Quick Sort)

8.1 Java 代码实现

private static void quick(int[] arr, int left, int right) {// 递归终止条件:当区间内元素小于等于1个时,直接返回(已排序)if (left >= right) {return;}// 选择基准值(pivot),这里用区间第一个元素int base = arr[left];// 初始化左右指针int i = left;int j = right;// 通过双指针扫描交换,将比基准值小的放左边,比基准值大的放右边while (i != j) {// 从右向左找第一个小于基准值的元素while (arr[j] >= base && i != j) {j--;}// 从左向右找第一个大于基准值的元素while (arr[i] <= base && i != j) {i++;}// 交换两个指针所指元素,保证左边元素小于基准,右边元素大于基准int temp = arr[i];arr[i] = arr[j];arr[j] = temp;}// 此时 i 和 j 相遇,将基准值放到指针所在位置(分割点)arr[left] = arr[i];arr[i] = base;// 递归排序基准左侧子数组sort(arr, left, i - 1);// 递归排序基准右侧子数组sort(arr, i + 1, right);
}

8.2 解释说明

快速排序是一种“分而治之”的排序算法,利用递归不断将数组划分为小于和大于基准值的两部分,再分别对这两部分递归排序。

核心思想
选取一个基准值(如第一个元素),将数组中小于它的放左边,大于它的放右边,然后再对左右子数组递归进行同样操作。

例如,对 [5, 2, 9, 1, 5, 6] 进行快速排序:

  1. 选 5 为基准 → [2, 1] [5] [9, 5, 6]

  2. 继续对左右子数组排序

  3. 最终输出:[1, 2, 5, 5, 6, 9]

8.3 复杂度分析

属性
最好时间复杂度O(n log n)
最坏时间复杂度O(n²)
空间复杂度O(log n)
稳定性❌ 不稳定排序

8.4 优劣比较

优点缺点
平均时间复杂度为 O(n log n),常数项小最坏情况下时间复杂度为 O(n²)
不需要额外空间,原地排序不稳定排序,可能改变相等元素顺序
排序速度快,尤其对大数组性能优于归并、堆排序递归过深会引发栈溢出

8.5 适用场景

  • 排序性能优先考虑,且可以接受不稳定排序;

  • 数据量较大,并且数据分布较随机;

  • 内存空间有限,希望使用原地排序

  • 可接受最坏情况的优化(如使用“三数取中”或随机化枢轴)。

9.总结

排序算法时间复杂度空间复杂度稳定性优点缺点适用场景
冒泡排序最坏 O(n²),最好 O(n)O(1)稳定简单,稳定效率低小规模数据,几乎有序数据
选择排序O(n²)O(1)不稳定交换少,简单时间复杂度高,不稳定交换代价高场景,小规模
插入排序最坏 O(n²),最好 O(n)O(1)稳定简单,稳定,对近乎有序好大规模无序数据效率低小规模或部分有序数据
希尔排序约 O(n^1.3~n^1.5)O(1)不稳定改进插入排序,效率较好算法复杂,不稳定中等规模数据
堆排序O(n log n)O(1)不稳定稳定性能,不受输入影响实现复杂,不稳定大规模数据,对最坏性能要求高
基数排序O(n × k)O(n + k)稳定非比较排序,效率高只适合整数,额外空间需求大量整数排序
归并排序O(n log n)O(n)稳定稳定,适合链表和外部排序额外空间,递归复杂大数据,链表,稳定性需求高
快速排序平均 O(n log n)O(log n)不稳定平均性能最好,空间低最坏 O(n²),不稳定,递归风险大多数场景,要求速度优先

       希望通过本文,你能够更清晰地理解各种排序算法的原理、优缺点以及适用场景,从而在实际开发中能更好地选择合适的排序方法。排序虽是基础,但掌握好它们对提升程序性能大有裨益。

       如果你觉得这篇文章对你有帮助,欢迎点赞、收藏或分享,也期待你在评论区留言交流你的想法和疑问。让我们一起进步,一起成长!

http://www.dtcms.com/a/319551.html

相关文章:

  • Redis持久化存储
  • 2025 年华数杯赛题浅析-助攻快速选题
  • Centos6停止服务后yum改用阿里云
  • Syzkaller实战教程10: MoonShine复现Trace2syz功能演示
  • 手动开发一个TCP服务器调试工具(三):使用 QWidget 构建 TCP 服务控制界面
  • 强化学习详解:从理论到前沿的全面解析
  • 【Redis面试精讲 Day 15】Redis分布式锁实现与挑战
  • C++ 类和对象(2)
  • Kubernetes学习
  • 安卓开发:网络状态监听封装的奥秘
  • 根据浏览器语言判断wordpress访问不同语言的站点
  • 计算机视觉前言-----OpenCV库介绍与计算机视觉入门准备
  • Python 偏函数(functools.partial)详解
  • MySQL ORDER BY 语句详细说明
  • SVG组件
  • 96-基于Flask的酷狗音乐数据可视化分析系统
  • 微信小程序常见功能实现
  • OpenCV 入门教程:开启计算机视觉之旅
  • uwsgi 启动 django 服务
  • Next.js 15 重磅发布:React 19 集成 + 性能革命,开发者必看新特性指南
  • CentOS 7 安装 Anaconda
  • 秋招笔记-8.7
  • Redis的三种特殊类型
  • 硬盘哨兵pe版本 v25.70.6 中文免费版
  • 【R语言】 高清美观的 MaxEnt 刀切图(Jackknife)绘制——提升论文质量
  • 基于Qt的Live2D模型显示以及控制
  • DAY33打卡
  • 【Unity输入系统】自定义与双击不冲突的单击Interaction
  • 【第八章】函数进阶宝典:参数、返回值与作用域全解析
  • RedisBloom使用