当前位置: 首页 > news >正文

Java基础——排序算法

排序算法不管是考试、面试、还是日常开发中都是一个特别高频的点。下面对八种排序算法做简单的介绍。


1. 冒泡排序(Bubble Sort)

原理:相邻元素比较,每一轮将最大元素“冒泡”到末尾。
示例数组[5, 3, 8, 1, 2]

public static void bubbleSort(int[] arr) {for (int i = 0; i < arr.length - 1; i++) {boolean swapped = false;for (int j = 0; j < arr.length - i - 1; j++) {if (arr[j] > arr[j + 1]) {int temp = arr[j];arr[j] = arr[j + 1];arr[j + 1] = temp;swapped = true;}}System.out.println("第 " + (i + 1) + " 轮后: " + Arrays.toString(arr));if (!swapped) break;}
}

输出

第 1 轮后: [3, 5, 1, 2, 8]  
第 2 轮后: [3, 1, 2, 5, 8]  
第 3 轮后: [1, 2, 3, 5, 8]  
第 4 轮后: [1, 2, 3, 5, 8]  

时间复杂度

  • 最好情况:O(n)(已有序时一轮扫描结束)
  • 最坏/平均:O(n²)(双重循环嵌套)

空间复杂度:O(1)(原地排序,仅需常数空间)

计算原理

  • 外层循环最多执行 n-1 次,内层循环每次执行 n-i-1 次
  • 总比较次数 ≈ (n-1) + (n-2) + … + 1 = n(n-1)/2 → O(n²)

2. 选择排序(Selection Sort)

原理:每轮选择最小的元素,放到已排序部分的末尾。
示例数组[5, 3, 8, 1, 2]

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;System.out.println("第 " + (i + 1) + " 轮后: " + Arrays.toString(arr));}
}

输出

第 1 轮后: [1, 3, 8, 5, 2]  
第 2 轮后: [1, 2, 8, 5, 3]  
第 3 轮后: [1, 2, 3, 5, 8]  
第 4 轮后: [1, 2, 3, 5, 8]  

时间复杂度

  • 所有情况:O(n²)(必须完整执行双重循环)

空间复杂度:O(1)(原地交换)

计算原理

  • 外层循环 n-1 次,内层循环每次执行 n-i-1 次
  • 总比较次数 = (n-1) + (n-2) + … + 1 = n(n-1)/2 → O(n²)

3. 插入排序(Insertion Sort)

原理:将未排序元素插入已排序部分的正确位置。
示例数组[5, 3, 8, 1, 2]

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;System.out.println("插入 " + key + " 后: " + Arrays.toString(arr));}
}

输出

插入 3 后: [3, 5, 8, 1, 2]  
插入 8 后: [3, 5, 8, 1, 2]  
插入 1 后: [1, 3, 5, 8, 2]  
插入 2 后: [1, 2, 3, 5, 8]  

时间复杂度

  • 最好:O(n)(已有序时仅需线性扫描)
  • 最坏/平均:O(n²)(每次插入需移动大量元素)

空间复杂度:O(1)

计算原理

  • 外层循环 n-1 次,内层循环平均移动 i/2 次
  • 总操作次数 ≈ 1 + 2 + … + (n-1) = n(n-1)/2 → O(n²)

4. 快速排序(Quick Sort)

原理:通过基准分区,递归排序左右子数组。
示例数组[5, 3, 8, 1, 2]

public static void quickSort(int[] arr, int low, int high) {if (low < high) {int pi = partition(arr, low, high);System.out.println("基准 " + arr[pi] + " 分区后: " + Arrays.toString(arr));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;
}

输出

基准 2 分区后: [1, 2, 8, 5, 3]  
基准 1 分区后: [1, 2, 8, 5, 3]  
基准 3 分区后: [1, 2, 3, 5, 8]  
基准 8 分区后: [1, 2, 3, 5, 8]  

时间复杂度

  • 最好/平均:O(n log n)(每次分区将数组分成两半)
  • 最坏:O(n²)(每次分区极不平衡,如已有序数组)

空间复杂度:O(log n)(递归调用栈的深度)

计算原理

  • 理想情况:每次分区后左右子数组大小相等,递归深度 log n,每层处理 O(n) → O(n log n)
  • 最坏情况:每次分区后一个子数组为空,递归深度 n → O(n²)

5. 归并排序(Merge Sort)

原理:分治法,将数组拆分为两半,合并有序子数组。
示例数组[5, 3, 8, 1, 2]

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);System.out.println("合并后: " + Arrays.toString(arr));}
}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) {if (arr[i] <= arr[j]) temp[k++] = arr[i++];else temp[k++] = arr[j++];}while (i <= mid) temp[k++] = arr[i++];while (j <= right) temp[k++] = arr[j++];System.arraycopy(temp, 0, arr, left, temp.length);
}

输出

合并后: [3, 5, 8, 1, 2]  
合并后: [3, 5, 1, 8, 2]  
合并后: [1, 2, 3, 5, 8]  

时间复杂度

  • 所有情况:O(n log n)(稳定分治策略)

空间复杂度:O(n)(合并时需要临时数组)

计算原理

  • 递归树深度为 log n,每层合并总时间 O(n) → O(n log n)
  • 合并操作需要额外空间存储临时数组

6. 堆排序(Heap Sort)

原理:构建最大堆,依次将堆顶元素与末尾交换。
示例数组[5, 3, 8, 1, 2]

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;System.out.println("交换堆顶后: " + Arrays.toString(arr));heapify(arr, i, 0);}
}private static void heapify(int[] arr, int n, int i) {int largest = i, left = 2 * i + 1, 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);}
}

输出

