下面是 Java 实现的快速排序,并带有详细的思路解析:
快速排序的核心思想:
- 选择基准(Pivot):从数组中选择一个元素作为基准(常见策略:选第一个、最后一个、中间、随机选)。
- 分区(Partition):调整数组,使得左侧的元素都小于基准,右侧的元素都大于基准。
- 递归排序(Recursion):对左右子数组分别递归执行快速排序。
Java 代码
import java.util.Arrays;
public class QuickSort {
public static void quickSort(int[] arr, int left, int right) {
if (left < right) {
// 1. 分区,找到 pivot(基准)索引
int pivotIndex = partition(arr, left, right);
// 2. 递归排序左半部分
quickSort(arr, left, pivotIndex - 1);
// 3. 递归排序右半部分
quickSort(arr, pivotIndex + 1, right);
}
}
private static int partition(int[] arr, int left, int right) {
int pivot = arr[right]; // 选择最右侧元素作为基准
int i = left - 1; // i 代表小于 pivot 的区域的索引
for (int j = left; j < right; j++) {
if (arr[j] < pivot) { // 发现比 pivot 小的元素
i++;
swap(arr, i, j); // 交换,把较小元素放到前面
}
}
swap(arr, i + 1, right); // 最后把 pivot 放到正确的位置
return i + 1; // 返回 pivot 的最终索引
}
private static void swap(int[] arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
public static void main(String[] args) {
int[] arr = {10, 7, 8, 9, 1, 5};
System.out.println("排序前: " + Arrays.toString(arr));
quickSort(arr, 0, arr.length - 1);
System.out.println("排序后: " + Arrays.toString(arr));
}
}
代码解析
1. quickSort(arr, left, right)
- 递归执行快排:
partition()
找到 pivot 的位置。- 对左半部分
quickSort(arr, left, pivotIndex - 1)
。 - 对右半部分
quickSort(arr, pivotIndex + 1, right)
。
2. partition(arr, left, right)
- 选取
arr[right]
作为基准pivot
。 - 遍历
arr[left]
到arr[right-1]
:- 若元素小于
pivot
,就交换到左边。
- 若元素小于
- 最后,把
pivot
放到正确的位置,并返回其索引。
3. swap(arr, i, j)
- 交换数组中的两个元素。
测试示例
输入:
int[] arr = {10, 7, 8, 9, 1, 5};
输出:
排序前: [10, 7, 8, 9, 1, 5]
排序后: [1, 5, 7, 8, 9, 10]
时间复杂度分析
场景 | 时间复杂度 |
---|---|
最优情况(均匀分割) | O(n log n) |
最坏情况(已排序或全部相等) | O(n²) |
平均情况 | O(n log n) |
优化点:
- 随机化基准(Randomized Pivot):避免最坏情况。
- 三数取中(Median of Three):选取
(arr[left], arr[mid], arr[right])
的中值作为pivot
,减少偏斜。 - 尾递归优化:减少栈调用,避免爆栈。
总结
- 快排核心是“分区 + 递归”。
- 避免最坏情况可以优化基准选择。
- 通常比归并排序快,因为不需要额外空间。
归并排序(Merge Sort)Java 实现
归并排序是一种 **分治(Divide and Conquer)**算法,它的核心思想是:
- 分割(Divide):把数组不断对半拆分,直到每个子数组只剩一个元素。
- 合并(Merge):从最小的子数组开始,两两合并,并保持有序。
- 递归执行:对左右两部分重复这个过程,最终完成排序。
Java 代码
import java.util.Arrays;
public class MergeSort {
public static void mergeSort(int[] arr, int left, int right) {
if (left < right) {
int mid = left + (right - left) / 2; // 计算中间点
mergeSort(arr, left, mid); // 递归排序左半部分
mergeSort(arr, mid + 1, right); // 递归排序右半部分
merge(arr, left, mid, right); // 合并两个有序数组
}
}
private static void merge(int[] arr, int left, int mid, int right) {
int n1 = mid - left + 1; // 左半部分大小
int n2 = right - mid; // 右半部分大小
// 创建临时数组
int[] leftArr = new int[n1];
int[] rightArr = new int[n2];
// 复制数据到临时数组
System.arraycopy(arr, left, leftArr, 0, n1);
System.arraycopy(arr, mid + 1, rightArr, 0, n2);
int i = 0, j = 0, k = left; // i:左数组索引, j:右数组索引, k:原数组索引
// 归并排序合并过程
while (i < n1 && j < n2) {
if (leftArr[i] <= rightArr[j]) {
arr[k++] = leftArr[i++];
} else {
arr[k++] = rightArr[j++];
}
}
// 处理剩余的元素(如果有)
while (i < n1) {
arr[k++] = leftArr[i++];
}
while (j < n2) {
arr[k++] = rightArr[j++];
}
}
public static void main(String[] args) {
int[] arr = {12, 11, 13, 5, 6, 7};
System.out.println("排序前: " + Arrays.toString(arr));
mergeSort(arr, 0, arr.length - 1);
System.out.println("排序后: " + Arrays.toString(arr));
}
}
代码解析
1. mergeSort(arr, left, right)
- 递归拆分 数组,直到每个子数组只剩下一个元素。
- 递归调用自身:
mergeSort(arr, left, mid)
和mergeSort(arr, mid + 1, right)
2. merge(arr, left, mid, right)
- 合并两个有序数组(
leftArr
和rightArr
)。 - 双指针 遍历
leftArr
和rightArr
,将较小的元素放入arr[k]
。 - 合并完成 后,将剩余的元素复制回
arr
。
测试示例
输入:
int[] arr = {12, 11, 13, 5, 6, 7};
输出:
排序前: [12, 11, 13, 5, 6, 7]
排序后: [5, 6, 7, 11, 12, 13]
时间复杂度分析
场景 | 时间复杂度 |
---|---|
最优情况 | O(n log n) |
最坏情况 | O(n log n) |
平均情况 | O(n log n) |
归并排序 稳定(不会改变相等元素的顺序),但空间复杂度 O(n),因为需要额外的临时数组。
优化点
- 优化小数组处理:
- 对较小的子数组(如长度 <= 10),使用插入排序代替归并排序。
- 原地归并(In-Place Merge):
- 避免额外空间使用,但实现复杂。
- 迭代归并(非递归):
- 用
for
循环按 2、4、8、16... 长度逐步合并。
- 用
总结
✅ 稳定排序,适用于大规模数据排序。
✅ 时间复杂度 O(n log n),比快速排序在最坏情况下更优。
❌ 额外空间开销 O(n),适合链表排序,不适合大数据数组。
适用于 大数据排序、链表排序、外部排序(磁盘数据),而 快速排序 在内存排序时通常更快。 🚀
堆排序(Heap Sort)Java 实现
堆排序是一种 基于堆数据结构 的排序算法,利用 二叉堆(Binary Heap) 进行排序。
它的 时间复杂度始终是 O(n log n),而且 不需要额外的存储空间(原地排序),但 不是稳定排序。
堆排序的核心思路
- 构建大顶堆(Max Heap):
- 让数组变成一个 最大堆(即堆顶是最大元素)。
- 从 最后一个非叶子节点 开始 向下调整(Heapify)。
- 排序(交换 + 调整堆):
- 将堆顶元素(最大值)与最后一个元素交换,缩小堆的范围。
- 对新的堆顶执行 向下调整(Heapify),恢复最大堆性质。
- 重复这个过程,直到数组完全有序。
Java 代码
import java.util.Arrays;
public class HeapSort {
public static void heapSort(int[] arr) {
int n = arr.length;
// 1. 构建最大堆(从最后一个非叶子节点开始调整)
for (int i = n / 2 - 1; i >= 0; i--) {
heapify(arr, n, i);
}
// 2. 依次取出堆顶元素(最大值),放到数组末尾,并调整堆
for (int i = n - 1; i > 0; i--) {
swap(arr, 0, i); // 交换堆顶和最后一个元素
heapify(arr, i, 0); // 重新调整堆(排除最后的已排序部分)
}
}
// 维护最大堆性质(向下调整)
private static void heapify(int[] arr, int n, int i) {
int largest = i; // 假设当前节点 i 是最大值
int left = 2 * i + 1; // 左子节点
int right = 2 * i + 2; // 右子节点
// 比较左子节点
if (left < n && arr[left] > arr[largest]) {
largest = left;
}
// 比较右子节点
if (right < n && arr[right] > arr[largest]) {
largest = right;
}
// 如果最大值不是当前节点,交换并继续向下调整
if (largest != i) {
swap(arr, i, largest);
heapify(arr, n, largest);
}
}
// 交换数组中的两个元素
private static void swap(int[] arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
public static void main(String[] args) {
int[] arr = {4, 10, 3, 5, 1};
System.out.println("排序前: " + Arrays.toString(arr));
heapSort(arr);
System.out.println("排序后: " + Arrays.toString(arr));
}
}
代码解析
1. heapSort(arr)
- 构建最大堆:
for (int i = n / 2 - 1; i >= 0; i--)
- 从最后一个非叶子节点开始(
n/2 - 1
),向下调整heapify()
。
- 从最后一个非叶子节点开始(
- 排序(交换 + 重新堆化)
- 交换堆顶(最大值)与最后一个元素
- 调用
heapify()
重新调整堆。
2. heapify(arr, n, i)
- 递归维护 最大堆,确保
arr[i]
及其子节点满足 堆的性质:- 找到当前节点
i
、左子2i+1
、右子2i+2
中最大值。 - 若
i
不是最大值,交换arr[i]
与最大值,并递归调整。
- 找到当前节点
测试示例
输入:
int[] arr = {4, 10, 3, 5, 1};
输出:
排序前: [4, 10, 3, 5, 1]
排序后: [1, 3, 4, 5, 10]
时间复杂度分析
操作 | 时间复杂度 |
---|---|
构建堆 | O(n) |
每次取出堆顶 & 重新堆化 | O(log n) |
总时间复杂度 | O(n log n) |
相比于快速排序(最坏 O(n²)),堆排序的时间复杂度始终是 O(n log n),适用于大数据排序。
堆排序 vs 其他排序算法
排序算法 | 时间复杂度 | 空间复杂度 | 稳定性 | 适用场景 |
---|---|---|---|---|
堆排序 | O(n log n) | O(1) | ❌ 不稳定 | 适用于大规模数据,无额外空间要求 |
快速排序 | O(n log n)(最坏 O(n²)) | O(log n) | ❌ 不稳定 | 大多数情况下比堆排序快 |
归并排序 | O(n log n) | O(n) | ✅ 稳定 | 适用于链表排序,外部排序 |
插入排序 | O(n²) | O(1) | ✅ 稳定 | 适用于小规模数据(n ≤ 50) |
优化点
- 减少递归调用
- 使用 非递归版本的 heapify,避免深度递归导致的栈溢出。
- 采用最小堆(Min Heap)
- 若要从小到大排序,可以使用 最小堆,这样堆顶是最小值,每次取出放入结果数组末尾。
总结
✅ 时间复杂度 O(n log n),适用于大数据排序。
✅ 空间复杂度 O(1),不需要额外存储空间。
❌ 不稳定排序,相同元素的相对位置可能会改变。
❌ 比快速排序慢(因为额外的堆调整)。
如果你对数据顺序要求稳定性(如事务处理),使用归并排序;如果追求速度,优先选择快速排序;如果内存有限,堆排序是不错的选择! 🚀
当然可以!下面是 希尔排序(Shell Sort) 和 基数排序(Radix Sort) 的详细原理解析和 Java 实现代码。
✅ 一、希尔排序(Shell Sort)
📌 排序原理(思路):
-
希尔排序是对插入排序的优化。
-
核心思想是将数组按某个“间隔 gap”进行分组,对每组进行插入排序。
-
每轮减少 gap,最终 gap 为 1 时进行标准插入排序,此时数据已经基本有序,所以效率较高。
-
常用 gap 序列如:
gap = gap / 2
、Knuth序列
等。
✅ Java 实现代码:
public class ShellSort {
public static void shellSort(int[] arr) {
int n = arr.length;
// 从数组长度的一半开始,不断缩小 gap,直到 gap = 1
for (int gap = n / 2; gap > 0; gap /= 2) {
// 对每个间隔进行插入排序
for (int i = gap; i < n; i++) {
int temp = arr[i];
int j = i;
// 类似插入排序,只是步长为 gap
while (j >= gap && arr[j - gap] > temp) {
arr[j] = arr[j - gap];
j -= gap;
}
arr[j] = temp;
}
}
}
// 示例测试
public static void main(String[] args) {
int[] arr = {9, 8, 3, 7, 5, 6, 4, 1};
System.out.println("排序前: " + java.util.Arrays.toString(arr));
shellSort(arr);
System.out.println("排序后: " + java.util.Arrays.toString(arr));
}
}
⏱️ 时间复杂度(取决于 gap 序列):
最好 | 最坏 | 平均 | 空间 | 稳定性 |
---|---|---|---|---|
O(n log n) | O(n²) | O(n^1.3) | O(1) | ❌ 不稳定 |
✅ 二、基数排序(Radix Sort)
📌 排序原理(思路):
-
基数排序是一种非比较型排序,基于“分配与收集”思想。
-
按位处理,从最低位到最高位(LSD法),逐位使用稳定的排序方法(如计数排序)排序。
-
避免了元素之间的比较,适用于非负整数或字符串排序。
示例:对 [170, 45, 75, 90, 802, 24, 2, 66]
先按个位排 → 十位 → 百位。
✅ Java 实现代码:
public class RadixSort {
// 主函数
public static void radixSort(int[] arr) {
int max = getMax(arr); // 获取最大数,确定位数
// 从个位开始排序(exp 表示位权:1, 10, 100...)
for (int exp = 1; max / exp > 0; exp *= 10) {
countingSortByDigit(arr, exp);
}
}
// 按当前位(exp)进行计数排序
private static void countingSortByDigit(int[] arr, int exp) {
int n = arr.length;
int[] output = new int[n];
int[] count = new int[10]; // 0~9 共10个数字
// 1. 统计当前位数字出现次数
for (int i = 0; i < n; i++) {
int digit = (arr[i] / exp) % 10;
count[digit]++;
}
// 2. 累加 count 数组,确定索引位置
for (int i = 1; i < 10; i++) {
count[i] += count[i - 1];
}
// 3. 从后往前遍历,构建 output(保持稳定性)
for (int i = n - 1; i >= 0; i--) {
int digit = (arr[i] / exp) % 10;
output[--count[digit]] = arr[i];
}
// 4. 拷贝回原数组
System.arraycopy(output, 0, arr, 0, n);
}
private static int getMax(int[] arr) {
int max = arr[0];
for (int val : arr) {
if (val > max) max = val;
}
return max;
}
// 示例测试
public static void main(String[] args) {
int[] arr = {170, 45, 75, 90, 802, 24, 2, 66};
System.out.println("排序前: " + java.util.Arrays.toString(arr));
radixSort(arr);
System.out.println("排序后: " + java.util.Arrays.toString(arr));
}
}
⏱️ 时间复杂度:
最好 | 最坏 | 平均 | 空间 | 稳定性 |
---|---|---|---|---|
O(nk) | O(nk) | O(nk) | O(n + k) | ✅ 稳定 |
其中
k
是数字位数,n
是元素数量。
如果你还需要基数排序处理负数或字符串的版本,也可以告诉我,我可以一起扩展。需要我整理成 Markdown 或 PDF 文件也没问题~