【数据结构和算法初阶(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&…

《Docker Compose深度解析:多环境配置、服务扩展与发现》

《Docker Compose深度解析&#xff1a;多环境配置、服务扩展与发现》 1. 引言 Docker Compose 是 Docker 官方提供的用于定义和运行多容器 Docker 应用程序的工具。在上一篇中&#xff0c;我们已经介绍了 Docker Compose 的基本概念和用法。本篇将进一步深入&#xff0c;探讨…

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

随着培训的重要性日益凸显&#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.…

折线图sns.lineplot()

折线图sns.lineplot 介绍代码介绍 season官网 sns.lineplot() 是 Seaborn 库中用于绘制折线图的函数。这个函数可以可视化数据集中不同变量之间的关系,特别适合展示随时间变化的数据趋势。 下面是 sns.lineplot() 函数的一般用法和一些常用参数: sns.lineplot(x=‘x轴数据…

Mysql8.0 数据库表的基本操作

1&#xff0c;创建表 CREATE TABLE 表名称( 字段名1 数据类型&#xff0c; 字段名2 数据类型&#xff0c; 字段名3 数据类型&#xff0c; ) mysql> create table tb_student(-> sid INT,-> sname VARCHAR(20)-> ); Query OK, 0 rows affected (1.19 sec)mysql> …

市场复盘总结 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…

2024年华为OD机试真题-高效货运-Python-OD统一考试(C卷)

题目描述&#xff1a; 1.老李是货运公司承运人&#xff0c;老李的货车额定载货重量为wt 2.现有两种货物&#xff0c;货物A单件重量为wa&#xff0c;单件运费利润为pa&#xff0c;货物B单件重量为wb&#xff0c;单件运费利润为pb 3.老李每次发车时载货总重量刚好为货车额定载货重…

ChatGPT第四讲

ChatGPT的回答从哪里来&#xff1f; ChatGPT回答问题时通常比问题本身更长&#xff0c;这是因为它需要通过补充额外的信息来提供完整的答案。它的回答来源于对现有信息的抽取和整合&#xff0c;那么具体是怎么进行抽取和整合的呢&#xff0c;下面我们带着这个疑问来详细讨论一下…

【转载】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;用到了这篇…

使用WebAssembly提升Web应用性能

文章目录 使用WebAssembly提升Web应用性能引言一、什么是WebAssembly1. WebAssembly的定义2. WebAssembly的目标和用途 二、WebAssembly与JavaScript的比较1. 执行速度2. 类型检查3. 内存管理4. 适用场景 三、WebAssembly的优势1. 提升性能2. 安全性3. 可移植性4. 集成性 四、如…

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;路径之…

真实与虚幻的边界:算法备案的重要不言而喻

曾经需要耗费大量时间和精力的人脸生成、替换&#xff0c;现在只需通过先进的深度合成算法便能轻松实现&#xff1b;而以往难以触及的人物属性编辑和操控&#xff0c;如今也已成为创作者手中的得力工具。深度合成技术在图像和视频编辑领域的应用&#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…

Vuex使用之Vue3

1. 搭建Vuex环境 创建文件&#xff1a;src/store/index.js //引入Vueximport { createStore } from vuex//准备actions对象——响应组件中用户的动作const actions {jia(context, value) {context.commit(JIA, value)}}//准备mutations对象——修改state中的数据const mutatio…

C++高级面试题:解释 C++ 中的静态断言(Static Assertion)

C高级面试题&#xff1a;解释 C 中的静态断言&#xff08;Static Assertion&#xff09;在C中&#xff0c;静态断言&#xff08;Static Assertion&#xff09;是一种在编译时对条件进行检查的机制。它类似于传统的 assert 宏&#xff0c;但是静态断言是在编译时进行检查&#x…

腾讯云又双叕降价,云服务器配置优惠价格表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…