Java-排序2

本篇讲解Java中选择排序、快速排序、归并排序、计数排序和桶排序这五种排序算法~~

一、选择排序 (Selection Sort)

原理

选择排序的核心思想是:每次从未排序的部分中选择最小(或最大)的元素,放到已排序部分的末尾。它是一种简单直观的排序算法,时间复杂度为O(n^2)。

算法思路与过程
  1. 初始状态:整个数组都是未排序的。
  2. 第一轮:从所有元素中找到最小的元素,将其与第一个元素交换位置。
  3. 第二轮:在剩余元素中找到最小的元素,将其与第二个元素交换位置。
  4. 重复过程:以此类推,直到所有元素都被排序。

示例过程 : 初始数组:[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;
            }
        }
    }
}
练习题目
  1. 基础实现 :使用选择排序对数组 [3, 1, 4, 1, 5, 9, 2, 6] 进行排序。
  2. 对象排序 :对 Student 对象数组按分数从低到高排序(假设 Student 类有 score 属性)。
  3. 降序排序:修改选择排序算法,使其按降序排列。
  4. 找最大值:使用选择排序思想,找出数组中的最大值及其位置。
  5. 稳定性问题:解释选择排序是否稳定,并举例说明。
题目详细讲解

题目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)

原理

快速排序采用分治法思想:

  1. 选基准:从数组中选择一个元素作为基准(pivot)。
  2. 分区:将数组分为两部分,小于基准的放左边,大于基准的放右边。
  3. 递归:对左右子数组递归进行快速排序。

平均时间复杂度为O(n log n),最坏情况(已排序)为O(n^2)。

算法思路与过程
  1. 选择基准:通常选择第一个元素、最后一个元素或随机元素。
  2. 分区操作
    • 设置两个指针 i(左)和 j(右)。
    • i 从左向右找大于基准的元素,j 从右向左找小于基准的元素。
    • 交换这两个元素,直到 ij 相遇。
  3. 递归排序:对基准左侧和右侧的子数组递归进行快速排序。

示例过程 : 数组:[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;
    }
}
练习题目
  1. 基础实现 :对数组 [3, 6, 8, 10, 1, 2, 1] 进行快速排序。
  2. 降序排序:修改快速排序,使其按降序排列。
  3. 处理空数组:优化代码,处理输入为空或长度为0的情况。
  4. 大量重复元素 :针对含大量重复元素的数组(如 [5, 5, 5, 2, 5])优化分区逻辑。
  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)

原理

归并排序采用分治法

  1. 分解:将数组分成两半。
  2. 递归排序:对两半分别递归排序。
  3. 合并:将两个有序数组合并成一个有序数组。

时间复杂度稳定为O(n \log n),需要额外O(n)空间。

算法思路与过程
  1. 分解:将数组从中间分成左右两部分。
  2. 递归:对左右子数组递归调用归并排序。
  3. 合并
    • 创建临时数组。
    • 比较左右子数组的元素,将较小的放入临时数组。
    • 将剩余元素复制到临时数组。
    • 将临时数组复制回原数组。

示例过程 : 数组:[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);
    }
}
练习题目
  1. 基础实现 :对数组 [12, 11, 13, 5, 6, 7] 进行归并排序。
  2. 链表排序:使用归并排序对单向链表排序(需实现链表节点类)。
  3. 逆序对计数:修改归并排序,计算数组中的逆序对数量。
  4. 外部排序:模拟大文件排序(分块读取→排序→合并)。
  5. 优化空间:实现非递归(迭代)版本的归并排序。
题目详细讲解

题目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)

原理

计数排序适用于整数范围较小的场景:

  1. 统计频率:计算每个元素出现的次数。
  2. 计算位置:根据频率计算每个元素的最终位置。
  3. 输出结果:按位置填充到结果数组。

时间复杂度为O(n + k)(k为整数范围),空间复杂度为O(k)。

算法思路与过程
  1. 找范围 :确定数组中的最大值 max 和最小值 min
  2. 建计数数组 :创建长度为 max - min + 1 的数组 count
  3. 统计频率:遍历原数组,统计每个元素出现的次数。
  4. 累加频率 :将 count 数组转换为累加数组(表示元素最后位置)。
  5. 填充结果 :从后往前遍历原数组,根据 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 放位置 03 放位置 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. 基础实现 :对数组 [1, 4, 1, 2, 7, 5, 2] 进行计数排序。
  2. 负数处理 :修改代码以支持负数(如 [-1, -3, 2, 4])。
  3. 对象排序 :对 Person 对象按年龄排序(年龄范围已知)。
  4. 优化空间 :不使用 output 数组,直接在原数组排序(困难)。
  5. 范围过大问题 :如果整数范围非常大(如 [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)。

算法思路与过程
  1. 分桶 :根据元素范围创建若干个桶(如区间 [min, max] 分成 n 个桶)。
  2. 入桶:遍历数组,将每个元素放入对应的桶。
  3. 桶内排序:对每个非空桶进行排序(如插入排序)。
  4. 合并桶:按顺序合并所有桶。

示例过程 : 数组:[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;
            }
        }
    }
}
练习题目
  1. 基础实现 :对 double[] arr = {0.78, 0.17, 0.39, 0.26, 0.72} 进行桶排序。
  2. 整数桶排序 :修改代码以支持整数数组(如 [22, 45, 12, 8, 10])。
  3. 自定义桶数量:允许用户指定桶的数量。
  4. 桶内排序算法 :将 Collections.sort 替换为插入排序。
  5. 非均匀分布优化:当元素分布不均时(如大部分元素在少数桶),如何优化?
题目详细讲解

题目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:非均匀分布优化

  • 动态调整桶大小:根据元素分布调整桶区间。
  • 使用链表:桶内使用链表减少移动成本。
  • 递归桶排序:对元素过多的桶递归使用桶排序。

以上五就是本篇讲解的一些排序算法啦~~~

相关推荐
编程彩机2 小时前
互联网大厂Java面试:从Spring WebFlux到分布式事务的技术场景解析
java·微服务·面试·分布式事务·spring webflux
Jm_洋洋2 小时前
【C++进阶】虚函数、虚表与虚指针:多态底层机制剖析
java·开发语言·c++
小马爱打代码2 小时前
MyBatis:缓存体系设计与避坑大全
java·缓存·mybatis
时艰.2 小时前
Java 并发编程:Callable、Future 与 CompletableFuture
java·网络
码云数智-园园2 小时前
深入理解与正确实现 .NET 中的 BackgroundService
java·开发语言
好好研究2 小时前
SpringBoot整合SpringMVC
xml·java·spring boot·后端·mvc
千寻技术帮2 小时前
10386_基于SpringBoot的外卖点餐管理系统
java·spring boot·vue·外卖点餐
曹轲恒2 小时前
SpringBoot整合SpringMVC(末)
java·spring boot·后端
_周游2 小时前
Java8 API 文档搜索引擎_2.索引模块(程序)
java·搜索引擎·intellij-idea