深入浅出Java排序:从基础算法到实战优化(上)

前言

排序是编程开发中最基础也最核心的操作之一,无论是日常业务开发中的数据整理,还是算法竞赛里的核心逻辑实现,掌握排序算法都至关重要。作为一名Java学习者,今天就带大家梳理Java中常用的排序算法,以及JDK内置排序工具的使用技巧。

今天的内容主要是两个部分,把常见的8中排序手动实现一下,然后介绍一下java内部的arrays.sort().

经典排序算法实现(Java版)

我们先从几种基础排序算法入手,理解其核心思想和代码实现,这对数据结构学习和蓝桥杯等竞赛备考也很有帮助。

1. 冒泡排序(Bubble Sort)

核心思想:重复遍历待排序数组,每次比较相邻两个元素,若顺序错误则交换,直到没有元素需要交换为止。就像气泡从水底逐渐上浮到水面一样,大的元素会逐步"冒泡"到数组末尾。

Java实现

java 复制代码
     public static void bubbleSort(int[] arr){
         //i表示的躺数
         for (int i = 0; i < arr.length - 1; i++) {
             boolean flg = false;
             for (int j = 0; j < arr.length -1 - i; j++) {
                 if (arr[j] > arr[j+1]){
                     swap(arr,j,j+1);
                     flg = true;  //说明我这一趟是有过交换的
                 }
             }
             if (!flg){
                 return;
             }
         }
     }

特点

  • 稳定排序;
  • 平均时间复杂度 O(n2)O(n^2)O(n2) 最好O(N)O(N)O(N)
  • 空间复杂度O(1)O(1)O(1)
  • 适合小规模数据排序。

2. 快速排序(Quick Sort)

算法原理:分治思想的典型应用。选择一个元素作为"基准",将数组分为两部分,一部分元素比基准小,另一部分比基准大;然后递归地对两部分数组进行排序。

Java实现

java 复制代码
//非递归形式
     public static void quickSortNor(int[] arr){
             if (arr == null || arr.length <= 1) {
                 return;
             }
             Stack<Integer> stack = new Stack<>();
             // 初始化栈,存入整个数组的边界:先存high,再存low(因为栈是后进先出)
             stack.push(arr.length - 1);
             stack.push(0);

             while (!stack.isEmpty()) {
                 int low = stack.pop();
                 int high = stack.pop();
                 // 分区操作
                 int par = parttionHoare(arr, low, high);

                 // 左子数组入栈:[low, par-1]
                 if (low < par - 1) {
                     stack.push(par - 1);
                     stack.push(low);
                 }
                 // 右子数组入栈:[par+1, high]
                 if (par + 1 < high) {
                     stack.push(high);
                     stack.push(par + 1);
                 }
             }
     }


     //快排序
    public static void quickSort(int[] arr){
         quick(arr,0,arr.length -1);
    }

    private static void quick(int[] arr, int start, int end) {
         if (start > end){
             return;
         }
         int par = parttion(arr,start,end);
         quick(arr,start,par-1);
         quick(arr,par +1 ,end);

    }
    private static int parttion(int[] arr, int low, int high) {
        //定义一个基准
        //int pivot = arr[low];
        // 优化:三数取中法选基准(可选,避免有序数组的最坏情况)
        int mid = low + (high - low) / 2;
        //比较前两个
        if(arr[mid] < arr[low]) swap(arr,mid,low);
        //比较第一个与最后一个  -- h是最小的
        if(arr[high] < arr[low]) swap(arr,high,low);
        //中间与最后
        if (arr[high] < arr[mid]) swap(arr,mid,high);
        swap(arr, low, mid);  // 把中间值放到 low 位置作为基准
        int tmp = arr[low];

        //标记一下low的位置
        int i = low;
        while (low < high){
            while (low < high && arr[high] >= tmp){
                high --;
            }
            arr[low] = arr[high];

            while (low < high && arr[low] <= tmp){
                low ++;
            }
            arr[high] = arr[low];
            //左边找到大的,右边找到小的了,此时交换
        }
        //交换基准值
        arr[high] = tmp;
        return high;
    }

    private static int parttionHoare(int[] arr, int low, int high) {
         //定义一个基准
        //int pivot = arr[low];
        // 优化:三数取中法选基准(可选,避免有序数组的最坏情况)
        int mid = low + (high - low) / 2;
       //比较前两个
        if(arr[mid] < arr[low]) swap(arr,mid,low);
        //比较第一个与最后一个  -- h是最小的
        if(arr[high] < arr[low]) swap(arr,high,low);
        //中间与最后
        if (arr[high] < arr[mid]) swap(arr,mid,high);
        swap(arr, low, mid);  // 把中间值放到 low 位置作为基准
        int pivot = arr[low];

        //标记一下low的位置
        int i = low;
        while (low < high){
            while (low < high && arr[high] >= pivot){
                high --;
            }
            while (low < high && arr[low] <= pivot){
                low ++;
            }
            //左边找到大的,右边找到小的了,此时交换
            swap(arr,low,high);
        }
        //交换基准值
        swap(arr,high,i);
        return high;
    }

