时间复杂度和空间复杂度

1.复杂度是什么?

首先需要知道怎么衡量复杂度。而在实际衡量时,我们通常会围绕以下2 个维度进行:时间复杂度和空间复杂度。

2.如何去计算复杂度?

复杂度是一个关于输入数据量 n 的函数。假设你的代码复杂度是 f(n),那么就用个大写字母 O 和括号,把 f(n) 括起来就可以了,即 O(f(n))。例如,O(n) 表示的是,复杂度与计算实例的个数 n 线性相关;O(logn) 表示的是,复杂度与计算实例的个数 n 对数相关。

3.复杂度计算的三个原则

O(n) = O(2n) 
O(n²)+O(n) = O(n²)  # 只用更大变化率的二阶多项式来表征复杂度
O(1) 表示通过有限可数的资源即可完成,即与输入数据量 n 无关

例子:对于输入的数组,输出与之逆序的数组。例如,输入 a=[1,2,3,4,5],输出 [5,4,3,2,1]。

先看方法一,建立并初始化数组 b,得到一个与输入数组等长的全零数组。通过一个 for 循环,从左到右将 a 数组的元素,从右到左地赋值到 b 数组中,最后输出数组 b 得到结果。

代码如下:

public void s1_1() {
	int a[] = { 1, 2, 3, 4, 5 };
	int b[] = new int[5];
	for (int i = 0; i < a.length; i++) {
		b[i] = a[i];
	}
	for (int i = 0; i < a.length; i++) {
		b[a.length - i - 1] = a[i];
	}
	System.out.println(Arrays.toString(b));
}

这段代码的输入数据是 a,数据量就等于数组 a 的长度。代码中有两个 for 循环,作用分别是给b 数组初始化和赋值,其执行次数都与输入数据量相等。因此,代码的时间复杂度就是 O(n)+O(n),也就是 O(n)。

空间方面主要体现在计算过程中,对于存储资源的消耗情况。上面这段代码中,我们定义了一个新的数组 b,它与输入数组 a 的长度相等。因此,空间复杂度就是 O(n)。

接着我们看一下第二种编码方法,它定义了缓存变量 tmp,接着通过一个 for 循环,从 0 遍历到a 数组长度的一半(即 len(a)/2)。每次遍历执行的是什么内容?就是交换首尾对应的元素。最后打印数组 a,得到结果。

代码如下:

public void s1_2() {
	int a[] = { 1, 2, 3, 4, 5 };
	int tmp = 0;
	for (int i = 0; i < (a.length / 2); i++) {
		tmp = a[i];
		a[i] = a[a.length - i - 1];
		a[a.length - i - 1] = tmp;
	}
	System.out.println(Arrays.toString(a));
}

这段代码包含了一个 for 循环,执行的次数是数组长度的一半,时间复杂度变成了 O(n/2)。根据复杂度与具体的常系数无关的性质,这段代码的时间复杂度也就是 O(n)。

空间方面,我们定义了一个 tmp 变量,它与数组长度无关。也就是说,输入是 5 个元素的数组,需要一个 tmp 变量;输入是 50 个元素的数组,依然只需要一个 tmp 变量。因此,空间复杂度与输入数组长度无关,即 O(1)。

我们在写代码的时候应考虑时间复杂度和空间复杂度,用尽可能少的时间损耗和尽可能少的空间损耗去完成任务。

4.时间复杂度与代码结构的关系

直接上例子:定义一个数组 a = [1, 4, 3],查找数组 a 中的最大值,代码如下:

public void s1_3() {
	int a[] = { 1, 4, 3 };
	int max_val = -1;
	int max_inx = -1;
	for (int i = 0; i < a.length; i++) {
		if (a[i] > max_val) {
			max_val = a[i];
			max_inx = i;
		}
	}
	System.out.println(max_val);
}

代码的结构上需要使用一个 for 循环,对数组所有元素处理一遍,所以时间复杂度为 O(n)。

又一个例子,定义一个数组 a = [1, 3, 4, 3, 4, 1, 3],并查找数组中出现次数最多的那个数字:

public void s1_4() {
	int a[] = { 1, 3, 4, 3, 4, 1, 3 };
	int val_max = -1;
	int time_max = 0;
	int time_tmp = 0;
	for (int i = 0; i < a.length; i++) {
		time_tmp = 0;
		for (int j = 0; j < a.length; j++) {
			if (a[i] == a[j]) {
				time_tmp += 1;
			}
			if (time_tmp > time_max) {
				time_max = time_tmp;
				val_max = a[i];
			}
		}
	}
	System.out.println(val_max);
}

代码采用了双层循环的方式计算:第一层循环,我们对数组中的每个元素进行遍历;第二层循环,对于每个元素计算出现的次数,并且通过当前元素次数 time_tmp 和全局最大次数变量 time_max 的大小关系,持续保存出现次数最多的那个元素及其出现次数。由于是双层循环,这段代码在时间方面的消耗就是 n*n 的复杂度,也就是 O(n²)。

5.结论

一个顺序结构的代码,时间复杂度是 O(1)。
二分查找,或者更通用地说是采用分而治之的二分策略,时间复杂度都是 O(logn)。
一个简单的 for 循环,时间复杂度是 O(n)。
两个顺序执行的 for 循环,时间复杂度是 O(n)+O(n)=O(2n),其实也是 O(n)。
两个嵌套的 for 循环,时间复杂度是 O(n²)。

本文参考拉钩教育学习视频

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值