数据结构与算法(C语言版)P1---算法效率

news2024/11/15 4:38:12

算法的效率:算法的时间复杂度和空间复杂度

【本节目标】

  • 1.算法效率
  • 2.时间复杂度
  • 3.空间复杂度
  • 4.常见时间复杂度以及复杂oj练习

1、算法效率

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

如何衡量一个算法的好坏呢?比如斐波那契数列:

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

斐波那契数列的递归实现方式非常简洁,但简洁一定好吗?那该如何衡量其好与坏呢?

1.2、算法的复杂度

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

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

2、时间复杂度

2.1、时间复杂度和概念

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

算法中的基本操作的执行次数,为算法的时间复杂度。

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

那下面举个例子:

//请计算一下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 = 0;
	while (M--)
	{
		++count;
	}
	printf("%d\n", count);
}

我们可以得出:时间复杂度的函数式:F(N) = N*N+2*N+10。

那这个时候我们赋予N具体的值:

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

我们想一下,当N越大,其实F(N)的值,就和__N*N__这个式子关系越大,后面的__2*N+10__所带来的值对整体的F(N)的值影响不大。

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

2.2、大O的渐进表示法

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

推导大O阶方法:

1、用常数1取代运行时间中的所有加法常数。(只要有常数项就用1去取代)

2、在修改后的运行次数函数中,只保留最高阶项。

3、如果最高阶项存在且不是1,则去除这个项目相乘的常数,得到的结果就是大O阶。

使用大O阶的渐进表示法以后,Func1的时间复杂度为:O(N^2)。

  • N=10,F(N)=100。
  • N=100,F(N)=10000。
  • N=1000,F(N)=1000000。

通过上面我们会发现大O的渐进表示法去掉了那些对结果影响不大的项,简洁明了的表示除了执行次数。

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

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

例如:在一个长度的N的数组中搜索一个数据x。

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

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

2.3、常见时间复杂度计算练习

下面我们多看几个案例,来练习一下大O阶:

2.3.1、单层循环时间复杂度计算

案例1:

//计算Func2的时间复杂度
void Func2(int N)
{
	int count = 0;

	for (int k = 0; k < 2 * N; ++k)
	{
		++count;
	}

	int M = 0;
	while (M--)
	{
		++count;
	}
	printf("%d\n", count);
}

大O阶表示法:O(N) = N

分析:F(N) = 2*N+10

随着N的数值越来越大,+10的这一项对F(N)的值影响不大,所以忽略。

然后高阶项是2*N,N的系数不是1,所以把N前面的系数省略。

所以用大O阶法表示就是O(N) = N

2.3.2、嵌套循环时间复杂度计算

//请计算一下Func1中++count语句总共运行了多少次?
void Func1(int N)
{
	int count = 0;
	for (int i = 0; i < N; ++i)
	{
		for (int j = 0; j < N; ++j)
		{
			++count;
		}
	}
}

大O阶表示法:O(N) = N^2

2.3.3、双重循环时间复杂度计算

案例2:

//计算Func3的时间复杂度
void Func3(int N,int M)
{
	int count = 0;
	for (int i = 0; i < N; ++i)
	{
		++count;
	}

	for (int i = 0; i < M; ++i)
	{
		++count;
	}
 printf("%d\n",count);
}

大O阶表示法:分情况

分析:

1、如果没有说明M和N的大小关系:

  • O(M+N)

2、如果说明了M和N的大小关系:

  • M远远大于N:O(M)
  • N远远大于M:O(N)
  • M差不多相等于N:O(M)或O(N)

2.3.4、常数循环的时间复杂度

案例3:

//计算Func4的时间复杂度
void Func4(int N,int M)
{
	int count = 0;
	for (int k = 0; k < 100; ++k)
	{
		++count;
	}
	printf("%d\n", count);
}
大O阶表示法:O(N) = O(1)

分析:由上面的大O阶规则:__用常数1取代运行时间中的所有加法常数。(只要有常数项就用1去取代)__来说。

此大O阶表示:O(1)。

【扩展】:在做题时,题目会要求:把这个题的时间复杂度优化到O(1)。那这意思并不是只能运算一次。而是说需要运算常数次。

2.3.5、strchar的时间复杂度

案例4:

//计算strchar的时间复杂度
//【补充】strchar是个库函数,用于查找,搜索相匹配的字符串
const char* strchar (const char* str,int character);

比如现在有个字符串:"hello world"

大O阶表示法:分情况

分如下几种情况:

  • 假设查找的是h,大O阶表示法:O(1)。
  • 假设查找的是w,大O阶表示法:O(N/2)。
  • 假设查找的是d,大O阶表示法:O(N)。