特点

  • 稳定排序:不稳定
  • 平均时间复杂度 O(nlog⁡n)O(n\log n)O(nlogn),最好 O(nlog⁡n)O(n\log n)O(nlogn),最坏 O(n2)O(n^2)O(n2)
  • 空间复杂度 O(log⁡n)O(\log n)O(logn)(递归版,函数栈帧消耗)/ O(log⁡n)O(\log n)O(logn)(非递归版,手动栈消耗)
  • 适合大规模数据排序

3.直接插入排序(insertionSort)

算法原理

初始化:默认数组的第一个元素属于「已排序区间」,从第二个元素开始处理未排序区间。

取出待插入元素:保存当前未排序区间的第一个元素(记为 current),避免后续移动元素时被覆盖。

向前比较并移动:将 current 和已排序区间的元素从后往前逐一比较:

如果已排序元素 > current,则将该元素向后移动一位。

如果遇到已排序元素 ≤ current,则停止比较,找到了插入位置。

插入元素:将 current 插入到找到的位置。

重复:直到所有未排序元素都完成插入。

java代码实现

java 复制代码
    public static void insertionSort(int[] arr) {
        // 空数组或只有一个元素,无需排序
        if (arr == null || arr.length == 1) {
            return;
        }

        // 从第2个元素开始(下标1),遍历未排序区间
        for (int i = 1; i < arr.length; i++) {
            int tmp = arr[i];
            int j = i-1;
            for (; j >= 0 ; j++) {
                //j>=0 处理越界情况  后面处理
                if (arr[j] >arr[j+1]){
                    arr[j+1] = arr[j];
                }else {
                    arr[j+1] = tmp;
                    break;
                }
            } //不走else
            arr[j+1] = tmp;
        }
    }

特点

  • 平均时间复杂度 O(n2)O(n^2)O(n2)、最好时间复杂度 O(n)O(n)O(n)、最坏时间复杂度 O(n2)O(n^2)O(n2);
  • 空间复杂度 O(1)O(1)O(1);
  • 适合小规模数据或基本有序数据的排序。

4.希尔排序(shell)

核心原理
1.增量分组 :先选定一个增量 gap,将数组按 gap 划分为若干子序列(下标差为 gap 的元素为一组)。
2.组内插入排序 :对每个子序列分别进行直接插入排序,让数组整体变得 "基本有序"。
3.缩小增量 :逐步缩小增量 gap(通常取 gap = gap / 2),重复分组和排序操作。
4.最终排序:当增量 gap = 1 时,整个数组被分为一组,此时执行一次直接插入排序,即可完成最终排序。

Java实现

