【数据结构】浅析时间复杂度与空间复杂度

数据结构 同时被 2 个专栏收录
11 篇文章 1 订阅
11 篇文章 1 订阅

众所周知,衡量算法效率的标准为:时间复杂度 和 空间复杂度

通俗地来讲,时间复杂度就是一个 程序要被执行的次数,它是一个近似值,而不是执行的时间。空间复杂度,是程序执行过程中所 占用的最大内存

接下来通过两段代码来说明下如何计算一个程序的 时间复杂度 以及 空间复杂度:

/*以 32 位机为例*/
int SumMemory(int n) // 时间复杂度:共执行 2n + 5 次,用大 O 表示法记为 O(n);空间复杂度: 4n + 12 字节,用大 O 表示法记为 O(n)
{
	int i = 0;  // 时间复杂度:执行 1 次;空间复杂度: i 为 int 型,占 4 个字节
	int ret = 0; // 时间复杂度:执行 1 次;空间复杂度: 4 个字节
	int * data = (int *)malloc(n * sizeof(int)); // 时间复杂度:执行 1 次;空间复杂度: 4 * n 个字节

	for (i = 0; i < n; i++) // 时间复杂度:循环 n 次,因此执行 n 次;空间复杂度: 0,因为这部分内容是在 CPU 中运行,不占用内存
	{
		data[i] = i + 1;
	}

	for (i = 0; i < n; i++) // 时间复杂度:循环 n 次,因此执行 n 次;空间复杂度: 0,因为这部分内容是在 CPU 中运行,不占用内存
	{
		ret += data[i];
	}

	free(data); // 时间复杂度:执行 1 次;空间复杂度: 0,因为这部分内容是在 CPU 中运行,不占用内存
	data = NULL; // 时间复杂度:执行 1次;空间复杂度: 4 个字节

	return ret;
}

这段代码一共执行了 2n + 5 次,当 n 趋于无穷大时,5 对于 2n 的影响少之又少,因此可以将其删掉,本质上来说,复杂度的计算是个近似运算,那么 n 的系数也可以置为 1,只需比较 n 的幂次即可。

/*以 32 位机为例*/
int SumLoop(int n) // 时间复杂度:共执行 n + 2 次,用大 O 表示法记为 O(n);空间复杂度: 8 个字节,用大 O 表示法记为 O(1)
{
	int i = 0; // 时间复杂度:执行 1 次;空间复杂度: i 为 int 型,占 4 个字节
	int ret = 0; // 时间复杂度:执行 1 次;空间复杂度: 4 个字节

	for (i = 0; i < n; i++) // 时间复杂度:循环 n 次,因此执行 n 次;空间复杂度: 0,因为这部分内容是在 CPU 中运行,不占用内存
	{
		ret += i;
	}

	return ret;
}

这段代码一共执行了 n + 2 次,当 n 趋于无穷大时,2 对于 n 的影响少之又少,因此可以将其删掉,本质上来说,复杂度的计算是个近似运算,只需比较 n 的幂次即可。

/*以 32 位机为例*/
int SumFormula(int n) // 时间复杂度:执行 2 次,用大 O 表示法记为 O(1);空间复杂度: 4 个字节,用大 O 表示法记为 O(1)
{
	int ret = 0; // 时间复杂度:执行 1 次;空间复杂度: 4 个字节

	if (n > 0)
	{
		ret = (1 + n) * n / 2; // 时间复杂度:加减乘除运算只是一个运算指令,因此执行 1 次;空间复杂度: 0,因为这部分内容是在 CPU 中运行,不占用内存
	}
	else
	{
		;
	}

	return ret;
}

这段代码一共执行了 2 次,本质上来说,复杂度的计算是个近似运算,将 2 看做 1 即可。

总结下推导 O(n) 的方法:

  1. 首先用常数 1 取代所有的加法常数;
  2. 只保留 n 的最高幂次;
  3. 若最高幂次存在且不为 1,则将这个最高幂次的系数置为 1 即可。

最后分析下折半查找和菲波那切数列数列的时间复杂度以及空间复杂度:

  • 折半(二分)查找
// 以 32 位机为例
/*
*	函数名称:BinarySearch
*
*	函数功能:二分查找排好序的数组中的某个元素
*
*	入口参数:pArr, key, len
*
*	出口参数:mid
*
*	返回类型:int
*/

int BinarySearch(int * pArr, int key, int len) // 空间复杂度:12 个字节,用大 O 表示法记为O(1)
{
	int left = 0;  // 空间复杂度:4 个字节
	int right = len - 1; // 空间复杂度:4 个字节
	int mid = 0; // 空间复杂度:4 个字节

	assert(NULL != pArr);
	
	while (left <= right) {
		mid = (left & right) + ((left ^ right) >> 1);

		if (pArr[mid] == key) {
			return mid;
		} else if (pArr[mid] > key) {
			right = mid - 1;	
		} else {
			left = mid + 1;
		}
	}

	return -1;
}

时间复杂度的分析:假设有 n 个元素,经过第一次查找后,查找区间缩短为 n / 2,经过第二次查找后,查找区间缩短为 n / 4,…… 那么经过 k 次查找后,查找区间变为 n / 2^k(ps: n 除上 2 的 k 次方)。不难发现,k 其实就是循环次数,那么最终找到元素 key 时,n / 2^k = 1(ps: 这里等于 1 就表示找到了元素 key),因此可以得出最坏情况下该算法的时间复杂度为 O(log2n)。(ps: log2n表示以 2 为底,n 的对数)

综上所述,折半(二分)查找 的时间复杂度为 O(log2n),空间复杂度为 O(1)

  • 递归实现斐波那契数列
/*
*	函数名称:Fibonacci
*
*	函数功能:求第n个菲波那切数(递归)
*
*	入口参数:n
*
*	出口参数:1 or Fibonacci(n-1) + Fibonacci(n-2)
*
*	返回类型:int
*/

int Fibonacci(int n)
{
	if (n <= 2) {
		return 1;
	} else {
		return Fibonacci(n-1) + Fibonacci(n-2);
	}
}

时间复杂度的分析:菲波那切数可以看做一个树结构,1 分为 2,2 分为 4,4 分为 8,... 而要去求第 n 个斐波那契数,就需要被分解成 2^n - 1 个数字,也就是需要执行 2^n - 1 次,用大 O 表示法记为 O(2^n)。

空间复杂度的分析:计算第 n 个菲波那切数,会调用 n - 2 次 Fibonacci() 函数,每次调用都会开辟栈空间,那么就会占用 4*(n-2)个字节,用大 O 表示法记为 O(n)。

综上所述,递归实现菲波那切数列的时间复杂度为 O(2^n),空间复杂度为 O(n)

  • 1
    点赞
  • 0
    评论
  • 5
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

©️2021 CSDN 皮肤主题: 黑客帝国 设计师:白松林 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值