那在实际情况中,当一个算法随着输入的不同,时间复杂度不同,时间复杂度一律做悲观预期处理,看最坏的情况,所以上面的大O阶表示法就是O(N)。

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

案例5:

冒泡排序的核心思想是:相邻两元素之间进行比较。

如果有N个元素,需要比较N-1次,第一次,比较N-1次,第二次比较N-2次…最后一次比较1次即可。

所以是个等差数列,[项数*(a1+an)] / 2,所以就等于[n*(n-1)]/2。

所以O(N) = N^2。

2.3.7、二分查找时间复杂度的计算

在学习C语言中,学习了二分查找算法,它的底层数学知识就是log2 N。

比如有8=2**3个数据,进行二分查找。

转化为数学知识就是:log2 8 = 3。所以说悲观期望,二分查找最多执行3次。

所以说使用大O阶法表示,也分三种情况:

  • 最坏情况:O(log2 N)次找到。
  • 平均情况:O((log2 N)/2)找到。
  • 最好情况:O(1)次找到。

所以综上,使用悲观期望,二分查找的时间复杂度大O阶法表示为O(log2 N)。

这里补充一点,二分查找是个很nb的算法,数据越多它越nb,但是二分查找有个缺陷就是,只能针对有序数列进行查找,如果想要使用二分,前面是需要先排序,但是排序是很消耗性能的。

所以说以后要学:

  • 树—>二叉树—>搜索二叉树—>平衡二叉树—>AVL Tree/RB Tree。

  • 哈希表。

  • B树系列。

2.3.8、阶乘的时间复杂度

实例7:

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

分析:Fac一共调用了N次。

所以大O阶表示法:O(N)。

2.3.9、斐波那契的时间复杂度

实例8:

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

大O阶表示法:O(N) = 2^N

递归算法:递归次数*每次递归调用的次数。

  • 递归次数:每一次只执行一次Fib,所以是O(1)。
  • 每次递归调用的次数:计算Fib(N),需要调用Fib(N-1)+FIB(N-2)。就类似的这个过程。

如下图分析:

在这里插入图片描述

可以发现这每一行的规律,它们是等比数列,然后减去省略的一部分x。

所以Fib(N) = 20+21+22+…+2(N-1)-x。

等比数列之和为:a1(1-q^n)/(1-q)

所以20+21+22+…+2(N-1) = (2^N) - 1。

又因为当N无限大时,减去x相当于没减。

所以最终Fib(N) = (2^N) - 1。

那使用大O阶表示为:O(N) = 2^N。

3、空间复杂度计算

空间复杂度也是一个数学表达式,是对一个算法在运行过程中__临时额外占用存储空间大小的量度。__

空间复杂度不是程序占用了多少bytes的空间,因为这个也没多大意义,所以__空间复杂度算的是变量的个数。__

空间复杂度计算规则基本跟时间复杂度类似,也是用__大O渐进表示法。__

注意:函数运行时所需要的栈空间(存储参数、局部变量、一些寄存器信息等)在编译期间已经确定好了,因此空间复杂度主要通过函数运行时显示申请的额外空间来确定。

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

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

//空间复杂度:O(1)。

分析:这里就看额外创建几个变量即可。

  • end变量是额外创建的。
  • i变量是额外创建的(i在一套冒泡排序完之后,++i,i还是占用同样的空间,所以整体下来i始终是一个变量)。
  • exchange变量是额外创建的。

所以N=3是常数,所以冒泡排序的空间复杂度用大O阶表示就是:O(1)。

3.2、计算斐波那契第n项的数组的空间复杂度

long long* Fibonacci(size_t n)
{
	if (n == 0)
		return NULL;
	long long* fibArray = (long long*)malloc(n + 1 * sizeof(long long));
	fibArray[0] = 0;
	fibArray[1] = 1;
	for (int i = 2; i <= n; ++i)
	{
		fibArray[i] = fibArray[i - 1] + fibArray[i - 2];
	}
	return fibArray;
}

//空间复杂度:O(N)

分析:这是计算斐波那契第N项个数的数组,所以需要计算N次。

  • fibArray计算N次。
  • 变量i也是额外创建的。

当N越来越大时,变量i可以忽略不计了。

所以此大O阶表示为:O(N)。

3.3、阶乘的空间复杂度计算

long long Fac(size_t N)
{
	if (N == 1)
		return 1;
	return Fac(N - 1) * N;
}

//空间复杂度:O(N)

分析:比如N=4,那就是4*3*2*1。

N=4时需要额外的空间。

N=3时需要额外的空间。

N=2时需要额外的空间。

N=1时需要额外的空间。

