时间复杂度介绍及其计算

news2025/4/5 14:44:45

时间复杂度

1.算法效率

如何衡量一个算法的好坏呢?看这段代码:

long long Fib(int N)
{
    if(N < 3)
        return 1;
    return Fib(N-1) + Fib(N-2);
}

这是斐波那契数列的递归代码,非常简洁,那么这就一定说明它好吗?答案显而易见。

2.算法的复杂度

算法在编写成可执行程序后,运行时需要耗费时间资源和空间(内存)资源 。因此衡量一个算法的好坏,一般是从时间空间两个维度来衡量的,即时间复杂度空间复杂度

==时间复杂度主要衡量一个算法的运行快慢,而空间复杂度主要衡量一个算法运行所需要的额外空间。==在计算机发展的早期,计算机的存储容量很小。所以对空间复杂度很是在乎。但是经过计算机行业的迅速发展,计算机的存储容量已经达到了很高的程度。所以我们如今已经不需要再特别关注一个算法的空间复杂度。

3.算法的时间复杂度

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

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

Test01

//计算Func1中的++count语句执行了多少次?
void Func1(int N)
{
	int count = 0;
	for (int i = 0; i < N; ++i)
	{
		for (int j = 0; j < N; ++j)
		{
			++count;
		}
	}
	for (int k = 0; k < 2 * N; ++k)
	{
		++count;
	}
	int M = 10;
	while (M--)
	{
		++count;
	}
    printf("%d\n", count);
}

count++语句的执行次数:F(N) = N^2 + 2N +10

  • N=10时:F(N) = 130
  • N=100,F(N) = 10210
  • n = 1000,F(N) = 1002010

实际中我们计算时间复杂度时,我们其实并不一定要计算精确的执行次数,而只需要大概执行次数,那么这里我们使用大O的渐进表示法

大O的渐进表示法

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

大O阶的推导方法:

  1. 若最高阶项不存在,则用常数1取代运行时间中所有的常数部分。
  2. 在修改后的运行函数次数中,只保留最高阶项。
  3. 如果最高阶项存在且不是1,则去除与这个项目相乘的常数。得到的结果就是大O阶。

那么在Test01中,使用大O表示法之后:时间复杂度为 O(N^2)

大O表示法实际上是去掉了那些对结果影响不大的项,简洁明了的表示出了执行次数。

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

最坏情况:任意输入规模的最大运行次数(上界)

平均情况:任意输入规模的期望运行次数

最好情况:任意输入规模的最小运行次数(下界)

例如:在一个不重复长度为N的数组中寻找一个值为x的下标。

最好情况:1次找到

最坏情况:N次找到

平均情况:N/2次找到

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

Test02:

//计算Func2中的++count语句执行了多少次?
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);
}

Test02的时间复杂度用大O的渐进表示法就为:O(N)。

原因解释:这里的++count语句严格计算的话:共执行了:2N+10次,但是根据大O的渐进表示规则:最高阶项是2N,这里将2去掉,剩下的部分就是时间复杂度。

Test03:

//计算Func3中的++count语句执行了多少次?
void Func3(int N, int M)
{
	int count = 0;
	for (int k = 0; k < M; ++k)
	{
		++count;
	}
	for (int k = 0; k < N; ++k)
	{
		++count;
	}
	printf("%d\n", count);
}

Test03的时间复杂度为:

  • 若N与M接近:则O(N)或者O(M)都可以。(当N与M接近时,++count语句的执行次数接近于2N或2M,再去掉常数部分,即可得到答案)
  • 若M>>N,则为O(M)。(当M>>N时,N就可忽略不计。)
  • 若N>>M,则为O(N)。

Test04:

void Func4(int N)
{
	int count = 0;
	for (int k = 0; k < 100; ++k)
	{
		++count;
	}
	printf("%d\n", count);
}

Test04的时间复杂度为:O(1)

解释:无论其他变量如何变化,++count语句始终会执行100次,始终为常数次,时间复杂度用大O的渐进表示法则为:O(1)。

Test05:

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

strchr函数的功能是:在str字符串中寻找character字符的下标,若不存在则返回-1。这个函数查找的可以分为最好和最坏两种情况:

  • 最好情况:1次就找到
  • 最坏情况:搜完整个字符串才找到或者不存在。

而在大O的渐进表示法中,一般表示最坏的情况,假设字符串的长度为N,那么strchr函数的时间复杂度就是O(N)了。

Test06:

// 计算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;
	}
}

Test06的时间复杂度为:O(N2).

原因:冒泡排序是由两层循环嵌套实现的,数组长度为n。假设最坏情况:数组中的元素由大到小排列。外层循环要执行n-1次,内层循环会随着外层循环的增加而减少,所以整体的执行次数为:(N-1) + (N-2) + (N-3) + (N-4) + ……+1,这是一串等差数列,最高阶项就是N2,所以时间复杂度也就是O(N2)。

