在上一节中,我们讨论了渐进分析是如何解决算法的朴素分析问题的。在本节中,我们以线性搜索为例,通过渐进分析来研究这个算法。
我们通过三种情况来分析一个算法:
- 最坏情况
- 平均情况
- 最佳情况
我们来看线性搜索的代码实现
public class GFG {
static int search(int[] arr, int n, int x) {
int i;
for (i = 0; i < n; i++) {
if (arr[i] == x) return i;
}
return -1;
}
public static void main(String[] args) {
int[] arr = { 1, 10, 30, 15 };
int x = 30;
int n = arr.length;
System.out.printf("%d is present at index %d", x, search(arr, n, x));
}
}
Output:
30 is present at index 2
最坏情况分析
在最坏情况下,我们计算算法运行时间的上界。我们必须知道操作数被执行最多次的情况。对于线性搜索来说,最坏的情况发生在被查找的元素(代码中的 x x x)不在数列中的时候。当不存在 x x x 时,search() 方法必须逐个比较 arr 中的所有元素。因此,最坏的时间复杂度为 O ( N ) O(N) O(N)。
平均情况分析
在平均情况下,我们获取所有可能的输入值并计算所有输入值的运行时间。将所有的计算结果相加并除以输入值的总数。我们必须清楚(或者预测)它们的分布情况。对于线性搜索问题,我们假设所有的情况是均匀分布的(包括
x
x
x 不在数列中的情况)。所以我们把所有结果相加并除以
n
+
1
n + 1
n+1。如下即为平均时间复杂度的值。
T
i
m
e
=
∑
i
=
1
n
+
1
O
(
i
)
n
+
1
=
O
(
(
n
+
1
)
∗
(
n
+
2
)
/
2
)
n
+
1
=
O
(
n
)
Time=\frac{\sum_{i=1}^{n+1}O(i)}{n+1}=\frac{O((n+1)*(n+2)/2)}{n+1}=O(n)
Time=n+1∑i=1n+1O(i)=n+1O((n+1)∗(n+2)/2)=O(n)
最佳情况分析
在最好情况下,我们计算算法运行时间的下界。我们必须知道操作数被执行最少次的情况。对于线性搜索来说,最好的情况就是 x x x 在数列的第一个位置。最好情况下操作数的次数是常数(与 n n n 无关)。所以最佳时间复杂度为 O ( 1 ) O(1) O(1)。
通常,我们通过最坏情况来分析算法。通过最坏情况分析,我们能够保证一个算法的运行时间上限。
平均情况在实际运用中不容易操作并且它很少用到。在平均情况分析中,我们必须了解(或者预测)所有输入值的数学分布。
最佳情况是不准确的。知道一个算法的下界并不能像最坏情况那样提供有效信息,这个算法可能会花上好几年才能运行出结果。
对于一些算法来说,所有情况下都是渐近相同的,也就是说没有最好最坏之分,例如归并排序。归并排序在所有情况下的复杂度都为
O
(
N
l
o
g
N
)
O(NlogN)
O(NlogN)。而其他大部分的排序算法都存在最好和最坏的情况。例如,快速排序的一般实现中(边界元素作为 pivot),最坏情况是输入的数列已经是有序的,最好情况是 pivot 元素始终能把数列平分为两部分。对于插入排序,最坏情况是数列是逆序的,最好情况则是数列与期望的顺序一致。