【数据结构和算法初阶(C语言)】时间复杂度(衡量算法快慢的高端玩家,搭配例题详细剖析)

目录

1.算法效率

1.1如何衡量一个算法的好坏

1.2 算法的复杂度

2.主菜-时间复杂度

2.1 时间复杂度的概念

2.2 大O的渐进表示法

2.2.1算法的最好,最坏和平均的情况

3.经典时间复杂度计算举例

3.1计算冒泡排序的时间复杂度

3.2计算折半查找的时间复杂度 

3.3.1直观数据对比暴力查找(遍历查找)和折半查找的效率

3.3计算递归函数的时间复杂度

3.4计算递归斐波那契数列的时间复杂度 

4.时间复杂度的oJ练习及解析

4.1消失的数字:链接

4.1.1思路1:遍历+循环

4.1.2思路2 

4.1.3思路3使用异或(单身狗思路)

5.结语


1.算法效率

1.1如何衡量一个算法的好坏

考察算法的性能如何 

引入例子:对斐波那契数列递归实现和循环实现的讨论:

补充斐波那契数列知识:

省流:后一个数是前两个数的和的数列

  • 首先是递归实现:
long long Fib(int N)
{if(N < 3)return 1;return Fib(N-1) + Fib(N-2);
}
  • 循环实现:
long long  fibonacci(int n) {if (n <= 1) {return n;}int a = 0, b = 1;for (int i = 2; i <= n; i++) {int temp = a + b;a = b;b = temp;}return b;
}

看似我们的递归代码简单,当我们运行两段代码:

运行循环

在运行递归

发现递归和循环输入同样的数据,循环很快就能打印出结果,但是我们的递归却还在很长时间的计算,因为调用的函数很多。

所以代码简洁不一定好,衡量算法的好坏该如何衡量,接下来引入算法的复杂度衡量我们算法的好坏。

1.2 算法的复杂度

  • 算法在编写成可执行程序后,运行时需要耗费时间资源空间(内存)资源 。因此衡量一个算法的好坏,一般 是从时间空间两个维度来衡量的,即时间复杂度空间复杂度
  • 时间复杂度主要衡量一个算法的运行快慢
  • 而空间复杂度主要衡量一个算法运行所需要的额外空间。
  • 在计算 机发展的早期,计算机的存储容量很小。所以对空间复杂度很是在乎。但是经过计算机行业的迅速发展,计 算机的存储容量已经达到了很高的程度。所以我们如今已经不需要再特别关注一个算法的空间复杂度。

2.主菜-时间复杂度

2.1 时间复杂度的概念

  • 时间复杂度的定义:在计算机科学中,算法的时间复杂度是一个函数,它定量描述了该算法的运行时间。一 个算法执行所耗费的时间,从理论上说,是不能算出来的,只有你把你的程序放在机器上跑起来,才能知 道。但是我们需要每个算法都上机测试吗?是可以都上机测试,但是这很麻烦,所以才有了时间复杂度这个 分析方式。而且有时候程序在好的cpu设备和坏的cpu设备上跑出来的时间也不一样,所以我们定义了以下方法:
  • 一个算法所花费的时间与其中语句的执行次数成正比例,算法中的基本操作的执行次数,为算法 的时间复杂度
  • 即:找到某条基本语句与问题规模N之间的数学表达式,就是算出了该算法的时间复杂度。

举例来说:

我们来看一下这个函数的时间复杂度:

我们找到这个函数中的一条基本语句count,看一下它执行的次数

所以:

上述Func1 执行的基本操作次数 :

                        F(N)= N^2+2*N+10

  •         N = 10           F(N) = 130
  •         N = 100         F(N) = 10210 
  •         N = 1000        F(N) = 1002010

那么我们就可以将上述的F(N)的数学函数表达式,称为我们这个函数实现的一个算法的时间复杂度。但是实际中我们计算时间复杂度时,我们其实并不一定要计算精确的执行次数,而只需要大概执行次数,那么这 里我们使用大O的渐进表示法。,即是抓大头,只要我们起决定作用的项,如果按照大O的渐进表示法,这个函数的时间复杂度就为O(N^2),因为在这个表达式中,当我们的N很大的时候,N^2后面表达式计算的结果甚至可以忽略不计。

