【常见的排序算法有哪些】


一、冒泡排序(Bubble Sort)

设计思想:像气泡上浮,两两比较相邻元素,顺序错误就交换,直到整个数组有序。
Java代码

public static void bubbleSort(int[] arr) {
    for (int i = 0; i < arr.length - 1; i++) {
        for (int j = 0; j < arr.length - 1 - i; j++) {
            if (arr[j] > arr[j + 1]) {
                int temp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = temp;
            }
        }
    }
}

复杂度
• 时间:平均/最坏 O(n²),最好 O(n)(已有序时)
• 空间:O(1)
适用场景:教学示例或小规模数据。


二、选择排序(Selection Sort)

设计思想:每次从剩余数组中选最小值,放到已排序部分的末尾。
Java代码

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;
        }
        int temp = arr[i];
        arr[i] = arr[minIndex];
        arr[minIndex] = temp;
    }
}

复杂度
• 时间:O(n²)(所有情况)
• 空间:O(1)
适用场景:简单实现,但效率低。


三、插入排序(Insertion Sort)

设计思想:像整理扑克牌,逐个将元素插入已排序部分的正确位置。
Java代码

public static void insertionSort(int[] arr) {
    for (int i = 1; i < arr.length; i++) {
        int key = arr[i];
        int j = i - 1;
        while (j >= 0 && arr[j] > key) {
            arr[j + 1] = arr[j];
            j--;
        }
        arr[j + 1] = key;
    }
}

复杂度
• 时间:平均/最坏 O(n²),最好 O(n)(已有序)
• 空间:O(1)
适用场景:小规模或基本有序数据。


四、希尔排序(Shell Sort)

设计思想:插入排序的改进版,通过分组并逐步缩小间隔,减少元素移动次数。
Java代码

public static void shellSort(int[] arr) {
    int n = arr.length;
    for (int gap = n / 2; gap > 0; gap /= 2) {
        for (int i = gap; i < n; i++) {
            int temp = arr[i], j;
            for (j = i; j >= gap && arr[j - gap] > temp; j -= gap) {
                arr[j] = arr[j - gap];
            }
            arr[j] = temp;
        }
    }
}

复杂度
• 时间:O(n log n) ~ O(n²)(取决于间隔序列)
• 空间:O(1)
适用场景:中等规模数据,优于简单插入排序。


五、归并排序(Merge Sort)

设计思想:分治法,将数组分成两半递归排序,再合并两个有序数组。
Java代码

public static void mergeSort(int[] arr, int left, int right) {
    if (left < right) {
        int mid = (left + right) / 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) {
        temp[k++] = arr[i] <= arr[j] ? arr[i++] : arr[j++];
    }
    while (i <= mid) temp[k++] = arr[i++];
    while (j <= right) temp[k++] = arr[j++];
    System.arraycopy(temp, 0, arr, left, temp.length);
}

复杂度
• 时间:O(n log n)(所有情况)
• 空间:O(n)(需要额外数组)
适用场景:需要稳定排序且空间足够时。


六、快速排序(Quick Sort)

设计思想:选一个基准元素,将数组分为比基准小和大的两部分,递归排序。
Java代码

public static 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 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++;
            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;
}

复杂度
• 时间:平均 O(n log n),最坏 O(n²)(如已有序)
• 空间:O(log n)(递归栈)
适用场景:大规模数据,实际应用最广泛。


七、堆排序(Heap Sort)

设计思想:利用堆结构(完全二叉树),构建大顶堆后逐个取堆顶元素。
Java代码

public static void heapSort(int[] arr) {
    // 构建大顶堆
    for (int i = arr.length / 2 - 1; i >= 0; i--) {
        heapify(arr, arr.length, i);
    }
    // 逐个提取堆顶元素
    for (int i = arr.length - 1; i > 0; i--) {
        int temp = arr[0];
        arr[0] = arr[i];
        arr[i] = temp;
        heapify(arr, i, 0);
    }
}

private static void heapify(int[] arr, int n, int i) {
    int largest = 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) {
        int temp = arr[i];
        arr[i] = arr[largest];
        arr[largest] = temp;
        heapify(arr, n, largest);
    }
}