java 复制代码
    public static void shellSort(int[] arr) {
        int gap = arr.length;
        while (gap >= 1) {
            gap = gap / 2;
            shell(arr, gap);
        }
    }

    private static void shell(int[] arr, int gap) {
        if (arr == null || arr.length == 1) {
            return;
        }
        for (int i = gap; i < arr.length; i++) {
            int tmp = arr[i];
            int j = i-gap;
            for (; j >= 0 ; j-=gap) {
                //j>=0 处理越界情况  后面处理
                if (arr[j] > tmp){
                    arr[j+gap] = arr[j];
                }else {
                    arr[j+gap] = tmp;
                    break;
                }
            } //不走else
            arr[j+gap] = tmp;
        }
    }

特点

  • 不稳定排序;
  • 时间复杂度与增量序列相关,平均时间复杂度约 O(n1.3)O(n^{1.3})O(n1.3) 、最好时间复杂度 O(n)O(n)O(n)、最坏时间复杂度 O(n2)O(n^2)O(n2);
  • 空间复杂度 O(1)O(1)O(1);
  • 适合中等规模数据排序,效率优于直接插入、冒泡排序。

5. 选择排序(Selection)

核心原理

  1. 划分区间 :将数组划分为已排序区间 (初始为空)和未排序区间(初始为整个数组)。
  2. 查找最值:遍历未排序区间,找到其中的最小(或最大)元素。
  3. 交换位置:将找到的最值元素与未排序区间的第一个元素交换,扩大已排序区间。
  4. 重复操作:重复上述步骤,直到未排序区间为空。

Java 实现

java 复制代码
public static void selectionSort(int[] arr) {
    if (arr == null || arr.length <= 1) {
        return;
    }
    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;
            }
        }
        // 交换最小元素和未排序区间首元素
        int tmp = arr[i];
        arr[i] = arr[minIndex];
        arr[minIndex] = tmp;
    }
}

特点

  • 不稳定排序;
  • 平均时间复杂度 O(n2)O(n^2)O(n2)、最好时间复杂度 O(n2)O(n^2)O(n2)、最坏时间复杂度 O(n2)O(n^2)O(n2);
  • 空间复杂度 O(1)O(1)O(1);
  • 适合小规模数据排序,数据移动次数少(仅需 n−1n-1n−1 次交换)。

6. 堆排序(Heap)

核心原理

  1. 构建大顶堆:将待排序数组构建成完全二叉树形式的大顶堆(每个父节点值 ≥ 子节点值)。
  2. 交换堆顶与堆尾:将堆顶的最大值与堆的最后一个元素交换,此时最大值进入已排序区间。
  3. 堆化调整:将剩余的未排序元素重新调整为大顶堆,恢复堆的性质。
  4. 重复操作:重复交换和堆化步骤,直到堆的大小缩减为 1。

Java 实现

java 复制代码
public static void heapSort(int[] arr) {
    if (arr == null || arr.length <= 1) {
        return;
    }
    int n = arr.length;
    // 1. 构建大顶堆
    for (int i = n / 2 - 1; i >= 0; i--) {
        heapify(arr, n, i);
    }
    // 2. 交换堆顶和堆尾,再堆化
    for (int i = n - 1; i > 0; i--) {
        int tmp = arr[0];
        arr[0] = arr[i];
        arr[i] = tmp;
        heapify(arr, i, 0);
    }
}

// 堆化函数:调整以 i 为根的子树为大顶堆
private static void heapify(int[] arr, int heapSize, int i) {
    int largest = i; // 根节点为最大值
    int left = 2 * i + 1; // 左子节点
    int right = 2 * i + 2; // 右子节点
    // 比较左子节点
    if (left < heapSize && arr[left] > arr[largest]) {
        largest = left;
    }
    // 比较右子节点
    if (right < heapSize && arr[right] > arr[largest]) {
        largest = right;
    }
    // 最大值不是根节点则交换并递归堆化
    if (largest != i) {
        int tmp = arr[i];
        arr[i] = arr[largest];
        arr[largest] = tmp;
        heapify(arr, heapSize, largest);
    }
}

