本篇讲解Java中选择排序、快速排序、归并排序、计数排序和桶排序这五种排序算法~~
一、选择排序 (Selection Sort)
原理
选择排序的核心思想是:每次从未排序的部分中选择最小(或最大)的元素,放到已排序部分的末尾。它是一种简单直观的排序算法,时间复杂度为O(n^2)。
算法思路与过程
- 初始状态:整个数组都是未排序的。
- 第一轮:从所有元素中找到最小的元素,将其与第一个元素交换位置。
- 第二轮:在剩余元素中找到最小的元素,将其与第二个元素交换位置。
- 重复过程:以此类推,直到所有元素都被排序。
示例过程 : 初始数组:[5, 2, 9, 1, 5, 6]
- 第1轮:找到最小元素
1,与5交换 →[1, 2, 9, 5, 5, 6] - 第2轮:在
[2, 9, 5, 5, 6]中找到最小元素2(已在位置,无需交换)→[1, 2, 9, 5, 5, 6] - 第3轮:在
[9, 5, 5, 6]中找到最小元素5,与9交换 →[1, 2, 5, 9, 5, 6] - 第4轮:在
[9, 5, 6]中找到最小元素5,与9交换 →[1, 2, 5, 5, 9, 6] - 第5轮:在
[9, 6]中找到最小元素6,与9交换 →[1, 2, 5, 5, 6, 9]
Java代码实现
java
public class SelectionSort {
public static void selectionSort(int[] arr) {
for (int i = 0; i < arr.length - 1; i++) {
int minIndex = i;
for (int j = i + 1; j < arr.length; j++) {
if (arr[j] < arr[minIndex]) {
minIndex = j;
}
}
if (minIndex != i) {
int temp = arr[i];
arr[i] = arr[minIndex];
arr[minIndex] = temp;
}
}
}
}
练习题目
- 基础实现 :使用选择排序对数组
[3, 1, 4, 1, 5, 9, 2, 6]进行排序。 - 对象排序 :对
Student对象数组按分数从低到高排序(假设Student类有score属性)。 - 降序排序:修改选择排序算法,使其按降序排列。
- 找最大值:使用选择排序思想,找出数组中的最大值及其位置。
- 稳定性问题:解释选择排序是否稳定,并举例说明。
题目详细讲解
题目1:基础实现
java
int[] arr = {3, 1, 4, 1, 5, 9, 2, 6};
SelectionSort.selectionSort(arr);
// 输出:[1, 1, 2, 3, 4, 5, 6, 9]
思路 :直接调用上述 selectionSort 方法即可。
题目2:对象排序
java
class Student {
int score;
Student(int score) { this.score = score; }
}
public static void selectionSort(Student[] arr) {
for (int i = 0; i < arr.length - 1; i++) {
int minIndex = i;
for (int j = i + 1; j < arr.length; j++) {
if (arr[j].score < arr[minIndex].score) {
minIndex = j;
}
}
if (minIndex != i) {
Student temp = arr[i];
arr[i] = arr[minIndex];
arr[minIndex] = temp;
}
}
}
思路 :将 int 数组改为 Student 数组,比较时使用 score 属性。
题目3:降序排序
java
public static void selectionSortDesc(int[] arr) {
for (int i = 0; i < arr.length - 1; i++) {
int maxIndex = i;
for (int j = i + 1; j < arr.length; j++) {
if (arr[j] > arr[maxIndex]) { // 改为找最大值
maxIndex = j;
}
}
if (maxIndex != i) {
int temp = arr[i];
arr[i] = arr[maxIndex];
arr[maxIndex] = temp;
}
}
}
思路:将寻找最小元素改为寻找最大元素。
题目4:找最大值
java
public static int findMax(int[] arr) {
int maxIndex = 0;
for (int i = 1; i < arr.length; i++) {
if (arr[i] > arr[maxIndex]) {
maxIndex = i;
}
}
return maxIndex; // 返回最大值的位置
}
思路:仅需遍历一次,记录最大值的位置。
题目5:稳定性问题 解答 :选择排序不稳定 。例如数组 [4, 2, 4, 1]:
- 第一轮:
1与第一个4交换 →[1, 2, 4, 4] - 两个
4的相对顺序改变。
二、快速排序 (Quick Sort)
原理
快速排序采用分治法思想:
- 选基准:从数组中选择一个元素作为基准(pivot)。
- 分区:将数组分为两部分,小于基准的放左边,大于基准的放右边。
- 递归:对左右子数组递归进行快速排序。
平均时间复杂度为O(n log n),最坏情况(已排序)为O(n^2)。
算法思路与过程
- 选择基准:通常选择第一个元素、最后一个元素或随机元素。
- 分区操作 :
- 设置两个指针
i(左)和j(右)。 i从左向右找大于基准的元素,j从右向左找小于基准的元素。- 交换这两个元素,直到
i和j相遇。
- 设置两个指针
- 递归排序:对基准左侧和右侧的子数组递归进行快速排序。
示例过程 : 数组:[10, 80, 30, 90, 40, 50, 70],基准选 70(最后一个元素):
- 分区:
[10, 30, 40, 50, 70, 90, 80] - 左子数组
[10, 30, 40, 50],右子数组[90, 80] - 递归排序左、右子数组。
Java代码实现
java
public class QuickSort {
public static void quickSort(int[] arr, int low, int high) {
if (low < high) {
int pivotIndex = partition(arr, low, high);
quickSort(arr, low, pivotIndex - 1);
quickSort(arr, pivotIndex + 1, high);
}
}
private static int partition(int[] arr, int low, int high) {
int pivot = arr[high]; // 选择最后一个元素作为基准
int i = low - 1;
for (int j = low; j < high; j++) {
if (arr[j] <= pivot) {
i++;
swap(arr, i, j);
}
}
swap(arr, i + 1, high);
return i + 1;
}
private static void swap(int[] arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
练习题目
- 基础实现 :对数组
[3, 6, 8, 10, 1, 2, 1]进行快速排序。 - 降序排序:修改快速排序,使其按降序排列。
- 处理空数组:优化代码,处理输入为空或长度为0的情况。
- 大量重复元素 :针对含大量重复元素的数组(如
[5, 5, 5, 2, 5])优化分区逻辑。 - 基准选择优化:实现随机选择基准的快速排序。
题目详细讲解
题目1:基础实现
java
int[] arr = {3, 6, 8, 10, 1, 2, 1};
QuickSort.quickSort(arr, 0, arr.length - 1);
// 输出:[1, 1, 2, 3, 6, 8, 10]
思路 :直接调用 quickSort 方法。
题目2:降序排序 修改 partition 中的比较逻辑:
java
if (arr[j] >= pivot) { // 改为 >=
i++;
swap(arr, i, j);
}
思路 :将 <= 改为 >=,使大于基准的元素移到左侧。
题目3:处理空数组 在 quickSort 方法开头添加检查:
java
if (arr == null || arr.length == 0) {
return;
}
题目4:大量重复元素优化 使用三路快速排序:
java
public static void quickSortThreeWay(int[] arr, int low, int high) {
if (low >= high) return;
int lt = low, gt = high;
int pivot = arr[low];
int i = low;
while (i <= gt) {
if (arr[i] < pivot) {
swap(arr, lt++, i++);
} else if (arr[i] > pivot) {
swap(arr, i, gt--);
} else {
i++;
}
}
quickSortThreeWay(arr, low, lt - 1);
quickSortThreeWay(arr, gt + 1, high);
}
思路 :将数组分为 < pivot、= pivot 和 > pivot 三部分。
题目5:随机基准 修改 partition 方法,随机选择基准:
java
private static int partitionRandom(int[] arr, int low, int high) {
int randomIndex = low + (int)(Math.random() * (high - low + 1));
swap(arr, randomIndex, high); // 将随机基准交换到末尾
return partition(arr, low, high); // 调用原partition
}
思路:随机选择基准并交换到末尾,避免最坏情况。
三、归并排序 (Merge Sort)
原理
归并排序采用分治法:
- 分解:将数组分成两半。
- 递归排序:对两半分别递归排序。
- 合并:将两个有序数组合并成一个有序数组。
时间复杂度稳定为O(n \log n),需要额外O(n)空间。
算法思路与过程
- 分解:将数组从中间分成左右两部分。
- 递归:对左右子数组递归调用归并排序。
- 合并 :
- 创建临时数组。
- 比较左右子数组的元素,将较小的放入临时数组。
- 将剩余元素复制到临时数组。
- 将临时数组复制回原数组。
示例过程 : 数组:[38, 27, 43, 3, 9, 82, 10]
- 分解:
[38, 27, 43, 3]和[9, 82, 10] - 继续分解至单个元素。
- 合并:
[27, 38]和[3, 43]→[3, 27, 38, 43]
Java代码实现
java
public class MergeSort {
public static void mergeSort(int[] arr) {
if (arr == null || arr.length <= 1) return;
mergeSort(arr, 0, arr.length - 1);
}
private 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[] temp = new int[right - left + 1];
int i = left, j = mid + 1, k = 0;
while (i <= mid && j <= right) {
if (arr[i] <= arr[j]) {
temp[k++] = arr[i++];
} else {
temp[k++] = arr[j++];
}
}
while (i <= mid) temp[k++] = arr[i++];
while (j <= right) temp[k++] = arr[j++];
System.arraycopy(temp, 0, arr, left, temp.length);
}
}
练习题目
- 基础实现 :对数组
[12, 11, 13, 5, 6, 7]进行归并排序。 - 链表排序:使用归并排序对单向链表排序(需实现链表节点类)。
- 逆序对计数:修改归并排序,计算数组中的逆序对数量。
- 外部排序:模拟大文件排序(分块读取→排序→合并)。
- 优化空间:实现非递归(迭代)版本的归并排序。
题目详细讲解
题目1:基础实现
java
int[] arr = {12, 11, 13, 5, 6, 7};
MergeSort.mergeSort(arr);
// 输出:[5, 6, 7, 11, 12, 13]
题目2:链表排序
java
class ListNode {
int val;
ListNode next;
ListNode(int val) { this.val = val; }
}
public ListNode mergeSortList(ListNode head) {
if (head == null || head.next == null) return head;
ListNode slow = head, fast = head.next;
while (fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
}
ListNode right = mergeSortList(slow.next);
slow.next = null;
ListNode left = mergeSortList(head);
return merge(left, right);
}
private ListNode merge(ListNode l1, ListNode l2) {
ListNode dummy = new ListNode(0);
ListNode cur = dummy;
while (l1 != null && l2 != null) {
if (l1.val <= l2.val) {
cur.next = l1;
l1 = l1.next;
} else {
cur.next = l2;
l2 = l2.next;
}
cur = cur.next;
}
cur.next = l1 != null ? l1 : l2;
return dummy.next;
}
思路:使用快慢指针找中点,递归排序左右部分后合并。
题目3:逆序对计数 在 merge 方法中添加计数:
java
private static int mergeAndCount(int[] arr, int left, int mid, int right) {
// ...合并逻辑...
while (i <= mid && j <= right) {
if (arr[i] <= arr[j]) {
temp[k++] = arr[i++];
} else {
count += (mid - i + 1); // 左半部分剩余元素均与arr[j]构成逆序
temp[k++] = arr[j++];
}
}
// ...
return count;
}
思路:在合并过程中,当右子数组元素小于左子数组元素时,左子数组剩余元素均与其构成逆序。
题目4:外部排序(简化版)
java
// 模拟:将大文件分成块,每块读取到内存排序后写回,再逐块合并
public void externalSort(String inputFile, String outputFile, int chunkSize) {
List<File> chunks = splitAndSort(inputFile, chunkSize);
mergeChunks(chunks, outputFile);
}
思路:分块读取→内存排序→写回临时文件→多路归并。
题目5:非递归归并排序
java
public static void iterativeMergeSort(int[] arr) {
int n = arr.length;
int[] temp = new int[n];
for (int size = 1; size < n; size *= 2) {
for (int left = 0; left < n; left += 2 * size) {
int mid = Math.min(left + size - 1, n - 1);
int right = Math.min(left + 2 * size - 1, n - 1);
merge(arr, left, mid, right, temp);
}
}
}
思路:使用循环代替递归,按块大小逐步合并。
四、计数排序 (Counting Sort)
原理
计数排序适用于整数范围较小的场景:
- 统计频率:计算每个元素出现的次数。
- 计算位置:根据频率计算每个元素的最终位置。
- 输出结果:按位置填充到结果数组。
时间复杂度为O(n + k)(k为整数范围),空间复杂度为O(k)。
算法思路与过程
- 找范围 :确定数组中的最大值
max和最小值min。 - 建计数数组 :创建长度为
max - min + 1的数组count。 - 统计频率:遍历原数组,统计每个元素出现的次数。
- 累加频率 :将
count数组转换为累加数组(表示元素最后位置)。 - 填充结果 :从后往前遍历原数组,根据
count数组将元素放到结果数组。
示例过程 : 数组:[4, 2, 2, 8, 3, 3, 1],范围 1~8:
- 计数数组:
[1, 2, 2, 1, 0, 0, 0, 1](对应值1~8) - 累加数组:
[1, 3, 5, 6, 6, 6, 6, 7] - 从后往前填充:
1放位置0,3放位置5(累加值减1)...
Java代码实现
java
public class CountingSort {
public static void countingSort(int[] arr) {
if (arr == null || arr.length == 0) return;
int max = Arrays.stream(arr).max().getAsInt();
int min = Arrays.stream(arr).min().getAsInt();
int range = max - min + 1;
int[] count = new int[range];
int[] output = new int[arr.length];
for (int num : arr) {
count[num - min]++;
}
for (int i = 1; i < range; i++) {
count[i] += count[i - 1];
}
for (int i = arr.length - 1; i >= 0; i--) {
output[count[arr[i] - min] - 1] = arr[i];
count[arr[i] - min]--;
}
System.arraycopy(output, 0, arr, 0, arr.length);
}
}
练习题目
- 基础实现 :对数组
[1, 4, 1, 2, 7, 5, 2]进行计数排序。 - 负数处理 :修改代码以支持负数(如
[-1, -3, 2, 4])。 - 对象排序 :对
Person对象按年龄排序(年龄范围已知)。 - 优化空间 :不使用
output数组,直接在原数组排序(困难)。 - 范围过大问题 :如果整数范围非常大(如
[1, 1000000]),如何优化?
题目详细讲解
题目1:基础实现
java
int[] arr = {1, 4, 1, 2, 7, 5, 2};
CountingSort.countingSort(arr);
// 输出:[1, 1, 2, 2, 4, 5, 7]
题目2:负数处理 代码中已通过 min 处理负数:
java
int min = Arrays.stream(arr).min().getAsInt(); // 如min=-3
count[num - min]++; // 例如-1映射到index=2
题目3:对象排序
java
class Person {
int age;
Person(int age) { this.age = age; }
}
public static void countingSort(Person[] arr) {
// 假设年龄范围已知为0~150
int[] count = new int[151];
Person[] output = new Person[arr.length];
for (Person p : arr) count[p.age]++;
for (int i = 1; i < 151; i++) count[i] += count[i - 1];
for (int i = arr.length - 1; i >= 0; i--) {
output[count[arr[i].age] - 1] = arr[i];
count[arr[i].age]--;
}
System.arraycopy(output, 0, arr, 0, arr.length);
}
题目4:原地排序(困难) 计数排序通常需要额外空间。若要求原地排序,可尝试类似桶排序的方法,但不常见。
题目5:范围过大优化 使用桶排序 或基数排序代替,或只统计频率不排序(如求Top K)。
五、桶排序 (Bucket Sort)
原理
桶排序将元素分配到不同的"桶"中,再对每个桶排序(可使用其他排序算法),最后合并。适用于元素均匀分布的场景。
时间复杂度平均为O(n + k)(k为桶数),最坏为O(n^2)。
算法思路与过程
- 分桶 :根据元素范围创建若干个桶(如区间
[min, max]分成n个桶)。 - 入桶:遍历数组,将每个元素放入对应的桶。
- 桶内排序:对每个非空桶进行排序(如插入排序)。
- 合并桶:按顺序合并所有桶。
示例过程 : 数组:[0.42, 0.32, 0.23, 0.52, 0.25, 0.47],分4个桶(0.2-0.3, 0.3-0.4, 0.4-0.5, 0.5-0.6):
- 桶1:
[0.23, 0.25]→ 排序后[0.23, 0.25] - 桶2:
[0.32] - 桶3:
[0.42, 0.47] - 桶4:
[0.52] - 合并:
[0.23, 0.25, 0.32, 0.42, 0.47, 0.52]
Java代码实现(以浮点数为例)
java
public class BucketSort {
public static void bucketSort(double[] arr) {
if (arr == null || arr.length == 0) return;
int n = arr.length;
double max = Arrays.stream(arr).max().getAsDouble();
double min = Arrays.stream(arr).min().getAsDouble();
double range = max - min;
int bucketNum = n; // 通常桶数等于元素数
List<List<Double>> buckets = new ArrayList<>();
for (int i = 0; i < bucketNum; i++) {
buckets.add(new ArrayList<>());
}
for (double num : arr) {
int index = (int) ((num - min) / range * (bucketNum - 1));
buckets.get(index).add(num);
}
int index = 0;
for (List<Double> bucket : buckets) {
Collections.sort(bucket); // 使用Collections.sort
for (double num : bucket) {
arr[index++] = num;
}
}
}
}
练习题目
- 基础实现 :对
double[] arr = {0.78, 0.17, 0.39, 0.26, 0.72}进行桶排序。 - 整数桶排序 :修改代码以支持整数数组(如
[22, 45, 12, 8, 10])。 - 自定义桶数量:允许用户指定桶的数量。
- 桶内排序算法 :将
Collections.sort替换为插入排序。 - 非均匀分布优化:当元素分布不均时(如大部分元素在少数桶),如何优化?
题目详细讲解
题目1:基础实现
java
double[] arr = {0.78, 0.17, 0.39, 0.26, 0.72};
BucketSort.bucketSort(arr);
// 输出:[0.17, 0.26, 0.39, 0.72, 0.78]
题目2:整数桶排序 修改分桶逻辑:
java
int index = (num - min) * bucketNum / (int)(range + 1); // +1避免除0
题目3:自定义桶数量 在方法参数中添加 bucketNum:
java
public static void bucketSort(double[] arr, int bucketNum) {
// ...
}
题目4:桶内插入排序
java
private static void insertionSort(List<Double> bucket) {
for (int i = 1; i < bucket.size(); i++) {
double key = bucket.get(i);
int j = i - 1;
while (j >= 0 && bucket.get(j) > key) {
bucket.set(j + 1, bucket.get(j));
j--;
}
bucket.set(j + 1, key);
}
}
题目5:非均匀分布优化
- 动态调整桶大小:根据元素分布调整桶区间。
- 使用链表:桶内使用链表减少移动成本。
- 递归桶排序:对元素过多的桶递归使用桶排序。
以上五就是本篇讲解的一些排序算法啦~~~