Python数据结构与算法--算法分析
在计算机科学中,算法分析(Analysis of algorithm)是分析执行一个给定算法需要消耗的计算资源数量(例如计算时间,存储器使用等)的过程。算法的效率或复杂度在理论上表示为一个函数。其定义域是输入数据的长度,值域通常是执行步骤数量(时间复杂度)或者存储器位置数量(空间复杂度)。算法分析是计算复杂度理论的重要组成部分。
本文地址:http://www.cnblogs.com/archimedes/p/python-datastruct-algorithm-analysis.html,转载请注明源地址。
一个有趣的问题经常出现,那就是两个看似不同的程序,到底哪个更好呢?
要回答这个问题, 我们必须知道程序和代表程序的算法有很大的区别. 算法是一个通用的, 解决问题的一条条的指令. 提供一个解决任何具有指定输入的实例问题方法, 算法产生期望的结果. 一个程序, 另一方面, 是将算法用某一门编程语言代码实现. 有很多的程序实现的同一算法, 取决于程序员和编程语言的使用.
进一步的探究这种差异, 考察下面的函数代码. 这个函数解决一个简单的问题, 计算前n个自然数的和. 解决方案遍历这 n 个整数, 相加后赋值到累加器.
- def sumOfN(n):
- theSum = 0
- for i in range(1,n+1):
- theSum = theSum + i
- return theSum
- print(sumOfN(10))
接下来看下面的代码. 第一眼看上去感觉很奇怪, 但是深入理解之后你将发现这个函数和上面的函数完成同样的工作. T原因是这个函数不是那么明显,代码难看. 我们没有使用好的变量名导致可读性很差, 并且还声明了没有必要声明的变量.
- def foo(tom):
- fred = 0
- for bill in range(1,tom+1):
- barney = bill
- fred = fred + barney
- return fred
- print(foo(10))
到底哪段代码更好呢.问题的答案取决于你的标准.如果你只关注可读性,函数sumOfN
肯定比 foo
好. 事实上, 你可能在你的编程启蒙课上见到过很多教你编写可读性好和易于理解的程序的例子. 然而在这里, 我们还对算法感兴趣.
作为替代空间的需求, 我们基于它们执行时间来分析和比较算法. 这种度量有时候被称为算法的“执行时间”或"运行时间". 我们测量 sumOfN
函数执行时间的一种方法是做个基准分析. 在Python, 我们可以通过一个函数针对我们所使用的系统上标记程序的起始和结束时刻. 在 time
模块有一个被称为 time
的函数,将返回系统的当前时间. 通过两次调用这个函数, 起始和结束, 然后计算差值, 我们可以得到准确的执行时间.
Listing 1
- import time
- def sumOfN2(n):
- start = time.time()
- theSum = 0
- for i in range(1,n+1):
- theSum = theSum + i
- end = time.time()
- return theSum,end-start
Listing 1 展示了sumOfN
函数在求和前后的时间开销. 测试结果如下:
- >>>for i in range(5):
- print("Sum is %d required %10.7f seconds"%sumOfN(10000))
- Sum is 50005000 required 0.0018950 seconds
- Sum is 50005000 required 0.0018620 seconds
- Sum is 50005000 required 0.0019171 seconds
- Sum is 50005000 required 0.0019162 seconds
- Sum is 50005000 required 0.0019360 seconds
我们发现时间相当的一致并且都平均花费 0.0019 秒执行程序. 那么假如我们将n增大到 100,000 会怎样呢?
- >>>for i in range(5):
- print("Sum is %d required %10.7f seconds"%sumOfN(100000))
- Sum is 5000050000 required 0.0199420 seconds
- Sum is 5000050000 required 0.0180972 seconds
- Sum is 5000050000 required 0.0194821 seconds
- Sum is 5000050000 required 0.0178988 seconds
- Sum is 5000050000 required 0.0188949 seconds
- >>>
再次, 时间更长, 非常的一致, 平均10倍的时间. 将 n
增大到 1,000,000 我们达到:
- >>>for i in range(5):
- print("Sum is %d required %10.7f seconds"%sumOfN(1000000))
- Sum is 500000500000 required 0.1948988 seconds
- Sum is 500000500000 required 0.1850290 seconds
- Sum is 500000500000 required 0.1809771 seconds
- Sum is 500000500000 required 0.1729250 seconds
- Sum is 500000500000 required 0.1646299 seconds
- >>>
在这种情况下, 平均执行时间又一次被证实是之前的10倍.
现在来看一下 Listing 2, 提出了一个不同的解决求和问题的方法. 这个函数, sumOfN3
, 运用了一个等式:∑ni = (n+1)n/2来计算前 n
个自然数取代循环计算.
Listing 2
- def sumOfN3(n):
- return (n*(n+1))/2
- print(sumOfN3(10))
如果我们针对 sumOfN3
做一些测试, 使用5种不同的n值(10,000, 100,000, 1,000,000, 10,000,000, and 100,000,000), 我们得到下面的结果:
- Sum is 50005000 required 0.00000095 seconds
- Sum is 5000050000 required 0.00000191 seconds
- Sum is 500000500000 required 0.00000095 seconds
- Sum is 50000005000000 required 0.00000095 seconds
- Sum is 5000000050000000 required 0.00000119 seconds
对于这个输出,有两个方面需要注意. 第一, 上面程序的运行时间比前面的任意一个的运行时间都短. 第二, 无论n为多大执行时间都是一致的.
但是这个标准真正地告诉我们什么?直观地说, 我们可以看到,迭代的解决方案似乎是因为一些程序步骤被重复而做更多的工作. 这是它占用更多运行时间可能的原因. 当我们增加 n
的时候循环方案执行时间也在增加. 然而,有一个问题. 如果我们跑相同的功能在不同的计算机或使用不同的编程语言,我们可能会得到不同的结果. 如果是老式计算机将可能在 sumOfN3上
执行更多的时间.
我们需要一种更好的方式来描述这些算法的执行时间。基准的方法计算实际的执行时间。它并不真的为我们提供了一个有用的测量,因为它是依赖于特定的机器,当前时间,编译,和编程语言。相反,我们要有一个特性,是独立于程序或计算机的使用。这一方法将独立地判断使用的算法是有用的,可以用来在实现算法比较。
一个易位构词实例
一个展示算法不同的数量级的例子是经典的字符串易位问题. 一个字符串和另一个字符串如果仅仅是字母的位置发生改变我们就称为易位. 例如, 'heart'
和 'earth'
就互为易位. 字符串'python'和
'typhon'
也是. 为简化问题的讨论,我们假设字符串中的字符为26个英文字母并且两个字符串的长度相同. 我们的目标是写一个boolean 类型的函数来判断两个给定的字符串是否互为易位.
方法1: 逐一检测
对于易位问题,我们的第一个解决方案是检测第一个字符串的每一个字母是否在第二个字符串中. 如果成功检测所有的字母, 那么两个字符串是易位的. 检查一个字母成功后将使用 Python的特殊值 None
取代. 然而, 因为在 Python 中string是不可变的, 第一步将字符串转换成 list. 看下面的代码:
- def anagramSolution1(s1,s2):
- alist = list(s2)
- pos1 = 0
- stillOK = True
- while pos1 < len(s1) and stillOK:
- pos2 = 0
- found = False
- while pos2 < len(alist) and not found:
- if s1[pos1] == alist[pos2]:
- found = True
- else:
- pos2 = pos2 + 1
- if found:
- alist[pos2] = None
- else:
- stillOK = False
- pos1 = pos1 + 1
- return stillOK
- print(anagramSolution1('abcd','dcba'))
方法2: 排序比较
另一个解决方案基于的思想是:即使两个字符串 s1
和 s2
不同, t它们易位当且仅当它们包含完全相同的字母集合. 因此, 如果我们首先将两个字符串的字符按照字典排序, 如果两个字符串易位,那么我们将得到完全一样的两个字符串. 在 Python 我们可以使用list的内建方法 sort
来简单的实现排序.看下面的代码:
- def anagramSolution2(s1,s2):
- alist1 = list(s1)
- alist2 = list(s2)
- alist1.sort()
- alist2.sort()
- pos = 0
- matches = True
- while pos < len(s1) and matches:
- if alist1[pos]==alist2[pos]:
- pos = pos + 1
- else:
- matches = False
- return matches
- print(anagramSolution2('abcde','edcba'))
第一眼看上去,你可能认为程序的时间复杂度为O(n), 因为只有一个简单的比较n个字母的循环. 然而, 两次调用 Python sort
函数都没有考虑开销. 以后我们会介绍, 排序将花费的时间复杂度为 O(n2) 或 O(nlogn), 于是排序相比循环占主导地位.
方法3: 暴力
一个 brute force 计数方法是枚举出所有的可能性. 对于这个问题, 我们可以使用 s1
的字母简单地生成所有的可能字符串并看 s2
是否出现. 然而,这种方法有一个难点. 我们列举出s1的所有可能性,第一个字母有 n 种可能,第二个位置有n-1种可能, 第三个位置有n-2种可能,……. 总共的可能性为:n*(n-1)*(n-1)*3*2*1 = n!.已经证明 n!递增非常快,当n非常大的时候, n! 递增速度超过 2n .
方法4: 计算和比较
最后一个解决方案是基于这样的一个事实:任意两个易位的字符串都有相同的'a'的数目,相同的'b'的数目,相同的'c'的数目……. 为了判断两个字符串是否易位,我们首先计算每一个字母的次数. 因为只有26个可能的字母, 我们可以使用一个list来保存26个计数, 每一个保存可能的字母. 每次当我们看到一个特别的字母,我们就增加对应的计数. 最后, 如果两个list的对应计数完全相同, 两个字符串就是易位的. 看下面的代码:
- def anagramSolution4(s1,s2):
- c1 = [0]*26
- c2 = [0]*26
- for i in range(len(s1)):
- pos = ord(s1[i])-ord('a')
- c1[pos] = c1[pos] + 1
- for i in range(len(s2)):
- pos = ord(s2[i])-ord('a')
- c2[pos] = c2[pos] + 1
- j = 0
- stillOK = True
- while j<26 and stillOK:
- if c1[j]==c2[j]:
- j = j + 1
- else:
- stillOK = False
- return stillOK
- print(anagramSolution4('apple','pleap'))
依然, 这种解决方案包含大量的循环. 然而, 与第一种方案不同, 它们都没有被嵌入. 前两个循环方案都在n的基础上计算字母. 第三个方案的循环, 比较两个字符串中counts的数目, 只需要 26 步 因为一个字符串只有26种可能的字母. 累加在一起我们得到 T(n)=2n+26 步. 即是 O(n). 我们找到了这个问题的线性时间解法.
离开这个例子之前,我们需要说的是空间开销.虽然最后的解决方案能够在线性时间内运行,它能成功必须要通过使用额外的存储保持两个列表中的字符数。换句话说,该算法使用了空间换时间.
这是一种常见的情况. 在许多场合,你需要做出决定的时间和空间之间的权衡。在目前的情况下,额外空间量是不显著的。然而,如果下面的字母有数百万字,就必须更多的关注空间开销。作为一个计算机科学家,当在选定算法的时候,主要由你来决定如何利用计算机资源来解决一个特定的问题.
您还可能感兴趣:
Python数据结构与算法--算法分析的更多相关文章
- Python数据结构与算法--List和Dictionaries
Lists 当实现 list 的数据结构的时候Python 的设计者有很多的选择. 每一个选择都有可能影响着 list 操作执行的快慢. 当然他们也试图优化一些不常见的操作. 但是当权衡的时候,它们还 ...
- python数据结构与算法
最近忙着准备各种笔试的东西,主要看什么数据结构啊,算法啦,balahbalah啊,以前一直就没看过这些,就挑了本简单的<啊哈算法>入门,不过里面的数据结构和算法都是用C语言写的,而自己对p ...
- Python数据结构与算法之图的最短路径(Dijkstra算法)完整实例
本文实例讲述了Python数据结构与算法之图的最短路径(Dijkstra算法).分享给大家供大家参考,具体如下: # coding:utf-8 # Dijkstra算法--通过边实现松弛 # 指定一个 ...
- Python数据结构与算法之图的广度优先与深度优先搜索算法示例
本文实例讲述了Python数据结构与算法之图的广度优先与深度优先搜索算法.分享给大家供大家参考,具体如下: 根据维基百科的伪代码实现: 广度优先BFS: 使用队列,集合 标记初始结点已被发现,放入队列 ...
- Python 数据结构和算法
阅读目录 什么是算法 算法效率衡量 算法分析 常见时间复杂度 Python内置类型性能分析 数据结构 顺序表 链表 栈 队列 双端队列 排序与搜索 冒泡排序 选择排序 插入排序 希尔排序 快速排序 归 ...
- python数据结构与算法之问题求解实例
关于问题求解,书中有一个实际的案例. 上图是一个交叉路口的模型,现在问题是,怎么安排红绿灯才可以保证相应的行驶路线互不交错. 第一步,就是把问题弄清楚. 怎么能让每一条行驶路线不冲突呢? 其实,就是给 ...
- python数据结构与算法之问题求解
懂得计算机的童鞋应该都知道,一条计算机程序由数据结构跟算法两大部分组成.所以,其实不管你使用哪种计算机语言编写程序,最终这两部分才是一个程序设计的核心.所以,一个不懂得数据结构与算法的程序员不是一个好 ...
- Python数据结构与算法(几种排序)
数据结构与算法(Python) 冒泡排序 冒泡排序(英语:Bubble Sort)是一种简单的排序算法.它重复地遍历要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来.遍历数列的工作是 ...
- Python - 数据结构与算法(Data Structure and Algorithms)
入门 The Algorithms Python https://github.com/TheAlgorithms/Python 从基本原理到代码实现的Python算法入门,简洁地展示问题怎样解决,因 ...
随机推荐
- zepto - toggle
<input type="text" value="123456789" /> <div id="too_long"> ...
- ruby include和exclude区别
很久没玩ruby了,今天看源码的时候,看到extend硬是缓不过神了,Google下extend和include的区别,做个记录 在class中include module, 那么module中的方法 ...
- 用于软件包管理的21个Linux YUM命令 转载
http://flycars001.iteye.com/blog/1949085 YUM到底是啥东东? YUM(Yellowdog Updater Modified)是一款开源命令行及图形化软件包管理 ...
- Hadoop第9周练习—Hive部署测试(含MySql部署)
1.1 2 :搭建Hive环境 内容 2.2 3 运行环境说明 1.1 硬软件环境 线程,主频2.2G,6G内存 l 虚拟软件:VMware® Workstation 9.0.0 build-8 ...
- Android 学习笔记之AndBase框架学习(二) 使用封装好的进度框,Toast框,弹出框,确认框...
PS:渐渐明白,在实验室呆三年都不如在企业呆一年... 学习内容: 1.使用AbActivity内部封装的方法实现进度框,Toast框,弹出框,确认框... AndBase中AbActivity封 ...
- 用cart(分类回归树)作为弱分类器实现adaboost
在之前的决策树到集成学习里我们说了决策树和集成学习的基本概念(用了adaboost昨晚集成学习的例子),其后我们分别学习了决策树分类原理和adaboost原理和实现, 上两篇我们学习了cart(决策分 ...
- [Architect] Abp 框架原理解析(2) EventBus
本节目录 原理介绍 Abp源码分析 代码实现 原理介绍 事件总线大致原理: (1) 在事件总线内部维护着一个事件与事件处理程序相映射的字典. (2) 利用反射,事件总线会将实现 ...
- Maven提高篇系列之(一)——多模块 vs 继承
这是一个Maven提高篇的系列,包含有以下文章: Maven提高篇系列之(一)——多模块 vs 继承 Maven提高篇系列之(二)——配置Plugin到某个Phase(以Selenium集成测试为例) ...
- .Net 配置文件——继承ConfigurationSection实现自定义处理类处理自定义配置节点
除了使用继承IConfigurationSectionHandler的方法定义处理自定义节点的类,还可以通过继承ConfigurationSection类实现同样效果. 首先说下.Net配置文件中一个 ...
- 【ASP.NET MVC 】让@Ajax.ActionLink获取的数据不进Cache
刚玩这个东西的时候,发现IE会进Cache,不管怎么删除,修改,后台删除了,前台还是一样,找了一下,HTML5只提供了 <meta http-equiv="pragma" c ...