前缀平均值(Prefix Averages)的三种算法实现分析

概念介绍

对于Python中给定的序列(list),同时存在一个前缀平均值序列。由n个数字组成的序列S,计算一个序列A,A[j]是S[0], S[1],……,S[j]的平均值,j = 0,……,n - 1。即:$ A[j] = \frac{\sum_{i = 0}^{j} S[i]}{j + 1} $。

实现一

来看第一种实现方式:

def prefix_average1(S):
    """Return list such that, for all j, A[j] equals average of S[0], ..., S[j]."""
    n = len(S)
    A = [0] * n                   # create new list of n zeros
    for j in range(n):
        total = 0                 # begin computing S[0] + ... + S[j]
        for i in range(j + 1):
            total += S[i]
        A[j] = total / (j + 1)    # record the average
    return A

分析:

  • n = len(S), 执行恒定的时间。
  • A = [0] * n,初始化序列长度为n,元素的所有值为0,因此,其时间复杂度为O(n)。
  • 接着是双层嵌套,外层循环由j控制,执行n次,j = 0,…,n - 1。total = 0和A[j] = total / (j + 1)分别执行n次。因此外层循环的时间复杂度为O(n)。
  • 再来看内层循环,total += S[i],执行次数由i和j共同确定。总共执行1+2+3+…+n = n(n + 1) / 2,因此,总得执行次数为O($ n^2 $)。

综上分析,整个算法的时间复杂度为O($ n^2 $)

实现二

来看第二种实现方式:

def prefix_average2(S):
    """Return list such that, for all j, A[j] equals average of S[0], ..., S[j]."""
	n = len(S)
	A = [0] * n                                # create new list of n zeros
	for j in range(n)::
	    A[j] = sum(S[0: j + 1]) / (j + 1)      # record the average
	return A

与实现一不同的是,实现二用了一个更简单的计算S[0] + ... + S[j]的方式,即sum(S[0: j + 1])。使用这种方式虽然简化了算法的表达,但仍需要考虑其内部的算法时间复杂度。
观察sum(S[0: j + 1]),首先使用list的切片操作获取子序列,创建一个长度为j + 1的序列,则其时间复杂度为O(j + 1)。则对于循环来说,A[j] = sum([0: j + 1])的时间复杂度仍然为1 + 2 + … + n。
综上分析,则总得时间复杂度仍为O($ n ^ 2 $)

实现三

最后一种实现方式:

def prefix_average3(S):
    """Return list such that, for all j, A[j] equals average of S[0], ..., S[j]."""
	n = len(S)
	A = [0] * n                                # create new list of n zeros
	total = 0                                  # compute prefix sum as S[0] + S[1] + ...
	for j in range(n):
	    total += S[j]                          # update prefix sum to include S[j]
	    A[j] = total / (j + 1)                 # compute average based on current sum
	return A

在前两种算法中,对于j的每个值重新计算前缀和。对于每个j,则花费了O(j)的运算时间,总共加起来,则是O($ n ^ 2 $)的时间复杂度。
第三个算法的分析:

  • 初始化变量n和total,时间复杂度为O(1)。
  • 初始化序列A,时间复杂度为O(n)。
  • 对于单个for循环,其时间复杂度为O(n)。
  • 循环体执行n次,j = 0, ..., n -1。total += S[j]和A[j] = total / (j + 1)则分别执行n次。

综上分析,该算法的时间复杂度为O(n)

总结

由此可知,不同的算法实现,会有不同的算法时间复杂度。而随着n的增长,算法时间复杂度的差值会显著增长,这对于实际软件项目的优化也是挺可观的。

posted @ 2018-06-05 16:29  Jeffrey_Yang  阅读(1347)  评论(0编辑  收藏  举报