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

news2024/11/15 11:35:39

目录

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;
 else
 return 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.coloradmin.cn/o/1469114.html

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

相关文章

SQL 中如何实现多表关联查询?

阅读本文之前请参阅----MySQL 数据库安装教程详解&#xff08;linux系统和windows系统&#xff09; 在SQL中&#xff0c;多表关联查询是通过使用JOIN操作来实现的&#xff0c;它允许你从两个或多个表中根据相关列的值来检索数据。以下是几种常见的JOIN类型&#xff1a; …

C语言:指针的进阶讲解

目录 1. 二级指针 1.1 二级指针是什么&#xff1f; 1.2 二级指针的作用 2. 一维数组和二维数组的本质 3. 指针数组 4. 数组指针 5. 函数指针 6. typedef的使用 7. 函数指针数组 7.1 转移表 1. 二级指针 如果了解了一级指针&#xff0c;那二级指针也是可以很好的理解…

(每日持续更新)jdk api之ObjectStreamException基础、应用、实战

博主18年的互联网软件开发经验&#xff0c;从一名程序员小白逐步成为了一名架构师&#xff0c;我想通过平台将经验分享给大家&#xff0c;因此博主每天会在各个大牛网站点赞量超高的博客等寻找该技术栈的资料结合自己的经验&#xff0c;晚上进行用心精简、整理、总结、定稿&…

联想开天昭阳N4620Z笔记本如何恢复出厂麒麟操作系统(图解)

联想开天昭阳N4620Z笔记本简单参数&#xff1a; 中央处理器&#xff1a;KX-6640MA G2 内存&#xff1a;8GB 固态硬盘&#xff1a;512GB SSD 显示器&#xff1a;14.0”FHD 电池&#xff1a;4Cell 操作系统&#xff1a;麒麟KOS中文RTM&#xff08;试用版&#xff09; 此款笔…

[深度学习]yolov9+bytetrack+pyqt5实现目标追踪

【简介】 目标追踪简介 目标追踪是计算机视觉领域中的一个热门研究方向&#xff0c;它涉及到从视频序列中实时地、准确地跟踪目标对象的位置和运动轨迹。随着深度学习技术的快速发展&#xff0c;基于深度学习的目标追踪方法逐渐展现出强大的性能。其中&#xff0c;YOLOv9&…

深入浅出:探究过完备字典矩阵

在数学和信号处理的世界里&#xff0c;我们总是在寻找表达数据的最佳方式。在这篇博文中&#xff0c;我们将探讨一种特殊的矩阵——过完备字典矩阵&#xff0c;这是线性代数和信号处理中一个非常有趣且实用的概念。 什么是过完备字典矩阵&#xff1f; 首先&#xff0c;我们先…

Github 2024-02-24 开源项目日报Top10

根据Github Trendings的统计&#xff0c;今日(2024-02-24统计)共有10个项目上榜。根据开发语言中项目的数量&#xff0c;汇总情况如下&#xff1a; 开发语言项目数量Python项目5TypeScript项目2C项目1Rust项目1JavaScript项目1HTML项目1Jupyter Notebook项目1 Python - 100天…

【黑马程序员】2、TypeScript介绍_黑马程序员前端TypeScript教程,TypeScript零基础入门到实战全套教程

课程地址&#xff1a;【黑马程序员前端TypeScript教程&#xff0c;TypeScript零基础入门到实战全套教程】 https://www.bilibili.com/video/BV14Z4y1u7pi/?share_sourcecopy_web&vd_sourceb1cb921b73fe3808550eaf2224d1c155 目录 2、TypeScript初体验 2.1 安装编译TS的工…

C# WPF 桌面应用程序使用 SQlite 数据库

我们在开发 WPF 桌面应用程序时&#xff0c;数据库存的使用是必不可少的&#xff0c;除非你的应用没有数据存储的需求&#xff0c;有了数据存储需求&#xff0c;我们就会面临使用什么样的数据库的选择问题&#xff0c;我的选择方案是&#xff0c;单机版的应用我优先选择 Sqlite…

Spring篇----第五篇

系列文章目录 文章目录 系列文章目录前言一、Spring IoC 的实现机制。二、什么是 spring bean?三、spring 提供了哪些配置方式?前言 前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站,这篇文章男女通用,看懂了就去分享…

手写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 // 新增列表数…

springboot197基于springboot的毕业设计系统的开发

简介 【毕设源码推荐 javaweb 项目】基于springbootvue 的毕业设计系统的开发 适用于计算机类毕业设计&#xff0c;课程设计参考与学习用途。仅供学习参考&#xff0c; 不得用于商业或者非法用途&#xff0c;否则&#xff0c;一切后果请用户自负。 看运行截图看 第五章 第四章 …

餐饮管理系统的设计与实现

餐饮管理系统的设计与实现 获取源码——》公主号&#xff1a;计算机专业毕设大全

金融知识分享系列之:五日线

金融知识分享系列之&#xff1a;五日线 一、股票均线二、五日线三、五日线加量能三、五日线案例四、五日线案例五、五日线案例六、五日线案例七、五日线案例八、五日线案例 一、股票均线 股票均线是一种用于平滑股票价格的指标。它是根据一段时间内的股票价格计算得出的平均值…

只需三步即可更改centos7系统语言,centos7系统语言更换,centos7系统中文互换

只需三步即可更改centos7系统语言,centos7系统语言更换,centos7系统中文互换 操作系统&#xff1a;centOS7.8 64位 ssh登录工具:FinalShell FinalShell可以点此下载 先查看系统的默认语言 locale #zh_CN 中文如何验证是中文&#xff0c;可以使用umtui来验证 umtui是一款…

5 buuctf解题

命令执行 [BJDCTF2020]EasySearch1 打开题目 尝试弱口令&#xff0c;发现没有用 扫描一下后台&#xff0c;最后用御剑扫描到了index.php.swp 访问一下得到源码 源码如下 <?phpob_start();function get_hash(){$chars ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstu…

浅析Linux设备驱动:DMA内存映射

文章目录 概述DMA与Cache一致性DMA映射类型一致性DMA映射dma_alloc_coherent 流式DMA映射dma_map_single数据同步操作dma_direct_sync_single_for_cpudma_direct_sync_single_for_device 相关参考 概述 现代计算机系统中&#xff0c;CPU访问内存需要经过Cache&#xff0c;但外…

Javaweb之SpringBootWeb案例之配置优先级的详细解析

1. 配置优先级 在我们前面的课程当中&#xff0c;我们已经讲解了SpringBoot项目当中支持的三类配置文件&#xff1a; application.properties application.yml application.yaml 在SpringBoot项目当中&#xff0c;我们要想配置一个属性&#xff0c;可以通过这三种方式当中…

Android LinearLayout 如何让子元素靠下居中对齐 center bottom

Android LinearLayout 如何让子元素靠下居中对齐 center bottom 首先你需要知道两个知识点&#xff1a; android:layout_gravity 指定的是当前元素在父元素中的位置android:gravity 指定的是当前元素子元素的排布位置 比如&#xff1a; 有这么一个布局&#xff0c;我需要让…

IDEA创建java项目

1. 创建单个项目 1.1 点击New Project 刚安装好会进入下面的创建页面&#xff0c;选择直接New Project创建新项目。 如果后续打开IDEA&#xff0c;并且上次的项目存在&#xff0c;则会打默认开上次的项目&#xff0c;此时可以选择File -> New->Project创建新项目。 …