一、排序算法的重要性

排序算法是计算机科学中最基础也最常用的算法之一。无论是面试还是实际开发,掌握各种排序算法的原理、时间复杂度和适用场景都至关重要。排序算法的选择直接影响程序的性能,尤其是在处理大规模数据时,一个高效的排序算法可以显著减少计算时间。

举个例子,假设你有一个包含100万个整数的数组需要排序,如果使用时间复杂度为O(n²)的冒泡排序,可能需要几秒钟甚至更长时间;而如果使用O(n log n)的快速排序,可能只需要几十毫秒。这就是为什么我们需要深入理解不同排序算法的特性。

二、十大排序算法概述

常见的十大排序算法包括:

  1. 冒泡排序(Bubble Sort)
  2. 选择排序(Selection Sort)
  3. 插入排序(Insertion Sort)
  4. 希尔排序(Shell Sort)
  5. 归并排序(Merge Sort)
  6. 快速排序(Quick Sort)
  7. 堆排序(Heap Sort)
  8. 计数排序(Counting Sort)
  9. 桶排序(Bucket Sort)
  10. 基数排序(Radix Sort)

每种算法都有其独特的优势和适用场景,接下来我们会逐一分析它们的时间复杂度、空间复杂度和稳定性。

三、时间复杂度与空间复杂度对比

1. 冒泡排序

  • 时间复杂度
    • 最好情况:O(n)(已经有序时)
    • 最坏情况:O(n²)(完全逆序时)
    • 平均情况:O(n²)
  • 空间复杂度:O(1)(原地排序)
  • 稳定性:稳定
// Java示例:冒泡排序
public void bubbleSort(int[] arr) {
    int n = arr.length;
    for (int i = 0; i < n - 1; i++) {
        boolean swapped = false; // 优化:如果某一轮没有交换,说明已经有序
        for (int j = 0; j < n - i - 1; j++) {
            if (arr[j] > arr[j + 1]) {
                // 交换相邻元素
                int temp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = temp;
                swapped = true;
            }
        }
        if (!swapped) break; // 提前退出
    }
}

2. 快速排序

  • 时间复杂度
    • 最好情况:O(n log n)
    • 最坏情况:O(n²)(极端不平衡分区时)
    • 平均情况:O(n log n)
  • 空间复杂度:O(log n)(递归栈空间)
  • 稳定性:不稳定
// Java示例:快速排序
public void quickSort(int[] arr, int low, int high) {
    if (low < high) {
        int pivot = partition(arr, low, high); // 分区
        quickSort(arr, low, pivot - 1);  // 递归左子数组
        quickSort(arr, pivot + 1, high); // 递归右子数组
    }
}

private 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++;
            // 交换元素
            int temp = arr[i];
            arr[i] = arr[j];
            arr[j] = temp;
        }
    }
    // 将基准放到正确位置
    int temp = arr[i + 1];
    arr[i + 1] = arr[high];
    arr[high] = temp;
    return i + 1;
}

四、稳定性分析

稳定性是指排序后相等元素的相对顺序是否保持不变。例如,如果原数组中有两个相同的数字5(记为5₁和5₂),排序后5₁仍然在5₂前面,则该算法是稳定的。

  • 稳定算法:冒泡排序、插入排序、归并排序、计数排序、桶排序、基数排序。
  • 不稳定算法:选择排序、希尔排序、快速排序、堆排序。

不稳定的典型例子是选择排序:

// Java示例:选择排序的不稳定性
public void selectionSort(int[] arr) {
    for (int i = 0; i < arr.length - 1; i++) {
        int minIdx = i;
        for (int j = i + 1; j < arr.length; j++) {
            if (arr[j] < arr[minIdx]) {
                minIdx = j;
            }
        }
        // 交换可能破坏稳定性
        int temp = arr[minIdx];
        arr[minIdx] = arr[i];
        arr[i] = temp;
    }
}

五、应用场景与选择建议

  1. 小规模数据:插入排序或冒泡排序(代码简单,常数因子小)。
  2. 大规模数据:快速排序、归并排序或堆排序(O(n log n)时间复杂度)。
  3. 数据范围已知且较小:计数排序或桶排序(线性时间复杂度)。
  4. 稳定性要求高:归并排序或计数排序。

六、总结

排序算法的选择需要综合考虑数据规模、数据分布、稳定性要求和实现复杂度。在实际开发中,快速排序和归并排序是最常用的算法,而计数排序和基数排序在特定场景下性能更优。掌握这些算法的核心思想和适用场景,能够帮助你在面试和实际工作中游刃有余。