一共是N个栈帧的创建。

所以大O阶表示:O(N)。

3.4、斐波那契数列的空间复杂度计算

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

大O阶表示法:O(N)

斐波那契数列按理说时间复杂度和空间复杂度是一样的,都应该是O(N) = 2^N。

但是这里为什么空间复杂度时O(N)呢?如果空间复杂度是2^N,那么会栈溢出。

这是因为:

  • 空间是可以重复利用,不累计的。
  • 时间是一去不复返,累计的。

那这里空间是如何重复利用呢?如下图所示:

在这里插入图片描述

我们将具体的斐波那契数列执行过程细分来看其实是这样的:

在这里插入图片描述

这样其它的会重复使用这个空间,这里使用了N个空间,所以最多建立N个栈帧。

所以说我们也可以感知到了斐波那契数列的空间复杂度就是O(N)。

4、常见的复杂度对比

5201314O(1)常数阶
3n+4O(N)线性阶
3n^2+4n+5O(N^2)平方阶
3log(2)n+4O(longn)对数阶
2n+3nlog(2)n+14O(nlogn)nlogn阶
n3+2n2+4n+6O(N^3)立方阶
2^nO(2^N)指数阶

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

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

相关文章

面试官:我们深入聊聊Java虚拟机吧

哈喽&#xff01;大家好&#xff0c;我是奇哥&#xff0c;一位专门给面试官添堵的职业面试员 文章持续更新&#xff0c;可以微信搜索【小奇JAVA面试】第一时间阅读&#xff0c;回复【资料】更有我为大家准备的福利哟&#xff01; 文章目录 前言面试Java虚拟机内存模型垃圾收集器…

2023年CRM系统成功落地的5个标准

企业做CRM选型时都在思考投入产出比&#xff0c;花费上万元、甚至几十万元和几个月的时间购买和实施CRM&#xff0c;能否为公司带来降本增效的变革&#xff1f;CRM上线后&#xff0c;需要多长时间才能真切地看到效果&#xff1f;评估CRM的使用效果&#xff0c;需要每个企业制定…

【C++STL基础入门】queue基础使用

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、队列是什么二、queue的基础使用2.1 queue的构造函数2.2 queue的属性函数 总结 前言 C标准模板库&#xff08;STL&#xff09;提供了一系列强大的容器和算法…

vi/vim编辑器

vi和vim区别 vi 和 vim 是常见的文本编辑器&#xff0c;以下是它们之间的区别&#xff1a; 功能和特性&#xff1a; vi 是最早的版本&#xff0c;是在早期Unix系统中广泛使用的编辑器。vi 相对较简单&#xff0c;功能主要集中在基本的文本编辑操作上&#xff0c;如插入、删除、…

MyBatis: 配置文件解析流程

XmlConfigurationBuilder类来解析配置文件。 调用了build方法&#xff0c;其代码如下&#xff1a; 其中通过parse方法返回一个Configuration对象&#xff0c;在传递给build方法。 parse方法代码如下&#xff1a; 其中调用了parseConfiguration方法&#xff0c; 可以看到其中…

hive 静态分区与动态分区(笔记)

目录 前言&#xff1a; 静态分区&#xff1a; 1.创建分区 2.删除分区 3.在分区中插入数据 4.查看分区表数据 动态分区 &#xff1a; 2.查看v表源数据 3.以emp_name为动态字段数据抽取到employee表 总结 前言&#xff1a; Hive中的分区就是把一张大表的数据按照业务需要…

华为云云耀云服务器L实例评测|华为云上安装监控服务Prometheus三件套安装

文章目录 华为云云耀云服务器L实例评测&#xff5c;华为云上试用监控服务Prometheus一、监控服务Prometheus三件套介绍二、华为云主机准备三、Prometheus安装四、Grafana安装五、alertmanager安装六、三个服务的启停管理1. Prometheus、Alertmanager 和 Grafana 启动顺序2. 使用…

ChatGPT与日本首相交流核废水事件-精准Prompt...

了解更多请点击&#xff1a;ChatGPT与日本首相交流核废水事件-精准Prompt...https://mp.weixin.qq.com/s?__bizMzg2NDY3NjY5NA&mid2247490070&idx1&snebdc608acd419bb3e71ca46acee04890&chksmce64e42ff9136d39743d16059e2c9509cc799a7b15e8f4d4f71caa25968554…

UG时的弹出框:提示没有可用许可证,No such feature exists(-5),

当过多用户使用UG时或UG优化失效时会出现此弹出框。如果该问题发生时&#xff0c;需立即联系管理员&#xff0c;查看许可管理平台&#xff0c;该服务器许可是否占满&#xff0c;如果占满&#xff0c;联系武汉格发管理员&#xff0c;请准备好服务器远程&#xff0c;可以及时查看…

