我们知道衡量算法好坏的两个因素分别是空间复杂度和时间复杂度。首先我们来看看空间复杂度。3 v. u2 r) f) l+ A
4 }$ ^- P j& Y/ @/ g5 x4 Z X那么,什么是空间复杂度呢?我们知道,时间复杂度是执行算法的时间成本,那么,笼统来讲,空间复杂度就是算法执行的空间成本。在运行一段程序时,我们不仅要执行各种运算指令,同时也会根据需要,存储一些临时的中间数据,以便后续指令可以更方便地继续执行。那么存储这些中间数据所需要地成本也属于算法的空间复杂度。那么什么时候需要存储一些中间数据呢?举例来说:有一个需求,需要从给定的一列数中找出重复的数字。那么针对这个需求,最朴素的方法就是双重循环,即遍历整个数列,每遍历到一个新的整数就开始回溯之前遍历过的所有整数,看看遍历过的这些整数里有没有与待遍历的数值相同的。假如给定的数列是:3、1、2、5、4、9、7、2。' Q- X, J: p b4 v" {2 B }6 ]
$ M! {0 w8 _% c7 H' V4 }5 Z 5 I: S. V! j Y7 |4 t
6 F5 O8 W7 J5 j
以上需求使用双重循环当然可以得到最终结果,但是其时间复杂度为。对我们来说,这个时间复杂度还是有些高的,我们需要改变算法来提高效率,最简单的方法就是使用中间数据来提高效率。 那么如何利用中间数据呢?还是以上述数列为例,当遍历整个数列时,每遍历一个整数,就把该整数存储起来,就像放到字典中一样。当遍历下一个整数时,不必再去回溯向前比较,而是直接去字典中查找,看看有没有对应的整数即可。那么遍历上述数列之后得到的一个字典结构的数据为: |0 c0 x1 s. a& A, [ ! J" ~# M5 V/ j) | " i. X) T. O w) t: _2 Y/ t/ e该字典结构的数据左边的 key 代表整数的值,右边的 value 代表整数出现的次数。当遍历到最后一个整数 2 时,发现 2 在字典中已经出现过,那么就证明该数列中重复的数字是 2。 % w- }; N$ {& g( _; G9 Q& m% I# k% P6 Q. r$ _9 j, d
由于读写字典本身的时间复杂度是,所以整个算法的时间复杂度是,和最初的双重循环的方法相比,效率大幅度提高了。上述提到的字典结构的数据,其实是一种特殊的数据结构,叫“散列表”。这个数据结构需要开辟一定的内存空间来存储有用的数据信息。但是内存空间是有限的,在时间复杂度相同的情况下,算法占用的空间自然是越小越好,即空间复杂度越小越好。和时间复杂度类似,空间复杂度是对一个算法在运行过程中临时占用存储空间大小的量度,它同样使用大 O 表示法。程序占用空间大小的计算公式为,其中 n 为问题的规模,f(n) 为算法所占用存储空间的函数。6 v& r' a5 Y2 S- E- q; h! f
. A* n$ o& N3 O0 e* f
那么空间复杂度该如何计算呢?和时间复杂度类似,空间复杂度也有几种不同的增长趋势。常见的空间复杂度有下面几种情形。* b& C2 N2 j% ?5 d3 k* y! _) `3 e0 A
2 l |* j2 s a. H" ^5 ?# x场景一:常量空间。当算法的存储空间大小固定,和输入规模没有直接的关系时,空间复杂度记作。 6 | L( p+ E4 X6 M 9 M) Z* I! |- i' Y' W( y( N场景二:线性空间。当算法分配的空间是一个线性的集合时,比如数组,并且集合大小和输入规模 n 成正比时,空间复杂度记作。5 \) `: c6 ^5 s7 F
1 B0 \9 F$ R j, @5 Y场景三:二维空间。当算法分配的空间是一个二维数组集合,并且集合的长度和宽度都与输入规模 n 成正比时,空间复杂度记作。, ]7 |2 x( ~! x- L" _- x5 w+ s