2.2 大O的渐进表示法

大O符号(Big O notation):是用于描述函数渐进行为的数学符号。

本质是就是抓主要影响的项就行,当我们的表达式中未知数非常大,比如N=200万亿,那么他的常数倍或者再增加常数,就像对于大海来说,多一碗水少一碗水没有区别,我们只用抓主要因素来大概估算我们算法和程序的时间复杂度就可以。

所以:重要:大O的渐进表示法去掉了那些对结果影响不大的项,简洁明了的表示出了执行次数。

推导大O阶方法:( 大O的渐进表示法的一些规则和使用方法) 

  • 用常数1取代运行时间中的所有加法常数。

比如这个函数的时间复杂度:

// 计算Func4的时间复杂度?
void Func4(int N)
{int count = 0;for (int k = 0; k < 100; ++ k){++count;}printf("%d\n", count);
}

这里我们的count的运行次数是100,那么我们的时间复杂度是O(100),但是据规则写为:

O(1)。当表达式中只有常数项的时候,表示执行常数次1,方便表示就表示为O(1),cpu每秒运算速度为上亿次。

这里大家完全不用担心,因为执行常数次速度很快,我们的K是整数,有符号的整型到无符号的整型就是21亿多到42亿多,cpu处理速度很快,所以对于cpu这个人类文明皇冠上的宝珠来说,执行这种常数次和一次没有很大的区别。

  • 在修改后的运行次数函数中,只保留最高阶项。
  • 如果最高阶项存在且不是1,则去除与这个项目相乘的常数。得到的结果就是大O阶。(就是去除未知数前面的系数,因为其对表达式的结果相对于未知数的次方数影响较

我们来看一下这个函数的时间复杂度:

// 计算Func2的时间复杂度?
void Func2(int N)
{int count = 0;for (int k = 0; k < 2 * N ; ++ k){++count;}int M = 10;while (M--){++count;}printf("%d\n", count);
}

由代码我们可以看一下count的执行次数F(N) = 2*N+10.

按照规则:在修改后的运行次数函数中,只保留最高阶项。得到2N,10可以写为10*N^0;

如果最高阶项存在且不是1,则去除与这个项目相乘的常数得到N

所以这个函数的时间复杂度就为o(N);

那么同样的,对于我们引入的例子:

时间复杂度就为O(N^2)

2.2.1算法的最好,最坏和平均的情况

有些算法的时间复杂度存在最好、平均和最坏情况:

  • 最坏情况:任意输入规模的最大运行次数(上界)
  • 平均情况:任意输入规模的期望运行次数
  • 最好情况:任意输入规模的最小运行次数(下界)

结论:时间复杂度在计算时,是一个最稳健的保守预期即是我们只关注最坏的情况。

看例子:

// 计算strchr的时间复杂度?
const char * strchr ( const char * str, int character );

这个函数实现的是在字符串或者字符数组中去查找一个字符

那么他的执行情况就有三种大类:

最好情况:1次找到 最坏情况:N次找到 平均情况:N/2次找到

在实际中一般情况关注的是算法的最坏运行情况,所以数组中搜索数据时间复杂度为O(N)

3.经典时间复杂度计算举例

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;}
}

我们知道冒泡排序的原理:就是元素两两进行比较然后交换位置,第一次我们需要比较n-1次,把最后一个数排好过后,第二次比较n-2次.......

  • 那么最好的情况就是:我们的这份数据是有序的,那么对于我们程序来说他还是要运行一次,看一下有没有数据之间有没有两两进行交换,这里执行n-1次,那么时间复杂度为O(N)
  • 平均情况,当我们的数据中有一个或者两个是乱序的,那么对于程序来说就要执行两次函数,数据对比n-1+n-2次就是2n-3也是O(N);
  • 最坏的情况,我们的数据完全乱序程序就要比较:n-1+n-2+n-3.......+1次,就是等差数列,(N*(N-1)/2次,通过推导大O阶方法+时间复杂度一般看最 坏,时间复杂度为 O(N^2)

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)
  • 最坏的情况:要找的数据在尽头活着没有要查找的数据,我们要的结果无非就是折半的次数假设有吗有N个数据,执行一次还有N/2个数据。执行第二次还有N/2/2个数据执行到最后只有一个数据的时候是不是我们的式子为:N/2/2/2/2.....=1