特点

  • 不稳定排序;
  • 平均时间复杂度 O(nlogn)O(nlogn)O(nlogn)、最好时间复杂度 O(nlogn)O(nlogn)O(nlogn)、最坏时间复杂度 O(nlogn)O(nlogn)O(nlogn);
  • 空间复杂度 O(1)O(1)O(1);
  • 适合大规模数据排序,不受数据初始状态影响。

7. 归并排序(Merge)

核心原理

  1. 分治拆分:采用分治法,将待排序数组递归拆分为两个长度相等的子数组,直到子数组长度为 1。
  2. 有序合并:将两个有序的子数组合并为一个有序数组,合并过程中需要临时空间存储合并结果。
  3. 递归回溯:逐层回溯合并,最终得到一个完全有序的数组。

Java 实现

java 复制代码
public static void mergeSort(int[] arr) {
    if (arr == null || arr.length <= 1) {
        return;
    }
    int[] tmpArr = new int[arr.length];
    mergeSort(arr, tmpArr, 0, arr.length - 1);
}

private static void mergeSort(int[] arr, int[] tmpArr, int left, int right) {
    if (left < right) {
        int mid = left + (right - left) / 2;
        // 递归拆分左半部分
        mergeSort(arr, tmpArr, left, mid);
        // 递归拆分右半部分
        mergeSort(arr, tmpArr, mid + 1, right);
        // 合并两个有序子数组
        merge(arr, tmpArr, left, mid, right);
    }
}

private static void merge(int[] arr, int[] tmpArr, 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]) {
            tmpArr[k++] = arr[i++];
        } else {
            tmpArr[k++] = arr[j++];
        }
    }
    // 拷贝左子数组剩余元素
    while (i <= mid) {
        tmpArr[k++] = arr[i++];
    }
    // 拷贝右子数组剩余元素
    while (j <= right) {
        tmpArr[k++] = arr[j++];
    }
    // 临时数组元素拷贝回原数组
    while (left <= right) {
        arr[left] = tmpArr[left];
        left++;
    }
}

特点

  • 稳定排序;
  • 平均时间复杂度 O(nlogn)O(nlogn)O(nlogn)、最好时间复杂度 O(nlogn)O(nlogn)O(nlogn)、最坏时间复杂度 O(nlogn)O(nlogn)O(nlogn);
  • 空间复杂度 O(n)O(n)O(n)(非原地实现,需临时数组);
  • 适合大规模数据排序,尤其适合链表等非连续存储数据。

8. 基数排序(了解)(Radix)

核心原理

  1. 确定位数 :找到数组中的最大值,确定其最高位数 ddd(如 123 的最高位是百位,d=3d=3d=3)。
  2. 按位排序:从最低位到最高位依次对数组进行排序,每一轮排序基于当前位的数值,采用桶排序或计数排序实现。
  3. 合并结果 :每一轮排序后,数组会按当前位有序,经过 ddd 轮排序后,数组整体有序。

Java 实现

java 复制代码
public static void radixSort(int[] arr) {
    if (arr == null || arr.length <= 1) {
        return;
    }
    // 1. 找到最大值,确定最大位数
    int max = arr[0];
    for (int num : arr) {
        if (num > max) {
            max = num;
        }
    }
    // 2. 按位排序:从个位到最高位
    for (int exp = 1; max / exp > 0; exp *= 10) {
        countingSortByDigit(arr, exp);
    }
}

// 按当前位(exp 对应位)进行计数排序
private static void countingSortByDigit(int[] arr, int exp) {
    int n = arr.length;
    int[] output = new int[n]; // 存储排序结果
    int[] count = new int[10]; // 0-9 的数字计数

    // 统计当前位的数字出现次数
    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 = n - 1; i >= 0; i--) {
        int digit = (arr[i] / exp) % 10;
        output[count[digit] - 1] = arr[i];
        count[digit]--;
    }
    // 拷贝结果回原数组
    System.arraycopy(output, 0, arr, 0, n);
}

