【数据结构和算法初阶(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://www.mzph.cn/news/710103.shtml

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

相关文章

Vue3 学习笔记(Day5)

「写在前面」 本文为尚硅谷禹神 Vue3 教程的学习笔记。本着自己学习、分享他人的态度&#xff0c;分享学习笔记&#xff0c;希望能对大家有所帮助。推荐先按顺序阅读往期内容&#xff1a; 1. Vue3 学习笔记&#xff08;Day1&#xff09; 2. Vue3 学习笔记&#xff08;Day2&…

提升培训考试效率的系统设计策略

随着培训的重要性日益凸显&#xff0c;如何提升培训考试系统的效率成为了许多组织和机构关注的焦点。 一、设计自适应的考试界面 培训考试系统的界面应该能够自适应不同的屏幕尺寸和设备类型&#xff0c;如电脑、平板电脑和手机。采用响应式设计技术&#xff0c;确保考生在不同…

Leetcode115. 不同的子序列 -代码随想录

题目&#xff1a; 代码(首刷看解析 2024年2月29日&#xff09;&#xff1a; 不晓得这种超过int和long的测试案例是用来恶心谁的&#xff0c;用DP都没机会取模 class Solution { public:// 动态规划const int MOD 1000000007;int numDistinct(string s, string t) {long n s.…

市场复盘总结 20240229

仅用于记录当天的市场情况&#xff0c;用于统计交易策略的适用情况&#xff0c;以便程序回测 短线核心&#xff1a;不参与任何级别的调整&#xff0c;采用龙空龙模式 一支股票 10%的时候可以操作&#xff0c; 90%的时间适合空仓等待 二进三&#xff1a; 进级率中 60% 最常用…

06|Mysql内部组件结构

1. 连接器 客户端要向mysql发起通信都必须先跟Server端建立通信连接&#xff0c;而建立连接的工作就是由连接器完成的 mysql -h host[数据库地址] -u root[用户] -p root[密码] -P 3306连接步骤: 1、如果用户名或密码不对&#xff0c;你就会收到一个"Access denied for us…

【转载】Windows 11 任务栏位置调整

更改注册表&#xff08;部分win11版本有效&#xff09; Win R快捷键打开「运行」——执行regedit命令打开「注册表编辑器」进入路径&#xff1a; 计算机\HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\StuckRects3 修改Settings这个二进制的第 2 行…

前端同时传递文件数据+非文件数据,前后端解决方案

之前录制视频《文件上传组件》的时候有位观众提了个问题&#xff0c;如果我没有理解错的话&#xff0c;应该就是前后同时传递文件数据 非文件数据&#xff0c;前后端数据该如何接收&#xff0c;这里我给出我自己的解决方案 tip:下文在编写前端代码的时候&#xff0c;用到了这篇…

MYSQL安装及卸载

目录 一、下载 二、解压 三、配置 1. 添加环境变量 2. 初始化MySQL 3. 注册MySQL服务 4. 启动MySQL服务 5. 修改默认账户密码 四、登录MySQL 五、卸载MySQL 一、下载 点开下面的链接&#xff1a;MySQL :: Download MySQL Community Server 点击Download 就可以下载对…

Linux-基础命令(黑马学习笔记)

Linux的目录结构 Linux的目录结构 Linux的目录结构是一个树形结构 Windows系统可以拥有多个盘符&#xff0c;如C盘、D盘、E盘 Linux没有盘符这个概念&#xff0c;只有一个根目录 /&#xff0c;所有文件都在它下面 Linux路径的描述方式 ● 在Linux系统中&#xff0c;路径之…

【AI绘画·24年1月最新】Stable Diffusion整合包安装!解压即用--秋葉aaaki 大佬的作品,试用

前言 Stable Diffusion 之前费老大的劲部署安装&#xff0c;解决报错。搞完之后&#xff0c;突然发现有个现成集成包可以用&#xff0c;真是效率高到不行&#xff0c;今天搞下来试试 我电脑配置&#xff1a; CPU: 12th Gen Intel Core™ i7-12700F 2.10 GHz 内存32G&#xff0…

腾讯云又双叕降价,云服务器配置优惠价格表2024新版报价

腾讯云服务器多少钱一年&#xff1f;62元一年起&#xff0c;2核2G3M配置&#xff0c;腾讯云2核4G5M轻量应用服务器218元一年、756元3年&#xff0c;4核16G12M服务器32元1个月、312元一年&#xff0c;8核32G22M服务器115元1个月、345元3个月&#xff0c;腾讯云服务器网txyfwq.co…

nginx使用详解--缓存使用

Nginx 是一个功能强大的 Web 服务器和反向代理服务器&#xff0c;它可以用于实现静态内容的缓存&#xff0c;缓存可以分为客户端缓存和服务端缓存。 客户端缓存 客户端缓存指的是浏览器缓存, 浏览器缓存是最快的缓存, 因为它直接从本地获取(但有可能需要发送一个协商缓存的请…

CAPL编程学习笔记--关于on 事件的详细解释

CAPL编程是比较有特色的一种面向通讯的编程语言。 1&#xff1a;on XXX类型&#xff08;即事件类型&#xff09; 维克多的官方文档对CAPL的描述是一门类C语言&#xff0c;说白了它也是用C写出来的。我们看on&#xff08;注意都是小写&#xff09;事件的代码结构 on * { }&…

设备管理系统解决方案

软件资料获取&#xff1a;软件项目开发全套文档下载_软件项目文档-CSDN博客 1.系统概述 1.1.需求描述 建立设备信息库&#xff0c;对设备相关档案的登录、整理。通过建立完善的设备档案&#xff0c;将设备的各类原始信息进行信息化管理&#xff0c;使设备档案查询工作方便快…

一图总结:华为销售体系(铁三角组织LTC流程)

《华为铁三角工作法》阅读了多遍&#xff0c;花了些时间整理了一张图对本书的框架性总结&#xff0c;从流程&#xff08;LTC&#xff09;、组织&#xff08;铁三角&#xff09;、激励和管理三个大方面概览华为销售体系。 核心是一靠流程&#xff0c;二靠团队&#xff0c;而前提…

阿里云服务器大降价20%,简单拥有五年三台2h4gECS,组建公网集群

要在阿里云ECS上组建集群&#xff0c;您可以按照以下步骤进行操作&#xff1a; 创建ECS实例&#xff1a;登录阿里云控制台&#xff0c;选择ECS实例&#xff0c;点击“创建实例”按钮。根据实际需求选择实例的配置参数&#xff0c;例如实例规格、操作系统、网络等。根据需要选择…

如何使用视频号下载提取器提取视频,推荐2种方法使用!

视频号下载提取视频号视频&#xff0c;推荐大家2个方法&#xff01; 前者简单&#xff0c;后者较为复杂&#xff0c;不过都可以提取视频号视频&#xff0c;大家可根据实际情况来使用。 01 视频号下载工具提取器&#xff1f; 1&#xff1a;通过搜一搜的这款搜索引擎找到自己…

【InternLM 实战营笔记】大模型评测

随着人工智能技术的快速发展&#xff0c; 大规模预训练自然语言模型成为了研究热点和关注焦点。OpenAI于2018年提出了第一代GPT模型&#xff0c;开辟了自然语言模型生成式预训练的路线。沿着这条路线&#xff0c;随后又陆续发布了GPT-2和GPT-3模型。与此同时&#xff0c;谷歌也…

【Go语言】Go语言中的数组

Go语言中的数组 1 数组的初始化和定义 在 Go 语言中&#xff0c;数组是固定长度的、同一类型的数据集合。数组中包含的每个数据项被称为数组元素&#xff0c;一个数组包含的元素个数被称为数组的长度。 在 Go 语言中&#xff0c;你可以通过 [] 来标识数组类型&#xff0c;但…

嵌入式学习-qt-Day4

嵌入式学习-qt-Day4 一、思维导图 二、作业 1.设计一个界面&#xff1a;显示系统时间&#xff1b;可以设置闹钟&#xff0c;在设置的时间到达后&#xff0c;显示五次字符串&#xff0c;并且语音播报。 Wight.h #ifndef WIDGET_H #define WIDGET_H #include <QWidget>…