那么我们的2的个数就是程序执行的此时:N=2^n,这个n就等于log2N,(这个2是角标)

那么我们的时间复杂度就有了,由于对数键值我们不好书写,所以优化为logN,有些资料会写为lgN.O(logN)

3.3.1直观数据对比暴力查找(遍历查找)和折半查找的效率

虽然折半查找的效率高,但是实际应用不这么好。因为他要求一个大前提,针对有序的数据,使用前还需要排序。

3.3计算递归函数的时间复杂度

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

递归的时间复杂度这里我们可以理解为FAc函数的调用次数。

对比这段代码:

long long Fac(size_t N)
{if(N==0)
{
return 1;
}
for(size_t i = 0;i<N;++i)
{
;
}
return Fac(N-1)*N;
}

这段代码不仅调用了N次Fac函数,每次调用后函数里面还执行了N次,所以这个递归的调用的时间复杂度为O(N^2).

3.4计算递归斐波那契数列的时间复杂度 

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

由上图,我们计算时间复杂度就是取大概,那么如果我们将N=4,N=5的最下面想象为有值也就是调用了,因为末尾多几次少几次调用在本次代码中并不是影响很大:

那么我们的时间复杂度就可以计算为:2^0+2^1.........2^(n-1)

使用大O表示法就是O(2^N)

所以这就是我们为什么最开始的时候使用递归方法计算我们的cpu还在计算,因为这个算法效率是在太慢了。

4.时间复杂度的oJ练习及解析

分析思路实现比较好的思路

4.1消失的数字:链接

4.1.1思路1:遍历+循环

思路一:遍历+循环

我们循环遍历这个数组中的所有数,直到找到一个数,他不是他的上一个数+1,这里还要排序。我们使用最快的快速排序:时间复杂度为O(logN*N),在加上我们的循环遍历的N,可以记为:O(logN*N)

4.1.2思路2 

思路二:既然知道这个数组里面的元素是0~n的元素缺一个,那么我们就可以先计算出0~n的所有数的和(可以循环也可以使用等差公式)再减去我们数组里面所有元素是不是就能够得到那个不见的数字。如果使用公式,时间复杂度为我们循环减去数组元素的循环次数就是O(N),如果使用循环来计算和,那么第一个执行次数为N+1,然后减法循环次数为N,时间复杂度为O(N),我们来实现一下:

4.1.3思路3使用异或(单身狗思路)

^  异或

  • 相同为0,相异为1
  • 0与任何数异或都是那个数本身
  • 两个相同的数异或为0,像2^3^2^3=0,那我我们上述的缺了一个数的数据和我们没有缺的数据异或起来,首先我们得先把缺的那个数赋值为0,0和任何数异或都是那个数本身。
  • 比如我们是0~3缺2

完整的数据:0,1,2,3

缺的数据:0,1,3

那么我们首先先让未知数和我们的这个缺的数据循环异或起来:x=0^0^1^3

这里时间复杂度为O(N+1)

接着我们在让x和完整数据异或起来:

x =0^0^1^3^0^1^3^2

就得到我们的2,这一步的时间复杂度为O(N)

那么这个算法的时间复杂度就为O(N),我们来实现一下:

5.结语

以上就是本期的所有内容,知识含量蛮多,大家可以配合解释和原码运行理解。创作不易,大家如果觉得还可以的话,欢迎大家三连,有问题的地方欢迎大家指正,一起交流学习,一起成长,我是Nicn,正在c++方向前行的奋斗者,数据结构内容持续更新中,感谢大家的关注与喜欢。

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

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

LLM权重量化

