【时间复杂度和空间复杂度】

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

1.时间复杂度

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

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

总的来说,时间复杂度就是大致计算代码运行的次数。

举一些例子分析:

1.嵌套的时间复杂度计算

// 请计算一下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);
}

实际中我们计算时间复杂度时,我们其实并不一定要计算精确的执行次数,而只需要大概执行次数,那么这

里我们使用大O的渐进表示法。

推导大O阶方法:

1、用常数1取代运行时间中的所有加法常数。

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

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

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

总的来说,1.就是使用O(1)来代表常数量级的运行次数

2.如果一段代码的运行次数是F(N) = N + 2,那么就保留最高阶项,时间复杂度为O(N)。

3.如果一段代码的运行次数是F(N) = 2*N^2,那么就去掉系数,时间复杂度为O(N^2)。

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

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

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

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

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

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

最好情况:1次找到

最坏情况:N次找到

平均情况:N/2次找到

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

常见的时间复杂度计算:

  1. 双重循环时间复杂度计算
// 计算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);
}

可以看出,运行次数为F(N) = 2*N +10 , 根据大O的表示法,时间复杂度为O(N)。

// 计算Func3的时间复杂度?
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);
}

先计算运行次数为F(N) = M*N,那么时间复杂度为O(N)或者O(M),取决于M和N的大小:

  1. 当M>>N 时,时间复杂度为O(M)
  2. 当N>>M 时,时间复杂度为O(N)
  3. 当M和N差不多大时,时间复杂度为O(M)或者O(N)

2.常数循环的时间复杂度

// 计算Func4的时间复杂度?
void Func4(int N)
{
int count = 0;
for (int k = 0; k < 100; ++ k)
{
++count;
}
printf("%d\n", count);
}

运行次数为F(N) = 100,所以时间复杂度为O(1).

注意:时间复杂度为O(1)并不代码代码只循环一次,而是常数次。

  1. strchar的时间复杂度。

strchar是从一个长度为N的字符串中查找一个字符的函数.


928c5c68192245409bb9f484d9b611d0.png

该函数的核心部分如上,假设从hello world中查找。


698f4fcaaf034c94b394df4d8edf164c.png

所以做悲观预期,时间复杂度为O(N)。

5.冒泡排序时间复杂度

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

对于一个冒泡排序,假设有10个数需要排序,第一次要排9次,第二次排8次,第三次排7次...

假设有n个数要排序,第一次排n-1次,第二次排n-2次,第三次排n-3次...

直到排到剩下一个数,就不需要排序了。

所以运行次数为F(N) = (N-1) +(N-2) + (N-3) +...+1,这是一个等差数列求和,所以可以认为,运行次数为F(N) = (N-1)*N/2,所以时间复杂度为O(N^2)。

6.二分查找的时间复杂度

// 计算BinarySearch的时间复杂度?
int BinarySearch(int* a, int n, int x)
{
assert(a);
int begin = 0;
int end = n-1;
while (begin < end)
{
int mid = begin + ((end-begin)>>1);
if (a[mid] < x)
begin = mid+1;
else if (a[mid] > x)
end = mid;
else
return mid;
}
return -1;

求时间复杂度不能只看循环的次数,需要结合算法的实际来看。

假设有100个数字,查找最坏的情况,查找一次,除以2,查找一次,除以2,最后,就只剩下一个数字。 也就是 100/2/2/2/2/2/2 =1 。 所以查找次数7次。

假设有n个数字,要查找x次才到1,那么就是 2^x = n , 所以x = log以2为底n的对数。

所以二分查找的时间复杂度为O(log以2为底n的对数)

7.递归算法的时间复杂度

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

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

递归次数为N-1,每次调用一次,所以时间复杂度为O(N)。

2.斐波那契数列时间复杂度

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

画出斐波那契的计算图:

27d813dcb55d411c9692a5affda445d8.png

计算次数是 2^0* 2^1* 2^2* ...* 2^N-2* 2^N-1 - X,这是一个等比数列,F(N) =2^N -1 -X ,

时间复杂度为O(2^N)。

2.空间复杂度

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

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

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

注意:函数运行时所需要的栈空间(存储参数、局部变量、一些寄存器信息等)在编译期间已经确定好了,因

此空间复杂度主要通过函数在运行时候显式申请的额外空间来确定。

总的来说,空间复杂度是计算变量个数的。

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,内部循环创建了一个变量i,但是,在出了内部循环后,i就销毁了, 再次进去才又创建了变量i,重新创建的变量i是占用之前创建的变量的空间的。 同理,exchange变量也是如此,总体来说,程序相当于只创建了3个变量,所以空间复杂度为O(1).


2.计算斐波那契额数列的空间复杂度

// 计算Fibonacci的空间复杂度?
// 返回斐波那契数列的前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;
}