Test07:

// 计算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;
}

Test07的时间复杂度是:O(logN),(以2为底,N的对数)。

解释:考虑最差情况:要寻找的数在边界上,即二分区间之内只有一个数。一个长度为N的数组,要执行多少次二分,才能让二分区间只有一个数字?答案是logN次。所以时间复杂度就为O(logN)。

二分查找的效率是非常高的,但是由于被二分的数组必须有序,那么二分查找才能有效执行,这就导致了二分查找是不经常使用的。

注意:logN这种写法,如无特殊说明,底数都是2.

Test08:

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

Test08的时间复杂度为:O(N)

解释:Fac是一个用于计算阶层的函数,这里的递归次数取决于参数N。递归调用的复杂度就是多次调用次数的累加,而在每一次的递归调用中,语句的执行次数为常数次,也就是O(1),这里的时间复杂度就是函数的调用次数了,也就是O(N)。

Test09:

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

Test09的时间复杂度为:O(N2)

解释:函数每调用一次,就会再次向下调用两次这个函数,直到一方调用到F(2)或F(1)为止。如图所示,如图的每一层,函数调用次数会随着函数调用深度而增加,由20 到 21 ,22,直到2 N-1 ,再对这些调用次数进行相加,再使用大O的渐进表示法,最后就能得到时间复杂度。调用到最后,会形成一个类似等腰三角形的形状。灰色部分是无函数调用,白色部分是函数调用。

> [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bn2iJWH4-1690516905323)(C:\Users\30539\AppData\Roaming\Typora\typora-user-images\image-20230728115653304.png)]

> [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cf44B8WL-1690516905324)(C:\Users\30539\AppData\Roaming\Typora\typora-user-images\image-20230728115613047.png)]

4.完结

时间复杂度的内容就到这里啦,若有不足,欢迎评论区指正,下期见!

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

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

相关文章

智能家居是否可与ChatGPT深度融合?

​ ChatGPT自2022年面世以来&#xff0c;已为亿万网民提供智能问答服务。然而我们是否曾想到&#xff0c;这一人工智能驱动的聊天机器人&#xff0c;是否可为智能家居赋能? 要实现ChatGPT与智能家居设备之间的无缝对话&#xff0c;单单依靠一台终端是远远不够的。ChatGPT必须…

Java基础_多线程

Java基础_多线程 什么是多线程并发, 并行多线程的实现方式继承Thread类实现Runnable接口实现Callable接口和Future接口实现方式对比 常见的成员方法常见方法进程的优先级守护线程礼让线程插入线程 线程安全线程的生命周期售票模拟同步代码块同步方法lock锁 死锁 生产者和消费者…

【C语言】通讯录1.0 (静态版)

前言 通讯录是一种记录联系人信息的工具&#xff0c;包括姓名、电话号码、电子邮件地址、住址等。 此通讯录是基于自定义类型的基础上进行制作&#xff0c;通讯录&#xff08;静态版&#xff09;&#xff0c;后期会进行通讯录的更新 ****** 有需要源代码&#xff0c;见文章末尾…

大于号在python中怎么打,python大于等于怎么写

大家好&#xff0c;小编为大家解答python中大于并小于一个数代码的问题。很多人还不知道python中大于等于且小于等于&#xff0c;现在让我们一起来看看吧&#xff01; 1、python 中不等于怎么表示 #!/usr/bin/python a1 b2 if ab: print "a 等于 b" if a!b: print &…

ArcGIS Pro 制作一张立体地形图

在各位关掉文章之前,先把成果贴上来 下面开始操作步骤贴图,这个真的很简单,没有什么复杂的软件联动和操作 这是哥斯达黎加部分区域的30mDEM,数据链接我放在最后。 首先,找到工具【栅格函数】—【统计分析】,选择下载好的栅格,领域设置行列数都改为6,点击创建新图层。然…

从官网认识 JDK,JRE,JVM 三者的关系

点击下方关注我&#xff0c;然后右上角点击...“设为星标”&#xff0c;就能第一时间收到更新推送啦~~~ JVM 是一些大厂面试必问点&#xff0c;要想解决 OOM、性能调优方面的问题&#xff0c;掌握 JVM 知识必不可少&#xff0c;从今天开始&#xff0c;将为大家介绍 JVM 的常用知…

ShardingSphere Pipeline 兼容 MySQL 时间类型 解读

背景 ShardingSphere 在日常中&#xff0c;开发者经常会用到时间类型&#xff0c;如果熟悉的时间类型涉及到了时区、精度&#xff0c;哪种时间类型更合适&#xff1f;JDBC 驱动中又有哪些注意事项&#xff1f;因此&#xff0c;对数据库时间类型有更深入的了解&#xff0c;有助于…

android framework车载桌面CarLauncher的TaskView详细源码分析