LLM权重量化 浮点表示的背景知识Nave 8位量化使用LLM.int8() 进行8位量化结论References 大型语言模型(llm)以其广泛的计算需求而闻名。通常&#xff0c;模型的大小是通过将**参数的数量(大小)乘以这些值的精度(数据类型)**来计算的。为了节省内存&#xff0c;可以通过称为量化…

Windows中的Git Bash运行conda命令:未找到命令的错误(已解决)

在windows中的Gitbash中 打开激活conda环境&#xff0c;并运行&#xff08;前提是你先安装好git&#xff08;自己去官网下载&#xff09;&#xff09;。 要能够在Gitbash上运行Conda&#xff0c; 临时配置 如果你只是临时用一下&#xff0c;就是临时爽一把&#xff0c;那就按…

【Linux】docker构建环境编译运行linux内核

文章目录 1. 使用docker构建linux内核编译运行环境1.1. 为普通用户安装docker并验证是否安装成功1.1.1. 安装docker稳定版1.1.2. 启动docker1.1.3. 将当前用户加入docker用户组1.1.4. 验证docker是否安装成功 1.2. docker基本使用1.2.1. 列出所有镜像1.2.2. 查看当前所有容器的…

C#之WPF学习之路(1)

目录 WPF的起源 C的qt和C#的wpf对比 winform 和 wpf有什么区别 安装 Visual Studio2022 创建 HelloWorld 程序 App.xaml与Application类 Application的生命周期 Window窗体的生命周期 WPF的起源 WPF&#xff08;Windows Presentation Foundation&#xff09;是一种用于…

基于SpringBoot的停车场管理系统

基于SpringBootVue的停车场管理系统的设计与实现~ 开发语言&#xff1a;Java数据库&#xff1a;MySQL技术&#xff1a;SpringBootMyBatis工具&#xff1a;IDEA/Ecilpse、Navicat、Maven 系统展示 前台首页 停车位 个人中心 管理员界面 摘要 摘要&#xff1a;随着城市化进程的…

ubantu设置mysql开机启动

阅读本文之前请参阅----MySQL 数据库安装教程详解&#xff08;linux系统和windows系统&#xff09; 在Ubuntu系统中设置MySQL开机启动&#xff0c;通常有以下几种方法&#xff1a; 1. **使用systemctl命令**&#xff1a; Ubuntu 16.04及更高版本使用systemd作为…

图像压缩感知的MATLAB实现(OMP)

前面实现了 压缩感知的图像仿真&#xff08;MATLAB源代码&#xff09; 效果还不错&#xff0c;缺点是速度慢如牛。 下面我们采用OMP对其进行优化&#xff0c;提升速度。具体代码如下&#xff1a; 仿真 构建了一个MATLAB文件&#xff0c;所有代码都在一个源文件里面&#xf…

【鸿蒙开发】第十四章 Stage模型应用组件-任务Mission

1 任务(Mission)管理场景 任务&#xff08;Mission&#xff09;管理相关的基本概念如下&#xff1a; AbilityRecord&#xff1a;系统服务侧管理一个UIAbility实例的最小单元&#xff0c;对应一个应用侧的UIAbility组件实例。系统服务侧管理UIAbility实例数量上限为512个。 Mi…

#FPGA(IRDA)

1.IDE:Quartus II 2.设备&#xff1a;Cyclone II EP2C8Q208C8N 3.实验&#xff1a;IRDA&#xff08;仿真接收一个来自0x57地址的数据0x22 (十进制34)&#xff09; 4.时序图&#xff1a; 5.步骤 6.代码&#xff1a; irda_receive.v module irda_receive ( input wire…

Llama2模型的优化版本:Llama-2-Onnx

Llama2模型的优化版本&#xff1a;Llama-2-Onnx。 Llama-2-Onnx是Llama2模型的优化版本。Llama2模型由一堆解码器层组成。每个解码器层&#xff08;或变换器块&#xff09;由一个自注意层和一个前馈多层感知器构成。与经典的变换器相比&#xff0c;Llama模型在前馈层中使用了不…

MySQL sql注意点