NLP(5)--自编码器

目录 一、自编码器 1、自编码器概述 2、降噪自编码器 二、特征分离 三、自编码器的其他应用 1、文本生成 2、图像压缩 3、异常检测 四、VAE 1、极大似然估计 2、GSM 3、GMM 4、VAE的引出 5、VAE 一、自编码器 1、自编码器概述 自编码器&#xff08;Auto-Encode…

【高阶数据结构】AVL树(C++实现)

⭐博客主页&#xff1a;️CS semi主页 ⭐欢迎关注&#xff1a;点赞收藏留言 ⭐系列专栏&#xff1a;C进阶 ⭐代码仓库&#xff1a;C进阶 家人们更新不易&#xff0c;你们的点赞和关注对我而言十分重要&#xff0c;友友们麻烦多多点赞&#xff0b;关注&#xff0c;你们的支持是我…

如何将一个字符串转换为驼峰命名法(camel case)?

聚沙成塔每天进步一点点 ⭐ 专栏简介⭐ 思路⭐ 示例⭐ 写在最后 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 记得点击上方或者右侧链接订阅本专栏哦 几何带你启航前端之旅 欢迎来到前端入门之旅&#xff01;这个专栏是为那些对Web开发感兴趣、刚刚踏入前端领…

ODrive移植keil(三)—— USB虚拟串口和快速正弦余弦运算

目录 一、USB虚拟串口1.1、硬件连接1.2、代码移植1.3、测试1.4、最终代码 二、快速正弦余弦运算2.1、硬件连接2.2、代码移植2.3、测试2.4、结论 三、软件中断3.1、配置中断3.2、官方代码的使用方式 ODrive、VESC和SimpleFOC 教程链接汇总&#xff1a;请点击 一、USB虚拟串口 单…

Pdf文件签名检查

如何检查pdf的签名 首先这里有一个已经签名的pdf文件&#xff0c;通过pdf软件可以看到文件的数字签名。 下面就是如何代码检查这里pdf文件的签名 1.引入依赖 <dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId&g…

彩色相机工作原理——bayer格式理解

早期&#xff0c;图像传感器只能记录光的强弱&#xff0c;无法记录光的颜色&#xff0c;所以只能拍摄黑白照片。 1974年,拜尔提出了bayer阵列&#xff0c;发明了bayer格式图片。不同于高成本的三个图像传感器方案&#xff0c;拜尔提出只用一个图像传感器&#xff0c;在其前面放…

【进阶C语言】数据在内存中的存储

一、数据类型的介绍 1.整形家族 &#xff08;1&#xff09;char--字符型 单位&#xff1a;一个字节&#xff0c;包括unsigned char和signed char (2)short--短整形 单位&#xff1a;两个字节&#xff0c;包括unsigned short[int]和signed short[int] (3)int--整形 单位&…

水果店如何通过小程序商城完成配送路径

水果店线上发展的主要目标就是销售卖货&#xff0c;随着电商经济发展&#xff0c;传统线下店面临不少困境&#xff0c;线上部分商家会选择进驻到电商平台及外卖平台&#xff0c;但收获流量的同时也有高昂的流量费、抽成等成本的支出&#xff0c;难以外部宣传及内部打通流程、较…

电脑工具远程定时任务关机开机

使用方法 定时跟远程是两回事情不要搞混了 定时 不需要 扫码登录 直接就可以 软件设置 时间 到规定时间 就自动关机 远程操作 关机 锁屏 只要扫码登录软件挂后台就可以远程操作了 用自己手机微信扫码登录 后发送&#xff08;口令&#xff09;到文件传输助手 就可以看到口令…

电子技术基础(三)__第1章电路分析基础_第13篇__正弦交流电的相量表示

本文讲解 正弦交流电的稳态分析————正弦量的相量表示 一 基本概念 接下来&#xff0c; 注意: 大写字母 上 加点 表示相量 例如&#xff1a; 因为这里有 I m I_{m} Im​ 是幅值&#xff0c; 所以此相量称为幅值相量。 相量 其实就是一个复数&#xff0c; 表示正弦量的复…

小鹏:交出最差的财报,展现最膨胀的信心

上市三年&#xff0c;小鹏在今年第二季度交出了几乎是史上最差的财报&#xff0c;多项惨烈的数据叠加在一起&#xff0c;远远望去&#xff0c;就像一张病危通知单。 自2020年上市后&#xff0c;小鹏的扩张速度令资本惊叹&#xff0c;截至2023年6月30日&#xff0c;小鹏的门店数…