复杂度
• 时间:O(n log n)(所有情况)
• 空间:O(1)
适用场景:需要原地排序且不要求稳定性时。


八、计数排序(Counting Sort)

设计思想:统计每个元素出现的次数,直接计算元素的位置。
Java代码

public static void countingSort(int[] arr) {
    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 < count.length; 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);
}

复杂度
• 时间:O(n + k)(k是数据范围)
• 空间:O(n + k)
适用场景:整数排序且数据范围较小。


九、桶排序(Bucket Sort)

设计思想:将数据分到多个有序的桶中,每个桶单独排序后再合并。
Java代码

public static void bucketSort(int[] arr, int bucketSize) {
    int min = Arrays.stream(arr).min().getAsInt();
    int max = Arrays.stream(arr).max().getAsInt();
    int bucketCount = (max - min) / bucketSize + 1;
    List<List<Integer>> buckets = new ArrayList<>(bucketCount);
    for (int i = 0; i < bucketCount; i++) buckets.add(new ArrayList<>());

    // 分到各个桶中
    for (int num : arr) {
        int index = (num - min) / bucketSize;
        buckets.get(index).add(num);
    }
    // 对每个桶排序并合并
    int index = 0;
    for (List<Integer> bucket : buckets) {
        Collections.sort(bucket);
        for (int num : bucket) arr[index++] = num;
    }
}

复杂度
• 时间:平均 O(n + k)(k是桶数量)
• 空间:O(n + k)
适用场景:数据分布均匀且易于分桶。


十、基数排序(Radix Sort)

设计思想:按每一位进行排序(从低位到高位),每次用稳定排序(如计数排序)。
Java代码

public static void radixSort(int[] arr) {
    int max = Arrays.stream(arr).max().getAsInt();
    for (int exp = 1; max / exp > 0; exp *= 10) {
        countingSortByDigit(arr, exp);
    }
}

private static void countingSortByDigit(int[] arr, int exp) {
    int[] output = new int[arr.length];
    int[] count = new int[10];
    // 统计当前位的数字出现次数
    for (int num : arr) count[(num / exp) % 10]++;
    // 计算累计位置
    for (int i = 1; i < 10; i++) count[i] += count[i - 1];
    // 从后向前排序(保证稳定性)
    for (int i = arr.length - 1; i >= 0; i--) {
        int digit = (arr[i] / exp) % 10;
        output[count[digit] - 1] = arr[i];
        count[digit]--;
    }
    System.arraycopy(output, 0, arr, 0, arr.length);
}

复杂度
• 时间:O(n * k)(k是最大位数)
• 空间:O(n + k)
适用场景:多关键字的整数或字符串排序。


总结对比

排序算法 时间复杂度(平均) 空间复杂度 稳定性 适用场景
冒泡排序 O(n²) O(1) 稳定 教学示例或小数据
选择排序 O(n²) O(1) 不稳定 简单实现
插入排序 O(n²) O(1) 稳定 小数据或基本有序
希尔排序 O(n log n) O(1) 不稳定 中等规模数据
归并排序 O(n log n) O(n) 稳定 需要稳定排序且空间足够
快速排序 O(n log n) O(log n) 不稳定 大规模数据,实际应用广泛
堆排序 O(n log n) O(1) 不稳定 原地排序,内存紧张时使用
计数排序 O(n + k) O(n + k) 稳定 整数且范围小
桶排序 O(n + k) O(n + k) 稳定 数据分布均匀
基数排序 O(n * k) O(n + k) 稳定 多关键字排序

实际应用选择建议

  1. 小规模数据:插入排序(简单且对部分有序高效)。
  2. 大规模通用数据:快速排序(默认选择,可通过随机化基准优化)。
  3. 需要稳定性:归并排序或基数排序。
  4. 内存紧张:堆排序(原地排序)。
  5. 整数且范围小:计数排序(速度最快)。
  6. 多关键字排序:基数排序(如手机号、日期)。

你可能感兴趣的:(排序算法,算法)