Java 中十大经典排序算法全面总结与优化

在 Java 编程的世界里,排序算法是数据处理中不可或缺的工具。掌握常见的排序算法,能让开发者更高效地处理各种数据场景。本文将深入总结 Java 中的十大经典排序算法,包括冒泡排序、选择排序、插入排序、希尔排序、归并排序、快速排序、堆排序、计数排序、桶排序和基数排序,并探讨它们的优化策略。

一、冒泡排序(Bubble Sort)

原理

比较相邻元素,若顺序不对则交换,每一轮将最大(或最小)元素 “浮” 到数组末尾。

代码实现

public class BubbleSort {

    public static void main(String[] args) {

        int[] array = {
            64,
            34,
            25,
            12,
            22,
            11,
            90
        };

        bubbleSort(array);

        for (int num: array) {

            System.out.print(num + " ");

        }

    }

    public static void bubbleSort(int[] arr) {

        int n = arr.length;

        for (int i = 0; i < n - 1; i++) {

            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;

                }

            }

        }

    }

}

性能分析

  • 时间复杂度:最好情况 O (n),最坏和平均情况 O (n²)。
  • 空间复杂度:O(1)。

优化策略

设置标志位,若某一轮没有交换,说明数组已有序,提前结束排序。

二、选择排序(Selection Sort)

原理

将数组分为已排序和未排序部分,每一轮从未排序部分选最小(或最大)元素,与未排序部分第一个元素交换。

代码实现

public class SelectionSort {

    public static void main(String[] args) {

        int[] array = {
            64,
            34,
            25,
            12,
            22,
            11,
            90
        };

        selectionSort(array);

        for (int num: array) {

            System.out.print(num + " ");

        }

    }

    public static void selectionSort(int[] arr) {

        int n = arr.length;

        for (int i = 0; i < n - 1; i++) {

            int minIndex = i;

            for (int j = i + 1; j < n; j++) {

                if (arr[j] < arr[minIndex]) {

                    minIndex = j;

                }

            }

            if (minIndex != i) {

                int temp = arr[i];

                arr[i] = arr[minIndex];

                arr[minIndex] = temp;

            }

        }

    }

}

性能分析

  • 时间复杂度:始终为 O (n²)。
  • 空间复杂度:O(1)。

优化策略

减少不必要交换,当找到的最小元素就是当前元素时,不进行交换。

三、插入排序(Insertion Sort)

原理

将数组分为已排序和未排序部分,从第二个元素起,将未排序元素插入已排序部分的合适位置。

代码实现

public class InsertionSort {

    public static void main(String[] args) {

        int[] array = {
            64,
            34,
            25,
            12,
            22,
            11,
            90
        };

        insertionSort(array);

        for (int num: array) {

            System.out.print(num + " ");

        }

    }

    public static void insertionSort(int[] arr) {

        int n = arr.length;

        for (int i = 1; i < n; i++) {

            int key = arr[i];

            int j = i - 1;

            while (j >= 0 && arr[j] > key) {

                arr[j + 1] = arr[j];

                j = j - 1;

            }

            arr[j + 1] = key;

        }

    }

}

性能分析

  • 时间复杂度:最好情况 O (n),最坏和平均情况 O (n²)。
  • 空间复杂度:O(1)。

优化策略

使用二分查找在已排序部分查找插入位置,减少比较次数。

四、希尔排序(Shell Sort)

原理

将原始数据分成多个子序列,对每个子序列进行插入排序,随着排序进行,步长逐渐减小,最终进行一次普通插入排序。

代码实现

public class ShellSort {

    public static void main(String[] args) {

        int[] array = {
            64,
            34,
            25,
            12,
            22,
            11,
            90
        };

        shellSort(array);

        for (int num: array) {

            System.out.print(num + " ");

        }

    }

    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];

                int 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)。

优化策略

选择更优步长序列,如 Hibbard 序列,可使时间复杂度接近 O (n log n)。

五、归并排序(Merge Sort)

原理

基于分治思想,将数组不断分成两个子数组,对每个子数组排序后再合并。

代码实现

public class MergeSort {

    public static void main(String[] args) {

        int[] array = {
            64,
            34,
            25,
            12,
            22,
            11,
            90
        };

        mergeSort(array);

        for (int num: array) {

            System.out.print(num + " ");

        }

    }

    public static void mergeSort(int[] arr) {

        if (arr == null || arr.length <= 1) {

            return;

        }

        int[] temp = new int[arr.length];

        mergeSortHelper(arr, temp, 0, arr.length - 1);

    }

    private static void mergeSortHelper(int[] arr, int[] temp, int left, int right) {

        if (left < right) {

            int mid = (left + right) / 2;

            mergeSortHelper(arr, temp, left, mid);

            mergeSortHelper(arr, temp, mid + 1, right);

            merge(arr, temp, left, mid, right);

        }

    }

