| 排序算法 | 平均时间复杂度 | 最坏时间复杂度 | 最好时间复杂度 | 空间复杂度 | 稳定性 |
| 冒泡排序 | 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(nlogn) | O(n²) | O(nlogn) | O(nlogn) | ❌ |
| 堆排序 | O(nlogn) | O(nlogn) | O(nlogn) | O(1) | ❌ |
| 归并排序 | O(nlogn) | O(nlogn) | O(nlogn) | O(n) | ✅ |
| 希尔排序 | O(nlogn) | O(n²) | O(nlogn) | O(1) | ❌ |
| 计数排序 | O(n+k) | O(n+k) | O(n+k) | O(n+k) | ✅ |
| 基数排序 | O(N*M) | O(N*M) | O(N*M) | O(M) | ✅ |
注:
1 归并排序可以通过手摇算法将空间复杂度降到O(1),但是时间复杂度会提高。
2 基数排序时间复杂度为O(N*M),其中N为数据个数,M为数据位数。


- int i = 1;
- int j = 2;
- ++i;
- j++;
- int m = i + j;
- int i = 1;
- while(i
- {
- i = i * 2;
- }
2.3 线性阶O(n)
- for(i=0; i<=n; i++)
- {
- System.out.println("hello");
- }
2.4 线性对数阶O(n)
- for(m=1; m
- {
- i = 1;
- while(i
- {
- i = i * 2;
- }
- }
2.5 平方阶O(n)
-
- for(x=1; i<=n; x++)
- {
- for(i=1; i<=n; i++)
- {
- System.out.println("hello");
- }
- }
2.6 K次方阶O(n)
- for(i=0; i<=n; i++)
- {
- for(j=0; i<=n; i++)
- {
- for(k=0; i<=n; i++)
- {
- System.out.println("hello");
- }
- }
- }
-
-
- // k = 3 , n ^ 3
上面从上至下依次的时间复杂度越来越大,执行的效率越来越低。
三、空间复杂度
3.1 常数阶O(1) —— 原地排序
只用到 temp 这么一个辅助空间
原地排序算法,就是空间复杂度为O(1)的算法,不牵涉额外得到其他空间~
- private static void swap(int[] nums, int i, int j) {
- int temp = nums[i];
- nums[i] = nums[j];
- nums[j] = temp;
- }
2.2 对数阶O(logN)
2.3 线性阶O(n)
- int[] newArray = new int[nums.length];
- for (int i = 0; i < nums.length; i++) {
- newArray[i] = nums[i];
- }
四、排序算法
4.1 冒泡排序
(思路:大的往后放)

4.1.1 代码
- private static void bubbleSort(int[] nums) {
- for (int i = 0; i < nums.length; i++) {
- for (int j = 0; j < nums.length - 1 - i; j++) {
- if (nums[j] > nums[j + 1]) {
- swap(nums, j, j + 1);
- }
- }
- }
- }
4.1.2 复杂度
时间复杂度: N^2
空间复杂度:1
最佳时间复杂度:N^2 (因为就算你内部循环只对比,不交换元素,也是一样是N)
稳定性:稳定的 (大于的才换,小于等于的不交换)
- // { 0,1,2,3,4}
- private static void bubbleSort(int[] nums) {
- for (int i = 0; i < nums.length; i++) {
- boolean isChange = false;
- for (int j = 0; j < nums.length - 1 - i; j++) {
- if (nums[j] > nums[j + 1]) {
- swap(nums, j, j + 1);
- isChange = true;
- }
- }
- if(!isChange){
- return;
- }
- }
- }
改进后的代码,最佳时间复杂度: N (因为假如第一轮对比就没有任何元素交换,那么可以直接退出,也就是只有一次外循环)
4.2 选择排序
(思路:最小的放最前)

4.2.1 代码
- private static void selectSort(int[] nums) {
- for (int i = 0; i < nums.length; i++) {
- int minIndex = i;
- for (int j = i + 1; j < nums.length; j++) {
- if (nums[j] < nums[minIndex]) {
- minIndex = j;
- }
- }
- swap(nums,minIndex,i);
- }
- }
4.2.2 复杂度
时间复杂度: N^2
空间复杂度:1
最佳时间复杂度:N^2
稳定性:不稳定的

4.3 直接插入排序
(思路:往排序好的数组中,找到合适的位置插进去)

4.3.1 代码
- private static void insertSort(int[] nums) {
- for (int i = 1; i < nums.length; i++) {
- int temp = nums[i];
- int j = i - 1;
- for (; j >= 0 && temp < nums[j]; j--) {
- nums[j + 1] = nums[j];
- }
- nums[j + 1] = temp;
- }
- }
4.3.2 复杂度
时间复杂度: N^2
空间复杂度:1
最佳时间复杂度:N (因为你不进入内部循环。 [1,2,3,4,5])

稳定性:稳定的
4.4 快速排序
(思路:利用数字target,把数组切成两边,左边比 target大,后边比 target小)

4.4.1 代码
- /**
- * 快速排序算法
- * @param nums 待排序的数组
- * @param beginIndex 排序起始索引
- * @param endIndex 排序结束索引
- */
- private static void quickSort(int[] nums, int beginIndex, int endIndex) {
- if (beginIndex >= endIndex) {
- return; // 递归终止条件:当开始索引大于等于结束索引时,表示已经完成排序
- }
- int mid = getMid(nums, beginIndex, endIndex); // 获取中间索引,用于分割数组
- quickSort(nums, beginIndex, mid - 1); // 对中间索引左侧的数组进行快速排序
- quickSort(nums, mid + 1, endIndex); // 对中间索引右侧的数组进行快速排序
- }
-
- /**
- * 获取分区中的中间元素的索引
- * @param nums 待排序的数组
- * @param beginIndex 分区的起始索引
- * @param endIndex 分区的结束索引
- * @return 中间元素的索引
- */
- private static int getMid(int[] nums, int beginIndex, int endIndex) {
- int target = nums[beginIndex]; // 以数组的起始元素作为基准值
- int left = beginIndex;
- int right = endIndex;
- boolean right2left = true; // 标识位,表示当前从右往左搜索
- while (right > left) {
- if (right2left) {
- while (right > left && nums[right] > target) {
- right--;
- }
- if (right > left) {
- nums[left] = nums[right]; // 当右侧元素较大时,将右侧元素移到插入位置
- right2left = false; // 切换为从左往右搜索
- }
- } else {
- while (right > left && nums[left] < target) {
- left++;
- }
- if (right > left) {
- nums[right] = nums[left]; // 当左侧元素较小时,将左侧元素移到插入位置
- right2left = true; // 切换为从右往左搜索
- }
- }
- }
- nums[left] = target; // 将基准值放入插入位置,完成一轮交换
- return left;
- }
4.4.2 复杂度
时间复杂度: N Log N (每个元素找到中间位置的,需要 LogN 时间,N个元素就是NLogN)
空间复杂度:N Log N (递归调用,需要栈空间)
最差时间复杂度:N ^ 2 ( 比如正序数组 [1,2,3,4,5] )

稳定性:不稳定的

4.4.3 相关面试题
1、奇偶分割
给你一个数组,奇数放左边,偶数放右边,不要求排序
- public static void main(String[] args) {
- // 题目,把奇数放左边,偶尔放右边
- // 思路:快速排序,交换元素
- int[] array = {13, 100, 17, 12, 25, 0,1,6};
- quickSort(array, 0, array.length - 1);
- System.out.println(JSON.toJSONString(array));
-
- }
-
- private static void quickSort(int[] array, int beginIndex, int endIndex) {
- if (beginIndex >= endIndex) {
- return;
- }
- int mid = getMid(array, beginIndex, endIndex);
- }
-
- private static int getMid(int[] array, int beginIndex, int endIndex) {
- int left = beginIndex;
- int right = endIndex;
- int temp = array[beginIndex];
-
- boolean right2Left = true;
- while (left < right) {
- if (right2Left) {
- // 如果是偶数
- if (array[right] % 2 == 0) {
- right--;
- } else {
- array[left++] = array[right];
- right2Left = false;
- }
- } else {
- // 如果是奇数
- if (array[left] % 2 != 0) {
- left++;
- } else {
- array[right--] = array[left];
- right2Left = true;
- }
- }
- }
- array[left] = temp;
- return left;
- }
这个方法的时间复杂度是O(N),空间复杂度是 O(1),不稳定
下面这个方法的时间复杂度是O(N),空间复杂度是 O(N),稳定
- public static void main(String[] args) {
- // 题目,把奇数放左边,偶尔放右边
- // 思路:快速排序,交换元素
- int[] array = {13, 100, 17, 12, 25, 0, 1, 6, 5, 5};
-
- int[] arrayNew = new int[array.length];
-
- int oddCount = 0;
- for (int i = 0; i < array.length; i++) {
- if (array[i] % 2 != 0) {
- oddCount++;
- }
- }
- System.out.println(oddCount);
- // 奇数的下标
- int oddIndex = 0;
- // 偶数的下标
- int evenIndex = oddCount;
- for (int i = 0; i < array.length; i++) {
- if (array[i] % 2 != 0) {
- arrayNew[oddIndex++] = array[i];
- }else {
- arrayNew[evenIndex++] = array[i];
- }
-
- }
- System.out.println(JSON.toJSONString(arrayNew));
- }
4.5 堆排序
(思路:最大放上面,然后与最后元素交换,继续建堆)

4.5.1 代码
- /**
- * 堆排序算法
- * @param nums 待排序的数组
- * @param beginIndex 排序的起始索引
- * @param endIndex 排序的结束索引
- */
- private static void heapSort(int[] nums, int beginIndex, int endIndex) {
- if (beginIndex >= endIndex) {
- return; // 当开始索引大于等于结束索引时,排序完成
- }
- for (int i = endIndex; i >= beginIndex; i--) {
- createHeap(nums, i); // 构建最大堆
- swap(nums, 0, i); // 将最大元素移到数组末尾
- }
- }
-
- /**
- * 构建最大堆
- * @param nums 待构建的数组
- * @param endIndex 当前堆的结束索引
- */
- private static void createHeap(int[] nums, int endIndex) {
- int lastFatherIndex = (endIndex - 1) / 2;
- for (int i = lastFatherIndex; i >= 0; i--) {
- int biggestIndex = i;
- int leftChildIndex = i * 2 + 1;
- int rightChildIndex = i * 2 + 2;
- if (leftChildIndex <= endIndex) {
- biggestIndex = nums[biggestIndex] > nums[leftChildIndex] ? biggestIndex : leftChildIndex;
- }
- if (rightChildIndex <= endIndex) {
- biggestIndex = nums[biggestIndex] > nums[rightChildIndex] ? biggestIndex : rightChildIndex;
- }
- swap(nums, biggestIndex, i); // 调整堆,确保最大元素位于堆顶
- }
- }
-
- /**
- * 交换数组中两个元素的位置
- * @param nums 数组
- * @param i 索引1
- * @param j 索引2
- */
- private static void swap(int[] nums, int i, int j) {
- int temp = nums[i];
- nums[i] = nums[j];
- nums[j] = temp;
- }
4.5.2 复杂度
时间复杂度: N Log N (每个元素都要构建1次堆,需要 LogN 时间,N个元素就是NLogN,任何情况下都一样)
空间复杂度:1 (原地排序)
最差时间复杂度:N ^ 2 ( 比如正序数组 [1,2,3,4,5] )
稳定性:不稳定的

4.6 归并排序
递归思路,左右两边排序好了,就已经排序好了

4.6.1 代码
- // 归并排序的主方法
- private static void mergeSort(int[] nums, int beginIndex, int endIndex) {
- // 如果起始索引大于等于结束索引,表示只有一个元素或没有元素,不需要排序
- if (beginIndex >= endIndex) {
- return;
- }
-
- // 计算数组的中间索引
- int mid = beginIndex + (endIndex - beginIndex) / 2;
-
- // 递归排序左半部分
- mergeSort(nums, beginIndex, mid);
-
- // 递归排序右半部分
- mergeSort(nums, mid + 1, endIndex);
-
- // 合并左右两部分
- merge(nums, beginIndex, mid, endIndex);
- }
-
- // 合并函数,用于将左右两部分合并成一个有序的数组
- private static void merge(int[] nums, int beginIndex, int mid, int endIndex) {
- int left = beginIndex;
- int right = mid + 1;
- int[] newArrays = new int[endIndex - beginIndex + 1];
- int newArraysIndex = 0;
-
- // 比较左右两部分的元素,将较小的元素放入新数组
- while (left <= mid && right <= endIndex) {
- newArrays[newArraysIndex++] = nums[left] <= nums[right] ? nums[left++] : nums[right++];
- }
-
- // 将剩余的左半部分元素复制到新数组
- while (left <= mid) {
- newArrays[newArraysIndex++] = nums[left++];
- }
-
- // 将剩余的右半部分元素复制到新数组
- while (right <= endIndex) {
- newArrays[newArraysIndex++] = nums[right++];
- }
-
- // 将合并后的新数组复制回原数组
- for (int i = 0; i < newArrays.length; i++) {
- nums[beginIndex + i] = newArrays[i];
- }
- }
4.6.2 复杂度
时间复杂度: N Log N (每个元素都要递归,需要 LogN 时间,N个元素就是NLogN,任何情况下都一样)
空间复杂度:N
稳定性:稳定的
4.7 希尔排序
思路:直接插入排序的升级版(分段式插入排序)

4.7.1 代码
- private static void quickSort(int[] nums) {
- // int gap = nums.length / 2;
- // while (gap > 0) {
- for (int i = 1; i < nums.length; i++) {
- int temp = nums[i];
- int j;
- for (j = i - 1; j >= 0 && temp < nums[j]; j--) {
- nums[j + 1] = nums[j];
- }
- nums[j + 1] = temp;
- }
- // gap = gap / 2;
- // }
- }
-
- // 把上面的快速排序改成shell排序,只需要把间隔1 改成gap
- private static void shellSort(int[] nums) {
- int gap = nums.length / 2;
- while (gap > 0) {
- for (int i = gap; i < nums.length; i++) {
- int temp = nums[i];
- int j;
- for (j = i - gap; j >= 0 && temp < nums[j]; j = j - gap) {
- nums[j + gap] = nums[j];// 如果当前元素比待插入元素大,将当前元素向后移动
- }
- nums[j + gap] = temp; // 因为上边 j=j-gap退出的时候,j已经被剪掉1次了,可能小于0了
- }
- gap = gap / 2;
- }
- }
4.7.2 复杂度
时间复杂度: N Log N
空间复杂度:1
稳定性:不稳定的

4.8 计数排序
前提:
1、数组规模不大,比如统计中国各个年龄的人数(因为年龄顶多就是0-150岁)

4.8.2 代码
- public static void main(String[] args) {
- int[] nums = {10, 18, 18, 18, 18, 18, 18, 20, 20, 20, 20, 2, 8, 3, 4, 1, 2, 3, 4};
- int[] ageCount = new int[200];
- for (int i = 0; i < nums.length; i++) {
- ageCount[nums[i]]++;
- }
- for (int i = 1; i < ageCount.length; i++) {
- int count = ageCount[i];
- for (int j = 0; j < count; j++) {
- System.out.print(i + ",");
- }
- }
- }
4.8.2 复杂度
时间复杂度: N (N是数组长度)
空间复杂度:K
稳定性:稳定的
4.9 基数排序

4.9.1 代码
如何取到指定位置的数字?
- public static void main(String[] args) {
- int number = 1234;
- // 1234 如何取到个位数 4,1234 % 10 = 4 ,4/1 = 4
- // 1234 如何取到十位数 3,1234 % 100 = 34 ,34/10 = 3
- // 1234 如何取到百位数 2,1234 % 1000 = 234 ,234/100 = 2
- // 1234 如何取到千位数 1,1234 % 10000 = 1234 ,234/1000 = 1
- for (int i = 0; i < getDigitLength(number); i++) {
- int num = (int) (number % Math.pow(10, i + 1) / Math.pow(10, i));
- System.out.println(num);
- }
- }
-
- // 1 就是1位数
- // 10 就是2位数
- // 100 就是3位数
- // 1000 就是4位数
- public static int getDigitLength(int number) {
- int length = 1;
- while (number / 10 > 0) { // 100
- length++;
- number = number / 10;
- }
- return length;
- }
基数排序算法:
- public static void main(String[] args) {
-
- int[] array = {13, 100, 17, 12, 25};
- Map
> bucketMap = new HashMap<>(); - for (int i = 0; i < 10; i++) {
- bucketMap.put(i, new ArrayList<>());
- }
-
- int maxDigitLength = 0;
- for (int i = 0; i < array.length; i++) {
- maxDigitLength = getDigitLength(array[i]) > maxDigitLength ? getDigitLength(array[i]) : maxDigitLength;
- }
- for (int i = 0; i < maxDigitLength; i++) { // 从后往前取数值
- for (int j = 0; j < array.length; j++) {
- int iValue = (int) (array[j] % Math.pow(10, i + 1) / Math.pow(10, i));
- bucketMap.get(iValue).add(array[j]); // 入桶
- }
-
- int arrayIndex = 0;
- for (int j = 0; j < 10; j++) {
- List
bucketList = bucketMap.get(j); - for (Integer num: bucketList) {
- array[arrayIndex++] = num; //倒出来
- }
- bucketMap.put(j,new ArrayList<>()); // 清空桶
- }
- System.out.println(JSON.toJSONString(array));
-
- }
- }
-
-
- // 1 就是1位数
- // 10 就是2位数
- // 100 就是3位数
- // 1000 就是4位数
- public static int getDigitLength(int number) {
- int length = 1;
- while (number / 10 > 0) { // 100
- length++;
- number = number / 10;
- }
- return length;
- }
4.9.2 复杂度
时间复杂度: N * d (N是数组长度,d是最大位数,对于整数排序的话,d = log N 级别,因此整体是 N Log N)
空间复杂度:N
稳定性:稳定的
-
相关阅读:
使用CFimagehost源码搭建无需数据库支持的PHP免费图片托管私人图床
Vuex使用一文搞懂
副业教程之如何通过出售API赚取美元含数据集和训练教程
运行报错(三)git bash报错fatal: detected dubious ownership in repository at
php程序设计的基本原则
Ubuntu Studio 23.10发布
MongoDB单实例安装(Linux)
在游戏博弈中才是博弈游戏的最佳实践
【计算机网络】IP协议第二讲(Mac帧、IP地址、碰撞检测、ARP协议介绍)
了解什么是JDBC
-
原文地址:https://blog.csdn.net/Sword52888/article/details/134252267