一文教你彻底掌握算法的时间复杂度和空间复杂度

光阴易逝,人生无常,把握住你能把握住的点滴。
今天让我们一起来学习算法的时间复杂度和空间复杂度ヾ(≧▽≦*)o

一、算法效率

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

时间复杂度主要衡量一个算法运行快慢,而空间复杂度主要衡量一个算法运行所需要的额外空间。
在计算机发展早期,计算机的存储容量很小,所以对空间复杂度很在乎,但是经过计算机行业的快速发展,计算机的存储容量已经达到了很高的程度,如今已不需要特别关注一个算法的空间复杂度。(摩尔定律:集成电路上可以容纳的晶体管数目大约每经过18个月便会增加一倍)

二、时间复杂度

2.1时间复杂度的定义

时间复杂度的定义:在计算机科学中,算法的时间复杂度是一个函数,它定量的描述该算法的运行时间。一个算法执行所耗费时间,从理论上说,是不能算出来的。只有你把你的程序放在机器上跑起来,才能知道,但是我们需要每个算法都上机测试吗?是可以,但很麻烦(并且,一个算法运行时间跟硬件配置有关系,所以一个算法是没有办法准确算出时间的),所以才有了时间复杂度这个分析方法。一个算法所花费的时间与其中语句执行的次数成正比例。算法中的基本操作的执行次数 ,为算法的时间复杂度。
即:找到某条基本语句与问题规模N之间的数学表达式,就是该算法的时间复杂度。
一个执行次数,不一定是一条语句,也可以是多条语句,但肯定是常数条语句。
2.2 大O的渐进表示法
先看一个代码:计算一下程序中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++;
	}
}

其准确的时间复杂度函数式为:F(N) = N * N+2 * N+10。
当N =10时,F(10)= 130 100 (估算)
当N=100时,F(100) = 10210 10000 (估算)
当N = 1000时,F(1000) = 1002010 100w(估算)
通过观察发现:随着N的增大,后两项对结果的影响几乎可以忽略不记。(一方面)

(另一方面):准确的时间复杂度函数式,不方便在算法之间进行比较。
大O的渐进表示法的出现。 (大概估算,方便比较)

2.2大O的渐进表示法

大O符号:是用于描述函数渐进行为的数学符号。
1、用常数1取代运行时间中的所有加法常数。
2、在修改后的运行次数函数中,只保留最高阶项。
3、如果最高阶项存在且不是1,则去除与这个项目相乘的常数,得到的结果就是大O阶。

所有上面题用大O阶表示为O(N^2)

//计算strchr的时间复杂度?(在字符串数组中查找一个字符)
const char* str(const char* str, int character)
{
	while (*str)
	{
		if (*str == character)
		{
			return str;
		}
		else
		{
			str++;
		}
	}
	return NULL;
}

在此次我们发现此算法有几种情况;
最坏情况:O(N)
最好情况:O (1)
平均情况: O (N/2)
在实际中一般情况关注的是算法的最坏运行情况(底线思维),所有为O(N)。

另外:值得大家注意的一点是,是不是计算时间复杂度就数循环就行了?答案是否定的。
时间复杂度不能去数循环,这个不一定准确,一定要看算法思想来进行计算。

比如:
冒泡排序:

void BubbleSort(int* a, int n)
{
	for (int end = n; end > 0; end--)
	{
		int exchange = 0;
		for (int i = 1; i < end; i++)
		{
			if (a[i - 1]>a[i])
			{
				int tmp = a[i - 1];
				a[i - 1] = a[i];
				a[i] = tmp;
				exchange = 1;
			}
		}
		if (exchange == 0)
		{
			break;
		}
	}
}

通过冒泡排序的思想,最坏情况为:N-1 + N-2 + N-3 +…+ 1=N (N-1) / 2,即O (N^2)
最好情况为:遍历一遍发现都不需要交换,即O (N)

比如:二分查找

int BinarySearch(int* a, int n, int x)
{
	int begin = 0;
	int end = n - 1;
	while (begin <= end)
	{
		int mid = (begin + end) / 2;
		if (a[mid] > x)
		{
			end = mid - 1;
		}
		else if (a[mid] < x)
		{
			begin = mid + 1;
		}
		else
		{
			return mid;
		}
	}
	return -1;
}

如果不看算法思想,通过数循坏,那它的时间复杂度为O(N),但是通过算法思想我们知道,其最坏情况的时间复杂度为O(log以2为底 N 的对数),最好情况的时间复杂度为O(1),所以大家要看一个算法的思想呀!
O(log以2为底 N 的对数) 为方便起见,通常写成O(log N),有些书也写成O(lg N),通过换底公式得到的,建议大家写成O(log N) 更规范一些。

2.3递归函数的时间复杂度

递归函数如何求其时间复杂度呢?
例一:

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

在这里插入图片描述
分析清楚其递归调用的次数。
空间复杂度为:O(N)

例二:计算斐波那契的第n项

long long Fib(size_t n)
{
	if (n < 3)
	{
		return 1;
	}
	return Fib(n - 1) + Fib(n - 2);
}

在这里插入图片描述
发现它的时间复杂度为O(2^n ),指数量级的,显然它不实用,递归该循坏后 变为O(N);
空间复杂度为:O(N)

求Fibonacci数列的前n项
代码如下:

long long* fibonacci(size_t n)
{
	if (n == 0)
	{
		return NULL;
	}

	long long* FibArray = (long long*)malloc(sizeof(long long)*(n + 1));
	FibArray[0] = 0;
	FibArray[1] = 1;

	for (int i = 2; i <= n; i++)
	{
		FibArray[i] = FibArray[i - 1] + FibArray[i - 2];
	}

	return FibArray;
}

三、空间复杂度

空间复杂度也是一个数学表达式,是对一个算法在运行过程中临时占用存储空间大小的量度。
空间复杂度不是程序占用了多少bytes的空间,因为这个也没有太大意义,所以空间复杂度计算的是变量的个数
它也用大O的渐进表示法。

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

例一:
在这里插入图片描述
例二:
在这里插入图片描述

3.1 注意

时间是可以累计的,空间是不累计的,可以重复使用。
举例:

void f1()
{
	int a = 0;
	printf("%p\n", &a);
}

void f2()
{
	int a = 0;
	printf("%p\n", &a);
}

int main()
{
	f1();
	f2();
	return 0;
}

运行结果:
在这里插入图片描述
发现地址一样,所以空间是可以重复利用的。

3.2常见的复杂度的对比

最常见的七个示例,按照运行效率从高到低排序。

1.O(1) — 常数复杂度
2.O(log n) — 对数复杂度
3.O(n) — 线性复杂度
4.O(n log n) — 对数线性复杂度
5.O(nᵏ) — 多项式复杂度
6.O(kⁿ) — 指数复杂度
7.O(n!) — 阶乘复杂度

在这里插入图片描述
你可以看到,随着输入规模的增长,红色阴影区域中算法的运行时间急剧增长。另一方面,在黄色和绿色阴影区域中的算法,当输入规模增长时,运行时间在变化不是很大,因此它们更高效,处理大量数据时更游刃有余。

好啦,文章到此就结束了,如果对你有所帮助,可以给个赞吗?
哈哈,期待下次再见!O(∩_∩)O🧙‍♂️

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值