【数据结构】算法的时间复杂度和空间复杂度

目录

1. 什么是数据结构?

2.什么是算法?

3.算法效率

4.时间复杂度

4.1时间复杂度的概念

4.2大O的渐进表示法

4.3常见时间复杂度计算举例

4.3.1冒泡排序:

4.3.2二分查找:

4.3.3递归阶乘

4.3.4斐波那契数列

4.4例题:消失的数字

5.空间复杂度

5.1空间复杂度的概念

5.2常见空间复杂度计算举例

5.2.1递归阶乘

5.2.2斐波那契数

5.3例题:轮转数组


  • 🎈个人主页:库库的里昂
  •  🎐C/C++领域新星创作者
  •  🎉欢迎 👍点赞✍评论⭐收藏
  • ✨收录专栏:数据结构与算法
  • 🤝希望作者的文章能对你有所帮助,有不足的地方请在评论区留言指正,大家一起学习交流!🤗

1. 什么是数据结构?

数据结构(Data Structure)是计算机存储、组织数据的方式,指相互之间存在一种或多种特定关系的数据元素的集合。

2.什么是算法?

算法(Algorithm):就是定义良好的计算过程,他取一个或一组的值为输入,并产生出一个或一组值作为输出。简单来说算法就是一系列的计算步骤,用来将输入数据转化成输出结果。

3.算法效率

  • 通常我们会用复杂度去衡量一个算法的好坏。算法在编写成可执行程序后,运行时需要消耗时间资源和空间资源(内存资源)。因此衡量一个算法的好坏,一般是从时间和空间两个维度来衡量的,即时间复杂度和空间复杂度。
  • 时间复杂度主要衡量一个算法的运行快慢,而空间复杂度主要衡量一个算法的运行所需要的额外空间。在计算机发展的早期,计算机的存储容量很小,所以对空间复杂度要求很高,但是现在随着计算机行业的快速发展,计算机的存储容量已经达到了很高的程度,内存成本逐渐降低。所以我们如今已经不再需要特别关心一个算法的空间复杂度。

4.时间复杂度

4.1时间复杂度的概念

定义:

 在计算机科学中,算法的时间复杂度是一个函数,它定量描述了该算法的运行时间。一个算法执行所消耗的时间,从理论上来说,是不能算出来的,只有你把你的程序放在机器上跑起来,才能知道。但是我们需要算法都上机测试嘛?是可以都上机测试,但是这很麻烦,并且同一个程序在不同的机器上运行时间可能差异很大,所以我们有了时间复杂度的分析方法。一个算法所花费的时间与其中语句的执行次数成正比例,算法中的基本操作执行的次数,为算法的时间复杂度。

 即:找到某条基本语句与问题规模N之间的数学表达式,就是算出了该算法的时间复杂度。

4.2大O的渐进表示法

实际中我们计算时间复杂度的时候其实并不需要计算精确的执行次数,而只需要知道大概执行次数,所以这里我们引入了大O的渐进表示法。其中大O符号是用于描述函数渐进行为的数学符号。

推导大O阶的方法:

  1. 用常数1取代运行时间中的所有加法常数
  2. 在修改后的运行次数函数中,只保留最高阶项
  3. 如果最高阶项存在且系数是不唯1的常系数,则去除最高项的系数,得到的结果就是大O阶
  4. 如果最终结果是O(1),则表示常数次,并不是代表一次
     
  • 最坏情况
     任意输入规模的最大运行次数(上界)
  • 平均情况
     任意输入规模的期望运行次数
  • 最好情况
     任意输入规模的最小运行次数(下界)

 一般我们比较关心的是一个算法的最坏情况

4.3常见时间复杂度计算举例

4.3.1冒泡排序:

// 计算BubbleSort的时间复杂度?
void BubbleSort(int* a, int n)
{assert(a);for (size_t end = n; end > 0; --end){int exchange = 0;for (size_t i = 1; i < end; ++i){if (a[i - 1] > a[i]){Swap(&a[i - 1], &a[i]);exchange = 1;}}if (exchange == 0)break;//如果没有条语句,最好情况也是O(N*N)}
}
  • 最好情况:O(N) 
  • 最坏情况:O(N^2)

 最好情况就是数组本身有序,虽然它有序,但是计算机最初并不知道它是有序的,仍需要遍历一遍数组才能知道它是有序的,因此冒泡排序的最好情况是:O(N)
 冒泡排序一趟可以排好一个元素,最坏情况是数组完全逆序,则第一趟需要交换N − 1 N-1N−1次,第二趟需要交换N − 2 N-2N−2次…直到最后一趟只交换一次,把所有的交换次数加起来就得到了冒泡排序最坏情况下的时间复杂度,其实也就是一个等差数列求和,所以最会情况下的时间复杂度是O(N^2)

4.3.2二分查找

// 计算BinarySearch的时间复杂度?
int BinarySearch(int* a, int n, int x)
{assert(a);int begin = 0;int end = n - 1;// [begin, end]:begin和end是左闭右闭区间,因此有=号while (begin <= end){int mid = begin + ((end - begin) >> 1);if (a[mid] < x)begin = mid + 1;else if (a[mid] > x)end = mid - 1;elsereturn mid;}return -1;
}
  • 最好情况:O(1) 
  • 最坏情况:O(log2N) 

最好情况是第一次查找就找到目标值,此时时间复杂度就是O(1)
 二分查找中每一次查找要么找到目标值,要么可以排除掉一半数据,因此最坏情况是执行到当区间只剩下一个值的时候,用数学语言来描述这个过程就是:每折半查找一次就会除一次二,一直到结果为1 11的时候,二分查找结束。N / 2 / 2 / 2..... / 2 = 1。计算执行次数就是看除了多少次2。其结果是除了log2N个2,所以最坏情况就是O(log2N),一般可以把简写成O(logN)(仅限于时间复杂度,且只有当底数是2的时候才能简化)

O(N)与O(log2N)的对比:

可见O(log2N)相较于O(N)在效率上有很大的提升,虽然二分查找的效率很高,但是他有一个致命的限制条件就是数组有序,对数组排序也是需要消耗时间的,因此二分查找在实际中使用的并不是很多,用的更多的是红黑树,它的时间复杂度也是O(log2N)

4.3.3递归阶乘

// 计算阶乘递归Fac的时间复杂度?
long long Fac(size_t N)
{if (0 == N )return 1;return Fac(N - 1) * N;
}
  • 时间复杂度:O(N)

 这里涉及到了递归,Fac一共被递归调用了N次,且每一次Fac中的执行次数是1,所以总的执行次数就是N + 1 N+1N+1个1 11相加,因此时间复杂度就是O ( N ) O(N)O(N)

4.3.4斐波那契数列

// 计算斐波那契递归Fib的时间复杂度?
long long Fib(size_t N)
{if (N < 3)return 1;return Fib(N - 1) + Fib(N - 2);
}
  • 时间复杂度:O(2^N)

用递归去求解斐波那契额数列,它的时间复杂度是等比数列求和,最终的时间复杂度就是 O(2^N)

常见时间复杂度关系

4.4例题:消失的数字

在线OJ:力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台

分析:
 这道题大家很容易想到的方法就是先对数组进行排序然后再利用二分查找找到缺失的数字。但是注意题目限制了时间复杂度只能在O(N),而我们常用的冒泡排序时间复杂度是O(N^2),快排的时间复杂度是O(N∗ logN) ,都不符合题目要求,因此这条路要被我们Pass掉。我们可以考虑异或来解决这个问题,异或有下面两条性质:a^a=0、a^0=a,并且异或满足交换律和结合律,所以我们可以先让0~N的所有数字进行异或,然后再和数组里面的所有元素进行异或,最终的结果就是缺失的那个数字,这种方法只需要遍历两边数组,时间复杂度是O(N)。下面来看一下这种思路的代码实现:

int missingNumber(int* nums, int numsSize){int num = 0;for(int i = 0; i <= numsSize; i++){num = num ^ i;}for(int i = 0; i < numsSize; i++){num = num ^ nums[i];}return num;
}

处理上面的这种思路还可以直接利用求和公式算出0~N的总和,再减去数组中的所有元素,最终的结果就是缺失的数字,此种方法只需要遍历一遍数组,因此时间复杂度也是O(N)。

5.空间复杂度

5.1空间复杂度的概念

定义:
 空间复杂度也是一个数学表达式,是对一个算法在运行过程中临时占用存储空间大小的量度。空间复杂度不是程序占用了多少bytes的空间,因为这个也没太大意义,所以空间复杂度算的是变量的个数。空间复杂度的计算规则和时间复杂度类似,也采用大O渐进表示法。
 注意:函数运行时所需要的栈空间(存储参数、局部变量、一些寄存器信息等)在编译期间已经确定好了,因此空间复杂度主要通过函数在运行时候显式申请的额外空间来确定。
 一般常见的空间复杂度都是O(1)或者O(N)(额外开辟数组)。

5.2常见空间复杂度计算举例

5.2.1递归阶乘

// 计算阶乘递归Fac的空间复杂度?
long long Fac(size_t N)
{if(N == 0)return 1;return Fac(N-1)*N;
}

首先每调用一次函数都会创建一个函数栈帧,而每个函数的空间复杂度可以看作是O ( 1 ) O(1)O(1),一共递归调用了N+1次Fac函数,因此空间复杂度是O ( N ) O(N)O(N)。 这里调用的N+1次Fac函数栈帧的地址是不同的,因为它们是递归调用的。
 

void Fun1()
{int a = 0;printf("%p\n", &a);
}void Fun2()
{int b = 0;printf("%p\n", &b);
}int main()
{Fun1();Fun2();return 0;
}

程序先调用Fun1函数,创建其相应的函数栈帧,调用结束的时候函数栈帧会被销毁,也就是把空间还给操作系统,接下来调用Fun2函数,创建其相应的函数栈帧,这也就是为什么两次打印的地址是一样的,因为Fun1调用结束的时候把空间还给了操作系统。

void Fun1()
{int a = 0;printf("%p\n", &a);
}void Fun2()
{int b = 0;printf("%p\n", &b);Fun1();
}int main()
{Fun2();return 0;
}

此时两次打印的地址不同,因为是在Fun2函数中调用的Fun1函数,它的执行过程是:先在主函数中调用Fun2函数,创建相应的函数栈帧,接着在Fun2函数中调用了Fun1函数,所以接下来会去创建Fun1的函数栈帧,这就是为什么两次打印的地址不同,函数的递归调用就是类似这样。

5.2.2斐波那契数

// 计算斐波那契递归Fib的空间复杂度?
long long Fib(size_t N)
{if (N < 3)return 1;return Fib(N - 1) + Fib(N - 2);
}

与普通递归不同的是,这里一次函数调用中会再递归调用两次,具体的调用过程以及调用次序如上图所示,第四次调用结束后函数栈指会销毁,接着进行第五次函数调用,因此第四次调用与第五次调用所创建的函数栈帧其实是同一块,同理第三次和第六次函数调用所创建的函数栈帧也是同一块。综上所述,当求N的斐波那契数列时,一共只会占用N − 1个函数栈帧的空间,因为有很多函数栈帧的创建用的是同一块空间,因此递归求解斐波那契数列的空间复杂度是O(N),这也说明时间一去不复返,而空间可以重复利用。

5.3例题:轮转数组

这道题可以采用暴力求解的方法,即一次旋转一个数字然后轮转k次,但这样做的时间复杂度O(N^2),对于这种轮转问题我们可以采用下面时间复杂度为O(N),空间复杂度是O(1)的三步来解决:

  1. 前n-k个逆置
  2. 后k个逆置
  3. 整体逆置
     
void rotate(int* nums, int numsSize, int k){k = k % numsSize;//一定要记得模,这样可以节约时间还可以避免越界int i = 0;int j = 0;int tmp = 0;for (i = 0, j = numsSize - k - 1; i < j; i++,j--)//对前n-k个进行逆置{tmp = nums[i];nums[i] = nums[j];nums[j] = tmp;}for (i = numsSize - k, j = numsSize - 1; i < j; i++, j--)//对后k个进行逆置{tmp = nums[i];nums[i] = nums[j];nums[j] = tmp;}for (i = 0, j = numsSize - 1; i < j; i++, j--)//对整体进行逆置{tmp = nums[i];nums[i] = nums[j];nums[j] = tmp;}
}

针对上面这个题我们还可以采用以空间换时间的办法,即新创建一个数组把原数组的后k个拷贝到新数组的前面,再把原数组的前n-k个拷贝到新数组的后面:

void rotate(int* nums, int numsSize, int k)
{k %= numsSize;int* arr = (int*)malloc(sizeof(int)*numsSize);memcpy(arr, nums+(numsSize-k), sizeof(int)*k);memcpy(arr+k, nums, sizeof(int)*(numsSize-k));memcpy(nums, arr, sizeof(int)*numsSize);free(arr);
}

此时的时间复杂度是O ( N ) O(N)O(N),表面上我们看着时间复杂度像是O(1),但其实并不是,因为memcpy函数内部是一个字节一个字节进行拷贝的,它的时间复杂度是O(N),由于新开了一个数组,所以空间复杂度也是O(N)。

本次的内容到这里就结束啦。希望大家阅读完可以有所收获,同时也感谢各位读者三连支持。文章有问题可以在评论区留言,博主一定认真认真修改,以后写出更好的文章。你们的支持就是博主最大的动力。

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

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

相关文章

Elasticsearch实践:ELK+Kafka+Beats对日志收集平台的实现

可以在短时间内搜索和分析大量数据。 Elasticsearch 不仅仅是一个全文搜索引擎&#xff0c;它还提供了分布式的多用户能力&#xff0c;实时的分析&#xff0c;以及对复杂搜索语句的处理能力&#xff0c;使其在众多场景下&#xff0c;如企业搜索&#xff0c;日志和事件数据分析等…

stable diffusion和midjourney哪个好

midjourney和stable diffusion哪个好&#xff1f;midjourney和stable diffusion的区别&#xff1f;那么今天就从这2款软件入手&#xff0c;来探索一下他们的功能的各项区别吧&#xff0c;让你选择更适合你的一款ai软件。 截至目前&#xff0c;我们目睹了生成式人工智能工具的在…

复杂的菱形继承及菱形虚拟继承(详解)

复杂的菱形继承及菱形虚拟继承 复杂的菱形继承及菱形虚拟继承虚拟继承解决数据冗余和二义性的原理笔试面试题 复杂的菱形继承及菱形虚拟继承 单继承&#xff1a;一个子类只有一个直接父类时称这个继承关系为单继承 多继承&#xff1a;一个子类有两个或以上直接父类时称这个继…

Leetcode—2525.根据规则将箱子分类【简单】

2023每日刷题&#xff08;五&#xff09; Leetcode—2525.根据规则将箱子分类 实现代码 char * categorizeBox(int length, int width, int height, int mass){long long volume;long long len (long long)length;long long wid (long long)width;long long heig (long lo…

ssrf漏洞学习

目录 ssrf漏洞 相关函数 相关协议 file协议 dict协议 gopher协议 ctfshow ssrf web351 web352 web353 web354过滤01 web355五位长度 web356 三位长度 web357 DNS重定向 web358 正则 ssrf漏洞 SSRF&#xff08;Server-Side Request Forgery&#xff0c;服务器端请…

搭建伪分布式Hadoop

文章目录 一、Hadoop部署模式&#xff08;一&#xff09;独立模式&#xff08;二&#xff09;伪分布式模式&#xff08;三&#xff09;完全分布式模式 二、搭建伪分布式Hadoop&#xff08;一&#xff09;登录虚拟机&#xff08;二&#xff09;上传安装包&#xff08;三&#xf…

(矩阵) 289. 生命游戏 ——【Leetcode每日一题】

❓ 289. 生命游戏 难度&#xff1a;中等 根据 百度百科 &#xff0c; 生命游戏 &#xff0c;简称为 生命 &#xff0c;是英国数学家约翰何顿康威在 1970 年发明的细胞自动机。 给定一个包含 m n 个格子的面板&#xff0c;每一个格子都可以看成是一个细胞。每个细胞都具有一…

C++类对象所占内存空间大小分析

前言 类占内存空间是只类实例化后占用内存空间的大小&#xff0c;类本身是不会占内存空间的。用 sizeof 计算类的大小时&#xff0c;实际上是计算该类实例化后对象的大小。空类占用1字节原因&#xff1a;C要求每个实例在内存中都有一个唯一地址&#xff0c;为了达到这个目的&am…

MySQL的索引——索引的介绍及其数据结构B+树 索引的类型 索引的使用及其失效场景 相关名词解释

前言 索引是存储引擎用于快速查找数据纪录的一种数据结构&#xff0c;索引是数据库中经常提及的一个词&#xff0c;究竟什么是索引&#xff0c;索引的数据结构是什么&#xff0c;索引有什么类型&#xff1f; 本篇博客尝试阐述数据库索引的相关内容&#xff0c;涉及什么是索引…

【LeetCode】62. 不同路径

1 问题 一个机器人位于一个 m x n 网格的左上角 &#xff08;起始点在下图中标记为 “Start” &#xff09;。 机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角&#xff08;在下图中标记为 “Finish” &#xff09;。 问总共有多少条不同的路径&#xff1f…

如何压缩ppt文件的大小?

如何压缩ppt文件的大小&#xff1f;要知道现在很多课件都是使用ppt文件&#xff0c;那么就导致ppt文件过大&#xff0c;我们很多时候电脑的存储空间就不够了。为了能够更好的存储这些ppt文件&#xff0c;我们通常会选择压缩ppt文件。怎么压缩ppt文件更快更好&#xff0c;没有损…

机器人制作开源方案 | 行星探测车实现WiFi视频遥控功能

1. 功能描述 本文示例所实现的功能为&#xff1a;用手机APP&#xff0c;通过WiFi通信遥控R261样机行星探测车移动&#xff0c;以及打开、关闭行星探测车太阳翼。 2. 电子硬件 在这个示例中&#xff0c;我们采用了以下硬件&#xff0c;请大家参考&#xff1a; 主控板 Basra主控…

1.1 网页的基本概念

思维导图&#xff1a; 网页设计基础知识 --- **导言&#xff1a;** 随着互联网的迅速蔓延&#xff0c;世界各地的数亿人群均可以通过网络实现聊天、购物、阅读新闻、查询天气等功能。而在幕后&#xff0c;是成千上万的网页支撑这一切。但这些网页是如何制作的&#xff1f;我们…

阿里低代码Low Code Engine快速上手

一、环境准备 在正式开始之前,我们需要先安装相应的软件:WSL、Node等。Window 环境需要使用 WSL 在 windows 下进行低代码引擎相关的开发。安装教程➡️ WSL 安装教程。对于 Window 环境来说,之后所有需要执行命令的操作都是在 WSL 终端执行的。 2.1 Node 推荐安装Node 1…

如何实现前端音频和视频播放?

聚沙成塔每天进步一点点 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 欢迎来到前端入门之旅&#xff01;感兴趣的可以订阅本专栏哦&#xff01;这个专栏是为那些对Web开发感兴趣、刚刚踏入前端领域的朋友们量身打造的。无论你是完全的新手还是有一些基础的开发…

2022年京东双11食品饮料品类数据回顾

2022年双11&#xff0c;根据京东官方发布的数据显示&#xff0c;京东百货中&#xff0c;京东新百货的589个品类10025个品牌成交额同比增长100%。而在食品饮料行业中&#xff0c;也有一些在大促期间成交额同比涨幅超过100%的品牌。 下面&#xff0c;结合鲸参谋平台提供的数据&am…

【c#】Quartz开源任务调度框架学习及练习Demo

Quartz开源任务调度框架学习及练习Demo 1、定义、作用 2、原理 3、使用步骤 4、使用场景 5、Demo代码参考示例 6、注意事项 7、一些Trigger属性说明 1、定义、作用 Quartz是一个开源的任务调度框架&#xff0c;作用是支持开发人员可以定时处理业务&#xff0c;比如定时…

npm publish发布到在线仓库时,提示:Scope not found

当npm publish发布时&#xff0c;控制台提示&#xff1a;Scope not found&#xff0c;具体错误信息如下&#xff1a; npm notice npm ERR! code E404 npm ERR! 404 Not Found - PUT https://registry.npmjs.org/xxx%2fxxx - Scope not found npm ERR! 404 npm ERR! 404 xxx/xx…

easyphoto 妙鸭相机

AIGC专栏7——EasyPhoto 人像训练与生成原理详解-CSDN博客如何训练一个高品质的人像Lora与应用高品质Lora的链路对于写真生成而言非常重要。由《LoRA: Low-Rank Adaptation of Large Language Models》 提出的一种基于低秩矩阵的对大参数模型进行少量参数微调训练的方法&#x…

【算法练习Day24】递增子序列全排列全排列 II

​&#x1f4dd;个人主页&#xff1a;Sherry的成长之路 &#x1f3e0;学习社区&#xff1a;Sherry的成长之路&#xff08;个人社区&#xff09; &#x1f4d6;专栏链接&#xff1a;练题 &#x1f3af;长路漫漫浩浩&#xff0c;万事皆有期待 文章目录 递增子序列容易出错的地方 …