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

目录

1. 概述

2. 分析时间复杂度和空间复杂度

3. 总结

4. 小试牛刀

4.1 折半(二分)查找

4.2 递归实现斐波那契数列


1. 概述

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

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

2. 分析时间复杂度和空间复杂度

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

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

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

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

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

	return ret;
}

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

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

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

	return ret;
}

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

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

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

	return ret;
}

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

3. 总结

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

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

4. 小试牛刀

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

4.1 折半(二分)查找

// 以 32 位机为例

// 空间复杂度:12 个字节,用大 O 表示法记为 O(1)
// 时间复杂度:O(log2N)
int BinarySearch(int * pArr, int key, int len) {
	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)

4.2 递归实现斐波那契数列

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)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值