特点

  • 稳定排序;
  • 平均时间复杂度 O(d×(n+r))O(d\times(n+r))O(d×(n+r))、最好时间复杂度 O(d×(n+r))O(d\times(n+r))O(d×(n+r))、最坏时间复杂度 O(d×(n+r))O(d\times(n+r))O(d×(n+r))(ddd 为位数,rrr 为基数);
  • 空间复杂度 O(n+r)O(n+r)O(n+r);
  • 适合整数、字符串等基于"位"或"关键字"排序的场景,数据范围较大时优势明显。

*在这里插入代码片*# 常见排序算法特性对比表

排序算法 稳定性 平均时间复杂度 最好时间复杂度 最坏时间复杂度 空间复杂度 适用场景
直接插入排序 稳定 O(n2)O(n^2)O(n2) O(n)O(n)O(n) O(n2)O(n^2)O(n2) O(1)O(1)O(1) 小规模数据、基本有序数据
希尔排序 不稳定 O(n1.3)O(n^{1.3})O(n1.3) O(n)O(n)O(n) O(n2)O(n^2)O(n2) O(1)O(1)O(1) 中等规模数据,优于插入/冒泡排序
选择排序 不稳定 O(n2)O(n^2)O(n2) O(n2)O(n^2)O(n2) O(n2)O(n^2)O(n2) O(1)O(1)O(1) 小规模数据,数据移动次数少的场景
堆排序 不稳定 O(nlogn)O(nlogn)O(nlogn) O(nlogn)O(nlogn)O(nlogn) O(nlogn)O(nlogn)O(nlogn) O(1)O(1)O(1) 大规模数据,不受数据初始状态影响
归并排序 稳定 O(nlogn)O(nlogn)O(nlogn) O(nlogn)O(nlogn)O(nlogn) O(nlogn)O(nlogn)O(nlogn) O(n)O(n)O(n) 大规模数据、链表等非连续存储数据
基数排序 稳定 O(d×(n+r))O(d\times(n+r))O(d×(n+r)) O(d×(n+r))O(d\times(n+r))O(d×(n+r)) O(d×(n+r))O(d\times(n+r))O(d×(n+r)) O(n+r)O(n+r)O(n+r) 整数/字符串排序、数据范围较大的场景

备注

  • 基数排序中,ddd 为数据的最大位数,rrr 为基数(如十进制 r=10r=10r=10);
  • 稳定性定义:排序后相等元素的相对位置不发生改变。
  • 到这里我的分享就先结束了~,希望对你有帮助
  • 我是dylan 下次见~
    • 无限进步
相关推荐
J_liaty1 小时前
前后端跨域处理全指南:Java后端+Vue前端完整解决方案
java·前端·vue.js·spring boot·后端
星火开发设计2 小时前
从公式到应用:卷积公式全面解析与实战指南
学习·算法·机器学习·概率论·知识·期末考试·卷积公式
颜淡慕潇2 小时前
深度解读 Spring Boot 3.5.9— 工程视角的稳健演进与价值释放
java·spring boot·后端·spring
夜郎king2 小时前
一文掌握:Java项目目录结构文档自动化生成
java·自动化·java原生目录生成
appearappear2 小时前
IntelliJ IDEA 2025.3.1 中 Export → SQL Updates 不带 WHERE 的真实原因与解决方案(OpenAI 协助整理)
java·数据库
玄〤2 小时前
黑马点评中的分布式锁设计与实现(Redis + Redisson)
java·数据库·redis·笔记·分布式·后端
码界奇点2 小时前
基于SpringBoot与Shiro的细粒度动态权限管理系统设计与实现
java·spring boot·后端·spring·毕业设计·源代码管理
小毅&Nora2 小时前
【Java线程安全实战】⑬ volatile的奥秘:从“共享冰箱“到内存可见性的终极解析
java·多线程·volatile
学嵌入式的小杨同学2 小时前
循环队列(顺序存储)完整解析与实现(数据结构专栏版)
c语言·开发语言·数据结构·c++·算法