Python数据结构与算法篇(一)-- 算法分析基础
1 前言
算法分析主要就是从计算资源消耗的角度来评判和比较算法,更高效利用计算资源,或者更少占用计算资源的算法,就是好算法。计算资源主要分为两种,一种是算法解决问题过程中需要的存储空间或内存,另一种是算法的执行时间。
温馨提示: 存储空间受到问题自身数据规模的变化影响要区分哪些存储空间是问题本身描述所需,哪些是算法占用,这个不容易
算法复杂度表示法:
- 大O(n)O(n)O(n)表示法:表示了所有上限中最小的那个上限
- 大ΩΩΩ表示法:表示了所有下限中最大的那个下限
- 大θθθ表示法:如果上下限相同,那么就可以用大θ表示
算法:解决问题的方法和步骤
- 评价算法的好坏:渐近时间复杂度和渐近空间复杂度。
- 渐近时间复杂度的大OOO标记:
- O(c)O(c)O(c)- 常量时间复杂度 - 布隆过滤器 / 哈希存储
- O(log2n)O(log_2n)O(log2n) - 对数时间复杂度 - 折半查找(二分查找)
- O(n)O(n)O(n) - 线性时间复杂度 - 顺序查找 / 计数排序
- O(n∗log2n)O(n*log_2n)O(n∗log2n) - 对数线性时间复杂度 - 高级排序算法(归并排序、快速排序)
- O(n2)O(n^2)O(n2) - 平方时间复杂度 - 简单排序算法(选择排序、插入排序、冒泡排序)
- O(n3)O(n^3)O(n3) - 立方时间复杂度 - Floyd算法 / 矩阵乘法运算
- O(2n)O(2^n)O(2n) - 几何级数时间复杂度 - 汉诺塔
- O(n!)O(n!)O(n!) - 阶乘时间复杂度 - 旅行经销商问题 - NPC
2 Python数据类型的性能
list类型各种操作(interface)的实现方法有很多,如何选择具体哪种实现方法?
总的方案就是,让最常用的操作性能最好,牺牲不太常用的操作80/20。准则:80%的功能其使用率只有20%
List基本操作的大O数量级
Operation | Big-O Efficiency |
---|---|
index[] | O(1) |
index assignment | O(1) |
append | O(1) |
pop() | O(1) |
pop(i) | O(n) |
insert(i, item) | O(n) |
del operator | O(n) |
iteration | O(n) |
contains(in) | O(n) |
get slice[x:y] | O(k) |
del slice | O(n) |
set slice | O(n+k) |
reverse | O(n) |
concatenate | O(k) |
sort | O(nlogn) |
multiply | O(nk) |
字典与列表不同,根据关键码(key)找到数据项,而列表是根据位置(index)。最常用的取值get和赋值set,其性能为O(1)O(1)O(1);另一个重要操作contains(in)
是判断字典中是否存在某个关键码(key),这个性能也是O(1)
Dict基本操作的大O数量级
Operation | Big-O Efficiency |
---|---|
copy | O(n) |
get item | O(1) |
set item | O(1) |
delete item | O(1) |
contains (in) | O(1) |
iteration | O(n) |
更多,请参考Python官方的算法复杂度网站:https://wiki.python.org/moin/TimeComplexity
例子
timeit
模块对函数计时:创建一个Timer
对象,指定需要反复运行的语句和只需要运行一次的“安装语句”;然后调用这个对象的timeit
方法,其中可以指定反复运行多少次,如下:
from timeit import Timer
t = Timer("函数名()","from __main__ import 函数名")
print(“%f seconds\n” % t.timeit(number = 1000))
from timeit import Timerdef test1():"""循环连接列表"""l = []for i in range(1000):l = l + [i]def test2():"""append方式"""l = []for i in range(1000):l.append(i)def test3():"""列表推导式"""l = [i for i in range(1000)]def test4():"""range函数调用转成列表"""l = list(range(1000))t1 = Timer("test1()", "from __main__ import test1")
print("concat %f seconds\n" % t1.timeit(number=1000)) # concat 1.077540 secondst2 = Timer("test2()", "from __main__ import test2")
print("append %f seconds\n" % t2.timeit(number=1000)) # append 0.049203 secondst3 = Timer("test3()", "from __main__ import test3")
print("comprehension %f seconds\n" % t3.timeit(number=1000)) # comprehension 0.039851 secondst4 = Timer("test4()", "from __main__ import test4")
print("list range %f seconds\n" % t4.timeit(number=1000)) # list range 0.016467 seconds
结论: 由于不同计算机,运算时间有差异,但可以发现:列表连接(concat)最慢,List range最快,速度相差近100倍,append也要比concat快得多
3 线性结构
线性结构是一种有序数据项的集合,其中每个数据项都有唯一的前驱和后继,除了第一个没有前驱,最后一个没有后继,新的数据项加入到数据集中时,只会加入到原有某个数据项之前或之后,具有这种性质的数据集,就称为线性结构。
不同线性结构的关键区别在于数据项增减的方式,有的结构只允许数据项从一端添加,而有的结构则允许数据项从两端移除。
接下来学习的主要是栈Stack,队列Queue,双端队列Deque和列表List
4 案例分析
案例一:“变位词”判断问题
问题描述:所谓“变位词”是指两个词之间存在组成字母的重新排列关系,如heart和earth,python和typhon,这里为了降低难度:假设参与判断的两个词仅由小写字母构成,而且长度相等。
解题目标:写一个bool函数,以两个词作为参数,返回这两个词是否变位词
1. 检查标记
每个位置进行比对,用字符串1的字符遍历字符串2,检查标记一个字符要用特定值None 来代替,作为标记。然而,由于字符串不可变,首先要把第二个字符串转化成一个列表。第一个字符串中的每一个字符都可以在列表的字符中去检查,如果找到,就用None 代替以示标记。算法复杂度:O(n2)O(n^2)O(n2)
参考代码:
def anagram_solution(s1, s2):a_list = list(s2)still_ok = Truepos1 = 0while pos1 < len(s1) and still_ok:pos2 = 0found = Falsewhile pos2 < len(s2) and not found:if s1[pos1] == a_list[pos2]:a_list[pos2] = Nonefound = Trueelse:pos2 += 1if found:pos1 += 1else:still_ok = Falsereturn still_okdef main():print(anagram_solution('abcd', 'cadb')) # Trueprint(anagram_solution('abababa', 'abab')) # False print(anagram_solution('abgsgg', 'ababjjsj')) # False main()
2. 排序比较法
尽管s1和s2并不相同,但若为变位词它们一定包含完全一样的字符,利用这一特点,我们可以采用另一种方法。我们首先从a到z给每一个字符串按字母顺序进行排序,如果它们是变位词,那么我们将得到两个完全一样的字符串。此外,我们可以先将字符串转化为列表,再利用Python 中内建的sort
方法对列表进行排序。下面代码展示了这种方法。由于用到排序算法,所以算法复杂度为O(nlogn)O(nlogn)O(nlogn)。
参考代码:
def anagram_solution(s1, s2):a_list1 = list(s1)a_list2 = list(s2)a_list1.sort()a_list2.sort()return (a_list1 == a_list2) # 排序后比较def main():print(anagram_solution('abcde', 'edcba')) # Trueprint(anagram_solution('abcd', 'edcba')) # Falseprint(anagram_solution('abababab', 'abab')) # Falsemain()
3. 暴力匹配法
解决这个问题的典型暴力方法是尝试所有的可能。为了解决变位词检测问题,我们可以简单地构造一个由s1中所有字符组成的所有可能的字符串的列表,并检查s2是否在列表中。然而这个方法有一个困难之处。当我们构造由s1中字符组成的所有可能字符串时,第一个字符有n个可能,第二个字符有n-1种可能,第三个则是n-2种,以此类推。所有可能字符串的总数是n∗(n−1)∗(n−2)∗...∗3∗2∗1n*(n-1)*(n-2)*...*3*2*1n∗(n−1)∗(n−2)∗...∗3∗2∗1。也就是n!。尽管这些字符串中的一些可能是重复的,但程序不能提前预见到,所以还是会产生n!n!n!个字符串。看前言里的算法复杂度,就知道O(n!)O(n!)O(n!)增长比O(2n)O(2^n)O(2n)还要快。
4. 计数比较法
解决变位词问题的最后一个方法利用了任何变位词都有相同数量的a,相同数量的b,相同数量的c等等。为判断两个字符串是否为变位词,我们首先计算每一个字符在字符串中出现的次数。由于共有26个可能的字符,我们可以利用有26 个计数器的列表,每个计数器对应一个字符。每当我们看到一个字符,就在相对应的计数器上加一。最终,如果这两个计数器列表相同,则这两个字符串是变位词。下面展示了这种方法:
def count(s):count_list = [0] * 26for i in range(len(s)):pos = ord(s[i]) - ord('a')count_list[pos] += 1return count_listdef anagram_solution(s1, s2):return count(s1) == count(s2)def main():print(anagram_solution('apple', 'pleap')) # Trueprint(anagram_solution('abd', 'ggsabad')) # Falseprint(anagram_solution('abab', 'bbaa')) # Trueprint(anagram_solution('gsgddjkdsdgds', 'dsdgdsgsgddjk')) # Truemain()
这个方法有一些循环操作。然而不同于第一个方法,所有循环都不是嵌套的。前两个计数字符数的循环都是n重。而因为字符串中总共有26种可能的字符,第三个比较两个计数列表的循环总是执行26步。把它们全部加起来就得到T(n)=2n+26T(n)=2n+26T(n)=2n+26,也就是O(n)O(n)O(n)。这样,我们就找到了一个解决这个问题的线性复杂度的算法。
小结
关于空间需求,尽管最后一个方法可以以线性的时间复杂度来运行,但是这是以使用了额外的空间来存储两个计数器列表为代价的。换句话说,这个算法牺牲了空间来换取时间。这是一个常见的现象。很多情况下你需要在时间和空间的权衡中做出选择。在这个例子中,额外的空间消耗并不足道。但是如果可能的字母多达几百万种,这将是一个问题。作为一个计算机科学家,当要做出算法选择时,需要你根据具体问题来决定利用计算资源的最好方式。
Python数据结构与算法篇(一)-- 算法分析基础相关推荐
- Python数据结构与算法篇(四)-- 滑动窗口算法
数组和链表代表着计算机最基本的两种存储形式:顺序存储和链式存储,所以他俩可以算是最基本的数据结构.数组是一种基础数据结构,可以用来处理常见的排序和二分搜索问题,典型的处理技巧包括双指针.滑动窗口等,数 ...
- Python数据结构与算法篇(五)-- 二分查找与二分答案
1 二分法介绍 1.1 定义 二分查找又称折半查找.二分搜索.折半搜索等,是一种在静态查找表中查找特定元素的算法. 所谓静态查找表,即只能对表内的元素做查找和读取操作,不允许插入或删除元素. 使用二分 ...
- python数据结构与算法篇:排序
1.冒泡排序(英语:Bubble Sort) 它重复地遍历要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来.遍历数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成. ...
- Python数据结构与算法(1.7)——算法分析
Python数据结构与算法(1.7)--算法分析 0. 学习目标 1. 算法的设计要求 1.1 算法评价的标准 1.2 算法选择的原则 2. 算法效率分析 2.1 大 OOO 表示法 2.2 常见算法 ...
- Python数据结构与算法(1.5)——Python基础之函数与异常
Python数据结构与算法(1.5)--Python基础之函数与异常 0. 学习目标 1. 函数 1.1 自定义函数 1.2 函数与参数 1.3 函数与返回值 2. 异常处理 2.1 raise 语句 ...
- Python数据结构与算法(1.4)——Python基础之控制结构
Python数据结构与算法(1.4)--Python基础之控制结构 0. 学习目标 1. 代码块与缩进 2. 条件语句 2.1 if 语句 2.2 if 语句的嵌套 2.3 断言 3. 循环 3.1 ...
- Python数据结构与算法(1.3)——Python基础之输入、输出与高阶赋值
Python数据结构与算法(1.3)--Python基础之输入.输出与高阶赋值 0. 学习目标 1. 输入.输出与注释 1.1 获取用户输入 1.2 格式化输出 1.2.1 基本方法 1.2.2 fo ...
- Python数据结构与算法(1.2)——Python基础之变量与内置数据类型
Python数据结构与算法(1.2)--Python基础之变量与内置数据类型 0. 学习目标 1. Python 程序的运行 1.1 Python 交互式解释器 1.2 Python 程序脚本 2. ...
- Python数据结构与算法(1.6)——Python基础之类与模块化
Python数据结构与算法(1.6)--Python基础之类与模块化 0. 学习目标 1. 面向对象编程:类 1.1 面向对象编程的基本概念 1.1.1 多态 1.1.2 封装 1.1.3 继承 1. ...
最新文章
- 鼠标平滑滤波_38年依然保持旺盛创作力,罗技G Hero16K撑起无线游戏鼠标时代
- 内核同步机制——互斥量
- LeetCode(1)Two Sum
- WinPcap编程调试解决办法
- linux创建文件内容三行,shell之创建文件及内容的方法示例
- HTML表格属性跨列,HTML表格的使用 与 跨行跨列
- vue 如何处理两个组件异步问题_Vue异步组件使用详解
- php代码自定义字段,[原创]栏目自定义字段调用教程及代码
- 2个flask服务器通信_nginx+uwsgi+flask环境部署
- ANSYS 15 直接优化分析
- 英文参考文献的正确引用格式详解
- 宏基4750网卡驱动linux,宏碁4750g无线网卡驱动下载
- react 移动端 h5 端日历组件 周日历 月日历 周视图 月视图
- 用IDEA在Windows上搭建chisel代码阅读环境——以香山处理器为例
- 计算机开机按f1,图文讲解开机按f1的解决方法,简单明了!
- Amit Chadha出任LT Technology Services 首席执行官兼董事总经理
- HEAD, ORIG_HEAD, FETCH_HEAD, MERGE_HEAD标志
- HTML+CSS大作业:使用html设计一个简单好看的公司官网首页 浮动布局
- excel操作技巧,你知道吗?反正我不知道
- 数据分析前景怎么样?好不好?
热门文章
- Chrome快捷键汇总
- java闰年判断原理_java 面试题闰年判断详解及实例
- LeetCode 2. Add Two Numbers (暴力)
- php iphone下载文件,如何使用MIME协议配合表单在iphone手机上下载文件?
- 计算机视觉基础(六)——图像边缘检测
- Kryo,Hessain,Protostuff三种序列化方式对比
- 大企业HR透露:什么样的应聘者更易被青睐!
- 机器人+AI,乐森打造全新家庭娱乐场景互动体验
- Linux音频驱动之一:音频驱动注册流程
- 若依部署前后端 502 (Bad Gateway)