快速入门时间复杂度&空间复杂度

目录

一、时间复杂度

1)​O(n)的含义

2)复杂表达式的简化

3)​O(n)不一定优于O(n^2)​

​4)递归的时间复杂度

二、空间复杂度

1) ​ O(1)空间复杂度​

2)​O(n)空间复杂度​

3)​O(mn)空间复杂度​

4)递归算法空间算法复杂度分析​


一、时间复杂度

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

1)的含义

  • 程序消耗的时间用算法的操作单元数来表示
  • 假设数据的规模为n,则用f(n)表示操作单元数的大小,而f(n)常被简化
  • O表示的是一般的情况,是一个估算值。而且它是在数据量级非常大的情况下所展现出的时间复杂度
  • 当算法的结果不明朗时,我们往往取最坏的情况作为我们的时间复杂度
  • 因为O代表的是一个一般的情况,数据规模不同时,看似时间复杂度低的不一定耗时短,需要具体分析

2)复杂表达式的简化

表达式简化遵循以下两个原则:

  • 去掉常数项
  • 只保留最高项

为例分析:

  1. 去掉常数项后为
  2. 只保留最高项后为

不难想象,当n趋一个非常大的数量级的时候,最高项将其决定性作用。但是若常数项也是一个非常大的数量级,那我们就不可以轻易将其舍去。

3)不一定优于

        由上面简化法则我们可以看到,常数项是被忽略的,如 ,当n < 20时前者的操作单位数是小于后者的。

所以在决定使用什么算法的时候并不是算法的时间复杂度越低越好,还需要考虑数据的规模

        那为什么我们默认 时间复杂度低于 呢?正如前面提到的关于O的定义:它是在数据量级非常大的情况下所展现出的时间复杂度,所以我们默认前者的时间复杂度更优。

​4)递归的时间复杂度

⭐递归的时间复杂度 = 递归次数 X 每次递归的操作次数。

现在我们从一道面试题来分析时间复杂度:求x的n次方

①直观的for循环遍历

int fuc1(int n)
{
	int ret = 1;
	for (int i = 1; i < n; i++)
		ret *= i;
	return ret;
}

【分析】时间复杂度为 ,因为操作单元数为n次​

②递归版本1

int fuc2(int n,int x)
{
	if (n == 0)
		return 1;
	if (n == 1)
		return x;
	return x * fuc2(n - 1, x);
}

【分析】递归次数为n次,每次相乘的时间复杂度为 ,所以时间复杂度仍为

​③递归版本2​

int fuc3(int n, int x)
{
	if (n == 0)
		return 1;
	if (n == 1)
		return x;
	if (n % 2 == 1)
		return fuc3(n / 2, x) * fuc3(n / 2, x) * x;//奇数次幂的情况
	return fuc3(n / 2, x) * fuc3(n / 2, x);		   //偶数次幂的情况
}

【分析】上面代码的时间复杂度为 吗?我们可以画二叉树来理解,以n = 16为例​

每一个结点都表示需要进行一次递归,因此结点数代表着递归次数,所以先我们计算二叉树结点数​

  • 一颗满二叉树的结点数根据等比数列求和公式可以求出为:​(m为二叉树深度)​
  • 二叉树深度m 计算公式​:(向下取整)​

        因为n为奇数时我们将其拆成偶数处理,如:

将深度公式代入结点总和公式我们可以得出, 节点数 = n - a(a为某个常数),所以时间复杂度还是

④递归版本3​

int fuc4(int n, int x)
{
	if (n == 0)
		return 1;
	else if (n == 1)
		return 1;
	int t = fuc4(n / 2, x);
	if (n % 2 == 1)
		return t * t * x;
	return t * t;
}

通过将递归操作抽离出来从而减少递归次数,我们真正实现了时间复杂度为​

我们再分析一下求斐波那契数列函递归解法时间复杂度:​

int fib(int n)
{
	if (n <= 0)
		return 1;
	if (n == 1)
		return 1;
	return fib(n - 1) + fib(n - 2);
}

同样的我们可以画二叉树来分析。求第k个斐波那契数,我们不难想象,我们将构造出一个深度为k的二叉树,深度为k的二叉树最多有 个结点,所以我们得出该算法的时间复杂度为 。优化的思路和上述求x的n次方的思路一样,主要是减少递归的调用次数​

int fib(int first, int second, int n)
{
	if (n <= 0)
		return 0;
	if (n < 3)
		return 1;
	else if (n == 3)
		return first + second;
	else
		return fib(second, first + second, n - 1);
}

二、空间复杂度

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

1) 空间复杂度​

程序占用空间不随n的变化而变化,即占用的空间是一个常数​

for(int j = 0; j < n; j++)
{
	j++;
}

程序占用的空间与n无关,上图中之涉及为j分配内存空间,是一个固定的常量​

2)空间复杂度​

程序占用空间随n增长而线性增长;​

int arr[n];

3)空间复杂度​

常常是定义一个二维集合,集合的大小与集合的长与宽相管​

int arr[row * col];

4)递归算法空间算法复杂度分析​

⭐递归算法空间复杂度 = 每次递归的空间复杂度 X 递归深度(递归调用栈的最大长度)

我们同样来分析上面提到的求斐波那契数函数的空间复杂度:​

int f(int n)
{
	if (n <= 0)
		return 1;
	if (n == 1)
		return 1;
	return f(n - 1) + f(n - 2);
}

上面代码的空间复杂度为 吗?答案是否定的。我们需要区分的是,空间不同于时间,空间可以被重复利用的。

 在递归的过程中首先依次将f(5),f(4), f(3),f(2),f(1)压栈,出栈时则将栈空间销毁,再次用到时则重新压栈,但是再次用到的空间都是和最初相同的,因为新压栈的栈空间从当前栈帧向下生长创建。由于每一次调用所占用的空间都为所以占用的空间为 ,所以上述代码的空间复杂度为​

 我们再来分析递归实现的二分查找的空间复杂度​:

int binary_search(int arr[], int l, int r, int x)
{
	if (r >= l)
	{
		int mid = l + (r - l) / 2; //避免先加后除产生溢出的错误
		if (arr[mid] == x)
			return mid;
		else if (arr[mid] < x)
			return binary_search(arr, mid + 1, r ,x);
		else
			return binary_search(arr, l, mid - 1, x);
	}
	return -1;
}

每次递归所占用的空间都是一定的,所以每次递归的空间复杂度为 ,而递归的最大深度为

,所以空间复杂度为

本文内容参考学习参考于《代码随想录》📚

  • 35
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 26
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 26
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

罅隙`

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值