算法的复杂性理论

我们如何衡量算法的速度

复杂度理论是对算法运行所需时间量(取决于输入大小)的研究。 这对于软件开发人员理解非常有用,因此他们可以高效地编写代码。 有两种类型的复杂性:

空间复杂度:一个算法需要运行多少内存时间复杂度:一个算法需要运行多少时间。

通常我们比时间复杂度更担心时间复杂度,因为我们可以重用算法需要运行的内存,但是我们不能重用运行时间。 买内存比买时间要容易。 如果您需要更多内存-您可以从Amazon,Google或Microsoft等提供商那里租用服务器空间。 您也可以购买更多计算机以增加内存,而无需占用服务器空间。 本文的其余部分将介绍如何优化时间复杂度。

我们如何衡量时间复杂度?

新计算机通常会比旧计算机快,而台式机通常会比智能手机快-那么我们如何才能真正知道算法所需的绝对时间呢?

为了测量绝对时间,我们考虑算法执行的操作数。 任何算法的构造块都是if语句和循环。 他们回答以下问题:(1)我们什么时候应该进行手术? (2)我们应该做几次? 我们希望使用尽可能少的if语句和循环来编写代码,以在任何计算机上实现最高效率。

为了分析算法,我们考虑输入大小n-输入项的数量。 我们想对算法的运行时间与输入大小n有何关系做出很好的猜测。 这是增长的顺序:给定输入大小n时,算法将如何缩放和运行。

1. Input 10 items -> 10 ms

2. Input 100 items -> 100 ms (Good, linear growth)

3. Input 1,000 items -> 10,000 ms (Bad, exponential growth)

在上面的示例中,当我们输入10个项目时,需要10毫秒来运行。 当我们输入100个项目时,它需要100毫秒-这很好,因为我们输入的增长与运行时间成比例地增加。

但是,下一步,我们输入了1,000个项目,这需要10,000毫秒。 相对于输入大小n的增加,我们现在的运行时间要长10倍。 现在,我们的运行时有了指数增长,而不是线性增长。 为了更好地理解不同的增长顺序,我们将介绍Big-O表示法。

Big-O 复杂度图表

big-o符号描述了当运行时趋于特定值或无穷大时算法的限制行为。 我们使用它根据算法对输入大小变化的响应方式进行分类。 我们将输入大小表示为n,将对输入执行的操作数表示为N。我的示例将用Python编码。

我们更喜欢在输入方面或更快方面具有线性增长顺序的算法,因为较慢的算法无法扩展到较大的输入大小。 这是从最低到最高的运行时复杂度列表:

· O(1):恒定时间复杂度

· O(log(n)):对数复杂度

· O(n):线性复杂度

· O(n * log(n)):线性题复杂度

· O(n ^ k):多项式复杂度(其中k> 1)

· O(c ^ n):指数复杂度(其中c为常数)

· O(n!):阶乘复杂度

恒定时间复杂度:O(1)

如果运行时的值不受输入大小的限制,则算法将在恒定时间内运行。

恒定时间算法的第一个示例是交换两个数字的函数。 如果我们将函数定义更改为以一百万个数字作为输入,并且将函数主体保留不变,那么它仍然只会执行相同的三个操作以及一个return语句。 运行时间不会根据输入的大小而变化。

def swapNums(num1, num2): temp = num1 num1 = num2 num2 = temp return (num1, num2)

在第二个示例中,我们将首先检查输入消息是否为' Hello World!'。 并将消息更改为另一个值(如果是)。 之后,它将循环执行3次,然后执行另一个循环以将消息打印100次-意味着该消息总共被打印300次。 尽管进行了所有这些操作-由于该函数不会根据输入大小执行更多操作-该算法仍会在恒定时间内运行。

def printMessage300Times(message): if(message == 'Hello World!')     message = 'Pick something more original!' for x in range(0, 3):   for x in range(0, 100):     print(message)

对数时间复杂度:O(log(n))

对数算法具有很好的可扩展性,因为当输入大小n增加时,操作数N与输入n大小的比率减小。 这是因为对数算法无法访问其输入的所有元素,正如我们在二分查找算法中所看到的那样。

在二进制搜索中,我们尝试在排序列表num_list中找到输入数字num。 我们的输入大小n是num_list的长度。

由于列表是经过排序的,因此我们可以将要搜索的数字与列表中间的数字进行比较。 如果num大于中点数,则我们知道num只能位于列表的较大一侧-因此我们可以完全丢弃列表的下端并节省时间,而无需进行处理。

