Python数据结构与算法(一)列表和元组
本系列总结了python常用的数据结构和算法,以及一些编程实现。
参考书籍:《数据结构与算法 Python语言实现》 【美】Michael T.Goodrich, Roberto Tamassia, Michael H.Goldwasser
更多文章可以移步楼主个人博客:一个算法工程师的修炼金字塔
文章目录
- 1 算法分析
- 1.1 常用的7种函数
- 1.2 渐进分析
- 1.3 简单的证明
- 2 基于数组的序列
- 2.1 动态数组
- Python实现动态数组
- 2.2 python序列类型的效率
- python的列表和元组类
- Python的字符串类
1 算法分析
关键词: 渐进分析、归纳、循环不变量
为了进行算法分析,把执行原子操作的数量描述为输入大小nnn的函数f(n)f(n)f(n)。
由于算法执行的原子操作数ttt与算法的真实运行时间成正比,因此f(n)f(n)f(n)可以用来比较不同算法的运行时间。
1.1 常用的7种函数
常数函数
表达式:f(n)=cf(n)=cf(n)=c
最基本的常数函数是g(n)=1g(n)=1g(n)=1,
任何其它函数都可以被写成常数cg(n)cg(n)cg(n),即f(n)=cg(n)f(n)=cg(n)f(n)=cg(n)对数函数
表达式:x=logbnx=\log_bnx=logbn
一般在计算机科学中,对数常见底数为2,此时经常会省略其符号,即:
logn=log2n\log n = \log_2nlogn=log2n线性函数
表达式:f(n)=nf(n)=nf(n)=nnlognn\log nnlogn函数
表达式:f(n)=nlognf(n)=n\log nf(n)=nlogn二次函数
表达式:f(n)=n2f(n)=n^2f(n)=n2
二次函数可能出现在嵌套循环中,如每次循环操作数加1,则总操作数:
1+2+3+...+(n−1)+n=n(n+1)21+2+3+...+(n-1)+n = \frac{n(n+1)}{2}1+2+3+...+(n−1)+n=2n(n+1)三次函数和其它多项式
表达式:f(n)=n3f(n)=n^3f(n)=n3指数函数
表达式:f(n)=bnf(n)=b^nf(n)=bn
指数函数与几何求和
假如有一个循环,每次迭代需要一个比前一个更长时间的乘法因子。则总操作数可表示为:
∑i=0nai=1+a+a2+...+an=an+1−1a−1,n>=0,a>0,a!=1\sum_{i=0}^n a^i=1+a+a^2+...+a^n=\frac{a^{n+1}-1}{a-1}, n>=0,a>0,a!=1i=0∑nai=1+a+a2+...+an=a−1an+1−1,n>=0,a>0,a!=1
1.2 渐进分析
大OOO符号
令f(n)f(n)f(n)和g(n)g(n)g(n)作为正实数映射正整数的函数。如果有实型常量c>0c>0c>0和整型常量n0>=1n_0>=1n0>=1满足f(n)<=cg(n),n>=n0f(n)<=cg(n),n>=n_0f(n)<=cg(n),n>=n0我们就说f(n)f(n)f(n)是O(g(n))O(g(n))O(g(n))。
例如:
函数8n+58n+58n+5 是 O(n)O(n)O(n);函数3logn+23\log n+23logn+2 是 O(logn)O(\log n)O(logn)等。大Ω\OmegaΩ符号
令f(n)f(n)f(n)和g(n)g(n)g(n)作为正实数映射正整数的函数。如果g(n)g(n)g(n)是O(f(n))O(f(n))O(f(n)),即存在实型常量c>0c>0c>0和整型常量n0>=1n_0>=1n0>=1满足f(n)>=cg(n),n>=n0f(n)>=cg(n),n>=n_0f(n)>=cg(n),n>=n0我们就说f(n)f(n)f(n)是Ω(g(n))\Omega(g(n))Ω(g(n))。大Θ\ThetaΘ符号
如果f(n)f(n)f(n)是O(g(n))O(g(n))O(g(n)),且f(n)f(n)f(n)是Ω(g(n))\Omega(g(n))Ω(g(n)),即存在实型常数c′>0、c′′>0c'>0、c^{\prime\prime}>0c′>0、c′′>0和整型常量n0>=1n_0>=1n0>=1满足c′g(n)<=f(n)<=c′′g(n),n>=n0c'g(n)<=f(n)<=c^{\prime\prime}g(n),n>=n_0c′g(n)<=f(n)<=c′′g(n),n>=n0我们就说f(n)f(n)f(n)是Θ(g(n))\Theta(g(n))Θ(g(n))。
1.3 简单的证明
反证
为了证明命题“如果ppp为真,那么qqq为真”,我们使用其逆否命题命题“如果qqq非真,那么ppp非真”来代替。
另一个反证法是通过矛盾来证明。即建立一个声明qqq是真的,首先假设qqq是假的,然后显示出由这个假设导致的矛盾。归纳
对于任何特定的n>=1n>=1n>=1,有一个有限序列的证明,从已知为真的部分开始,最终得出q(n)q(n)q(n)为真的结论。
具体的说,通过证明当n=1n=1n=1时,q(n)q(n)q(n)为真,然后假设当n=kn=kn=k时命题成立,以验证的条件和假设的条件作为论证的依据进行推导当n=k+1n=k+1n=k+1时命题也成立。循环不变量
为了证明一些关于循环的语句LLL是正确的,我们依据一系列较小的语句L0,L1,...,LkL_0,L_1,...,L_kL0,L1,...,Lk来定义LLL,其中:
1)在循环开始之前,最初要求L0L_0L0为真。
2)如果在迭代jjj之前Lj−1L_{j-1}Lj−1为真,那么在迭代jjj之后LjL_jLj也会为真。
3)最后的语句LkL_kLk意味着要证明的语句LLL为真。
举例见书籍p91。
2 基于数组的序列
关键词: 引用数组、动态数组、元组
Python使用数组内部存储机制,即对象引用来表示序列或元组实例。
Python数组存储的是对象的地址,通过这种方式Python可以以常量时间访问元素列表或元组。
2.1 动态数组
本节主要介绍动态数组的原理和实现。
Python列表的大小没有限制,其依赖于动态数组。
简单理解动态数组就是,当数组元素到达当前数组的长度时,增加一个更大的数组并初始化,使其前面部分与之前数组一样,原来数组将被回收。
下面的代码可以用来探究列表长度和底层大小关系:
import sys
data = []
for k in range(n):a = len(data)b = sys.getsizeof(data)print('Length: {0:3d}; Size in bytes: {1:4d}'.format(a,b))data.append(None)
# 样例输出
Length: 0; Size in bytes: 72
Length: 1; Size in bytes: 104
Length: 2; Size in bytes: 104
Length: 3; Size in bytes: 104
Length: 4; Size in bytes: 104
Length: 5; Size in bytes: 136
Length: 6; Size in bytes: 136
Length: 7; Size in bytes: 136
Length: 8; Size in bytes: 136
Length: 9; Size in bytes: 136
Length: 10; Size in bytes: 200
Python实现动态数组
当底层数组已满,而有的元素要添入列表时,可以使用以下步骤实现动态数组“扩展”:
1)分配一个更大的数组BBB。
2)设B[i]=A[i](i=0,...,n−1)B[i]=A[i](i=0,...,n-1)B[i]=A[i](i=0,...,n−1),nnn表示当前数组的元素数量。
3)设A=BA=BA=B,此后使用BBB作为新的数组。
4)在BBB中增添元素。
“扩展”时需要考虑一个问题:新的数组应该多大?通常做法是:新的数组大小是已满旧数组大小的2倍。
下面代码是动态数组的一种实现:
# 使用ctypes模块提供的原始数组实现DynamicArray类
import ctypesclass DynamicArray:'''A dynamic array class akin to a samplified Python list.'''def __init__(self):'''Create an empty array.'''self._n = 0 # count actual elementsself._capacity = 1 # default array capacityself._A = self._make_array(self._capacity) # low-level arraydef __len__(self):'''Return number of elements stored in the array.'''return self._ndef __getitem__(self,k):'''Return element at index k.'''if not 0<=k<self._n:raise IndexError('invalid index')return self._A[k]def append(slef,obj):'''Add. object to end of the array.'''if self._n == self._capacity:self._resize(2*self._capacity)self._A[self._n] = objself._n += 1def _resize(self,c):'''Resize internal array to capacity c.'''B = self._make_array(c)for k in range(self._n):B[k] = self._A[k]self._A = Bself._capacity = cdef _make_array(self,c):'''Return new array with capacity c.'''return (c*ctypes.py_object)()
- 动态数组的摊销分析
命题2-1:
设SSS是一个具有初始大小的动态数组实现的数组,实现策略为:当数组已满时,将此数组大小扩大为原来的2倍。SSS最初为空,对SSS连续执行nnn个增添操作的运行时间为O(n)O(n)O(n)。
命题2-2:
对初识为空的动态数组执行连续nnn个增添操作,若每次调整数组大小时采用固定的增量,则运行时间为Ω(n2)\Omega(n^2)Ω(n2)。
证明略。
- python的列表类摊销实验分析
python的list类的append方法实现了摊销常量时间的行为。下面代码可以验证这一结论:
from time import timedef compute_avg(n):'''Perform n appends to an empty list and return average time elapsed.'''data = []start = time()for k in range(n):data.append(None)end = time()return (end-start)/n
随着n的增大,增添操作的平均运行时间如下表:(运行环境-macOS i5 2.3GHz)
n | 100 | 1000 | 10000 | 100000 | 1000000 | 10000000 | 100000000 |
---|---|---|---|---|---|---|---|
µs | 0.370 | 0.192 | 0.224 | 0.111 | 0.085 | 0.077 | 0.074 |
2.2 python序列类型的效率
python的列表和元组类
本节分析Python的列表和元组类的一些重要方法的运行时间性能。
Python的列表类使用动态数组的形式来存储内容,元组类比列表的内存利用率更高,这是因为元组是固定不变的,没有必要创建拥有剩余空间的动态数组。
下表是列表和元组类的nonmutating(不变)行为的渐近性能。(k表示被搜索值在最左边出现时的索引)
操作 | 运行时间 |
---|---|
len(data) | O(1) |
data[i] | O(1) |
data.count(value) | O(n) |
data.index(value) | O(k+1) |
value in data | O(k+1) |
data1==data2,(!=,<,>,<=,>=) | O(k+1) |
data[j:k] | O(k-j+1) |
data1+data2 | O(n1+n2) |
C*data | O(cn) |
下表是列表类的可变行为的渐近性能。
操作 | 运行时间 |
---|---|
data[j]=val | O(1) |
data.append(value) | O(1)* |
data.insert(k,value) | O(n-k+1)* |
data.pop() | O(1)* |
data.pop(k), del data[k] | O(n-k)* |
data.remove(value) | O(n)* |
data1.extend(data2),data1 += data2 | O(n2)* |
data.reverse() | O(n) |
data.sort() | O(nlogn) |
*摊销
列表的insert方法性能分析
list的insert方法的运行时间与插入位置有关。
一般情况下,在列表的开始位置和接近中间位置进行插入操作的平均运行时间是Ω(n)\Omega(n)Ω(n),在结束位置插入操作的平均运行时间是O(1)O(1)O(1)。列表的extend方法性能分析
在实践中,相比于重复调用append方法,倾向于选择extend方法。
extend方法的高效率主要缘于两个方面:
1)与调用很多独立函数相比,调用一个函数完成所有工作的开销更小。
2)extend在更新列表时能够提前计算出列表的最终大小。比如使用append方法,底层动态数组会有多次调整大小的风险,而extend方法最多执行一次调整操作。
Python的字符串类
Python字符串是不可变的。本节分析一些常用的字符串方法的运行时间性能。
- 组成字符串性能分析
假定有一个较大的字符串document,目标是生成一个新的字符串letters,该字符串仅包含原字符串的英文字母,一种实现代码如下:
letters = ''
for c in document:if c.isalpha():letters += c
上面代码效率非常低,这是因为字符串大小固定,代码letters += c
很可能计算串联部分letters+c
,并把结果作为新的字符串重新分配给letters
。因此构造新字符串所用时间与该字符串长度成正比,假如最终结果有n个字符,连续串联计算所花费时间为Ω(n2)\Omega(n^2)Ω(n2)。
如何改善?
可以利用list,代码如下:
temp = []
for c in document:if c.isalpha():temp.append(c)
letters = ''.join(temp)
该方法能确保运行时间为O(n)O(n)O(n)。
进一步优化:
letters = ''.join([c for c in document if c.isalpha()])
利用生成器再优化:
letters = ''.join(c for c in document if c.isalpha())
下一篇文章:Python数据结构与算法(二)栈和队列
Python数据结构与算法(一)列表和元组相关推荐
- Python数据结构:序列(列表[]、元组())与映射(字典{})语法总结
一.概述: Python中两种基本的数据结构是序列和映射,序列包含:可变的列表和不可变的元组:而当序列不够用时就出现了映射:字典.列表中的元素是可以变化的,元组里面的元素一旦初始化后就不可更改.列表和 ...
- python中的列表是采用链式结构实现的_Python数据结构与算法之列表(链表,linked list)简单实现...
Python数据结构与算法之列表(链表,linked list)简单实现 Python 中的 list 并不是我们传统(计算机科学)意义上的列表,这也是其 append 操作会比 insert 操作效 ...
- python乘法函数_Python中列表与元组的乘法操作示例
本文实例讲述了Python中列表与元组的乘法操作.分享给大家供大家参考,具体如下: 直接上code吧,还可以这么玩儿 列表乘法: li=[1,] li=li*3 print(li) out: [1, ...
- Python数据结构与算法(二)栈和队列
本系列总结了python常用的数据结构和算法,以及一些编程实现. 参考书籍:<数据结构与算法 Python语言实现> [美]Michael T.Goodrich, Roberto Tama ...
- Python数据结构与算法(1.2)——Python基础之变量与内置数据类型
Python数据结构与算法(1.2)--Python基础之变量与内置数据类型 0. 学习目标 1. Python 程序的运行 1.1 Python 交互式解释器 1.2 Python 程序脚本 2. ...
- python数据结构和算法 时间复杂度分析 乱序单词检测 线性数据结构 栈stack 字符匹配 表达式求值 queue队列 链表 递归 动态规划 排序和搜索 树 图
python数据结构和算法 参考 本文github 计算机科学是解决问题的研究.计算机科学使用抽象作为表示过程和数据的工具.抽象的数据类型允许程序员通过隐藏数据的细节来管理问题领域的复杂性.Pytho ...
- python数据结构与算法练习-Printer Queue
python数据结构与算法练习-队列 Printer Queue python实现 需要注意的知识点: Printer Queue 链接: link. The only printer in the ...
- python字典(dict)+常用方法操作+列表、元组、集合、字典的互相转换
python字典(dict)+常用方法操作+列表.元组.集合.字典的互相转换 字典也是 Python 提供的一种常用的数据结构,它用于存放具有映射关系的数据. 为了保存具有映射关系的数据,Python ...
- Python数据结构与算法--数据类型
从数据类型开始 Python支持面向对象的编程范式,这意味着Python把数据看成解决问题的关键. 在Python中,类似其他的面向对象的编程语言, 我们定义一个类,用来描述数据是什么 (状态) 和数 ...
最新文章
- 用python随机生成数字_如何实现python随机生成数字?
- 医疗信息化、医学相关资料下载
- springboot+vue在线音乐网站
- Spring 5.1.13 和 Spring Boot 2.2.3 发布
- 云炬Android开发笔记 19参考面包多商城优化“我的”页面
- 【牛客 - 272D】Where are you(Tarjan求桥)
- php获取当前世界,php获取网站alexa世界流量排名代码
- android ble蓝牙接收不到数据_Android蓝牙4.0 Ble读写数据详解 -2
- MySQL笔记(八)存储过程procedure
- 怎么判断软件公司是否靠谱
- Java常量池学习总结-1
- Zabbix 系统监控(三)VMware 虚拟平台监控、邮件告警、企业微信告警配置
- linux SVN web 同步
- h文件中报错 unterminated conditional directive的原因
- 破解 语序点选验证码
- McAfee卸载工具及卡巴KIS2009注册码
- 2022全新玖五社区系统源码V9.8版
- 【CA-TA实战系列九】安全驱动OP-TEE(华为tzdriver)
- 数字化门店| 旧衣回收店系统 | 智慧门店小程序开发教程
- 15个权威的PLC学习资料下载地址 (西门子、三菱、欧姆龙)
热门文章
- 选择与Git进行提交意味着什么?
- HTML复选框可以设置为只读吗?
- php access类,一个简洁的PHP操作Access类
- g90舵机怎么一直在抖_抖音怎么带货?这样做抖音号,抖音小白也能月入10w
- 【git私服推送文件出现的问题】refusing to update checked out branch: refs/heads/master
- python特点 可移植性_下面的选项中,不属于Python特点的是( )_学小易找答案
- 注释和特殊字符(HTML)
- 如何理解什么是放射?
- js方法点击复制文本
- html选择器 并列,CSS 中的选择器 (二)- 组合选择器