本文列取了常用但是容易遗漏的一些知识点。另外关键词一般大写&#xff0c;为了便于阅读所以很多小写。也为了给自己查缺补漏。 distinct&#xff08;去重&#xff09; 也许你经常对单个字段去重&#xff0c;并且知道不建议用distinct&#xff0c;而是group by&#xff0c;因为…

FL Studio21中文版功能、特点、使用场景及适用人群详解

一、功能介绍 FL Studio21中文版作为一款功能全面的数字音频工作站&#xff08;DAW&#xff09;&#xff0c;提供了从音乐创作到后期混音所需的完整工具集。以下是其主要功能&#xff1a; FL Studio 21.2.3 Win-安装包下载如下: https://wm.makeding.com/iclk/?zoneid55981 …

Vue3_基础使用_3_Hooks模块化

今天主要学习的是hooks, vue3的使用比vue2方便很多了&#xff0c;但是呢各个功能块的逻辑有时候还是会缠绕在一起&#xff0c;这个时候使用hooks进行模块化管理开发&#xff0c;说白了就是将每个单独的业务放到自己的.ts中去写&#xff0c;以后修改就找到这个ts 不用到处去翻…

VsCode的leetcode插件无法登录

前提 想使用VsCode的leetcode插件进行刷题&#xff0c;然后按照网上的教程进行安装下载&#xff0c;但是到了登录这一步&#xff0c;死活也登录不了&#xff0c;然后查看log一直报的错误是invalid password。 解决方法 首先确定在插件中设置的站点是Leetcode中国&#xff0c…

Stable Diffusion 绘画入门教程(webui)-ControlNet(Tile/Blur)

上篇文章介绍了y语义分割Seg&#xff0c;这篇文章介绍下Tile/Blur&#xff08;增加/减少细节&#xff09; Tile用于增加图片细节&#xff0c;一般用于高清修复&#xff0c;Blur用于减少图片细节&#xff08;图片模糊&#xff09;&#xff0c;如下图&#xff0c;用Tile做修复&a…

apidoc接口文档的自动更新与发布

文章目录 一、概述二、环境准备三、接口文档生成1. 下载源码2. 初始化3.执行 四、文档发布五&#xff0c;配置定时运行六&#xff0c;docker运行 一、概述 最近忙于某开源项目的接口文档整理&#xff0c;采用了apidoc来整理生成接口文档。 apidoc是一个可以将源代码中的注释直…

说说设备像素、css像素、设备独立像素、dpr、ppi 之间的区别

文章目录 一、背景二、介绍CSS像素设备像素设备独立像素dprppi 三、总结参考文献 一、背景 在css中我们通常使用px作为单位&#xff0c;在PC浏览器中css的1个像素都是对应着电脑屏幕的1个物理像素 这会造成一种错觉&#xff0c;我们会认为css中的像素就是设备的物理像素 但实…

谷歌连发 Gemini1.5、Gemma两种大模型,Groq让模型输出速度快18倍

本周&#xff0c;我们观察到以下AI领域的新动向和新趋势&#xff1a; 1.谷歌连发Gemini1.5和Gemma两种大模型&#xff0c; 其中Gemini1.5采用MoE架构&#xff0c;并拥有100万token上下文长度&#xff0c;相比Gemini 1.0性能大幅提升。Gemma是谷歌新推出的开源模型&#xff0c;…

精品基于SpringBoot+Vue的常规应急物资管理系统

《[含文档PPT源码等]精品基于SpringBootVue的常规应急物资管理系统[包运行成功]》该项目含有源码、文档、PPT、配套开发软件、软件安装教程、项目发布教程、包运行成功&#xff01; 软件开发环境及开发工具&#xff1a; Java——涉及技术&#xff1a; 前端使用技术&#xff…

手写redux和applyMiddleware中间件react示例

目录 一 核心代码 1.reducer 2.store.js 二 关于context API的使用 1. MyContext 2. createContext 3. ContextProvider 4. connect 三 组件验证效果 1. Todo 2. TodoList 3.TodoItem 4.TodoInput 5. App组件引入Todo组件 一 核心代码 1.reducer // 新增列表数…