排序算法的空间复杂度和时间复杂度

一、排序算法的时间复杂度和空间复杂度

排序算法

平均时间复杂度

最坏时间复杂度

最好时间复杂度

空间复杂度

稳定性

冒泡排序

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为数据位数。

1.1 复杂度辅助记忆

  1. 冒泡、选择、直接 排序需要两个for循环,每次只关注一个元素,平均时间复杂度为O(n²))(一遍找元素O(n),一遍找位置O(n))
  2. 快速、归并、希尔、堆基于二分思想,log以2为底,平均时间复杂度为O(nlogn)(一遍找元素O(n),一遍找位置O(logn))

1.2 稳定性辅助记忆

  • 稳定性记忆-“快希选堆”(快牺牲稳定性) 
  • 排序算法的稳定性:排序前后相同元素的相对位置不变,则称排序算法是稳定的;否则排序算法是不稳定的。

二、理解时间复杂度

2.1 常数阶O(1)

int i = 1;
int j = 2;
++i;
j++;
int m = i + j;

 2.2 对数阶O(logN)

int i = 1;
while(i<n)
{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<n; m++)
{i = 1;while(i<n){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.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 改成gapprivate 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

稳定性:稳定的 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/168253.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

《研发效能(DevOps)工程师》课程简介(五)丨IDCF

由国家工业和信息化部教育与考试中心颁发的职业技术证书&#xff0c;也是国内首个研发效能&#xff08;DevOps&#xff09;职业技术认证&#xff0c;内涵1000页学习教材2000分钟的课程内容讲解460多个技术知识点300多道练习题。 在这里&#xff0c;你不仅可以了解到华为、微软、…

在新的服务器上成功安装mysqlclient的方法【解决No matching distribution found for mysqlclient的问题】

前言&#xff1a;在某台Centos服务器上安装mysqlclient时一直报下面的错&#xff1a; WARNING: Discarding https://mirrors.aliyun.com/pypi/packages/6a/91/bdfe808fb5dc99a5f65833b370818161b77ef6d1e19b488e4c146ab615aa/mysqlclient-1.3.0.tar.gz#sha25606eb5664e3738b28…

【C语言 | 基础】计算机的位(bit)、字节(Byte)、字(word)、双字(DWord)、四字(QWord)分别代表什么

&#x1f601;博客主页&#x1f601;&#xff1a;&#x1f680;https://blog.csdn.net/wkd_007&#x1f680; &#x1f911;博客内容&#x1f911;&#xff1a;&#x1f36d;嵌入式开发、Linux、C语言、C、数据结构、音视频&#x1f36d; &#x1f923;本文内容&#x1f923;&a…

Markdown使用教程

这里写自定义目录标题 欢迎使用Markdown编辑器新的改变功能快捷键合理的创建标题&#xff0c;有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、居右SmartyPants 创建一个自定义列表如何创建一个…

centos k8s安装dapr

文章目录 安装helm更新helm库初始化dapr高可用方式安装 卸载dapr验证k8s的dapr安装rocketmq总结 安装helm 三个包放到一个目录下 chmod x get ./get helm version更新helm库 helm repo add dapr https://dapr.github.io/helm-charts/ helm repo update helm search repo dapr …

拍摄视频的时候相机断电导致视频文件损坏,怎么修复

3-4 现在好多人都有自己的相机&#xff0c;但是专业用来录像的机器应该是不太可能都有的&#xff0c;相机的稳定性会比专业的机器差一些&#xff0c;如果用于比较重要的场景&#xff0c;比如婚庆、会议录像、家庭录像使用等&#xff0c;有较少的概率会出现一些奇怪的情况&…

拓扑排序软件设计——ToplogicalSort_app(含有源码、需求分析、可行性分析、概要设计、用户使用手册)

拓扑排序软件设计 前言1. 需求分析2. 可行性分析2.1 简介2.2 技术可行性分析2.2.1 技术实现方案2.2.2 开发人员技能要求2.2.3 可行性 2.3 操作可行性分析2.4 结论 3. 项目报告3.1 修订历史记录3.2 软硬件环境3.3 需求分析3.4 详细设计3.4.1 类设计3.4.2 核心流程描述3.4.3 核心…

CSS特效第一弹:右上角tag标志纯代码前端实现(非图片)

&#x1f60e;效果&#xff1a; &#x1f937;‍♂️思路&#xff1a; 分为2个部分&#xff1a; 1.文字方块右下角折角 文字方块用绝对定位z-index让文字方块悬浮在右上角的位置 2.右下角折角通过before伪元素border属性实现(三角形实现方法&#xff09; &#x1f44d;核心代…

Nignx及负载均衡动静分离

目录 一.Nginx负载均衡 1.1.下载 1.2.安装 1.3.负载均衡 二.前端部署 2.1. 准备工作 2.2.部署 好啦今天就到这里了哦&#xff01;&#xff01;&#xff01;希望能帮到你哦&#xff01;&#xff01;&#xff01; 一.Nginx负载均衡 1.1.下载 输入命令 : cd javaCloudJun/…

Android 13.0 Settings主页面去掉FocusRecyclerView相关功能

1.前言 在13.0的系统rom产品定制化开发中,在系统Settings主页面的主菜单中,在测试某些功能的时候,比如开启护眼模式和改变系统密度会在主菜单第一项的网络菜单头部增加 自定义您的设备和设置护眼模式时间安排 等等相关的设置模块 这对于菜单布局显示相当不美观,所以根据系…

Spark 读取ES采坑系列

目录 一、使用的插件 二、ES集群和Elasticsearch-hadoop版本问题 三、Elasticsearch-hadoop 和Scala版本以及Spark版本&#xff08;版本不匹配会有各种异常信息 一、使用的插件 <dependency><groupId>org.elasticsearch</groupId><artifactId>elas…

muduo源码剖析之TcpClient客户端类

简介 muduo用TcpClient发起连接&#xff0c;TcpClient有一个Connector连接器&#xff0c;TCPClient使用Conneccor发起连接, 连接建立成功后, 用socket创建TcpConnection来管理连接, 每个TcpClient class只管理一个TcpConnecction&#xff0c;连接建立成功后设置相应的回调函数…