首先,这是一个非常好的问题。人们通常会忘记一个关键步骤:确定基本操作。
时间效率的理论分析
通过将基本操作的重复次数确定为输入大小的函数来分析时间效率。
基本操作:对算法运行时间贡献最大的操作。
我们尝试选择可以在 O(1) 时间或接近该时间执行的 基本操作 的东西,实际上我们选择的通常是次线性操作。例如:
- 为变量赋值
- 在数组中查找特定元素的值
- 比较两个值
- 增加一个值
- 基本算术运算,例如加法和乘法
例如,基本算术运算显然不是 O(1)。请参阅计算机科学的这个问题:https://cs.stackexchange.com/questions/1643/how-can-we-assume-that-basic-operations-on-numbers-take-constant-time
因此,在决定基本操作时,我们近似 O(1)。
设C(n)为基本操作需要执行的次数。并设execTime为特定计算机上基本操作的执行时间。
那么我们可以通过以下方式估计算法的运行时间T(n):
T(n) ≈ execTime * C(n)
如您所见,效率等级(例如 Big-O)仅取决于 C(n),因为无论如何 execTime 将是恒定的(而我们不这样做) t 关心渐近分析中的常数)。所以,任务是找到C(n)。
代码分析
首先我们决定输入参数,显然是N。
然后我们决定基本操作,这就是与众不同的部分。
如果我们决定将X(N);作为基本运算,那么:
T(N) ≈ execTime * C(N)
C(N) = N
O(execTime * N) = O(n)
如果我们知道 X(N) 内部发生了什么,我们可以决定一个 基本操作,它在每个 N 次中发生 N em>X(N) 运算例如,则:
T(N) ≈ execTime * C(N)
C(N) = N * N
O(execTime * N * N) = O(N * N) = O(n^2)
我们也可以选择整个循环作为基本操作:
T(N) ≈ execTime * C(N)
C(N) = 1
O(execTime * 1) = O(1)
那么哪一个是正确的?他们都是。哪一个有意义?这完全取决于您要进行什么比较。
例如比较排序算法的性能时,基本操作就是key比较,因为排序算法中最重要的工作就是比较key,而所有排序算法做key比较,这样我们就可以互相比较了。
总结一下,你可以选择X(N)作为基本运算,说算法复杂度是O(N)。 X(N) 不一定是严格的 O(1),正如我上面解释的,我们已经忽略了比 O(1) 更复杂的基本操作。这里真正重要的是您要进行什么比较。
假设您想比较这两种算法。
for(int i = 0; i < N; ++i)
X(N);
和
for(int j = 0; j < N; j++)
for(int i = 0; i < N; ++i)
X(N);
这里,如果你选择X(N)作为两者的基本运算,你会比较苹果和苹果,你会评估O(N) vs O(N^2) 会完美很好。
如果你觉得这是错误的,因为 X(N) 不是 O(1),请考虑这个:
for(int i = 0; i < N; ++i)
System.out.print(N);
来自 Java 的简单打印方法。我们不假思索地选择它作为基本操作,因为它只是一个打印语句,并假设它是O(1)。但事实并非如此。它的复杂性是次线性的,但绝对不是恒定的。打印所需的时间随着打印项目的大小(在本例中为 N)的增长而增长。
那么,X(N) 还是 System.out.println(N),有关系吗?
附:我没有发明上面的近似技术。我确实使用this 书准备了答案,主要是第二章。我建议你看看。