八种常见排序方法

概览

序号名称时间复杂度空间复杂度稳定性备注应用范围
1冒泡排序O(N^2)O(1)适用于小规模数据排序,由于效率低,很少在实际应用中使用。
2选择排序O(N^2)O(1)适用于数据量小或内存空间非常紧张时的简单排序。
3插入排序O(N^2)O(1)适合小数据量或近乎有序的数据,常用于复杂排序算法(如快速排序)的小规模数据优化。
4希尔排序O(N*logN)O(1)适合中等规模数据的排序。常用于对缓存友好的排序场景。
5快速排序O(N*logN)O(logN)适合大规模数据的排序,因其在大多数情况下效率较高,是实际应用中常用的排序算法之一。
6归并排序O(N*logN)O(N)适合对大规模数据进行外部排序(如磁盘文件排序)以及需要稳定排序的情况。
7堆排序O(N*logN)O(1)适合对数据量较大的数组进行排序,不需要额外空间且有较好性能,但不适合需要稳定性的场景。
8基数排序O(d⋅(n+k))O(n+k)其中 d 是位数,k 是位的取值范围(如十进制 k = 10)适合整数排序或特定位数数据的排序,常用于数据量较大且范围有限的情况(如账户 ID、电话号码排序)。

示例代码

1. 冒泡排序

冒泡排序通过多次比较相邻元素,将较大的元素逐步“冒泡”到数组的右边,最终使得整个数组有序。具体过程是从头到尾遍历数组,每次相邻两个元素比较并交换位置,使较大的元素向右移动。经过
n−1 轮遍历,数组会逐渐变得有序。冒泡排序是稳定的,因为相同元素之间不会改变相对位置。

    /**
     * 冒泡排序
     */
    public static void bubbleSort(int[] arr) {
        if (arr == null || arr.length < 2)
            return;
        for (int i = 0; i < arr.length - 1; i++) {
            for (int j = 0; j < arr.length - i - 1; j++) {
                if (arr[j] > arr[j + 1]) {
                    swap(arr, j, j + 1);
                }
            }
        }
    }

2.选择排序

选择排序每一轮从未排序部分中找到最小的元素,然后将其放到已排序部分的末尾。第一轮找到最小元素,放到第一个位置,第二轮找到次小的元素,放到第二个位置,以此类推。这个过程直到所有元素都排序完毕。由于每次交换操作直接定位元素,选择排序是不稳定的。

    /**
     * 选择排序
     */
    public static void selectionSort(int[] arr) {
        if (arr == null || arr.length < 2)
            return;
        for (int i = 0; i < arr.length - 1; i++) {
            int minIndex = i;
            for (int j = i + 1; j < arr.length; j++) {
                minIndex = arr[j] < arr[minIndex] ? j : minIndex;
            }
            swap(arr, i, minIndex);
        }
    }

3.插入排序

插入排序从第二个元素开始,将每个元素插入到前面已排序部分的正确位置,逐步构建有序数组。每次选择一个新元素,从后向前与已排序元素比较,找到合适位置插入。在数据量较少或接近有序的情况下,插入排序效率较高,因为其移动次数少。插入排序是稳定的,因为在插入时不会改变相同元素的相对位置。

    /**
     * 插入排序
     */
    public static void insertionSort(int[] arr) {
        if (arr == null || arr.length < 2)
            return;
        for (int i = 1; i < arr.length; i++) {
            for (int j = i - 1; j >= 0 && arr[j] > arr[j + 1]; j--) {
                swap(arr, j, j + 1);
            }
        }
    }

4.希尔排序

希尔排序是插入排序的改进版,它通过分组排序来减少数据移动次数。希尔排序先将数组分为多个间隔较大的组,对每组进行插入排序。随着排序进行,逐渐减小分组间隔,直到最后一轮间隔为 1 时,完成插入排序。这样做可以快速减少“较大”元素的移动次数,最终减少排序时间。希尔排序是不稳定的。

    /**
     * 希尔排序
     */
    public static void shellSort(int[] arr) {
        if (arr == null || arr.length < 2)
            return;
        int gap = arr.length / 2;
        while (gap > 0) {
            for (int i = gap; i < arr.length; i++) {
                int temp = arr[i];
                int j = i - gap;
                for (; j >= 0 && arr[j] > temp; j -= gap) {
                    arr[j + gap] = arr[j];
                }
                arr[j + gap] = temp;
            }
            gap /= 2;
        }
    }

5.快速排序

