目录

  • 运行时间会随着输入(Input)的大小如何变化?
  • 最好的情况:运行时间的上限(最少运行时间)由最简单的输入决定;$提供了所有输入的最终优化目标$
  • 最差的情况:运行时间的下限(最多运行时间)有最复杂的输入决定;$提供了所有输入的保障时间$
  • 平均情况:随机输入的运行时间的期望,需要建立随机输入的模型$是一种评价算法表现的方法$
  • 平均时间很难测定,所以通常情况下关注最差情况下的运行时间
    图片

程序用例

O(1)- (constant常数)
#头 
import time
import matplotlib.pyplot as plt
import random
import math
%matplotlib inline  

def random_list(l):
    return [[int(1000*random.random()) for i in range(l * n)] for n in range(1, 20)]
def square(x):
    return x * x

square(3)
#查看第一个元素
def first(x): # x is a list
    start = time.time()
    r = x[0]
    t = time.time() - start
    return r, len(x), t

#查看中间元素
def middle(x):
    start = time.time()
    r = x[len(x)//2]
    t = time.time() - start
    return r, len(x), t
#查看最后的元素
def last(x):
    start = time.time()
    r = x[-1]
    t = time.time() - start
    return r, len(x), t

Question:查看这三个元素的时间一样吗?
我们通过运行来看一下:

#产生一个很大的数组,并运行很多次
random_lists = random_list(10000)
rst = [last(l) for l in random_lists]
len(rst)
rst

Output:

[(48, 10000, 0.0),
 (716, 20000, 0.0),
 (99, 30000, 0.0),
 (641, 40000, 0.0),
 (216, 50000, 0.0),
 (390, 60000, 0.0),
 (841, 70000, 0.0),
 (674, 80000, 0.0),
 (152, 90000, 0.0),
 (512, 100000, 0.0),
 (260, 110000, 0.0),
 (686, 120000, 0.0),
 (773, 130000, 0.0),
 (450, 140000, 0.0),
 (606, 150000, 0.0),
 (279, 160000, 0.0),
 (877, 170000, 0.0),
 (193, 180000, 0.0),
 (255, 190000, 0.0)]
#画图
x = list(zip(*rst))[1]
y = list(zip(*rst))[2]
plt.plot(x, y)

Output:

截屏2020-01-09上午9.35.18
O(lgn)

典型例子–二分

import time
import matplotlib.pyplot as plt
import random
import math
import bisect
def bs(nums, target):
    sorted(nums)
    start = time.time()
    i = bisect.bisect_left(nums, target)
    if i != len(nums) and nums[i] == target:
        t = time.time() - start
        return i, len(nums), t
    t = time.time() - start
    return -1, len(nums), t
random_lists = random_list(10000)
rst = [bs(l, 100) for l in random_lists]
len(rst)
print(rst)

Output:

[(-1, 10000, 1.0013580322265625e-05),
 (-1, 20000, 9.059906005859375e-06), 
(-1, 30000, 7.152557373046875e-06), 
(-1, 40000, 3.814697265625e-06), 
(-1, 50000, 1.0013580322265625e-05),
 (-1, 60000, 1.1205673217773438e-05), 
 (-1, 70000, 1.0967254638671875e-05), 
 (-1, 80000, 1.3113021850585938e-05),
  (-1, 90000, 1.1682510375976562e-05), 
  (-1, 100000, 1.1682510375976562e-05), 
  (-1, 110000, 1.2159347534179688e-05), 
  (-1, 120000, 1.5020370483398438e-05), 
  (-1, 130000, 1.1920928955078125e-05), 
  (-1, 140000, 1.621246337890625e-05), 
  (-1, 150000, 1.5020370483398438e-05), 
  (-1, 160000, 1.4066696166992188e-05), 
  (-1, 170000, 1.3113021850585938e-05),
   (-1, 180000, 1.2159347534179688e-05), 
   (-1, 190000, 4.982948303222656e-05)]

绘图:

x = list(zip(*rst))[1]
y = list(zip(*rst))[2]
plt.plot(x, y)

输出:

截屏2020-01-09上午9.48.16

可以看出时间使增加的,但是增加相当缓慢,近似于一条直线,所以时间复杂度为o(lgn)是一种很好的算法.
O(n)
def find_max(l):
    start = time.time()

    if l == None:
        return None
    mx = l[0]
    for n in l:
        if n > mx:
            mx = n

    t = time.time() - start

    return mx, len(l), t
random_lists = random_list(20000)
rst = [find_max(l) for l in random_lists]
len(rst)
print(rst)

Output:

[(999, 20000, 0.0010004043579101562),
 (999, 40000, 0.0019998550415039062),
 (999, 60000, 0.003000020980834961),
 (999, 80000, 0.01699995994567871),
 (999, 100000, 0.005000114440917969),
 (999, 120000, 0.010999917984008789),
 (999, 140000, 0.01399993896484375),
 (999, 160000, 0.00800013542175293),
 (999, 180000, 0.010999679565429688),
 (999, 200000, 0.012000322341918945),
 (999, 220000, 0.00800013542175293),
 (999, 240000, 0.008999824523925781),
 (999, 260000, 0.015000104904174805),
 (999, 280000, 0.009999752044677734),
 (999, 300000, 0.015000104904174805),
 (999, 320000, 0.034999847412109375),
 (999, 340000, 0.017000198364257812),
 (999, 360000, 0.017999887466430664),
 (999, 380000, 0.014000177383422852)]

绘图:

x = list(zip(*rst))[1]
y = list(zip(*rst))[2]
plt.plot(x, y)
-w386
O(nlgn)
def mysort(l):
    start = time.time()
    l.sort()
    t = time.time() - start
    return l[0], len(l), t
random_lists = random_list(10000)
rst = [mysort(l) for l in random_lists]
len(rst)
print(rst)

Output:

[(0, 10000, 0.005000114440917969),
 (0, 20000, 0.010999917984008789),
 (0, 30000, 0.011999845504760742),
 (0, 40000, 0.01900005340576172),
 (0, 50000, 0.026999950408935547),
 (0, 60000, 0.03299999237060547),
 (0, 70000, 0.03600001335144043),
 (0, 80000, 0.04200005531311035),
 (0, 90000, 0.04200005531311035),
 (0, 100000, 0.06599998474121094),
 (0, 110000, 0.06800007820129395),
 (0, 120000, 0.06099987030029297),
 (0, 130000, 0.10300016403198242),
 (0, 140000, 0.06999993324279785),
 (0, 150000, 0.08099985122680664),
 (0, 160000, 0.08900022506713867),
 (0, 170000, 0.09499979019165039),
 (0, 180000, 0.09100008010864258),
 (0, 190000, 0.10500001907348633)]

绘图:

x = list(zip(*rst))[1]
y = list(zip(*rst))[2]
plt.plot(x, y)
-w408
O(n^2)
def has_duplicate(l):
    start = time.time()

    rst = False
    for i in range(len(l)):
        for j in range(i + 1, len(l)):
            if l[i] == l[j]:
                rst = True

    t = time.time() - start
    return rst, len(l), t
random_lists = random_list(100)
rst = [has_duplicate(l) for l in random_lists]

x = list(zip(*rst))[1]
y = list(zip(*rst))[2]
plt.plot(x, y)
-w381

评估算法运行时间

理论分析
  • 归纳算法本质,而不是采用流程模式
  • 建立运行函数关于输入大小(n)的函数
  • 考虑各种可能的输入
  • 在硬件和软件上分别评估算法的速度
  • 伪代码:
    找到一个数组中的最大数字:

    #伪代码
    Algorithm arrayMax(A,n)--------------#(operation)
    currentMax currentMax --------------2(n-1)
            currentMax
  • 算法基本操作
    1.评估表达式
    2.赋值给变量
    3.数组索引
    4.调用方法
    5.从一个方法中得到返回值
  • 通过检查伪代码,我们可以找到被算法执行的基本操作的最大数字,也就是找到关于输入大小的函数
    1.找出算法中的基本语句
    2.计算基本语句执行次数的数量级
    3.用大写O表示算法的时间性能
  • 算法arrayMax在最差情况下执行了8n-3个基本操作。定义:
    1.a = 最快的基本操作所需要的运行时间
    2.b = 最慢的基本操作所需要的运行时间
  • 令T(n)为arrayMax的运行时间。则应有:
    a(8n-3)$\leq$T(n)$\leq$b(8n-3)
  • 因此,运行时间T(n)由两条线性函数所划定的范围
近似记法
Big - O 记法
  • 一般习惯用Θ (n)记法来渐进界定算法运行时间的常数函数边界,有时,我们只希望这个常用函数代表算法运行时间的上界。
  • 尽管在最差情况下二分法搜索的运行时间为Θ (lgn),认为在所有情况下二分法搜索的搜索时间为 Θ (lgn) 是错误的。
  • 二分法搜索的时间从来不会超过 Θ (lgn),多数情况下其搜索时间都会少于Θ (lgn)
    -w400

横坐标:Input Size
纵坐标:Time
如图,存在$n_0$,当$n\geq n_0$,存在常数k,使得$k\cdot f(n) > running time$,则称红色线为$O(f(n))$ 。简单理解即$k\cdot f(n)$为上界。

Small - O

*只需$k\cdot f(n)\geq running time$

Big - $\Theta$
  • 当特别说明运行时间为 Θ (n)时: 当n变得很大的时候,算法的运行时间介于k1·n 和k2·n之间,其中k1和k2 是常数。
    -w352
Big - $\Omega$
  • 有的算法我们只能描述其至少要运行多少时间而无法给出其运行时间的上限时,采用Big - $\Omega$记法。
  • 如果算法的运行时间是$\Omega(f (n))$, 则对于足够大的n来说,运行的时间至少是k·f (n),其中k为常数。
时间复杂度
  • 一般情况下,算法中基本操作重复执行的次数是问题规模n的某个函数,用$T(n)$表示,若有某个辅助函数$f(n)$ ,使得当n趋近于无穷大时,$\frac{T(n)}{f(n)}$的极限值为不等于零的常数,则称$f(n)$是$T(n)$的同数量级函数。 记作$T(n)=O(f(n))$,称$O(f(n))$为算法的渐进时间复杂度,简称时间复杂度。
  • $T (n) = Ο(f (n))$ 表示存在一个常数C,使得在当n趋于正无穷时总有 $T (n) ≤ C * f(n)$。简单来说,就是T(n)在n趋于正无穷时最大也就跟$f(n)$差不多大。也就是说当n趋于正无穷时T (n)的上界是$C \times f(n)$。
记法总结
-w663