然后,我们可以在列表的较大部分上递归地重复此过程(其行为类似于循环),每次迭代时都将丢弃剩余的num_list的一半。 这就是我们如何实现对数时间复杂度的方法。

def binarySearch(num_list, left_i, right_i, num): if right_i >= left_i: midpoint = left_i + (right_i - left_i)/2 if num_list[midpoint] == num: return midpoint elif num_list[midpoint] > num: return binarySearch(num_list, left_i, midpoint-1, num) else: return binarySearch(num_list, midpoint+1, right_i, num) else: return 'Number not in collection'

线性时间复杂度:O(n)

当运行时间最多与输入n的大小成比例增加时,算法以线性时间运行。 如果我们将输入乘以10,则运行时也应乘以10或更少。 这是因为在线性时间算法中,我们通常在输入的每个元素上运行操作。

在未排序的数字集合中查找最大值是一种可以在线性时间内运行的算法,因为我们必须检查一次输入中的每个元素才能解决该问题:

def findMaxNum(list_of_nums): max = list_of_nums[0] for i in range(1, len(list_of_nums.length)):   if(list_of_nums[i] > max):     max = list_of_nums[i] return max

在for循环中,我们遍历输入n中的每个元素,如果需要,在返回最后的最大值之前更新最大值。 线性时间算法的更多示例包括检查无序列表中的重复项或查找列表的总和。

线性时间复杂度:O(n * log(n))

线性时间算法比线性时间算法稍慢,并且仍然可以扩展。

这是一种中等程度的复杂性,会在线性时间附近浮动,直到输入达到足够大的大小为止。 在线性运算时间内运行的算法的最流行示例是排序算法,例如mergeSort,quickSort和heapSort。 我们来看一下mergeSort:

def mergeSort(num_list): if len(num_list) > 1: midpoint = len(arr)//2 L = num_list[:midpoint] # Dividing 'n' R = num_list[midpoint:] # into 2 halves mergeSort(L) # Sort first half mergeSort(R) # Sort second half i = j = k = 0 # Copy data to temp arrays L[] and R[] while i < len(L) and j < len(R): if L[i] < R[j]: num_list[k] = L[i] i+=1 else: num_list[k] = R[j] j+=1 k+=1 # Checking if any element was left in L while i < len(L): num_list[k] = L[i] i+=1 k+=1 # Checking if any element was left in R while j < len(R): num_list[k] = R[j] j+=1 k+=1

' mergeSort'的工作方式如下:

· 递归地划分num_list,直到元素为两个或更少

· 迭代地对每对项目进行排序

· 迭代合并结果数组

通过这种方法,我们可以实现线性运算时间,因为必须对整个输入n进行迭代,并且必须发生O(log(n))次(输入只能减半O(log(n))次)。 使n个项目遍历log(n)次会导致运行时O(n * log(n)),也称为线性时间。

多项式时间复杂度:O(n ^ c)其中c> 1

如果所有输入大小n的运行时间增加相同的指数c,则算法将在多项式时间内运行。

这种时间上的复杂性以及随后的复杂性无法扩展! 这意味着随着输入大小的增加,运行时间最终将变得太长而无法使算法可行。 有时,我们遇到的问题无法用更快的方式解决,我们需要在如何限制输入大小方面发挥创意,这样我们就不会经历多项式算法会耗费较长的处理时间。 多项式算法的示例是bubbleSort:

def bubbleSort(num_list): n = len(num_list) for i in range(n):   # Last i elements are already in place     for j in range(0, n-i-1):     # Swap if the element found is greater     # than the next element     if num_list[j] > num_list[j+1] :      temp = num_list[j] num_list[j] = num_list[j+1] num_list[j+1] = temp

bubbleSort将一遍又一遍地遍历列表中的所有元素,并在发现相邻数字混乱时交换它们。 仅当发现所有数字的顺序正确时,它才会停止。

在下面的图片中,我们只有7个项目,并且可以对整个集合进行3次迭代以对数字进行排序-但如果是100个数字,则很容易看出运行时间会变得很长。 这没有规模。

指数时间复杂度:O(c ^ n)其中c是常数

当运行时随着输入数据集的增加而加倍时,算法将以指数时间运行。 递归计算斐波那契数是指数时间算法的一个示例:

def fibonacci(n): if n == 0: return 0 elif n == 1: return 1 else: return fibonacci(n-1) + fibonacci(n-2)

该算法在最后一行调用了两次,一次是n-1,一次是n-2。 这意味着如果我们从n = 7开始,我们将总共调用该函数25次! 随着输入的增长,运行非常昂贵。

