复杂度也称为渐进复杂度,包括渐进时间复杂度和渐进空间复杂度,描述算法随数据规模变化而逐渐变化的趋势。复杂度分析是评估算法好坏的基础理论方法,因此掌握好复杂度分析方法是颇有必要的。算法
首先,学习数据结构是为了解决“快”和“省”的问题,那么如何去评估算法的速度快和省空间呢?这就须要掌握时间和空间复杂度分析。同一段代码运行在不一样环境、不一样配置机器、处理不一样量级数据…效率确定不会相同。时间复杂度和空间复杂度是不运行代码,从理论上粗略估计算法执行效率的方法。时间复杂度通常用O来表示,以下例子:计算1,2,3…n的和。CPU执行每行代码时间很快,假设每行执行时间都同样为unit_time,第2行为一个unit_time,第三、4行都执行了n遍,那么下面这段代码执行的耗时时间能够这么计算:(1+2*n) * unit_time。数组
1 public int sum(int n) { 2 int sum = 0; 3 for (int i = 1; i <= n; i++) { 4 sum = sum + i; 5 } 6 return sum; 7 }
相似的再看一个例子:数据结构
1 public int sum(int n) { 2 int sum = 0; 3 int i = 1; 4 int j; 5 for (; i <= n; i++) { 6 j = 1; 7 for (; j <= n; j++) { 8 sum = sum + i * j; 9 } 10 } 11 return sum; 12 }
第二、三、4行分别执行执行了一次,时间为3unit_time,第五、6两行循环了n次为2n * unit_time,第七、8两行执行了n*n次为(n²) * unit_time,因此总的执行时间为:(2n²+2n+3) * unit_time学习
能够看出来,全部代码执行时间T(n)与每行代码执行次数成正比。能够用以下公式来表示:spa
T(n) = O(f(n))code
T(n)表示代码的执行时间;blog
n表示数据规模大小;it
f(n)表示每行代码执行的次数和,是一个表达式;class
O表示执行时间T(n)和f(n)表达式成正比效率
那么上面两个时间复杂度能够表示为:
T(n) = O(1+2*n) 和 T(n) = O(2n²+2n+3)
实际上O并不表示具体的执行时间,只是表示代码执行时间随数据规模变化的趋势,因此时间复杂度其实是渐进时间复杂度的简称。当n很大时,系数对结果的影响很小能够忽略,上面两个例子的时间复杂度能够粗略简化为:
T(n) = O(n) 和 T(n) = O(n²)
由于时间复杂度是表示的一种趋势,因此经常忽略常量、低阶、系数,只须要最大阶量级就能够了。
上面例子能够看出,复杂度忽略了低阶、常量和系数,因此执行最多的那一段最能表达时间复杂度的趋势。
仍是上面的例子,总的时间复杂度等于各部分代码时间复杂度的和,求和以后再用最能表达趋势的项来表示整段代码的时间复杂度。
上面第二段代码,j 循环段嵌套在 i 循环内部,因此 j 循环体内的时间复杂度等于单独 i 的时间复杂度乘以单独 j 的时间复杂度。
常见的复杂度有如下几种
能够这么来理解:若是一段代码有1000或10000行甚至更多,行数是一个常量,不会随着数据规模增大而变化,咱们就认为时间复杂度为一个常量,用O(1)表示。
这几种复杂度效率曲线比较
模拟一个数组动态扩容例子,若是数组长度够,直接往里面插入一条数据;反之,将数组扩充一倍,而后往里面插入一条数据:
1 int[] arr = new int[10]; 2 int len = arr.length; 3 int i = 0; 4 public void add(int item) { 5 if (i >= len) { 6 int[] new_arr = new int[len * 2]; 7 for (int i = 0; i < len; i++) { 8 new_arr[i] = arr[i]; 9 } 10 arr = new_arr; 11 len = arr.length; 12 } 13 arr[i] = item; 14 i++; 15 }
最好状况下某个算法的时间复杂度。最好状况下,数组空间足够,只须要执行插入数据就能够了,此时时间复杂度是O(1)。
最坏状况下某个算法的时间复杂度。最坏状况下数组满了,须要先申请一个空间为原来两倍的数组,而后将数据拷贝进去,此时时间复杂度为O(n)。通常状况下咱们说算法复杂度就是指的最坏状况时间复杂度,由于算法时间复杂度不会比最坏状况复杂度更差了。
最好时间复杂度和最坏时间复杂度都是极端状况下的时间复杂度,发生的几率并不算很大。平均时间复杂度是描述各类状况下平均的时间复杂度。上面的动态扩容例子将1到n+1次为一组来分析,前面n次的时间复杂度都是1,第n+1次时间复杂度是n,将一个数插入数组里的1 至 (n+1)个位置几率都为1/(n+1),因此平均时间复杂度为:
O(n) = (1 + 1 + 1 + …+n)/(n+1) = O(1)
对一个数据结构进行一组连续的操做中,大部分状况下时间复杂度都很低,只有个别状况下时间复杂度比较高,并且这些操做之间存在先后连续的关系。而且和这组数据类型的状况循环往复出现,这时候能够将这一组数据做为一个总体来分析,看看是否能够将最后一个耗时的操做复杂度均摊到其余的操做上,若是能够,那么这种分析方法就是均摊时间复杂度分析法。上面的例子来说,第n+1次插入数据时候,数组恰好发生扩容,时间复杂度为O(n),前面n次恰好将数组填满,每次时间复杂度都为O(1),此时能够将第n+1次均摊到前面的n次上去,因此总的均摊时间复杂度仍是O(1)。
类比时间复杂度,以下代码所示,第2行申请了一个长度为n的数据,第三行申请一个变量i为常量能够忽略,因此空间复杂度为O(n)
1 public void init(int n) { 2 int[] arr = new int[n]; 3 int i = 0; 4 for (; i < n; i++) { 5 arr[i] = i + 1; 6 } 7 }
通常状况下,一个程序在机器上执行时,除了须要存储程序自己的指令、常数、变量和输入数据外,还须要存储对数据操做的存储单元,若输入数据所占空间只取决于问题自己,和算法无关,这样只须要分析该算法在实现时所需的辅助单元便可。若算法执行时所需的辅助空间相对于输入数据量而言是个常数,则称此算法为原地工做,空间复杂度为O(1)。