关注公众号 MageByte,设置星标点「在看」是咱们创造好文的动力。后台回复 “加群” 进入技术交流群获更多技术成长。 本文来自 MageByte-青叶编写
上次咱们说过 时间复杂度与空间复度,列举了一些分析技巧以及一些常见的复杂度分析好比 O(1)、O(logn)、O(n)、O(nlogn),今天会继续细化时间复杂度。java
1. 最好状况时间复杂度(best case time complexity)算法
2.最坏状况时间复杂度(worst case time complexity)数组
3. 平均状况时间复杂度(average case time complexity)数据结构
4.均摊时间复杂度(amortized time complexity)函数
public int findGirl(int[] girlArray, int number) { int i = 0; int pos = -1; int n = girlArray.lentgh(); for (; i < n; ++i) { if (girlArray[i] == number) { pos = i; break; } } return pos; }
代码逻辑你应该很容易看出来,在无序数组 中查找 number 出现的位置,若是没找到就返回 -1。《唐伯虎点秋香》主角星爷经过这个方法遍历数组找到秋香,由于此刻咱们尚未学会各类风骚的算法,只能从头至尾查验是否是秋香,因此只能遍历数组。girlArray 数组保存着秋香、冬香、春香……的编码,如今唐伯虎经过 选择 number 这个编码比对是不是秋香。学习
这段代码在不一样的状况下,时间复杂度是不同的,因此为了描述代码在不一样状况下的不一样时间复杂度,咱们引入了==最好、最坏、平均时间复杂度==。n = girlArray 数组的长度。编码
在最理想的状况下,执行这段代码的时间,也就是「唐伯虎」最快点中秋香。假如 这一排姑娘就表明 girlArray 数组,number 变量就是秋香的编码。假如第一个姑娘就是「秋香」那时间复杂度就是 O(1)。spa
在最糟糕的状况下,执行这段代码的时间复杂度。也就是要一个个查验真个数组的长度 O(n)。code
其实最好与最坏状况是极端状况,发生的几率并不大。因此为了更准确的表示平均状况下的时间复杂度,引入另外一个改变:平均状况时间复杂度。blog
仍是上面的「找秋香」代码,判断 number 编码在循环中出现的位置,有 ==n + 1==种状况:
在数组 0~n-1 中和不在这个数组中。在数组中共有 n 种状况,加上不在数组中则就是 n + 1 种了。 每种状况要遍历的姑娘人数都不一样。咱们把每种状况须要查找姑娘的数量累加,而后再除以 全部状况数量 (n + 1),就获得须要遍历次数的平均值。敲黑板了:公式就是平均状况复杂度 = 累加每种遍历的元素个数 / 全部的状况数量
平均状况复杂度为:
$$\frac {((1+2+3… +n) + n)} {(n+1)} = \frac {n(n+3)} {2(n+1)}$$
推导过程:
$$\because 1+2+3 …+ n = n + (n-1) + (n-2)… + 1$$
$$\therefore (1 +2 +3… + n) = \frac {n(1+n)} {2}$$
$$\therefore (1+2+3+…+n) + n = \frac {n(3+n)} {2}$$
根据咱们以前学的 时间复杂度与空间复度 大 O 表示法,省略系数、地接、常量,因此平均状况时间复杂度是 O(n)。
上面的平均状况时间复杂度推导没有考虑每种状况的发生几率,这里的 n+1 种状况,每种状况发生的几率是不同的,因此还要引入各自发生的几率再具体分析。
秋香的编号 number 要么在 0 ~ n-1 中,要么不在 0~n-1 中,因此他们的几率是 $\frac {1} {2}$。
同时 number 在 0~n-1 各个位置的几率是同样的为 1/n。根据几率乘法法则,number 在 0~n-1 中任意位置的几率是 $$\frac {1} {2n}$$。
因此在前面推导的基础上,咱们再把每种状况发生的几率考虑进去,那么平均状况时间复杂度的计算过程就是:
考虑几率的平均状况复杂度:
$$(1 \frac {1} {2n} + 2 \frac {1} {2n}+ 3 \frac {1} {2n}…+n\frac {n} {2n} ) + n \frac {1} {2} = \frac {3n+1} {4}$$
这就是几率论中的加权平均值,也叫作指望值,因此平均时间复杂度全称叫:加权平均时间复杂度或者指望时间复杂度。
引入几率以后,平均复杂度变为 O($$\frac {3n+1} {4}$$),忽略系数以及常量,最后获得的加权平均时间复杂度为 O(n)。终于分析推导完了,同窗们能够松一口气。
注意:
多数状况下,咱们不须要区分最好、最坏、平均状况时间复杂度。只有同一块代码在不一样状况下时间复杂度有量级差距,咱们才会区分 3 种状况,为的是更有效的描述代码的时间复杂度。
最后一个硬骨头来了,了解了上面加上几率的指望时间复杂度再看这个就容易多了。均摊时间复杂度,听起来跟平均时间复杂度有点儿像。
均摊复杂度是一个更加高级的概念,它是一种特殊的状况,应用的场景也更加特殊和有限。
对应的分析方式称为:摊还分析或平摊分析。
// array 表示一个长度为 n 的数组 // 代码中的 array.length 就等于 n int[] array = new int[n]; int count = 0; public void insert(int val) { if (count == array.length) { int sum = 0; for (int i = 0; i < array.length; ++i) { sum = sum + array[i]; } array[0] = sum; count = 1; } array[count] = val; ++count; }
代码逻辑:向一个数组插入数据,当数组满了后 count == array.lenth,遍历数组求和,将求和以后的 sum 值放到数组的第一个位置,而后再将新的数据插入。但若是数组一开始就有空闲空间,则直接将数据插入数组。这里的数据满:对于可反复读写的存储空间,使用者认为它是空的它就是空的。若是你定义清空是所有重写为 0 或者某个值,那也能够!使用者只关心要存的新值!
分析上述的时间复杂度:
平均时间复杂度
数组长度为 n,由于能够插入不一样位置,因此有 n 种状况,每种复杂度为 O(1)。
还有一种特殊状况,没有空闲空间插入的时候,复杂度是 O(n),一共就是 n+1 种状况,且每种状况的几率都是 $$\frac{1} {n+1}$$。因此根据加权平均计算法,平均时间复杂度:
$$(1 \frac {1} {n+1} + 1 \frac {1} {n+1}+ 1 \frac {1} {n+1}…+1\frac {1} {n+1} ) + n \frac {1} {n+1} = \frac {2n} {n+1}$$
当省略系数及常量后,平均时间复杂度为 O(1)。
其实咱们不须要这么复杂,对比 findGirl 跟 insert 方法。
摊还分析法
分析上述示例的平均复杂度分析并不须要如此复杂,无需引入几率论的知识。
由于经过分析能够看出,上述示例代码复杂度大多数为 O(1),极端状况下复杂度才较高为 O(n)。同时复杂度遵循必定的规律,通常为 1 个 O(n),和 n 个 O(1)。针对这样一种特殊场景使用更简单的分析方法:摊还分析法。
经过摊还分析法获得的时间复杂度为均摊时间复杂度。
大体思路:每一次 O(n)都会跟着 n 次 O(1),因此把耗时多的复杂度均摊到耗时低的复杂度。获得的均摊时间复杂度为 O(1)。
应用场景:均摊时间复杂度和摊还分析应用场景较为特殊,对一个数据进行连续操做,大部分状况下时间复杂度都很低,只有个别状况下时间复杂度较高。而这组操做其存在先后连贯的时序关系。
这个时候咱们将这一组操做放在一块儿分析,将高复杂度均摊到其他低复杂度上,因此通常均摊时间复杂度就等于最好状况时间复杂度。
注意: 均摊时间复杂度是一种特殊的平均复杂度(特殊应用场景下使用),掌握分析方式便可。
均摊时间复杂度就是一种特殊的平均时间复杂度,咱们不必花太多精力去区分它们。你最应该掌握的是它的分析方法,摊还分析。至于分析出来的结果是叫平均仍是叫均摊,这只是个说法,并不重要。
最后留一个问题给你们,用本文学习的只是分析下面代码的「最好」、「最坏」、「均摊」时间复杂度。
/ 全局变量,大小为 10 的数组 array,长度 len,下标 i。 int array[] = new int[10]; int len = 10; int i = 0; // 往数组中添加一个元素 void add(int element) { if (i >= len) { // 数组空间不够了 // 从新申请一个 2 倍大小的数组空间 int new_array[] = new int[len*2]; // 把原来 array 数组中的数据依次 copy 到 new_array for (int j = 0; j < len; ++j) { new_array[j] = array[j]; } // new_array 复制给 array,array 如今大小就是 2 倍 len 了 array = new_array; len = 2 * len; } // 将 element 放到下标为 i 的位置,下标 i 加一 array[i] = element; ++i; }
整体的含义就是向数组添加一个元素,当空间不够的时候从新生情一个原来两倍空间的数组并把原来的数组数据依次复制到新数组中。
其实同窗们这里还能够拓展到 HashMap 的拓容,当元素大刀负载因子 0.75 的容量,HashMap 须要拓容为原来的两倍而后再从新 把元素放到新数组中。那么时间复杂度又是多少呢?
关注公众号 MageByte 后台回复 「add」获取本题目答案,也能够回复「加群」加入技术群跟咱们一块儿分享你的见解,咱们第一是时间反馈。
参考文献:《数据结构与算法之美》