交换堆顶后: [5, 3, 2, 1, 8]  
交换堆顶后: [1, 3, 2, 5, 8]  
交换堆顶后: [2, 1, 3, 5, 8]  
交换堆顶后: [1, 2, 3, 5, 8]  

时间复杂度

  • 所有情况:O(n log n)(建堆 O(n),每次调整堆 O(log n))

空间复杂度:O(1)(原地排序)

计算原理

  • 建堆时间复杂度为 O(n)(非叶子节点下沉操作)
  • 每次交换堆顶后调整堆需要 O(log n) 时间,共 n-1 次 → O(n log n)

7. 希尔排序(Shell Sort)

原理:按间隔分组,逐步缩小间隔进行插入排序。
示例数组[5, 3, 8, 1, 2]

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;}System.out.println("间隔 " + gap + " 排序后: " + Arrays.toString(arr));}
}

输出

间隔 2 排序后: [5, 1, 2, 3, 8]  
间隔 1 排序后: [1, 2, 3, 5, 8]  

时间复杂度

  • 取决于间隔序列
    • 最坏 O(n²)(如使用原始希尔序列)
    • 平均 O(n log n)(如使用Knuth序列)

空间复杂度:O(1)

计算原理

  • 通过分组减少数据移动距离
  • 不同间隔序列的时间复杂度不同,例如:
    • 希尔原始序列(n/2^k):O(n²)
    • Knuth序列(3k-1):O(n1.5)

8. 基数排序(Radix Sort)

原理:按位数从低到高排序(需稳定排序辅助)。
示例数组[170, 45, 75, 90, 802, 24, 2, 66]

public static void radixSort(int[] arr) {int max = Arrays.stream(arr).max().getAsInt();for (int exp = 1; max / exp > 0; exp *= 10) {countingSort(arr, exp);System.out.println("按第 " + exp + " 位排序后: " + Arrays.toString(arr));}
}private static void countingSort(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--) {output[count[(arr[i] / exp) % 10] - 1] = arr[i];count[(arr[i] / exp) % 10]--;}System.arraycopy(output, 0, arr, 0, arr.length);
}

输出

按第 1 位排序后: [170, 90, 802, 2, 24, 45, 75, 66]  
按第 10 位排序后: [802, 2, 24, 45, 66, 170, 75, 90]  
按第 100 位排序后: [2, 24, 45, 66, 75, 90, 170, 802]  

时间复杂度:O(nk)(k为最大数字位数)

空间复杂度:O(n + k)(计数排序的额外空间)

计算原理

  • 对每个位数进行稳定排序(如计数排序)
  • 若最大数为 d 位,则进行 d 轮排序,每轮 O(n) → 总时间 O(dn)

总结表格

排序算法最好时间平均时间最坏时间空间复杂度稳定性
冒泡排序O(n)O(n²)O(n²)O(1)稳定
选择排序O(n²)O(n²)O(n²)O(1)不稳定
插入排序O(n)O(n²)O(n²)O(1)稳定
快速排序O(n log n)O(n log n)O(n²)O(log n)不稳定
归并排序O(n log n)O(n log n)O(n log n)O(n)稳定
堆排序O(n log n)O(n log n)O(n log n)O(1)不稳定
希尔排序O(n log n)取决于间隔O(n²)O(1)不稳定
基数排序O(nk)O(nk)O(nk)O(n + k)稳定

复杂度计算核心思想

  1. 循环嵌套次数

    • 双重循环(如冒泡、选择、插入)→ O(n²)
    • 分治法(快速、归并)→ 递归深度 log n × 每层 O(n) → O(n log n)
  2. 数据移动代价

    • 插入排序的移动次数与逆序对数量相关
    • 堆排序的调整代价与树高度 log n 相关
  3. 额外空间使用

    • 递归算法的栈空间(快速排序 O(log n))
    • 合并排序的临时数组(O(n))
    • 基数排序的计数数组(O(n + k))

相关文章:

  • Nacos-SpringBoot 配置无法自动刷新问题排查
  • 高德地图 API 拿到当前定位和目的地址转经纬度,实现路径规划
  • Matlab算例运行
  • 数据库关系模型的总结
  • 5、Rag基础:RAG 专题
  • Golang | 向倒排索引上添加删除文档
  • 【PCL】实现CloudCompare的连通域点云聚类功能
  • 机器学习基础——Seaborn使用
  • AI 数据中心 vs 传统数据中心:从硬件架构到网络设计的全面进化
  • Python教程(二)——控制流工具前半部分
  • 常用的性能提升手段--提纲
  • 关于华为高斯数据库出现Invalid or unsupported by client SCRAM mechanisms定位解决的过程
  • 互斥量函数组
  • 谢飞机的Java面试之旅:从Spring Boot到Kubernetes的挑战
  • rockermq多线程消费者配置
  • 【数据可视化-38】基于Plotly得泰坦尼克号数据集的多维度可视化分析
  • 目标跟踪最新文章阅读列表
  • PlatformIO 入门学习笔记(二):开发环境介绍
  • 国标GB28181视频平台EasyGBS打造生产监控智能体系,推动企业数字化升级
  • 2025蓝桥杯省赛网络安全组wp
  • 五万吨级半潜船在沪完成装备装载
  • 央行回应美债波动:单一市场、单一资产变动对我国外储影响总体有限
  • 下任美联储主席热门人选沃什:美联储犯下“系统性错误”,未能控制一代人以来最严重的通胀
  • 梅花画与咏梅诗
  • 持续更新丨伊朗内政部长:港口爆炸已致8人死亡750人受伤
  • 我驻美使馆:中美并没有就关税问题磋商谈判,更谈不上达成协议