    private static void merge(int[] arr, int[] temp, int left, int mid, int right) {

        int i = left;

        int j = mid + 1;

        int k = left;

        while (i <= mid && j <= right) {

            if (arr[i] <= arr[j]) {

                temp[k] = arr[i];

                i++;

            } else {

                temp[k] = arr[j];

                j++;

            }

            k++;

        }

        while (i <= mid) {

            temp[k] = arr[i];

            i++;

            k++;

        }

        while (j <= right) {

            temp[k] = arr[j];

            j++;

            k++;

        }

        for (i = left; i <= right; i++) {

            arr[i] = temp[i];

        }

    }

}

性能分析

  • 时间复杂度:始终为 O (n log n)。
  • 空间复杂度:O(n)。

优化策略

减少临时数组创建次数,在最外层创建一次并传递给递归函数;对小规模数组使用插入排序。

六、快速排序(Quick Sort)

原理

选择基准元素,将数组分为两部分,使左边元素小于等于基准,右边元素大于等于基准,递归对两部分排序。

代码实现

public class QuickSort {

    public static void main(String[] args) {

        int[] array = {
            64,
            34,
            25,
            12,
            22,
            11,
            90
        };

        quickSort(array, 0, array.length - 1);

        for (int num: array) {

            System.out.print(num + " ");

        }

    }

