算法分析和大O简介

在本文中,我们将讨论如何分析算法以及为什么这样做很重要!

为什么要分析算法?

在我们开始之前,让我们澄清一下什么是算法。在本文中,算法只是解决问题的过程或公式。有些问题非常有名,以至于算法都有名字,而有些程序非常常见,以至于与之相关的算法也有名字。所以现在我们有一个很好的问题需要回答:

如何分析算法以及如何比较算法?

想象一下,如果你和一个朋友都想出了从0到N的求和函数。如何比较函数和函数中的算法?让我们假设你俩都有这两个独立的功能:

def sum1(n):
    '''
    Take an input of n and return the sum of the numbers from 0 to n
    '''
    final_sum = 0
    for x in range(n+1): 
        final_sum += x
    
    return final_sum
sum1(10)
# 55
def sum2(n):
    """
    Take an input of n and return the sum of the numbers from 0 to n
    """
    return (n*(n+1))/2
sum2(10)
# 55.0

你会注意到这两个函数有相同的结果,但算法完全不同。你会注意到,第一个函数迭代地添加数字,而第二个函数使用:
在这里插入图片描述
那么,我们如何客观地比较这些算法呢?我们可以比较它们在内存中占用的空间量,或者我们也可以比较每个函数运行所需的时间。我们可以使用jupyter中内置的%timeit魔术函数来比较函数的时间。Jupyter Notebooks中的%timeit魔术将重复循环迭代一定次数并获得最佳结果。

让我们继续比较运行函数所花费的时间:

%timeit sum1(100)
# 5.17 µs ± 94.4 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
%timeit sum2(100)
# 161 ns ± 9.09 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

我们可以看到,第二个函数的效率要高得多!以比第一个更快的速度运行。但是,我们不能用“运行时间”作为一个客观的衡量标准,因为这将取决于计算机本身的速度和硬件能力。因此,我们需要使用另一种方法,Big-O!

大O符号

在本节中,我们将讨论Big-O符号的语法是如何工作的,以及我们如何使用Big-O符号来描述算法!
我们之前讨论了以下功能:

def sum1(n):
    '''
    Take an input of n and return the sum of the numbers from 0 to n
    '''
    final_sum = 0
    for x in range(n+1): 
        final_sum += x
    
    return final_sum
def sum2(n):
    """
    Take an input of n and return the sum of the numbers from 0 to n
    """
    return (n*(n+1))/2

现在我们想开发一种符号来客观地比较这两种算法的效率。一个好的开始是比较每个算法所做的分配数量。
原始的sum 1函数将创建n+1次赋值,我们可以从基于范围的函数中看到这一点。这意味着它将对final_sum变量赋值n+1次。然后我们可以说,对于一个大小为n的问题(在这种情况下只是一个数字n),这个函数将需要1+n步。
这个n符号允许我们比较解决方案和算法相对于问题的大小,因为sum 1(10)和sum 1(100000)将需要非常不同的时间来运行,但使用相同的算法。我们还可以注意到,当n变得非常大时,+1不会有太大的影响。因此,让我们开始讨论如何为这种表示法构建语法。
现在我们将讨论如何形式化这个符号和概念。
Big-O符号描述了当输入变得任意大时,运行时相对于输入增长的速度。
让我们更仔细地检查其中的一些要点:

  • 请记住,我们想要比较运行时的增长速度,而不是比较确切的运行时,因为这些运行时可能会因硬件而异。
  • 由于我们希望比较各种输入大小,因此我们只关心相对于输入的运行时增长。这就是为什么我们用n来表示符号。
  • 当n变得任意大时,我们只需要考虑随着n变大而增长最快的项,在这一点上,大O分析也被称为渐近分析

对于语法sum 1(),可以说是O(n),因为它的运行时间随着输入大小线性增长。在下一节中,我们将讨论各种O()类型和示例的更具体的示例。在结束本节时,我们将展示Big-O函数运行时的巨大差异。

常见Big-O函数的运行时

以下是常见的Big-O函数表:
算法分析和大O简介_第1张图片
现在,让我们绘制运行时与Big-O的图,以比较运行时。我们将使用一个简单的matplotlib来绘制下面的图。

from math import log
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
plt.style.use('bmh')

# Set up runtime comparisons
n = np.linspace(1,10,1000)
labels = ['Constant','Logarithmic','Linear','Log Linear','Quadratic','Cubic','Exponential']
big_o = [np.ones(n.shape),np.log(n),n,n*np.log(n),n**2,n**3,2**n]

# Plot setup
plt.figure(figsize=(12,10))
plt.ylim(0,50)

for i in range(len(big_o)):
    plt.plot(n,big_o[i],label = labels[i])


plt.legend(loc=0)
plt.ylabel('Relative Runtime')
plt.xlabel('n')

算法分析和大O简介_第2张图片
请注意,对于相同的n值,Big-O效率可以对预计的运行时产生多大的差异!显然,我们希望选择远离任何指数、二次或三次行为的算法!
在下一章中,我们将学习如何正确地表示Big-O,并查看各种问题的示例并计算其中的Big-O!

你可能感兴趣的:(算法,python)