一、关于增删改查
序列 | list | tuple | dict | set | deque |
---|---|---|---|---|---|
能否增加元素 | √ | × | √ | √ | √ |
是否有序 | √ | √ | × | × | √ |
能否删除 | √ | × | √ | √ | √ |
可否哈希 | × | √ | × | × | × |
序列 | list | tuple | dict | set | deque |
---|---|---|---|---|---|
增加方法 | append、extend、insert | × | update | add、update | append/appendleft、extend/extendleft (py3增加了copy/index/insert方法,这里py3也有insert) |
删除方法 | pop、remove | × (tuple只有count和index两个方法) | pop、clear(这两个是方法)/del(函数) del dict/dict[key] | pop、remove | 和list类似,但多一个popleft(它和其他的区别在于双端,append/extend/pop都多一个left) |
优点(只是部分) | 功能相对比较齐全 | 可以生成器,占内存小,安全,遍历速度比list快,可一赋多值 | 查找和插入速度快 | 不用判断重复的元素 | 插入速度快 |
缺点 | 相对tuple占内存,查找和insert时间较慢 | 不能添加和更改元素 | 占内存大 | 不能存储可变对象,例如list | remove和获取索引的时候比较慢 |
二、关于时间复杂度
2.1、时间复杂度:
- 时间复杂度:是指执行这个算法所需要的计算工作量
- 空间复杂度:是指执行这个算法所需要的内存空间
常见时间复杂度说明(按复杂度从小到大的顺序,不包含k那个):
- O(1):常数阶,意思即时间保持在一个固定的范围内,不会随序列的长度和大小而增长。例如哈希。
- O(log n):对数阶,log没有标明底数的时候默认是以2为底N的对数,例如n在原来的程度上翻了8倍,则时间复杂度翻了log8=3倍。就是2的3次方等于8,反过来知道底数和总数,求指数。因为是2的倍速,反过来想就是每次查找可以减少一半的可能。当n越大,则耗时增大log n倍。如二分查找。
- *O(n):线性阶,时间与序列的大小成正比,即序列元素越多,越长,所花时间越多。例如一般的for循环。
- O(nlog n):线性对数阶,是n倍的log n。还是假设n为8,则8 x log8=8 x 3 = 24倍。 跟O(n)一样受常数n的影响,但O(nlog n)复杂度高于O(n)。如归并排序、快速排序、堆排序。(快速排序最差的情况是O(n^2),但它的常数因子很小,大多数情况下表现比归并要好)
- O(n^2):平方阶,对n个数的排序,需要循环nn次。n越大结果复杂度就翻倍的增长,例如冒泡排序、插入排序、选择排序。
- *O(k):官网上说,“n”是容器中当前的元素的数量。'k’是参数的值或参数中元素的数量。根据查阅,目前涉及到k的算法是桶排序、计数排序、基数排序,但是目前还未涉略,也不涉及本章内容,有兴趣可以自己去查查看。
关于算法,可以参考我另一篇:https://blog.csdn.net/qq_28304687/article/details/126820544
2.2、比较:
-
- 因为tuple不能增删改,所以这里不做比较。
-
- 因为deque只是和list样子相似,但作用和queue相似,看名字就知道了,所以它只能从两端增删,不能从中间增删,它也就没有insert或者update这样的方法。
-
- pop(),各种方法有些不一样,另外我们知道pop的时候它会返回被删掉的数据。因此,pop我们会分为pop last、pop(index[list]/key[dict]),但实际上他们的命令都是pop:
- 3.1. deque:popleft是其独有,但它的pop不能从指定的位置删
- 3.2. list:list/dict都可以从指定位置删,list简单直接给pop(index)即可
- 3.3. set:set其实有pop,但它既不能指定,且没有所谓最后一个,也是随机,其他得用remove或者discard(区别在于如果元素不存在,前者会报错而后者不会)
- 3.4. dict: 根据官网来看,dict的复杂度平均是O(1),最坏的结果才是O(n)。只是占内存一些,dict的pop比较特殊:
- 3.4.1. - popitem():这个尤其特别,它随机返回并删除字典中的一对键和值。为什么随机呢,因为dict是无序的,没有所谓最后一个
- 3.4.2. - pop(key[,default]):删除字典给定键 key 所对应的值,返回值为被删除的值。key值必须给出。 否则,返回default值。
-
- 由于无序序列的存在,设置了中括号标注下哪个方法是对应哪个序列的,这里的中括号不代表索引,这里索引直接用index代替
平均情况下:
序列 | list | deque | dict | set |
---|---|---|---|---|
insert | √ O(n) | × | × | × |
append | √ O(1) | √ O(1) | × | × |
appendleft | × | √ O(1) | × | × |
extend | √ O(k) | √ O(1) | × | × |
extendleft | × | √ O(1) | × | × |
add | × | × | × | √ O(1) |
update | × | × | √ O(1) | √O(1) |
remove | √ | √O(n) | × | √ |
clear | × | √ | √ | √ |
del | √ O(n) | √ | √ O(1) | √ |
popleft | × | √ O(1) | × | × |
pop last(pop()[list]) | √ O(1) | √ O(1) | × | × |
pop(index[list]/key[dict]) | √ O(k) | √ O(1) | √ O(1) | × |
popitem() | × | × | √ O(1) | × |
Iteration(迭代) | √ O(n) | √ | √ O(n) | √ O(n) |
x in s (查找) | √ O(n) | √ O(n) | √ O(1) | √ O(1) |
2.3、特点:
-
- tuple:
- 1.1. tuple可哈希,所以它可转换成dict和set,它做dict时——{():value}
- 1.2. tuple的优点:
- 1.2.1. 函数返回多个值;
- 1.2.2. 字符串里有多个元素,如果刚好这些元素处于一个列表或tuple内,可以直接用,但是列表需转换;
- 1.2.3. 可以快速调换赋值,如a,b = b,a;
- 1.3. 定义只有一个元素的tuple时候,必须写成这种格式,即加个逗号, 如a = (1,),否则默认为进行()的运算。
-
- dict:
- 2.1. dict的最好和平均时间是O(1),最差是O(n),set大多和dict差不多。
-
- set:
- 3.1. set存储的元素和dict的key类似,必须是不变对象,所以set不支持存储list/dict(set和list/dict可直接相互转换,但set不能直接包含list/dict对象)。但它可以通过update的方式将list的元素一个个添加到set里,但不支持存在整个list(add是添加单个元素,update是批量)。set 和dict转换只会用到它的key而不是value)
- 3.2. 虽然dict和set都有大括号,假设
a = set([1,2,3])
和b = {1,2,3}
,a和b的结果你都会得到set类型的结果{1, 2, 3}
,但如果你想初始化一个空的set对象,你必须c = set()
,如果你用c = {}初始化,c的type会是dict。
- 3.3. 不过它转换成list很方便,只需要list(set())即可,而不用遍历set中的元素。
- 3.4.set在使用推导式的时候,
set(i for i in range(n))
比set([i for i in range(n)])
要快一些,因为前者用到了生成器,但是如果要遍历,取元素来用的话,后者可能更快(参考链接9)
-
- 查找(即x in s):dict,set是常数查找时间(O(1)),list、tuple是线性查找时间(O(n))。
- 4.1 为什么dict和set是O(1),我们知道在dict中key必须是可hash的,而hash之所以快,哈希即散列表的原理是,通过设定的一个关键字和一个映射函数,就可以直接获得访问数据的地址,所以可以实现O(1)的数据访问效率。我之前看到一个很好懂的说法,虽然不完全是这样,但是很有:哈希组成是10个数字和26个字母,在同种加密算法下长度也是一致的。也就是说这串hash值的每一位的可能性都是只会是这36种的其中一个,查询的时候,从hash的第一个值可以匹配,比如是a,那我们只需要找剩下的所有hash存储值以a开头的hash值即可,相当于一下子就排除了35/36的内容。接下来每一位的匹配都类似,所以不管查询的库有多大,它的时间复杂度都是相对最低的。
2.4、优化:
-
- list因为占用的内存会随着元素的增大而增大,所以最好不要用 list 来保存中间结果,而是通过 iterable 对象来迭代。(参考下面的链接)
-
- 如果想对list进行remove的操作,尽量使用新list保存符合条件的,append的效率高于remove。
-
- 如果非要进行remove操作,切记对list进行切片再遍历,否则对原数组进行remove却还处在for之中,每次remove会改变后面的元素的索引,遍历会报错。例如进行
for i in lst[:]
类似的操作再remove
- 如果非要进行remove操作,切记对list进行切片再遍历,否则对原数组进行remove却还处在for之中,每次remove会改变后面的元素的索引,遍历会报错。例如进行
-
- 由表3可知,在判断某个元素是否在某个序列中的时候,dict是O(1),list需要遍历,所以是O(n),这时候尽量不要用list,能够用字典、set进行存储,尽量不要用list(只是查找,对顺序没有要求,允许去重)。
-
- 一般情况下,只是查找,list和set转换就可以,虽然也可以利用dict的kes()转换,但set和list的相互之间的转换比较方便。
-
- list转dict的使用场景往往是较为复杂的情况,例如多个list组合对应dict的key和value;或是list作为keys,生成一些元素为空的values之类的。
-
- 存储的时候似情况而定用list还是set,这样可以省去转换。(如果需要求交集、去重之类的用set最好)
参考链接:
- 主要基于python2.7,不是完全完整,基于目前所学做了一个简单的归纳;
- 关于时间复杂度,参考:
· 英文:https://wiki.python.org/moin/TimeComplexity
· 中文:http://www.orangecube.net/python-time-complexity - 前四种算是基本数据结构,最后一种是from collections这个内置库,是双向队列。它相当于队列和列表的结合,并且支持两端增删。它其实更常用于和多线程,redis使用,之所以放在这里,是因为它和list的相似性;
- 关于tuple的优点,知乎有很好的答案:https://www.zhihu.com/question/60574107
- 关于dict的删除:
1) http://www.runoob.com/python/python-dictionary.html
2) http://www.iplaypy.com/jinjie/jj116.html - 关于dict的插入:https://www.zhihu.com/question/62050494
- 关于set的删除: http://blog.csdn.net/jcjc918/article/details/9359503
- 性能比较:https://www.cnblogs.com/cfang90/p/6220956.html
- 优化建议:http://www.jb51.net/article/56699.htm
- 算法时间复杂度:https://www.ibm.com/developerworks/cn/linux/l-cn-python-optim/