快速排序通过选择一个基准元素(pivot),将数组分为小于基准和大于基准的两部分,对这两部分分别递归排序,最终形成有序数组。具体步骤是:选择基准后,左右扫描,左边找到比基准大的元素,右边找到比基准小的元素并交换,直到左右指针相遇。快速排序是效率较高的不稳定排序算法,因为元素交换会改变相同元素的相对位置。

    /**
     * 快速排序
     */
    public static void quickSort(int[] arr,int L,int R){
        if(L >= R) return ;
        int pivotIndex = partition(arr,L,R);
        quickSort(arr,L,pivotIndex - 1);
        quickSort(arr,pivotIndex + 1,R);
    }
    public static int partition(int[] arr,int L,int R){
        if(L >= R) return L;
        int less = L - 1;
        int more = R;
        int index = L;
        while(index < more){
            if(arr[index] < arr[R]){
                swap(arr,++less,index);
            }else if(arr[index] > arr[R]){
                swap(arr,--more,index);
            }
            index++;
        }
        swap(arr,more,R);
        return more;
    }

6.归并排序

归并排序采用分治法,递归地将数组分成两部分,分别对两部分排序,然后将它们合并成一个有序数组。这个过程直到分解到每个子数组只有一个元素(单元素数组本身有序)为止。然后从底层向上合并,逐步构建有序数组。归并排序是稳定的,因为合并时保持相同元素的相对顺序。

    /**
     * 归并排序
     */
    public static void mergeSort(int[] arr, int L, int R) {
        if (L >= R)
            return;
        int mid = L + ((R - L) >> 1);
        mergeSort(arr, L, mid);
        mergeSort(arr, mid + 1, R);
        merge(arr, L, mid, R);
    }

    public static void merge(int[] arr, int L, int mid, int R) {
        int[] h = new int[R - L + 1];
        int p1 = L;
        int p2 = mid + 1;
        int i = 0;
        while (p1 <= mid && p2 <= R) {
            h[i++] = arr[p1] < arr[p2] ? arr[p1++] : arr[p2++];
        }
        while (p1 <= mid) {
            h[i++] = arr[p1++];
        }
        while (p2 <= R) {
            h[i++] = arr[p2++];
        }
        for (i = 0; i < h.length; i++) {
            arr[L + i] = h[i];
        }
    }

7. 堆排序

堆排序首先将数组构造成一个大顶堆(最大元素在堆顶),然后取出堆顶元素并与末尾元素交换,将剩下的元素重新调整为大顶堆,重复该过程直到数组完全有序。堆排序是一种利用二叉堆结构的选择排序,不需要额外的空间但不稳定。堆排序适合需要较少内存的场景。

    /**
     * 堆排序
     */
    public static void heapSort(int[] arr){
        if(arr == null || arr.length < 2) return;

        int heapSize = arr.length;
        buildHeap(arr,heapSize);
        while(heapSize >0){
            swap(arr,0,--heapSize);
            heapify(arr,0,heapSize);
        }
    }
    public static void buildHeap(int[] arr,int heapSize){
        for(int i = (heapSize - 1)/ 2;i >= 0;i--){
            heapify(arr,i,heapSize);
        }
    }
    public static void heapify(int[] arr,int index,int heapSize){
        int left = index * 2 + 1;
        int right = index * 2 + 2;
        int largeset = index;
        if(left < heapSize && arr[left] > arr[largeset]){
            largeset = left;
        }
        if(right < heapSize && arr[right] > arr[largeset]){
            largeset = right;
        }
        if(largeset != index){
            swap(arr,index,largeset);
            heapify(arr,largeset,heapSize);
        }
    }

8.基数排序

基数排序适用于整数或字符串的排序,按照每个位数逐步排序。它从最低有效位(如个位)开始,使用计数排序对每个位进行排序,然后依次对更高位排序,直到最高位为止。每一位排序保持前一位排序结果的顺序,保证最终数组有序。基数排序是稳定的,适合特定场景下的数据(如 ID、手机号)排序。

    /**
     * 基数排序
     */
    public static void radixSort(int[] arr) {
        if (arr == null || arr.length < 2)
            return;

        int max = getMax(arr);
        for (int exp = 1; max / exp > 0; exp *= 10) {
            countingSortByDigit(arr, exp);
        }
    }

    public static int getMax(int[] arr) {
        int max = arr[0];
        for (int i = 1; i < arr.length; i++) {
            max = Math.max(max, arr[i]);
        }
        return max;
    }

    public static void countingSortByDigit(int[] arr, int exp) {
        int n = arr.length;
        int[] output = new int[n]; // 存储排序结果的临时数组
        int[] count = new int[10]; // 计数数组,十进制所以长度为10

        // 记录当前位(个位、十位、百位等)的出现次数
        for (int i = 0; i < n; i++) {
            count[(arr[i] / exp) % 10]++;
        }
        // 将计数数组转换为位置数组
        for (int i = 1; i < 10; i++) {
            count[i] += count[i - 1];
        }
        // 根据当前位对数组进行排序,构建输出数组
        for (int i = n - 1; i >= 0; i--) {
            int digit = (arr[i] / exp) % 10;
            output[count[digit] - 1] = arr[i];
            count[digit]--;
        }
        // 将排序后的结果复制回原数组
        for (int i = 0; i < n; i++) {
            arr[i] = output[i];
        }
    }
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值