    public static void quickSort(int[] arr, int low, int high) {

        if (low < high) {

            int pi = partition(arr, low, high);

            quickSort(arr, low, pi - 1);

            quickSort(arr, pi + 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),最坏 O (n)。

优化策略

随机选择基准元素;三数取中选择基准元素;对小规模数组使用插入排序。

七、堆排序(Heap Sort)

原理

利用堆(最大堆或最小堆)的数据结构,先构建堆,然后将堆顶元素与堆末尾元素交换,再调整堆。

代码实现

public class HeapSort {

    public static void main(String[] args) {

        int[] array = {
            64,
            34,
            25,
            12,
            22,
            11,
            90
        };

        heapSort(array);

        for (int num: array) {

            System.out.print(num + " ");

        }

    }

    public static void heapSort(int[] arr) {

        int n = arr.length;

        for (int i = n / 2 - 1; i >= 0; i--) {

            heapify(arr, n, i);

        }

        for (int i = n - 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 swap = arr[i];

            arr[i] = arr[largest];

            arr[largest] = swap;

            heapify(arr, n, largest);

        }

    }

}

性能分析

  • 时间复杂度:构建堆 O (n),排序 O (n log n),总体 O (n log n)。
  • 空间复杂度:O(1)。

优化策略

减少比较次数,预先计算好比较范围,减少越界检查;使用索引堆,减少对象交换开销。

八、计数排序(Counting Sort)

原理

统计每个元素在数组中出现的次数,根据统计信息重建有序数组。

代码实现

public class CountingSort {

    public static void main(String[] args) {

        int[] array = {
            64,
            34,
            25,
            12,
            22,
            11,
            90
        };

        int[] sortedArray = countingSort(array);

        for (int num: sortedArray) {

            System.out.print(num + " ");

        }

    }

    public static int[] countingSort(int[] arr) {

        if (arr == null || arr.length == 0) {

            return arr;

        }

        int min = arr[0];

        int max = arr[0];

        for (int num: arr) {

            if (num < min) {

                min = num;

            }

            if (num > max) {

                max = num;

            }

        }

        int[] countArray = new int[max - min + 1];

        for (int num: arr) {

            countArray[num - min]++;

        }

        for (int i = 1; i < countArray.length; i++) {

            countArray[i] += countArray[i - 1];

        }

        int[] sortedArray = new int[arr.length];

        for (int i = arr.length - 1; i >= 0; i--) {

            sortedArray[countArray[arr[i] - min] - 1] = arr[i];

            countArray[arr[i] - min]--;

        }

        return sortedArray;

    }

}

性能分析

  • 时间复杂度:O (n + k),n 为数组长度,k 为数据范围。
  • 空间复杂度:O(n + k)。

优化策略

减少计数数组空间,根据数据范围调整计数数组;处理负数,通过偏移量将负数转换为非负数。

九、桶排序(Bucket Sort)

原理

将数据分到有限数量的桶中,对每个桶内数据排序,再按顺序合并桶中数据。

代码实现

import java.util.ArrayList;

import java.util.Arrays;

import java.util.Collections;

import java.util.List;

public class BucketSort {

    public static void main(String[] args) {

        double[] array = {
            0.897,
            0.565,
            0.656,
            0.1234,
            0.665,
            0.3434
        };

        double[] sortedArray = bucketSort(array);

        for (double num: sortedArray) {

            System.out.print(num + " ");

        }

    }

    public static double[] bucketSort(double[] arr) {

        int n = arr.length;

        List < List < Double >> buckets = new ArrayList < > (n);

        for (int i = 0; i < n; i++) {

            buckets.add(new ArrayList < > ());

        }

        for (double num: arr) {

            int bucketIndex = (int)(num * n);

            buckets.get(bucketIndex).add(num);

        }

        for (List < Double > bucket: buckets) {

            Collections.sort(bucket);

        }

        int index = 0;

        for (List < Double > bucket: buckets) {

            for (double num: bucket) {

                arr[index++] = num;

            }

        }

        return arr;

    }

}

性能分析

  • 时间复杂度:理想 O (n),最坏 O (n²)。
  • 空间复杂度:O (n + k),k 为每个桶内最大数据量。

优化策略

动态调整桶的数量;选择合适的桶内排序算法;减少数据分配时的计算量。

十、基数排序(Radix Sort)

原理

按数据的每一位数字进行排序,从最低位到最高位依次处理。

代码实现


性能分析

  • 时间复杂度:假设数据的最大位数为 d ,数组长度为 n,基数为 r(对于十进制 r = 10),则每一轮的时间复杂度为 O (n + r),总共需要 d 轮,所以基数排序的时间复杂度为 O (d * (n + r))。当 d 和 r 相对 n 较小时,基数排序接近线性时间复杂度。
  • 空间复杂度:在每一轮排序中,需要额外的空间来存储计数数组和输出数组,计数数组大小为 r,输出数组大小为 n,所以空间复杂度为 O (n + r)。

优化策略

  • 减少不必要的计算:在确定最大数和计算每一位数字时,可以采用更高效的算法。比如确定最大数时,利用分治法,将数组分成多个子数组,分别计算子数组的最大数,最后合并得到整个数组的最大数,以此减少比较次数。
  • 并行处理:针对大规模数据,可利用多线程或并行计算加速基数排序。每一轮的分配和收集操作能够并行开展,从而提高排序效率。但在实现并行处理时,必须考虑线程安全和数据同步等问题。
  • 自适应基数选择:依据数据的特点,动态调整基数的大小。若数据集中在较小的范围内,可选择较小的基数,减少桶的数量和计算量;要是数据范围较大,则可适当增大基数,提升排序效率。

综合对比与总结

这十大经典排序算法各有优劣 ,在实际应用中,需要根据数据规模、数据分布、数据类型以及稳定性要求等因素来选择合适的排序算法。

  • 小规模数据:插入排序、冒泡排序和选择排序简单直观,对于小规模数据可以使用。其中插入排序在部分有序的数据上表现较好;冒泡排序代码简单,但效率相对较低;选择排序的比较次数固定,不受数据初始状态影响。
  • 大规模数据
    • 基于比较的排序:快速排序、归并排序和堆排序的平均时间复杂度为 O (n log n) ,适用于大规模数据排序。快速排序平均性能最优,但最坏情况下性能较差;归并排序性能稳定,且是稳定排序算法;堆排序是原地排序,空间复杂度低。
    • 非比较排序:计数排序、桶排序和基数排序适用于特定的数据类型和分布。计数排序适用于数据范围较小的整数排序;桶排序假设数据服从均匀分布,对浮点数等数据有较好的效果;基数排序适用于整数类型,且数据位数相对固定或者数据范围不是特别大的情况。

深入理解这些排序算法的原理、性能以及优化策略,能够帮助开发者在 Java 编程中更高效地处理各种排序任务,提升程序的整体性能。

public class RadixSort {

    public static void main(String[] args) {

        int[] array = {
            170,
            45,
            75,
            90,
            802,
            24,
            2,
            66
        };

        radixSort(array);

        for (int num: array) {

            System.out.print(num + " ");

        }

    }

    public static void radixSort(int[] arr) {

        int max = getMax(arr);

        for (int exp = 1; max / exp > 0; exp *= 10) {

            countingSort(arr, exp);

        }

    }

    private static int getMax(int[] arr) {

        int max = arr[0];

        for (

            int num: arr) {

            if (num > max) {

                max = num;

            }

        }

        return max;

    }

    private static void countingSort(int[] arr, int exp) {

        int[] output = new int[arr.length];

        int[] count = new int[10];

        for (int num: arr) {

            int digit = (num / exp) % 10;

            count[digit]++;

        }

        for (int i = 1; i < 10; i++) {

            count[i] += count[i - 1];

        }

        for (int i = arr.length - 1; i >= 0; i--) {

            int digit = (num / exp) % 10;

            output[count[digit] - 1] = arr[i];

            count[digit]--;

        }

        for (int i = 0; i < arr.length; i++) {

            arr[i] = output[i];

        }

    }

}

更多推荐