1、概念
时间复杂度
时间复杂度是指算法执行所需的时间,通常用算法执行的操作次数来表示。时间复杂度通常用大 O 表示法来表示,其中 O 表示算法的渐近时间复杂度。例如,O(n)表示算法的执行时间与输入规模 n 成正比,O(n^2)表示算法的执行时间与输入规模 n 的平方成正比。
时间频度T(n)中,n称为问题的规模,当n不断变化时,时间频度T(n)也会不断变化。但有时我们想知道它变化时呈现什么规律,为此我们引入时间复杂度的概念。算法的时间复杂度也就是算法的时间度量,记作:T(n) = O(f(n))。它表示随问题规模n的增大,算法执行时间的增长率和f(n)的增长率相同,称作算法的渐进时间复杂度,简称「时间复杂度」。
空间复杂度
空间复杂度是指算法执行所需的空间,通常用算法所需的存储空间来表示。空间复杂度也通常用大 O 表示法来表示。例如,O(n)表示算法所需的存储空间与输入规模 n 成正比,O(n^2)表示算法所需的存储空间与输入规模 n 的平方成正比。
S(n)=O(f(n)),其中n为问题的规模,S(n)表示空间复杂度。
2、计算方法
时间复杂度
下面是几种常见的时间复杂度计算方法:
-
常数时间复杂度(O(1)):如果算法的执行时间与输入规模 n 无关,即无论输入规模如何,算法的执行时间都是固定的,则称该算法具有常数时间复杂度。例如,简单的赋值操作和基本的数学运算(如加、减、乘、除)通常具有常数时间复杂度。
-
线性时间复杂度(O(n)):如果算法的执行时间与输入规模 n 成正比,则称该算法具有线性时间复杂度。例如,遍历一个长度为 n 的数组或链表的操作通常具有线性时间复杂度。
-
平方时间复杂度(O(n^2)):如果算法的执行时间与输入规模 n 的平方成正比,则称该算法具有平方时间复杂度。例如,对一个长度为 n 的数组进行排序的冒泡排序算法具有平方时间复杂度。
-
对数时间复杂度(O(log n)):如果算法的执行时间与输入规模 n 的对数成正比,则称该算法具有对数时间复杂度。例如,二分查找算法具有对数时间复杂度。
-
指数时间复杂度(O(2^n)或 O(n!)):如果算法的执行时间与输入规模 n 的指数成正比,则称该算法具有指数时间复杂度。这种情况通常发生在算法包含递归或迭代次数与输入规模成指数关系的情况下。指数时间复杂度的算法通常在实际应用中是不可接受的,因为它们对于较大的输入规模可能需要非常长的时间来执行。
空间复杂度
下面是几种常见的空间复杂度计算方法:
-
常数空间复杂度(O(1)):如果算法的执行时间与输入规模 n 无关,即无论输入规模如何,算法的执行时间都是固定的,则称该算法具有常数空间复杂度。例如,简单的赋值操作和基本的数学运算(如加、减、乘、除)通常具有常数空间复杂度。
-
线性空间复杂度(O(n)):如果算法的执行时间与输入规模 n 成正比,则称该算法具有线性空间复杂度。例如,遍历一个长度为 n 的数组或链表的操作通常具有线性空间复杂度。
-
平方空间复杂度(O(n^2)):如果算法的执行时间与输入规模 n 的平方成正比,则称该算法具有平方空间复杂度。例如,对一个长度为 n 的数组进行排序的冒泡排序算法具有平方空间复杂度。
-
对数空间复杂度(O(log n)):如果算法的执行时间与输入规模 n 的对数成正比,则称该算法具有对数空间复杂度。例如,二分查找算法具有对数空间复杂度。
-
指数空间复杂度(O(2^n)或 O(n!)):如果算法的执行时间与输入规模 n 的指数成正比,则称该算法具有指数空间复杂度。这种情况通常发生在算法包含递归或迭代次数与输入规模成指数关系的情况下。指数空间复杂度的算法通常在实际应用中是不可接受的,因为它们对于较大的输入规模可能需要非常长的时间来执行。
常见的空间复杂度仍然以O(1)、O(n)、O(n^2)为主。
3、举例说明
3.1 时间复杂度
常数时间复杂度(O(1))
表示该算法的执行时间(或执行时占用空间)总是为一个常量,不论输入的数据集是大是小,只要是没有循环等复杂结构,那这个代码的时间复杂度就都是O(1)
int i = 1;
int j = 2;
int k = i + j;
上述代码在执行的时候,它消耗的时候并不随着某个变量的增长而增长,那么无论这类代码有多长,即使有几万几十万行,都可以用O(1)来表示它的时间复杂度。
线性空间复杂度(O(n))
表示一个算法的性能会随着输入数据的大小变化而线性变化,如
for (int i = 0; i < n; i++) {
System.out.println(i);
}
这段代码,for循环里面的代码会执行n遍,因此它消耗的时间是随着n的变化而变化的,因此这类代码都可以用O(n)来表示它的时间复杂度。
平方时间复杂度(O(n^2))
表示一个算法的性能将会随着输入数据的增长而呈现出二次增长。最常见的就是对输入数据进行嵌套循环。如果嵌套层级不断深入的话,算法的性能将会变为立方阶O(n^3),O(n^4),O(n^k)以此类推
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
System.out.println(i);
}
}
对数时间复杂度(O(log n))
while (i < n) {
i = i * 2;
System.out.println(i);
}
上面的代码,在while循环里面,每次都将 i 乘以 2,乘完之后,i 距离 n 就越来越近了,直到i不小于n退出。我们试着求解一下,假设循环次数为x,也就是说 2 的 x 次方等于 n,则由2^x=n得出x=logn。因此这个代码的时间复杂度为O(log n)。
指数时间复杂度(O(2^n))
表示一个算法的性能会随着输入数据的每次增加而增大两倍,典型的方法就是裴波那契数列的递归计算实现
public int fibonacci(int n) {
if (n <= 1) {
return n;
}
return fibonacci(n - 1) + fibonacci(n - 2);
}
3.2 空间复杂度
常数时间复杂度(O(1))
如果算法执行所需要的临时空间不随着某个变量n的大小而变化,即此算法空间复杂度为一个常量,可表示为 O(1)
int i = 1;
int j = 2;
int k = i + j;
代码中的 i、j、k 所分配的空间都不随着处理数据量变化,因此它的空间复杂度 S(n) = O(1)
线性空间复杂度(O(n))
int m=new int[n];
for(int i=0;i<n;i++){
j=i;
j++;
}
这段代码中,第一行new了一个数组出来,这个数据占用的大小为n,这段代码的2-6行,虽然有循环,但没有再分配新的空间,因此,这段代码的空间复杂度主要看第一行即可,即 S(n) = O(n) 。
平方时间复杂度(O(n^2))
int m = new int[n];
int squareM = new int[n][n];
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
squareM[i][j] = m[i] * m[j];
}
}
这段代码中,我们首先创建了一个大小为n的整数数组,并初始化每个元素。然后,我们创建了一个二维整数数组,其中每个元素都是原始数组中两个元素的乘积。在这个过程中,我们使用的额外存储空间与输入数据的大小的平方成正比,因此空间复杂度为O(n^2)。
3.3 复杂度曲线
4、总结
在实际应用中,我们通常更关注时间复杂度,因为它对算法的性能影响更大。然而,在某些情况下,例如处理大规模数据集时,空间复杂度也可能非常重要。