list 列表
什么是list
列表是Python中特有的一种线性数据结构,列表是可变的,有序的,我们可以用选择操作符来改变任意位置的值,和数组不同的是,列表可以同时保存不同类型的元素(异构)。
在CPython中,list是一个存储指针的长度可变的数组(用C++的话来说是一个动态数组)。也就是说列表中的每个元素存储的并不是对象本身,而是一个指向对象的引用。
list有哪些功能
list的创建
我们可以直接使用选择操作符来创建列表,也可以使用list()来创建
stuff = list()
stuff = []
list的方法
索引
因为列表的本质依然是一个数组,所以也支持数组的索引操作
时间复杂度:
下表索引: O ( 1 ) O(1) O(1)
切片索引: O ( k ) O(k) O(k)
增加元素
append()
append(x)
操作可以再列表的末尾添加一个元素
时间复杂度:
和数组一样,时间复杂度为
O
(
1
)
O(1)
O(1)
insert()
insert(i, x)
可以用来在索引i
处添加元素x
时间复杂度:
要想在索引i
插入某个元素,我们必须先将索引i
及之后的元素向后移动一位,然后再给索引i
赋值
因此插入元素时需要的后移操作的次数和列表内元素的个数有关,所以时间复杂度为 O ( n ) O(n) O(n)
extend()
extend(iterable)
操作将一个可迭代对象的每一个元素添加到列表末尾
lis = [1, 2, 3, 'a', 4]
lis.extend("hello")
print(lis)
>>>
[1, 2, 3, 'a', 4, 'h', 'e', 'l', 'l', 'o']
时间复杂度:
操作过程为先遍历读取可迭代对象的每个元素,然后添加到列表末尾,时间复杂度为 O ( k ) O(k) O(k)
删除元素
pop()
pop(i=-1)
用来移除列表中的某个元素并返回,可以传入索引值,默认为-1。也就是说pop()
默认移除最后一位元素。
这里我们可以看到,当我们使用pop()
时,尽管索引为4的元素仍然指向4,但是我们的列表长度已经缩短了。有兴趣的可以参考Python的列表实现,以及resize()
的用法
时间复杂度:
如果只是移除最后一位元素,那么时间复杂度为 O ( 1 ) O(1) O(1)
如果移除的是列表中的元素,那么还需要进行数据前移,前移的次数也和列表大小有关,因此时间复杂度为
O
(
n
)
O(n)
O(n)
remove()
remove(x)
操作可以移除x
元素在数组中的第一个匹配项
当我们remove(5)
时,会从列表中删除对元素5的引用。
时间复杂度:
remove(x)
操作可以视为先查找再移除最后数据前移,而查找和数据前移都和数组的大小有关,所以时间复杂度为
O
(
n
)
O(n)
O(n)。但实际使用上,remove()
的操作效率较低,因为同时执行了查找和数据前移操作。
del
del
操作可以根据索引值删除列表中的元素,也可以切片删除或直接删除整个列表
lis = [1,2,3,4,5]
del lis[0]
>>>
[2, 3, 4, 5]
del lis[0:2]
>>>
[4, 5]
del lis[:]
>>>
[]
同时,del
操作也可以用来删除变量,要注意的是del
删除的是引用而不是对象本身。关于del
语句的详细用法以后再细说。可阅读
时间复杂度:
因为del
也需要先查找并再删除后前移元素,所以时间复杂度以也是
O
(
n
)
O(n)
O(n)
clear()
clear()
操作等价于del list[:]
,直接删除列表全部元素
时间复杂度:
根据直觉,我认为clear()
操作的时间复杂度为
O
(
n
)
O(n)
O(n),因为我们需要不断地循环来删除所有的元素。但是在这篇文档中提到clear()
的时间复杂度为
O
(
1
)
O(1)
O(1)。
后来在这里找到了解释。clear()
操作的时间复杂度取决于我们的Python解释器。对于CPthyon来说,时间复杂度确实是
O
(
n
)
O(n)
O(n),因为list需要去主动删除每一个引用以避免内存泄漏。但是对于其他的解释器(PyPy)来说,有可能实现
O
(
1
)
O(1)
O(1)。
import time
for size in 10000, 100000, 1000000, 10000000:
lis = [0] * size
start = time.time()
lis.clear()
end = time.time()
print(f'{size:<10} : {end - start}')
>>>
10000 : 2.47955322265625e-05
100000 : 0.0002560615539550781
1000000 : 0.00399017333984375
10000000 : 0.03731894493103027
可以看到,对于CPython来说,时间确实是呈线性增长的。
修改元素
list的元素修改直接通过索引完成
时间复杂度:
如果是下标索引,时间复杂度为 O ( 1 ) O(1) O(1)
如果是切片索引,时间复杂度为 O ( k ) O(k) O(k),可看成下标索引重复 k k k次
查找元素
index()
index(x, start, end)
操作可以返回元素x
第一次出现的索引值,如果没找到会出现ValueError
异常
时间复杂度:
为了查找特定元素,我们需要遍历列表,所以时间复杂度为 O ( n ) O(n) O(n)
count()
count(x)
操作可返回元素x
在列表中出现的次数
时间复杂度:
同样的,需要遍历整个列表才能得到结果,所以时间复杂度为 O ( n ) O(n) O(n)
总结
其实Python中的list和C++中的vector很类似,如果想看底层实现,可以去看看cpython解释器的具体实现。
方法 | 时间复杂度 | 说明 |
---|---|---|
[i] | O ( 1 ) O(1) O(1) | |
[i1, i2] | O ( k ) O(k) O(k) | |
append(x) | O ( 1 ) O(1) O(1) | |
insert(i, x) | O ( n ) O(n) O(n) | |
extend(iterable) | O ( k ) O(k) O(k) | |
pop() | O ( 1 ) O(1) O(1) | |
pop(i) | O ( n ) O(n) O(n) | |
remove(x) | O ( n ) O(n) O(n) | |
del | O ( n ) O(n) O(n) | |
clear() | O ( n ) O(n) O(n) | 取决于解释器 |
index(x) | O ( n ) O(n) O(n) | |
count(x) | O ( n ) O(n) O(n) |
其他常用方法
Split()函数
split() 函数可以将字符串按照规律分割为小片段(也被称为字),然后存储在一个列表中,例如:
>>> string = "hello world"
>>> words = string.split()
>>> print(words)
['hello', 'world']
我们还可以指定分隔符(默认为空格),例如:
>>> string = "Hello_world_what_a_good_day"
>>> words = string.split()
>>> print(words)
['Hello_world_what_a_good_day']
>>> words = string.split("_")
>>> print(words)
['Hello', 'world', 'what', 'a', 'good', 'day']
list的优点和缺点
优点:
- 可以添加任意类型的元素
- 通过索引快速访问
- 适合做末尾的添加操作
- Python将列表设计成了一个很强大的数据结构,主要体现在列表的特性上,例如列表推导式(List comprehensions)等,详情见list的特性
缺点:
- 不利于做元素的删除和插入操作
list的特性
待补充…
相关章节