跳到主要内容Javajava算法
Java 七大排序算法(中篇):冒泡与快速排序实现
冒泡排序通过相邻元素比较交换逐步将最大元素移至末尾,优化后可在最好情况下达到 O(N)。快速排序基于分治思想,选取基准值划分左右子序列。本文详细讲解 Hoare 法、挖坑法及前后指针法的分区逻辑,并引入三数取中和小数组插入排序优化递归深度,提升整体性能。
刀狂5 浏览 数据结构:冒泡排序与快速排序详解
1. 冒泡排序
1.1 算法思路
冒泡排序的核心在于通过相邻元素的比较和交换,将较大的元素逐步'浮'到数组末尾。具体过程如下:
- 从前往后依次比较相邻元素,若前一个比后一个大则交换。
- 一趟遍历后,当前未排序部分的最大元素会到达末尾。
- 重复上述过程,直到所有元素有序。

1.2 实现步骤
我们定义两个索引变量:i 表示已排序的趟数,j 为当前比较的起始位置。
每一趟遍历未排序区间,比较 array[j] 与 array[j+1],若前者大则交换,否则 j 自增。
1.3 基础代码
public static void swap(int[] array, int i, int j) {
int tmp = array[i];
array[i] = array[j];
array[j] = tmp;
}
public static void bubbleSort(int[] array) {
for (int i = 0; i < array.length - 1; i++) {
for (int j = 0; j < array.length - 1 - i; j++) {
if (array[j] > array[j + 1]) {
swap(array, j, j + );
}
}
}
}
1
1.4 优化策略
如果某一趟遍历中没有发生任何交换,说明数组已经有序,可以提前结束。
我们可以引入一个标记位 flag,若发生交换则置为 true。若一趟结束 flag 仍为 false,直接返回。
public static void bubbleSortOptimized(int[] array) {
for (int i = 0; i < array.length - 1; i++) {
boolean flag = false;
for (int j = 0; j < array.length - 1 - i; j++) {
if (array[j] > array[j + 1]) {
swap(array, j, j + 1);
flag = true;
}
}
if (!flag) {
return;
}
}
}
public static void swap(int[] array, int i, int j) {
int tmp = array[i];
array[i] = array[j];
array[j] = tmp;
}
1.5 特性总结
- 理解难度:低,非常适合入门。
- 时间复杂度:平均和最坏情况均为 O(N^2)。
- 空间复杂度:O(1),原地排序。
- 稳定性:稳定,相等元素不会改变相对顺序。
2. 快速排序
快速排序由 Hoare 于 1962 年提出,是一种基于二叉树结构的交换排序方法。其核心思想是分治:选取基准值,将序列分割为小于基准值的左子序列和大于基准值的右子序列,然后递归处理。
2.1 递归框架
快速排序的递归结构与二叉树的前序遍历非常相似。在处理时,可以先写出递归骨架,再专注于如何划分区间。
private static void quick(int[] array, int start, int end) {
if (start >= end) return;
int pivot = partition(array, start, end);
quick(array, start, pivot - 1);
quick(array, pivot + 1, end);
}
2.2 分区实现方式
2.2.1 Hoare 法
这是最经典的版本。以首元素为基准 key,先从右向左找比 key 小的数,再从左向右找比 key 大的数,交换它们。当左右指针相遇时,将 key 与相遇点交换。
注意:为什么先走右边?如果先走左边,相遇点的值可能比 key 大,导致基准值放错位置;先走右边能保证相遇点一定小于等于 key。等号必须取,防止指针不动越界。
private static int partitionHoare(int[] array, int left, int right) {
int key = array[left];
int i = left;
while (left < right) {
while (left < right && array[right] >= key) {
right--;
}
while (left < right && array[left] <= key) {
left++;
}
swap(array, left, right);
}
swap(array, i, left);
return left;
}
2.2.2 挖坑法
将首元素作为基准 key 取出,此时 left 位置成为一个'坑'。从右边找小填坑,再从左边找大填右边的坑,循环往复,最后将 key 填入最终的坑位。
private static int partitionPit(int[] array, int left, int right) {
int key = array[left];
while (left < right) {
while (left < right && array[right] >= key) {
right--;
}
array[left] = array[right];
while (left < right && array[left] <= key) {
left++;
}
array[right] = array[left];
}
array[left] = key;
return left;
}
2.2.3 前后指针法
使用 prev 和 cur 两个指针。cur 负责寻找小于基准值的元素,找到后 prev 前进并交换。此方法逻辑较难直观理解,建议结合图示掌握。
private static int partitionPrePost(int[] array, int left, int right) {
int prev = left;
int cur = left + 1;
while (cur <= right) {
if (array[cur] < array[left] && array[++prev] != array[cur]) {
swap(array, cur, prev);
}
cur++;
}
swap(array, prev, left);
return prev;
}
2.3 快速排序优化
优化一:三数取中法
当数组本身有序时,快排退化为单分支递归,性能急剧下降。我们可以通过'三数取中'选择更合适的基准值,避免极端情况。
private static int midOfThree(int[] array, int left, int right) {
int mid = (left + right) / 2;
if (array[right] > array[left]) {
if (array[mid] < array[left]) return left;
else if (array[mid] > array[right]) return right;
else return mid;
} else {
if (array[mid] > array[left]) return left;
else if (array[mid] < array[right]) return right;
else return mid;
}
}
int index = midOfThree(array, start, end);
swap(array, index, start);
优化二:小区间插入排序
递归深度过深会增加栈开销。当子数组长度较小时(如小于 15),直接使用插入排序效率更高。
private static void insertSortRange(int[] array, int begin, int end) {
for (int i = begin + 1; i <= end; i++) {
int tmp = array[i];
int j = i - 1;
for (; j >= begin; j--) {
if (array[j] > tmp) {
array[j + 1] = array[j];
} else {
break;
}
}
array[j + 1] = tmp;
}
}
if (end - start + 1 <= 15) {
insertSortRange(array, start, end);
return;
}
2.4 完整递归实现示例
整合上述优化,以下是基于挖坑法的完整快速排序结构:
public static void quickSort(int[] array) {
quick2(array, 0, array.length - 1);
}
private static void quick2(int[] array, int start, int end) {
if (start >= end) return;
if (end - start + 1 <= 15) {
insertSortRange(array, start, end);
return;
}
int index = midOfThree(array, start, end);
swap(array, index, start);
int pivot = partitionPit(array, start, end);
quick2(array, start, pivot - 1);
quick2(array, pivot + 1, end);
}
快速排序的非递归实现在后续内容中展开。理解这些算法的关键在于动手画图模拟过程,一旦掌握了分治与划分的逻辑,编写代码自然水到渠成。
相关免费在线工具
- Keycode 信息
查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
- Escape 与 Native 编解码
JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
- JavaScript / HTML 格式化
使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
- JavaScript 压缩与混淆
Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
- 加密/解密文本
使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
- Gemini 图片去水印
基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online