列表

列表(list)是一个抽象的数据结构概念,它表示元素的有序集合,支持元素访问、修改、添加、删除和遍历等操作,无须使用者考虑容量限制的问题。列表可以基于链表或数组实现。

  • 链表天然可以看作一个列表,其支持元素增删查改操作,并且可以灵活动态扩容。
  • 数组也支持元素增删查改,但由于其长度不可变,因此只能看作一个具有长度限制的列表。

当使用数组实现列表时,长度不可变的性质会导致列表的实用性降低。这是因为我们通常无法事先确定需要存储多少数据,从而难以选择合适的列表长度。若长度过小,则很可能无法满足使用需求;若长度过大,则会造成内存空间浪费。

为解决此问题,我们可以使用动态数组(dynamic array)来实现列表。它继承了数组的各项优点,并且可以在程序运行过程中进行动态扩容。

实际上,许多编程语言中的标准库提供的列表是基于动态数组实现的,例如 Python 中的 list 、Java 中的 ArrayList 、C++ 中的 vector 和 C# 中的 List 等。在接下来的讨论中,我们将把“列表”和“动态数组”视为等同的概念。

列表常用操作

初始化列表

我们通常使用“无初始值”和“有初始值”这两种初始化方法:

1
2
3
4
5
# 初始化列表
# 无初始值
nums1: list[int] = []
# 有初始值
nums: list[int] = [1, 3, 2, 5, 4]

访问元素

列表本质上是数组,因此可以在 O(1)O(1) 时间内访问和更新元素,效率很高。

1
2
3
4
5
# 访问元素
num: int = nums[1] # 访问索引 1 处的元素

# 更新元素
nums[1] = 0 # 将索引 1 处的元素更新为 0

插入与删除元素

相较于数组,列表可以自由地添加与删除元素。在列表尾部添加元素的时间复杂度为 O(1)O(1) ,但插入和删除元素的效率仍与数组相同,时间复杂度为 O(n)O(n)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 清空列表
nums.clear()

# 在尾部添加元素
nums.append(1)
nums.append(3)
nums.append(2)
nums.append(5)
nums.append(4)

# 在中间插入元素
nums.insert(3, 6) # 在索引 3 处插入数字 6

# 删除元素
nums.pop(3) # 删除索引 3 处的元素

遍历列表

与数组一样,列表可以根据索引遍历,也可以直接遍历各元素。

1
2
3
4
5
6
7
8
# 通过索引遍历列表
count = 0
for i in range(len(nums)):
count += nums[i]

# 直接遍历列表元素(num只是nums数组中每一个元素的副本,无法修改数组元素原本的值)
for num in nums:
count += num

拼接列表

给定一个新列表 nums1 ,我们可以将其拼接到原列表的尾部。

1
2
3
# 拼接两个列表
nums1: list[int] = [6, 8, 7, 10, 9]
nums += nums1 # 将列表 nums1 拼接到 nums 之后

排序列表

完成列表排序后,我们便可以使用在数组类算法题中经常考查的“二分查找”和“双指针”算法。

1
2
# 排序列表
nums.sort() # 排序后,列表元素从小到大排列

列表实现

许多编程语言内置了列表,例如 Java、C++、Python 等。它们的实现比较复杂,各个参数的设定也非常考究,例如初始容量、扩容倍数等。感兴趣的读者可以查阅源码进行学习。

为了加深对列表工作原理的理解,我们尝试实现一个简易版列表,包括以下三个重点设计。

  • 初始容量:选取一个合理的数组初始容量。在本示例中,我们选择 10 作为初始容量。
  • 数量记录:声明一个变量 size ,用于记录列表当前元素数量,并随着元素插入和删除实时更新。根据此变量,我们可以定位列表尾部,以及判断是否需要扩容。
  • 扩容机制:若插入元素时列表容量已满,则需要进行扩容。先根据扩容倍数创建一个更大的数组,再将当前数组的所有元素依次移动至新数组。在本示例中,我们规定每次将数组扩容至之前的 2 倍。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
class MyList:
"""列表类"""

def __init__(self):
"""构造方法"""
self._capacity: int = 10 # 列表容量
self._arr: list[int] = [0] * self._capacity # 数组(存储列表元素)
self._size: int = 0 # 列表长度(当前元素数量)
self._extend_ratio: int = 2 # 每次列表扩容的倍数

def size(self) -> int:
"""获取列表长度(当前元素数量)"""
return self._size

def capacity(self) -> int:
"""获取列表容量"""
return self._capacity

def get(self, index: int) -> int:
"""访问元素"""
# 索引如果越界,则抛出异常,下同
if index < 0 or index >= self._size:
raise IndexError("索引越界")
return self._arr[index]

def set(self, num: int, index: int):
"""更新元素"""
if index < 0 or index >= self._size:
raise IndexError("索引越界")
self._arr[index] = num

def add(self, num: int):
"""在尾部添加元素"""
# 元素数量超出容量时,触发扩容机制
if self.size() == self.capacity():
self.extend_capacity()
self._arr[self._size] = num
self._size += 1

def insert(self, num: int, index: int):
"""在中间插入元素"""
if index < 0 or index >= self._size:
raise IndexError("索引越界")
# 元素数量超出容量时,触发扩容机制
if self._size == self.capacity():
self.extend_capacity()
# 将索引 index 以及之后的元素都向后移动一位
for j in range(self._size - 1, index - 1, -1):
self._arr[j + 1] = self._arr[j]
self._arr[index] = num
# 更新元素数量
self._size += 1

def remove(self, index: int) -> int:
"""删除元素"""
if index < 0 or index >= self._size:
raise IndexError("索引越界")
num = self._arr[index]
# 将索引 index 之后的元素都向前移动一位
for j in range(index, self._size - 1):
self._arr[j] = self._arr[j + 1]
# 更新元素数量
self._size -= 1
# 返回被删除的元素
return num

def extend_capacity(self):
"""列表扩容"""
# 新建一个长度为原数组 _extend_ratio 倍的新数组,并将原数组复制到新数组
self._arr = self._arr + [0] * self.capacity() * (self._extend_ratio - 1)
# 更新列表容量
self._capacity = len(self._arr)

def to_array(self) -> list[int]:
"""返回有效长度的列表"""
return self._arr[: self._size]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
"""Driver Code"""
if __name__ == "__main__":
# 初始化列表
nums = MyList()
# 在尾部添加元素
nums.add(1)
nums.add(3)
nums.add(2)
nums.add(5)
nums.add(4)
print(f"列表 nums = {nums.to_array()} ,容量 = {nums.capacity()} ,长度 = {nums.size()}")

# 在中间插入元素
nums.insert(6, index=3)
print("在索引 3 处插入数字 6 ,得到 nums =", nums.to_array())

# 删除元素
nums.remove(3)
print("删除索引 3 处的元素,得到 nums =", nums.to_array())

# 访问元素
num = nums.get(1)
print("访问索引 1 处的元素,得到 num =", num)

# 更新元素
nums.set(0, 1)
print("将索引 1 处的元素更新为 0 ,得到 nums =", nums.to_array())

# 测试扩容机制
for i in range(10):
# 在 i = 5 时,列表长度将超出列表容量,此时触发扩容机制
nums.add(i)
print(f"扩容后的列表 {nums.to_array()} ,容量 = {nums.capacity()} ,长度 = {nums.size()}")