e8baff62c158420a89b398d1c34fcd42.png

对于变量的创建和销毁来说,先创建Fib(N),再有Fib(N-1),再有Fib(N-2),一直往下走,最后创建Fib(2),Fib(1)。随后销毁之前创建的N个变量,再去创建Fib(N-1),到Fib(N-3),一直往下走,之后每一次创建的变量均小于第一次创建的N个变量个数,所以空间复杂度是O(N).


3.阶乘递归的空间复杂度:

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

与斐波那契额数列同理,阶乘递归的空间复杂度是O(N).

相关文章
|
Python
Ubuntu22.04编译安装OpenVINO
Ubuntu22.04编译安装OpenVINO
1442 1
Ubuntu22.04编译安装OpenVINO
|
8月前
|
数据采集 SQL 人工智能
长文详解|DataWorks Data+AI一体化开发实战图谱
DataWorks是一站式智能大数据开发治理平台,内置阿里巴巴15年大数据建设方法论,深度适配阿里云MaxCompute、EMR、Hologres、Flink、PAI 等数十种大数据和AI计算服务,为数仓、数据湖、OpenLake湖仓一体数据架构提供智能化ETL开发、数据分析与主动式数据资产治理服务,助力“Data+AI”全生命周期的数据管理。
1370 5
|
Oracle 关系型数据库 数据库
Oracle数据恢复—Oracle数据库文件有坏快损坏的数据恢复案例
一台Oracle数据库打开报错,报错信息: “system01.dbf需要更多的恢复来保持一致性,数据库无法打开”。管理员联系我们数据恢复中心寻求帮助,并提供了Oracle_Home目录的所有文件。用户方要求恢复zxfg用户下的数据。 由于数据库没有备份,无法通过备份去恢复数据库。
鸿蒙原生开发手记:02-服务卡片开发
服务卡片是桌面小组件,分为静态和动态两类。本文介绍如何在 DevEco 中创建静态服务卡片,并实现点击事件传参和参数接收。创建时需选择支持的卡片大小,使用 FormLink 实现跳转,参数在 `entryability` 的生命周期方法中接收。注意:服务卡片不支持热重载。
518 1
鸿蒙原生开发手记:02-服务卡片开发
|
移动开发 小程序 API
uniapp中各种状态的按钮
uniapp中各种状态的按钮
767 0
|
设计模式 Java 测试技术
spring复习04,静态代理动态代理,AOP
这篇文章讲解了Java代理模式的相关知识,包括静态代理和动态代理(JDK动态代理和CGLIB),以及AOP(面向切面编程)的概念和在Spring框架中的应用。文章还提供了详细的示例代码,演示了如何使用Spring AOP进行方法增强和代理对象的创建。
spring复习04,静态代理动态代理,AOP
|
机器人 Java 测试技术
《手把手教你》系列技巧篇(六十)-java+ selenium自动化测试 - 截图三剑客 -中篇(详细教程)
【6月更文挑战第1天】本文介绍了使用Java和Selenium进行自动化测试时的另一种截图方法,即利用Robot类实现全屏截图。Robot类能够捕获屏幕上的所有内容,包括任务栏和浏览器元素。测试场景包括访问指定网站、调用截图方法和保存截图。示例代码展示了如何使用Robot创建全屏截图并保存到特定文件夹。在运行代码前,需确保指定的保存路径存在,否则会报错。
228 4
【核磁共振成像】相位差重建
【核磁共振成像】相位差重建
|
前端开发
HTML代码示例
HTML代码示例
280 1
|
存储 运维 JavaScript
SaaS云HIS平台源码 采用云部署模式,部署一套可支持多家医院共同使用
通过基于SaaS模式的医院管理系统,院内的医护人员、患者可快速建立互联协同。不仅如此,通过SaaS模式提供的解决方案,医院机构可实现远程医疗,从而为不同地区的患者带来优质医疗资源,促进医疗公平。
432 5