阶乘时间复杂度:O(n!)

最后,如果算法在输入n上迭代等于n乘以所有小于n的正整数的次数,则它将在阶乘时间内运行。 这是我们将在本文中讨论的最慢的时间复杂度,主要用于计算集合的排列:

def getListPermutation(items_list): results = [] i = 0 l = len(items_list) while i < l:   j, k = i, i + 1 while k <= l:     results.append(' '.join(items_list[j:k])) k = k + 1 i = i + 1 print results

结论

谢谢阅读! 我很想听听您的意见或提出任何问题。

(本文翻译自Cody Nicholson的文章《Complexity Theory for Algorithms》,参考:
https://medium.com/better-programming/complexity-theory-for-algorithms-fabd5691260d)

(0)

相关推荐

  • 插入排序

    一.插入排序(InsertSort) 插入排序从第二个数开始,拿出第二个数进行向前插入排序,一直到最后一个数向前做插入排序.算法稳定. 插入排序的时间复杂度为O(n^2),空间复杂度为O(1).最好的 ...

  • Python学习——排序算法实现

    文章目录 时间复杂度 空间复杂度 二分查找 冒泡排序 选择排序 插入排序 快速排序 归并排序 堆排序 希尔排序 计数排序 一直以来,我只是在大学学过C语言的数据结构中关于冒泡排序的算法,到现在这么多年 ...

  • 关于复杂性理论的若干随想

    第一推动丛书综合系列:复杂 作者:[美] 梅拉妮?米歇尔 1.秩序如何从混沌中涌现? "世界历史中,秩序如何从chaos中涌现?"我的理解是只能对历史进行事后描述,而无法进行事前预 ...

  • 复杂性理论101:问题分类

    如何知道问题的类型和分类 作为数据科学家(或开发人员),我们每天致力于为面临的问题构建和开发新的解决方案.我们创建算法,编写代码,并针对我们遇到的问题的不同实例进行测试. 此过程中的一个重要步骤是定义 ...

  • 课程改革中英语教师教学观念转变及其促进——基于复杂性理论

    作者简介 杜小双/北京外国语大学英语学院博士研究生 新时代以来,我国基础英语教育进入新的发展阶段,致力于培养具有中国情怀.国际视野和跨文化沟通能力的时代新人.新一轮基于核心素养的英语课程改革更加凸显学 ...

  • 2021年理论计算机最高荣誉“哥德尔奖”出炉!两位华人学者获奖,AdaBoost算法曾获该奖

    哥德尔理论计算机科学杰出论文奖由EATCS和ACM SIGACT联合主办.该奖项的设立是为了纪念库尔特·哥德尔(Kurt Gödel)在数理逻辑方面做出的重大贡献,因而以他的名字而命名.哥德尔在约翰· ...

  • 求职指南【6】-算法理论100问

    AI研习图书馆,发现不一样的精彩世界 算法 面试 工程师面试知识点总结六 一.前言 2020年,由于诸多客观因素的影响,一方面学无所成,另一方面企无扩招,对于2021届毕业生来说无疑是雪上加霜,算法岗 ...

  • 第116天:机器学习算法之朴素贝叶斯理论

    朴素贝叶斯(Naive Bayesian Mode,NBM) 贝叶斯由来 贝叶斯是由英国学者托马斯·贝叶斯 提出的一种纳推理的理论,后来发展为一种系统的统计推断方法.被称为贝叶斯方法. 朴素贝叶斯 朴 ...

  • 可信性理论8个基本概念、6个基础定理、3个模拟算法

    8个基本概念:可信性测度.模糊变量.隶属函数.期望值.方差.关键值.熵.距离 6个基础定理:可信性次可加定理.可信性扩展定理.可信性半连续法则.乘积可信性定理.可信性反演定理.Zadeh扩展原理 3个 ...

  • 梅奥诊所算法工程师:医疗AI从理论到实践“最后一英里”有多远?

    药明康德AI/报道 一位名叫Zachi Attia的工程师,在全球最负盛名的医院中,显得有些格格不入,这位梅奥诊所(Mayo Clinic)的工程师既没有专业成体系的外科知识储备,也没有接受过正规的外 ...

  • Lasso算法理论介绍

    先看一波过拟合: 图中,红色的线存在明显的过拟合,绿色的线才是合理的拟合曲线,为了避免过拟合,我们可以引入正则化. 下面可以利用正则化来解决曲线拟合过程中的过拟合发生,存在均方根误差也叫标准误差,即为 ...