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

news/2024/11/16 14:47:46/

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)

  1. 当N>>M 时,时间复杂度为O(N)

  1. 当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的字符串中查找一个字符的函数。

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

所以做悲观预期,时间复杂度为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);
}

画出斐波那契的计算图:

计算次数是 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;
}

对于变量的创建和销毁来说,先创建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).


http://www.ppmy.cn/news/19633.html

相关文章

JavaScript 语句

JavaScript 语句 我们可以使用设置语言来告诉浏览器需要做些什么事情&#xff0c;语句就是向浏览器发出一些命令。 那么JavaScript中怎样定义语句呢? 首先语句是用来给浏览器发送命令的。命令是用来告诉浏览器该做哪些事情的。 举例说明&#xff0c;我们想idtest的元素中设…

K8s:通过 Helmify 实现将 YAML 文件 转化为 Helm Charts

写在前面 分享一个 Yaml 资源文件转 Helm Charts 包的小工具 helmify博文内容涉及&#xff1a;helmify 工具安装&#xff0c;简单使用YAML 静态文件转化为 HELM charts 包从 kustomize 输出转化为 Helm理解不足小伙伴帮忙指正博文涉及 helmify 我心匪石&#xff0c;不可转也。我…

C语言深度解剖-关键字(1)

目录 1.初步了解关键字 2.第一个C程序 3.深刻理解变量 是什么&#xff1f; 怎么用&#xff1f; 为什么&#xff1f; 4.深刻理解定义与声明 5.auto关键字的理解 6.理解关键字register 认识&#xff1a; 本质&#xff1a; register 修饰变量 写在最后&#xff1a; 1…

Python装饰器使用方法详解

文章目录1 装饰器背景知识1.1 基本概念1.2 应用场景2 简单的装饰器代码3 使用装饰器记录函数执行次数4 带参数的装饰器5 装饰器处理有返回值的函数1 装饰器背景知识 1.1 基本概念 装饰器&#xff08;Decorator&#xff09;是 Python 中一种函数或类&#xff0c;用来修饰其他函…

Redis消息队列 | 黑马点评

目录 一、认识消息队列 二、List模拟消息队列 三、PubSub的消息队列 四、Stream的消息队列&#xff08;重点&#xff09; 1、单消费模式 2、消费者组 五、redis三种消息队列对比 六、优化秒杀实战 1、创建消息队列 2、修改下单脚本 3、接收消息处理 一、认识消息队列 …

APT之木马静态免杀

前言 这篇文章主要是记录手动编写代码进行木马免杀&#xff0c;使用工具也可以免杀&#xff0c;只不过太脚本小子了&#xff0c;而且工具的特征也容易被杀软抓到&#xff0c;指不定哪天就用不了了&#xff0c;所以要学一下手动去免杀木马&#xff0c;也方便以后开发一个只属于…

游戏启动器:LaunchBox Premium with Big Box v13.1

LaunchBox知道您会喜欢的功能&#xff0c;具有风格的游戏启动器&#xff0c;我们最初将 Launchbox 构建为 DOSBox 的一个有吸引力的前端&#xff0c;但它现在拥有对现代游戏和复古游戏模拟的支持。我们让您的所有游戏看起来都很漂亮。 整理您的游戏收藏 我们不仅漂亮&#xff…

第五十六章 历史监视器 - 基本指标

文章目录第五十六章 历史监视器基本指标收集数据第五十六章 历史监视器 History Monitor 维护性能和系统使用指标的历史数据库。其主要目的是&#xff1a; 提供性能基准并帮助分析性能问题。帮助分析一段时间内的系统使用情况以进行容量规划。 该数据库在 SYS.History 类包中…