1、构建相关的TaskView&#xff0c;装载到对应的ViewGroup b站免费视频教程讲解&#xff1a; https://www.bilibili.com/video/BV1wj411o7A9/ //packages/apps/Car/Launcher/src/com/android/car/carlauncher/CarLauncher.java void onCreate() { //ignoresetContentView(R.…

管理类联考——写作——记忆篇——论证有效性分析——析错口诀

析错口诀 概念不明确&#xff0c;我就说它概念模糊&#xff0c;并做不利它的解释。【有概念模糊之嫌&#xff0c;A是理解1&#xff1f;还是理解2&#xff1f;】概念有变换&#xff0c;我就说它混淆概念&#xff0c;并指出混淆的环节。&#xff08;概念推概念&#xff0c;我就看…

类方法(成员方法)与构造方法的区别与联系

&#xff08;粗略理解为&#xff1a;除了构造方法以外的所有方法都是成员方法&#xff09; 区别&#xff1a; 构造方法在创建对象时用new调用&#xff0c;是为了给对象的数据进行初始化&#xff0c;没有返回值。 成员方法是实现对类中成员变量的操作&#xff0c;提供某些功能…

【图论】kruskal算法

一.介绍 Kruskal&#xff08;克鲁斯卡尔&#xff09;算法是一种用于解决最小生成树问题的贪心算法。最小生成树是指在一个连通无向图中&#xff0c;选择一棵包含所有顶点且边权重之和最小的树。 下面是Kruskal算法的基本步骤&#xff1a; 将图中的所有边按照权重从小到大进行…

JUC并发工具类

一、ReentrantLock 特点&#xff1a;独占、可重入、公平/非公平、可中断、支持多个条件变量 1、常用api ReentrantLock实现了Lock接口&#xff0c;Lock类规范定义了如下方法 lock()&#xff1a;获取锁&#xff0c;调用该方法的线程会获取锁&#xff0c;当锁获得后&#xff0…

想做上位机,学C#还是QT?

学习C#还是Qt&#xff0c;取决于你的具体需求和偏好。 如果你计划开发跨平台的桌面应用程序&#xff0c;并且希望使用一种更轻量级、直观的界面框架&#xff0c;那么Qt可能是一个不错的选择。Qt是一个功能丰富且成熟的跨平台框架&#xff0c;支持多种开发语言&#xff08;包括…

第J2周:ResNet50V2算法实战与解析

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f366; 参考文章&#xff1a;365天深度学习训练营-第J2周&#xff1a;ResNet50V2算法实战与解析&#x1f356; 原作者&#xff1a;K同学啊|接辅导、项目定制 目录 一、论文解读1. ResNetV2结构与…

【需求响应】一种新的需求响应机制DR-VCG研究

目录 1 主要内容 2 部分代码 3 程序结果 4 程序链接 1 主要内容 该程序对应文章《Contract Design for Energy Demand Response》&#xff0c;电力系统需求响应&#xff08;DR&#xff09;用来调节用户对电能的需求&#xff0c;即在预测的需求高于电能供应时&#xff0c;希…

VS Code调试Darknet

一、安装插件 二、连接服务器 三、调试darknet工程 {"version": "2.0.0","options": {"cwd": "${workspaceFolder}"},"tasks": [{"label": "clean","type": "shell",&qu…

数据结构之动态顺序表(附带完整程序)

&#x1f388;基本概念 &#x1f308;一.线性表、顺序表的定义 ☀️&#xff08;1&#xff09;线性表&#xff1a; 是n个具有相同特性的数据元素的有限序列。线性表在逻辑上是线性结构&#xff0c;但在物理上存储时&#xff0c;通常以数组和链式结构的形式存储。 ☀️&…

C# 关于使用newlife包将webapi接口寄宿于一个控制台程序、winform程序、wpf程序运行

C# 关于使用newlife包将webapi接口寄宿于一个控制台程序、winform程序、wpf程序运行 安装newlife包 Program的Main()函数源码 using ConsoleApp3; using NewLife.Log;var server new NewLife.Http.HttpServer {Port 8080,Log XTrace.Log,SessionLog XTrace.Log }; serv…

hcip——ospf综合

要求 1. 搭建toop 2.地址规划 协议范围路由器地址 RIP 172.16.0.0 17 R12 loop0&#xff1a;172.16.0.0 24 loop1&#xff1a;172.16.1.0 24 OSPF 172.16.128.0 17 area1 172.16.144.0 20 R1 g0:172.16.144.1 24 loop0:172.16.145.1 24 R2 g0:172.16.144.2 24 loop:172…

iOS - Apple开发者账户添加新测试设备

获取UUID 首先将设备连接XCode&#xff0c;打开Window -> Devices and Simulators&#xff0c;通过下方位置查看 之后登录(苹果开发者网站)[https://developer.apple.com/account/] &#xff0c;点击设备 点击加号添加新设备 填写信息之后点击Continue&#xff0c;并一路继续…