数据结构

List 列表概述

列表是一种有序、可变的数据集合,可以容纳不同类型的元素。

  • 列表就是一个排列整齐的队伍,在 Python 中,列表就是顺序表实现的
  • 列表内的个体称作元素,由若干元素组成列表
  • 元素可以是任意对象(数字、字符串、对象、列表等)
  • 列表内元素有顺序,可以使用索引
  • 线性的数据结构
  • 使用[]表示
  • 列表是可变的

列表是非常重要的数据结构,对其内存结构和操作方法必须烂熟于心。

列表(List)是Python中最常用的数据结构之一,它是一种有序的可变序列,可以存储任意类型的数据。列表的元素可以通过下标进行访问,下标从0开始,也可以使用切片(Slice)来访问列表中的一段元素。

列表的特点如下:

  1. 列表中的元素可以是任意类型的数据,包括数字、字符串、布尔值、列表、元组等。
  2. 列表是有序的,每个元素都有一个对应的索引。
  3. 列表是可变的,可以通过添加、删除、修改元素等操作改变列表的内容。
  4. 列表中可以包含重复的元素。
  5. 列表支持嵌套,即列表中可以包含其他列表。

常见的列表操作包括:

  1. 创建列表:可以使用方括号[]或者list()函数来创建一个空的列表,也可以在方括号中添加元素来创建一个非空的列表。
  2. 访问列表元素:可以通过下标来访问列表中的元素,下标从0开始,也可以使用切片来访问一段元素。
  3. 添加元素:可以使用append()方法在列表末尾添加一个元素,使用insert()方法在列表中的任意位置插入一个元素,使用extend()方法将一个列表中的元素添加到另一个列表中。
  4. 删除元素:可以使用remove()方法删除指定的元素,使用pop()方法删除指定位置的元素,并返回该元素的值,使用del语句删除指定位置的元素。
  5. 修改元素:可以通过下标来修改列表中的元素。
  6. 查找元素:可以使用in关键字判断列表中是否存在指定的元素,使用index()方法查找指定元素的位置。
  7. 排序和反转:可以使用sort()方法对列表进行排序,使用reverse()方法对列表进行反转。

在Python中,列表是非常常用的数据结构之一,可以用于各种数据处理和计算任务,也是很多Python库和框架的基础数据结构。

线性数据结构

线性数据结构是指数据元素之间存在一对一的相邻关系的数据结构。常见的线性数据结构包括数组、链表、栈和队列等。

  1. 数组(Array):是一种基本的数据结构,它由一组连续的内存单元组成,可以容纳一组具有相同类型的元素。数组中的元素按照线性顺序排列,可以使用下标操作符来访问数组中的元素,时间复杂度为 O(1)。
  2. 链表(Linked List):是一种动态数据结构,它由一系列节点组成,每个节点包含数据和指向下一个节点的指针。链表可以分为单向链表、双向链表和循环链表。链表中的元素不需要连续的内存空间,可以动态的添加、删除节点,时间复杂度为 O(1)。
  3. 栈(Stack):是一种后进先出(LIFO)的数据结构,它只允许在栈顶进行插入和删除操作。栈可以用数组或链表实现。
  4. 队列(Queue):是一种先进先出(FIFO)的数据结构,它可以在队尾插入元素,在队首删除元素。队列可以用数组或链表实现。

线性数据结构在计算机科学中应用广泛,例如算法、数据存储和处理等领域。根据具体的应用场景和要求,选择适合的线性数据结构可以提高程序的效率和性能。

线性表

线性表是一种基本的数据结构,它由 n(n>=0) 个数据元素组成,这些元素按照线性顺序排列,每个元素都有唯一的直接前驱和直接后继。线性表中只有一个数据元素没有直接前驱,称之为表头,只有一个数据元素没有直接后继,称之为表尾。

线性表的存储结构通常有两种实现方式:

  1. 数组实现:线性表的每个元素都存储在数组的一个位置上,通过下标可以直接访问到每个元素。数组实现的优点是访问元素速度快,缺点是插入和删除元素需要移动其他元素,时间复杂度为 O(n)。
  2. 链表实现:线性表的每个元素都存储在一个节点中,节点包含数据和指向下一个节点的指针。链表实现的优点是插入和删除元素只需要改变指针的指向,时间复杂度为 O(1),缺点是访问元素速度较慢,时间复杂度为 O(n)。

线性表的常见操作包括:

  1. 插入元素:将一个新元素插入到线性表的任意位置。
  2. 删除元素:从线性表中删除一个元素。
  3. 查找元素:查找线性表中某个元素的位置。
  4. 遍历元素:依次访问线性表中的每个元素。

线性表是一种非常基础的数据结构,它在计算机科学中的应用非常广泛,例如数组、链表、栈和队列等数据结构都可以通过线性表来实现。

顺序表(列表 List)

顺序表是一种线性表的存储结构,它是通过一段连续的内存空间来存储线性表中的元素。在顺序表中,线性表中的每个元素都按照一定的顺序存储在连续的内存空间中,每个元素占用一个固定的存储单元,也就是数组的一个元素。

顺序表的优点是支持随机访问,即可以通过下标来直接访问任意一个元素,访问速度很快。同时,由于顺序表是连续存储的,所以空间利用率也比较高。缺点是插入和删除元素比较困难,因为需要移动其他元素的位置。

顺序表通常由一个数组和一个记录元素个数的变量组成。数组用于存储元素,变量用于记录顺序表中实际存储的元素个数。顺序表中的元素可以是任意类型的数据,包括整数、浮点数、字符串等。

顺序表的常见操作包括:

  1. 初始化操作:创建一个空的顺序表。
  2. 插入元素:将一个新元素插入到顺序表的任意位置。
  3. 删除元素:从顺序表中删除一个元素。
  4. 查找元素:查找顺序表中某个元素的位置。
  5. 遍历元素:依次访问顺序表中的每个元素。

Python中的列表(List)就是一种顺序表的实现方式,它可以通过下标来访问任意一个元素,并且支持插入、删除和遍历等常见操作。在Python中,列表可以包含不同类型的元素,甚至可以包含其他列表,因此非常灵活。

链接表(链接表 Linked List)

链接表是一种线性表的存储结构,它是通过指针来实现线性表中元素之间的链接关系。在链接表中,每个元素都由两部分组成:数据域和指针域。数据域用于存储元素的值,指针域用于存储指向下一个元素的指针。通过这种方式,所有元素按照一定的顺序连接起来,形成一个链式结构。

链接表有单向链接表和双向链接表两种类型:

  1. 单向链接表:每个节点只包含一个指向下一个节点的指针,最后一个节点指向空值。
  2. 双向链接表:每个节点包含一个指向下一个节点的指针和一个指向前一个节点的指针,首节点的前一个节点指向空值,尾节点的后一个节点指向空值。

链接表的优点是插入和删除元素非常方便,只需要调整指针即可,不需要移动其他元素的位置。同时,链接表可以动态地分配内存空间,不需要预先指定存储空间大小。缺点是访问元素的效率比较低,需要从首节点开始依次遍历链表,访问速度较慢。

链接表的常见操作包括:

  1. 初始化操作:创建一个空的链接表。
  2. 插入元素:将一个新元素插入到链接表的任意位置。
  3. 删除元素:从链接表中删除一个元素。
  4. 查找元素:查找链接表中某个元素的位置。
  5. 遍历元素:依次访问链接表中的每个元素。

Python中的链表(List)可以通过指针实现,但是通常不会直接使用链表来存储数据,而是使用列表(List)或其他数据结构。但是在某些场景下,比如处理大量数据的时候,链表可以提供非常高效的存储和访问方式。

栈 Stack

  • 后进先出 LIFO
  • 栈使用列表还是链表?
    • 栈使无论是用列表还是用链表性能都好,因为栈是从尾部取元素,但通常还是使用链表较多些

队列 Queue

  • 先进先出 FIFO
  • 有栏杆,不能插队,不能离队,只能首尾操作
  • 队列使用列表还是链表?
    • 使用列表性能不好,因为如果取的是列表首部的元素,后面的元素需要前移
    • 使用链表性能很好,因为如果取的是链表首部的元素,后面的元素无需前移

列表索引

  • 索引又称下标
  • 使用索引定位访问元素的时间复杂度为O(1),这是最快的方式,是列表最好的使用方式。

在 Python 中,列表中的每个元素都有一个对应的索引值,可以使用这个索引值来访问列表中的元素。Python 支持正索引和负索引两种方式来访问列表中的元素。

  • 正索引是从 0 开始的整数序列,也就是说,列表中第一个元素的索引是 0,第二个元素的索引是 1,以此类推。
  • 负索引则是从 -1 开始的整数序列,也就是说,列表中最后一个元素的索引是 -1,倒数第二个元素的索引是 -2,以此类推。例如,对于一个长度为 n 的列表,第一个元素的索引是 -n,第二个元素的索引是 -n+1。

下面是一个示例代码,演示如何使用正索引和负索引来访问列表中的元素:

my_list = ['a', 'b', 'c', 'd', 'e']

## 使用正索引访问列表中的元素
print(my_list[0])   # 输出 'a'
print(my_list[2])   # 输出 'c'

## 使用负索引访问列表中的元素
print(my_list[-1])  # 输出 'e'
print(my_list[-2])  # 输出 'd'
print(my_list[-4])  # 输出 'b'

需要注意的是,当使用索引访问列表中的元素时,如果指定的索引超出了列表的范围,Python 会抛出 IndexError 异常。因此,在编写代码时应该确保索引的值在列表的范围内。

引用数据类型 & ?

创建列表

  • 在定义一个列表时,可以包含任意类型的数据,包括数字、字符串、布尔值、列表、元组等。
  • 在Python中,可以使用方括号[] 或者 list()函数来定义一个列表。具体如下:

[]

  • 使用方括号定义一个空列表:
my_list = []
  • 使用方括号定义一个非空列表:
my_list = [1, 2, 3, "a", "b", "c"]
  • 包含数字:
num_list = [1, 2, 3, 4, 5]
  • 包含字符串:
str_list = ["apple", "banana", "orange", "peach"]
  • 包含布尔值:
bool_list = [True, False, True]
  • 包含列表:
list_list = [[1, 2, 3], ["a", "b", "c"], [True, False]]
  • 包含元组:
tuple_list = [(1, 2), ("a", "b"), (True, False)]
  • 包含不同类型的数据:
mixed_list = [1, "apple", True, [1, 2, 3], (4, 5, 6)]

list()

  • 使用list()函数定义一个空列表:
my_list = list()
  • 使用list()函数定义一个非空列表:
my_list = list([1, 2, 3, "a", "b", "c"])
  • 创建一个包含 0 到 4 的整数的列表,并将其赋值给变量 l5
l5 = list(range(5))
print(l5)  # 输出结果为[0, 1, 2, 3, 4]
  • 调用 range(5) 函数创建一个包含 0 到 4 的整数序列,该序列不包括 5。该函数返回的是一个 range 对象,而不是一个列表。
  • 调用 list() 函数将上一步返回的 range 对象转换成一个列表,该列表包含 0 到 4 这五个整数。
  • 将上一步返回的列表赋值给变量 l5
  • 调用 print() 函数打印变量 l5 的值。

添加元素

在Python中,可以使用多种方式向列表中添加元素,具体如下:

注意事项:

  • append()insert()extend() 方法都是修改原始列表
  • +* 运算符则是创建一个新的列表。

append()

append()的时间复杂度:

  • 在Python中,append()是用于在列表(list)末尾添加一个元素的方法。其时间复杂度一般情况下为常数时间复杂度O(1)。
  • 这是因为,当我们调用append()方法时,Python会直接将新元素添加到列表的末尾,并更新列表的长度信息。由于列表是一种基于数组实现的线性数据结构,在内存中连续存储着一系列元素,因此,添加新元素时,只需要将新元素添加到列表的末尾,并更新列表的长度信息,这个过程的时间复杂度是常数时间复杂度O(1)。
  • 需要注意的是,当列表的长度超过了其内部数组的容量时,Python会自动分配更多的内存空间,以容纳更多的元素。此时,append()方法的时间复杂度可能会变为线性时间复杂度O(n),其中n为列表的长度。但是,由于Python会动态调整列表的容量,因此这种情况出现的概率较小,一般情况下append()的时间复杂度是常数时间复杂度O(1)。
    • 和垃圾回收有关

范例

  • 使用append()方法向列表末尾(列表内部的尾巴)添加一个元素:
my_list = [1, 2, 3]

my_list.append(4)

print(my_list)  # 输出 [1, 2, 3, 4]

insert()

insert() 的时间复杂度:

  • 在Python中,insert()是用于在列表的指定位置插入一个元素的方法。其时间复杂度取决于插入位置的位置。

    • 如果要在列表的末尾插入元素,则时间复杂度为常数时间复杂度O(1),与append()方法的时间复杂度相同。
    • 如果要在列表的其他位置插入元素,则需要将插入位置后面的所有元素向后移动一位,以腾出空间插入新元素。这个过程的时间复杂度与插入位置和列表长度有关,是线性时间复杂度O(n),其中n为列表的长度。
  • 需要注意的是,当列表的长度超过了其内部数组的容量时,Python会自动分配更多的内存空间,以容纳更多的元素。此时,insert()方法的时间复杂度可能会更高,因为需要将更多的元素向后移动。但是,由于Python会动态调整列表的容量,因此这种情况出现的概率较小。

注意事项:

  • 在使用insert()方法时需要指定要插入的位置,如果该位置不存在则会抛出IndexError异常。

范例

  • 使用insert()方法在指定位置添加一个元素:
my_list = [1, 2, 3]

my_list.insert(1, 4)  # 在下标为1的位置插入4(下标的前面插队)

print(my_list)  # 输出 [1, 4, 2, 3]
  • insert() 是在前面插队,不同于append()
my_list = [1, 2, 3]

my_list.insert(-1, 4)  # 在下标为-1的位置插入4(下标的前面插队)

print(my_list)  # 输出 [1, 2, 4, 3]
  • insert() 的索引如果超过右边界,则同于append(),即插入到行尾
my_list = [1, 2, 3]

my_list.insert(999, 4)  # 在下标为999的位置插入4,因为下标最大为2,所以默认会将object追加到末尾

print(my_list)  # 输出 [1, 2, 3, 4]
  • 索引如果超过左边界,则插入到行首
my_list = [1, 2, 3]

my_list.insert(-999, 4)  # 在下标为-999的位置插入4,因为下标最大为-3,所以默认会将object追加到行首

print(my_list)  # 输出 [4, 1, 2, 3]
  • 还可以插入列表
my_list = [1, 2, 3]

my_list.insert(2, [10, 10])  # 插入列表(插入的列表将被视为一个元素)

print(my_list)  # 输出 [1, 2, [10, 10], 3]

extend()

extend()的时间复杂度:

  • 在Python中,extend()是用于将一个可迭代对象中的所有元素添加到列表(list)末尾的方法。其时间复杂度取决于要添加的元素数量。
  • 如果要添加的元素数量为m,则extend()方法的时间复杂度为O(m)。这是因为,在执行extend()方法时,Python会遍历要添加的可迭代对象,依次将其中的所有元素添加到列表的末尾。由于添加每个元素的时间复杂度是常数时间复杂度O(1),因此,添加m个元素的时间复杂度是O(m)。
  • 需要注意的是,如果要添加的可迭代对象为空,则extend()方法的时间复杂度为常数时间复杂度O(1)。这是因为,当要添加的可迭代对象为空时,extend()方法不会进行任何操作,直接返回。

范例

  • 使用extend()方法将另一个列表的所有元素添加到当前列表末尾:
my_list = [1, 2, 3]

new_list = [4, 5, 6]

my_list.extend(new_list)

print(my_list)  # 输出 [1, 2, 3, 4, 5, 6]
  • 配合range()进行扩展
my_list = [1, 2, 3]

my_list.extend(range(110, 150, 10))

print(my_list)  # 输出 [1, 2, 3, 110, 120, 130, 140] ,150不包

+

  • 使用+运算符将两个列表合并成一个新的列表
my_list = [1, 2, 3]

new_list = [4, 5, 6]

result = my_list + new_list

print(result)  # 输出 [1, 2, 3, 4, 5, 6]
  • 特殊情况
my_list = [1, 2, 3]

new_list = [[4, 5, 6]]

result = my_list + new_list

print(result)  # 输出 [1, 2, 3, [4, 5, 6]]

*

注意事项:

  • * 是浅拷贝(涉及到复制原理)

范例

  • 使用*运算符将列表中的元素重复n次合并成一个新的列表
my_list = [1, 2, 3]

result = my_list * 3

print(result)  # 输出 [1, 2, 3, 1, 2, 3, 1, 2, 3]
  • 特殊情况
my_list_a = [1]
result_a = my_list_a * 3
print(result_a)  # 输出 [1, 1, 1]

my_list_b = [[1]]
result_b = my_list_b * 3
print(result_b)  # 输出 [[1], [1], [1]]

注意事项

  • *用不好的话很危险!
a = [[1]]
b = a * 3
b[1][0] = 100
print(b)  # 结果是[[100], [100], [100]],而不是[[1], [100], [1]]

为什么b的结果是[[100], [100], [100]]?

因为在 Python 中,列表是可变对象,当使用乘法运算符 * 复制列表时,复制的是列表的引用,而不是列表的副本。因此,复制出来的多个列表都引用同一个内存地址中的子列表对象,对其中一个子列表对象的修改会影响到所有引用它的列表。

解释一,在上面的示例代码中:

  • 列表 a 包含了一个子列表 [1],然后使用乘法运算符复制了 3 次,得到一个新的列表 b
  • **因为 b 中的每个子列表都是对 a 中子列表 [1] 的引用,所以当对 b[1][0] 进行赋值操作时,实际上是修改了 [1] 中的元素值,而不是创建一个新的子列表对象。**因此,b 的输出结果为 [[100], [100], [100]]

解释二,在上面的示例代码中:

  • 首先定义了一个包含一个元素的二维列表 a,然后将其复制了 3 次,得到了一个新的二维列表 b
  • 此时 ab 引用的是同一个子列表对象 [1]。可以使用 id() 函数来验证它们的内存地址是否相同:
a = [[1]]
b = a * 3
print(id(a[0]), id(b[0]), id(b[1]), id(b[2]))  # 输出 140622368032080 140622368032080 140622368032080 140622368032080
  • 由于 b 中的所有子列表对象都是对 a 中子列表对象的引用,因此对 b[1][0] 进行赋值操作实际上是修改了 [1] 中的元素值,而不是创建一个新的子列表对象。
  • 因此,b 的输出结果为 [[100], [100], [100]],而不是 [[1], [100], [1]]

解释三:

这是因为在 Python 中,列表的乘法操作符 * 会重复引用原始列表的引用,而不是创建新的列表。因此,当我们用 b = a * 3 创建 b 列表时,实际上是将 a 列表的引用复制了三次,而不是创建了三个独立的列表

这意味着当我们修改 b[1][0] 的值时,实际上是修改了原始列表 a 中的值,因为 b 中的三个子列表都引用了 a 列表中的同一个子列表。

以下是代码逐行分析:

  • 创建一个包含一个元素 [1] 的列表 a。
a = [[1]]
  • 将 a 列表复制三次,并将结果存储在 b 变量中。
b = a * 3
  • 修改 b 列表中第二个子列表的第一个元素为 100。
b[1][0] = 100
  • 打印 b 列表。
print(b)

输出为 [[100], [100], [100]],因为修改 b[1][0] 的值实际上修改了原始列表 a 中的值。所以结果中三个子列表都包含值为 100 的元素。

解决方法一:

如果想避免这个问题,可以使用列表推导式或循环创建多个子列表对象,而不是使用乘法运算符复制列表。例如,下面的代码创建了 3 个独立的子列表对象:

a = [1]
b = [a.copy() for _ in range(3)]
b[1][0] = 100
print(b)  # 输出 [[1], [100], [1]]

注意这里使用了 copy() 方法创建了新的子列表对象,而不是对原有的子列表对象进行修改。这样,每个子列表对象都是独立的,对其中任何一个子列表对象的修改都不会影响到其它子列表对象。

解决方法二:

可以使用 copy() 方法创建一个新的子列表对象,避免多个列表共享同一个子列表对象。修改代码如下:

a = [[1]]
b = [a[0].copy()] * 3
b[1][0] = 100
print(b)  # 输出 [[1], [100], [1]]

这里使用 copy() 方法来创建一个新的子列表对象 [1],并将其复制 3 次,得到一个新的二维列表 b。此时 b 中的每个子列表对象都是独立的,对其中任何一个子列表对象进行修改都不会影响其它子列表对象。

删除元素

在Python中,可以使用多种方式删除列表中的元素,具体如下:

del

del 是 Python 的一个关键字,用于删除对象,可以删除列表中的元素、删除变量、删除对象等。

在列表中,del 语句用于删除指定位置的元素,语法格式如下:

del list[index]
  • list 是要删除元素的列表对象
  • index 是要删除元素的索引。

del的时间复杂度:

  • 在Python中,del语句用于从列表(list)中删除指定位置的元素,其时间复杂度也取决于要删除的元素的位置。
  • 如果要删除的元素位于列表的末尾,那么del语句的时间复杂度为常数时间复杂度O(1),因为只需要将列表的末尾元素删除,并更新列表的长度即可。
  • 但是,如果要删除的元素不在列表的末尾,那么del语句的时间复杂度为O(n),其中n为列表中的元素数量。这是因为,在删除指定位置的元素后,该位置后面的所有元素都需要向前移动一个位置,以填补被删除元素的空缺。因此,需要将位于被删除元素之后的所有元素依次向前移动一个位置,直到最后一个元素移动到被删除元素的位置,然后再将列表的长度减1。由于每个元素最多只需要向前移动一次,因此需要移动的元素数量为n-1,即时间复杂度为O(n)。
  • 需要注意的是,与pop()方法不同,del语句可以用于删除任意位置的元素,而不仅仅是列表的末尾元素。此外,del语句还可以用于删除整个列表对象。在这种情况下,del语句的时间复杂度为常数时间复杂度O(1),因为只需要将列表对象从内存中删除即可。

注意事项:

  • 使用del语句删除元素时,如果指定的下标超出了列表的范围,会抛出IndexError异常;

范例

  • 使用del语句删除指定位置的元素:
my_list = [1, 2, 3, 4, 5]

del my_list[2]  # 删除下标为2的元素

print(my_list)  # 输出 [1, 2, 4, 5]
  • 创建一个包含 5 个元素的列表 my_list,然后使用 del 语句删除第 2 个元素:
my_list = [1, 2, 3, 4, 5]
del my_list[1]
print(my_list)  # 输出 [1, 3, 4, 5]
  • 删除整个列表或其他类型的对象:
my_list = [1, 2, 3, 4, 5]
del my_list  # 删除整个列表对象
print(my_list)  # 报错,因为 my_list 已被删除

remove()

remove() 是 Python 列表的一个方法,用于删除列表中指定的元素。其语法格式如下:

list.remove(x)
  • list 是要操作的列表对象
  • x` 是要删除的元素。

remove()的时间复杂度:

  • 在Python中,remove()是用于从列表(list)中删除指定元素的方法。其时间复杂度取决于列表中要删除的元素的数量和位置。
  • 如果要删除的元素位于列表的末尾,那么remove()方法的时间复杂度为常数时间复杂度O(1),因为只需要将列表的末尾元素删除,并更新列表的长度即可。
  • 但是,如果要删除的元素不在列表的末尾,那么remove()方法的时间复杂度为O(n),其中n为列表中的元素数量。这是因为,在删除元素后,列表中的所有元素都需要向前移动一个位置,以填补被删除元素的空缺。因此,需要将位于被删除元素之后的所有元素依次向前移动一个位置,直到最后一个元素移动到被删除元素的位置,然后再将列表的长度减1。由于每个元素最多只需要向前移动一次,因此需要移动的元素数量为n-1,即时间复杂度为O(n)。

注意事项:

  • 使用remove()方法删除元素时,如果要删除的元素不存在于列表中,会抛出ValueError异常。
  • 如果要删除的元素在列表中出现了多次,remove()方法只会删除第一次出现的元素,并且其时间复杂度也是O(n),其中n为列表中的元素数量。

范例

  • 使用remove()方法删除指定元素:
my_list = [1, 2, 3, 4, 5]

my_list.remove(3)  # 删除元素3

print(my_list)  # 输出 [1, 2, 4, 5]

pop()

pop() 是 Python 列表的一个方法,用于移除列表中的一个元素,并返回该元素的值。其语法格式如下:

list.pop([index])
  • list 是要操作的列表对象
  • index 是要移除的元素的索引,如果不指定索引,则默认移除列表中的最后一个元素。

pop()的时间复杂度:

  • 在Python中,pop()是用于从列表(list)中删除指定位置的元素的方法。其时间复杂度取决于要删除的元素的位置。
  • 如果要删除的元素位于列表的末尾,那么pop()方法的时间复杂度为常数时间复杂度O(1),因为只需要将列表的末尾元素删除,并更新列表的长度即可。
  • 但是,如果要删除的元素不在列表的末尾,那么pop()方法的时间复杂度为O(n),其中n为列表中的元素数量。这是因为,在删除指定位置的元素后,该位置后面的所有元素都需要向前移动一个位置,以填补被删除元素的空缺。因此,需要将位于被删除元素之后的所有元素依次向前移动一个位置,直到最后一个元素移动到被删除元素的位置,然后再将列表的长度减1。由于每个元素最多只需要向前移动一次,因此需要移动的元素数量为n-1,即时间复杂度为O(n)。

注意事项:

  • pop()方法在不指定下标时默认删除最后一个元素,如果列表为空,则会抛出IndexError异常。
  • 如果没有指定要删除的元素的位置,即不传递参数给pop()方法,则默认删除列表的最后一个元素。在这种情况下,pop()方法的时间复杂度仍然是O(1),因为只需要将列表的末尾元素删除,并更新列表的长度即可。

范例

  • 使用pop()方法删除指定位置的元素,并返回被删除的元素:
my_list = [1, 2, 3, 4, 5]

deleted = my_list.pop(2)  # 删除下标为2的元素,返回被删除的元素3

print(my_list)  # 输出 [1, 2, 4, 5]

print(deleted)  # 输出 3

clear()

clear() 是 Python 列表的一个方法,用于移除列表中的所有元素,使其变为空列表。其语法格式如下:

list.clear()
  • list 是要操作的列表对象。

clear()的时间复杂度:

  • 在Python中,clear()是用于从列表(list)中删除所有元素的方法。其时间复杂度为O(n),其中n为列表中的元素数量。这是因为,clear()方法需要依次删除列表中的每个元素,并且由于列表中的所有元素都需要被删除,因此需要执行n次删除操作。
  • 需要注意的是,虽然clear()方法的时间复杂度与列表中的元素数量成线性关系,但是其实际运行时间可能与元素数量成正比例,也可能与元素数量无关。这取决于Python解释器内部的实现方式以及计算机硬件的性能。在一般情况下,clear()方法的时间复杂度是一个很好的参考指标,可以用来比较不同算法或数据结构的性能。

注意事项:

  • 慎用!此函数会删除列表中的所有元素
  • 该方法不返回任何值,仅会直接修改原列表。
  • clear() 方法只能用于列表对象,而不能用于元组、字符串等其他类型的序列对象。如果需要清空其他类型的序列对象,可以使用对应类型的清空方法或重新创建一个新的空序列对象来实现。

范例

  • 下面的代码创建一个包含 5 个元素的列表 my_list,然后使用 clear() 方法移除列表中的所有元素:
my_list = [1, 2, 3, 4, 5]
my_list.clear()
print(my_list)  # 输出 []

在执行 my_list.clear() 之后,列表 my_list 中的所有元素都被删除了,列表变为空列表 []

修改元素

my_list[index] = value

范例

在Python中,可以使用下标来修改列表中的元素,具体如下:

my_list = [1, 2, 3, 4, 5]

my_list[2] = 6  # 修改下标为2的元素为6

print(my_list)  # 输出 [1, 2, 6, 4, 5]

需要注意的是,使用下标修改元素时,如果指定的下标超出了列表的范围,会抛出IndexError异常。此外,由于列表中可以包含任意类型的数据,因此修改元素时需要保证新值与列表中原有元素的类型相同。

查询元素

在Python中,可以使用多种方式查找列表中的元素,具体如下:

[n]

在 Python 列表中,你可以使用不同的方式来表示索引 n。这里列举了一些常见的表达方式:

  1. 正整数索引:最常见的方式,从0开始递增,用于访问列表中的元素。

    • my_list[0]:访问列表中的第一个元素。
    • my_list[1]:访问列表中的第二个元素。
    • 依此类推。
  2. 负整数索引:从列表的末尾开始递减,用于从后向前访问列表中的元素。

    • my_list[-1]:访问列表中的最后一个元素。
    • my_list[-2]:访问列表中的倒数第二个元素。
    • 依此类推。
  3. 切片(Slice):可以使用切片来访问列表中的连续元素子集。

    • my_list[start:end]:获取从索引 start 开始到索引 end - 1 的元素子集。
    • my_list[start:]:获取从索引 start 开始直到列表末尾的元素子集。
    • my_list[:end]:获取从列表开头直到索引 end - 1 的元素子集。
    • my_list[start:end:step]:带步长(step)的切片,可以按照指定步长获取元素子集。
  4. 变量索引:将索引值存储在变量中,然后使用该变量来访问列表中的元素。

    • index = 2
      my_list[index]  # 使用变量 index 来访问列表中的第三个元素

这些都是访问列表中元素的不同方式,可以根据需要选择适合你情况的方式来操作列表。

my_list[:] 使用切片的方式来表示对整个列表的复制或提取。

这个切片表示法中的 : 表示从列表的开头到末尾的范围,因此 my_list[:] 就代表了整个列表的复制。这意味着它会创建一个新的列表,并将 my_list 中所有的元素复制到这个新列表中。这样做非常有用,特别是当你想对列表进行修改但又不想影响到原始列表时。

例如:

my_list = [1, 2, 3, 4, 5]
new_list = my_list[:]  # 复制整个列表

## 修改新列表,不影响原始列表
new_list[0] = 100

print(my_list)  # 输出原始列表
print(new_list)  # 输出修改后的新列表

这段代码中,new_listmy_list 的一个副本。通过对 new_list 的修改,不会影响到原始的 my_list

列表中的元素可以通过下标进行访问,下标从0开始。例如,可以使用以下语句来访问列表中的第一个元素和第二个元素:

my_list = [1, 2, 3, "a", "b", "c"]

print(my_list[0])  # 输出1
print(my_list[1])  # 输出2
  • 这种方式访问列表中的元素,时间复杂度为O(1)
    • 因为列表(list)是一种基于数组实现的线性数据结构,在内存中连续存储着一系列元素。因此,要访问列表中某个元素,只需要通过该元素的索引值进行访问,它的时间复杂度为常数时间复杂度O(1)。
    • 具体来说,当我们访问列表中某个元素时,Python会根据该元素的索引值计算出该元素在内存中的地址,然后直接在内存中访问该地址所存储的值。这个过程是非常快速和高效的,因此,这种方式访问列表中的元素的时间复杂度为O(1)。

在Python中,当我们使用索引访问列表中的元素时,实际上是通过计算偏移量来访问该元素的。具体来说,Python会将元素的索引值乘以每个元素的大小(即字节数)得到该元素在内存中的偏移量,然后通过该偏移量来访问该元素所存储的值。

由于Python的列表(list)是一种基于数组实现的线性数据结构,在内存中连续存储着一系列元素,因此,计算元素的偏移量非常简单和高效,只需要乘以一个常数即可,这个常数就是每个元素的大小,通常为4或8字节,取决于操作系统和Python的版本。

因此,当我们使用索引访问列表中的元素时,它的时间复杂度为常数时间复杂度O(1)。这也是列表这种数据结构的一个优势,它可以在常数时间内访问任意位置的元素。

in

in是Python中的一个关键字,用于判断某个元素是否包含在一个序列(如列表、元组、字符串等)中。具体来说,可以通过以下方式使用in关键字:

if element in sequence:
    # do something
  • element表示要查找的元素
  • sequence表示要在其中查找元素的序列,如列表、元组、字符串等。
  • 如果elementsequence中存在,那么表达式element in sequence的值为True,否则为False

范例

  • 使用in关键字判断一个元素是否在列表中:
my_list = [1, 2, 3, 4, 5]

if 3 in my_list:
    print("3在列表中")
else:
    print("3不在列表中")

index()

index() 是 Python 列表对象的一个方法,用于返回指定元素在列表中的索引位置。该方法的语法如下:

list.index(x[, start[, end]])
  • list 是要进行操作的列表对象,x 是要查找的元素值。
  • 可选参数 startend 分别表示查找的起始位置和结束位置(不包含),默认值为列表的开头和结尾。

index()的时间复杂度:

  • O(n)

注意事项:

  • 如果指定的元素在列表中不存在,index() 方法会抛出 ValueError 异常。
  • 如果指定了起始位置和结束位置,但是指定的范围内没有找到指定的元素,index() 方法也会抛出 ValueError 异常。
  • index() 方法只返回找到的第一个匹配元素的位置。如果列表中有多个相同的元素,可以使用切片等方式来查找其它匹配的位置。此外,如果只是判断列表中是否包含某个元素,可以使用 in 关键字来判断。

范例

  • 使用index()方法查找一个元素在列表中第一次出现的位置:
my_list = [1, 2, 3, 4, 5]

index = my_list.index(3)  # 查找元素3在列表中的下标

print(index)  # 输出 2
  • 下面是一个示例代码,演示如何使用 index() 方法在列表中查找指定元素的位置:
my_list = ['a', 'b', 'c', 'd', 'e', 'f']

## 查找元素 'c' 在列表中的位置
print(my_list.index('c'))  # 输出 2

## 查找元素 'f' 在列表中的位置
print(my_list.index('f'))  # 输出 5

## 查找元素 'x' 在列表中的位置
## 由于 'x' 不在列表中,会抛出 ValueError 异常
print(my_list.index('x'))  # 抛出 ValueError 异常

## 查找元素 'c' 在列表中位置 3 之后的位置
## 由于 'c' 不在指定范围内,会抛出 ValueError 异常
print(my_list.index('c', 3))  # 抛出 ValueError 异常

count()

  • 如果要查找多个元素在列表中出现的次数,可以使用count()方法

index()的时间复杂度:

  • O(n)

范例

my_list = [1, 2, 3, 2, 4, 5, 2]

count_2 = my_list.count(2)  # 查找元素2在列表中出现的次数
count_23 = my_list.count(2) + my_list.count(3)  # 查找元素2和3在列表中出现的次数

print(count_2)  # 输出 3
print(count_23)  # 输出 4

需要注意的是,如果要查找多个元素在列表中出现的次数,需要将它们的计数相加。

len()

  • 查询列表的长度,即列表中所有元素的数量

len()的时间复杂度:

  • O(1)
  • len()函数用于返回某个序列(如列表、元组、字符串等)的长度,其时间复杂度为O(1)。

len()的时间复杂度为什么是O(1)?

  • 这是因为,在Python中,序列的长度通常是在其头部或尾部存储的,因此,计算序列的长度只需要获取头部或尾部存储的长度信息即可,这个过程的时间复杂度是常数时间复杂度O(1)。
  • 对于列表(list)来说,它在内存中以数组的形式存储,而 其长度信息是存储在列表对象的头部的,因此,获取列表的长度只需要访问列表对象的头部即可,这个过程的时间复杂度是O(1)。
  • 需要注意的是,对于某些序列类型,如字典(dict)和集合(set),其长度信息不是直接存储在头部或尾部,而是需要遍历整个序列来计算,因此,它们的len()函数的时间复杂度为O(n),其中n为序列的长度。

范例

my_list = [1, 2, 3]

print(len(my_list))  # 输出3

排序

在Python中,可以使用多种方式对列表进行排序,具体如下:

  • sorted()sort() 默认都是从小到大排序,也可以通过定义 reverse 的方式从大到小排序

sorted()

sorted()是Python内置的排序函数,它接受一个可迭代对象作为输入,并返回一个新的已排序的列表。它的基本语法是:

sorted(iterable, key=None, reverse=False)
  • iterable是需要排序的可迭代对象,如列表、元组、集合、字典等。
  • key是一个函数,用于指定排序的键。默认为None,表示按照元素自身进行排序。例如,如果iterable是一个由字典组成的列表,可以使用key参数指定按照某个字典键的值进行排序,例如key=lambda x: x['age']
  • reverse是一个布尔值,用于指定是否降序排列。默认为False,表示升序排列。

注意事项:

  • sorted()返回的结果是一个新的列表,原始的可迭代对象并没有被修改。如果需要在原地进行排序,可以使用列表的sort()方法。
  • 使用key参数时需要传入一个函数,函数的返回值将用于排序。

范例

  • 对列表进行升序排序:
lst = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5]
sorted_lst = sorted(lst)
print(sorted_lst)  # [1, 1, 2, 3, 3, 4, 5, 5, 5, 6, 9]
  • 对列表进行降序排序:
lst = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5]
sorted_lst = sorted(lst, reverse=True)
print(sorted_lst)  # [9, 6, 5, 5, 5, 4, 3, 3, 2, 1, 1]
  • 对元组列表按照元组中的第二个元素进行升序排序:
lst = [('apple', 10), ('orange', 20), ('banana', 5), ('lemon', 15)]
sorted_lst = sorted(lst, key=lambda x: x[1])
print(sorted_lst)  # [('banana', 5), ('apple', 10), ('lemon', 15), ('orange', 20)]
  • 对字典按照值进行升序排序:
d = {'apple': 10, 'orange': 20, 'banana': 5, 'lemon': 15}
sorted_d = sorted(d.items(), key=lambda x: x[1])
print(sorted_d)  # [('banana', 5), ('apple', 10), ('lemon', 15), ('orange', 20)]
  • 在这个例子中,我们使用items()方法将字典转换为键值对列表,然后使用lambda函数作为key参数,指定按照值进行排序。

sort()

sort()是Python列表的方法之一,用于对列表进行排序。与sorted()函数不同,sort()方法会直接修改原始列表,而不是返回一个新的已排序列表sort()方法的基本语法是:

list.sort(key=None, reverse=False)
  • key参数与sorted()函数中的相同,用于指定排序的键。
  • reverse参数与sorted()函数中的相同,用于指定是否降序排列。

注意事项:

  • 需要注意的是,sort()方法只能用于列表,而不能用于其他可迭代对象。如果需要对其他可迭代对象进行排序,应该使用sorted()函数。
  • 此外,由于sort()方法会直接修改原始列表,因此在进行排序之前应该确保原始列表的内容可以被修改,或者使用原始列表的副本进行排序。
  • 使用key参数时需要传入一个函数,函数的返回值将用于排序。

范例

  • 对列表进行升序排序:
lst = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5]
lst.sort()
print(lst)  # [1, 1, 2, 3, 3, 4, 5, 5, 5, 6, 9]
  • 对列表进行降序排序:
lst = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5]
lst.sort(reverse=True)
print(lst)  # [9, 6, 5, 5, 5, 4, 3, 3, 2, 1, 1]
  • 可以使用list.sort()方法的key参数来指定排序的关键字,例如对字符串列表按照字符串长度排序:
my_list = ['cat', 'apple', 'dog', 'banana']

my_list.sort(key=len)  # 按照字符串长度排序

print(my_list)  # 输出 ['cat', 'dog', 'apple', 'banana']
  • intstr间不能进行排序
## 会报错,TypeError: '<' not supported between instances of 'str' and 'int'
lst = [1, 2, 3, 'a']
lst.sort()
print(lst)


## 会报错,ValueError: invalid literal for int() with base 10: 'a'
lst = [1, 2, 3, 'a']
lst.sort(key=int)
print(lst)


## 不报错
lst = [1, 2, 3, 'a']
lst.sort(key=str)
print(lst)  # [1, 2, 3, 'a']

第一段代码:

  • 第一段代码会报错是因为列表lst中既包含整数类型的元素,又包含字符串类型的元素,这使得sort()方法无法确定如何对这些元素进行排序。在比较整数和字符串时,Python会抛出TypeError异常,提示无法对不同类型的对象进行比较。
  • 为了避免这个问题,应该确保列表中的元素类型相同,或者使用key参数来指定排序的键。如果列表中既包含整数类型的元素,又包含字符串类型的元素,可以使用key=str将所有元素转换成字符串再进行排序。如果列表中包含的元素类型非常多,可以考虑使用key参数自定义一个函数,用于将不同类型的元素转换成可比较的类型。

第二段代码:

  • 第二段代码会报错是因为使用了int作为排序的键,这会将列表中的所有元素都尝试转换成整数,如果有无法转换的字符串类型元素,就会抛出ValueError异常,提示无法将其转换为整数。
  • 在这种情况下,列表中的第四个元素是字符串类型的'a',无法转换成整数,因此会抛出ValueError异常。为了避免这个问题,应该确保列表中的元素都是可以转换成排序键类型的,或者使用key参数来自定义一个函数,用于将元素转换成可比较的类型。

第三段代码:

  • 第三段代码不报错是因为使用了字符串类型的键来排序,这使得排序算法将列表中的所有元素都转换成字符串再进行排序,从而避免了之前的类型错误。

  • 在这种情况下,sort()方法将根据字符串的字典序对元素进行排序,即按照首字符的ASCII码值依次进行比较。

  • 不同的key,排序的结果不同

lst = [3, 2, 1, 11]
lst.sort()
print(lst)  # [1, 2, 3, 11]


lst = [3, 2, 1, 11]
lst.sort(key=str)
print(lst)  # [1, 11, 2, 3]
  • 这两段代码的排序结果不一样是因为排序键的不同。

  • 在第一段代码中,sort()方法使用默认的排序键,即比较元素的大小,由于元素的类型都是整数,因此会按照从小到大的顺序进行排序。因此,排序结果为[1, 2, 3, 11],即从小到大排序。

  • 而在第二段代码中,sort()方法使用str作为排序键,这会将所有元素都转换成字符串类型再进行比较。

  • 由于字符串类型的比较方式是按照字典序进行比较,即按照首字符的ASCII码值依次进行比较,因此排序结果为[1, 11, 2, 3],即按照字符串类型的字典序进行排序。

  • 在这个结果中,数字类型的元素按照其字符串形式的字典序排序,因此11排在2的前面。

  • 对列表中的字典按照键值进行升序排序:

lst = [{'name': 'Alice', 'age': 20},
       {'name': 'Bob', 'age': 25},
       {'name': 'Charlie', 'age': 18}]
lst.sort(key=lambda x: x['age'])
print(lst)  # [{'name': 'Charlie', 'age': 18}, {'name': 'Alice', 'age': 20}, {'name': 'Bob', 'age': 25}]

还可以使用sorted()对字典进行排序,它可以通过指定key参数来排序字典的值。

例如:

d = {'apple': 10, 'orange': 20, 'banana': 5, 'lemon': 15}
sorted_d = sorted(d.items(), key=lambda x: x[1])
print(sorted_d)  # [('banana', 5), ('apple', 10), ('lemon', 15), ('orange', 20)]

这将返回一个元组列表,其中每个元组由键值对组成,根据字典的值排序。在这个例子中,我们使用了一个lambda函数作为key参数,它返回每个键值对的值。

sorted() & sort()

sorted()sort()都是Python中的排序函数,但它们的使用方式和返回结果略有不同。

sort()是列表对象的方法,可以在原地对列表进行排序,也就是说,它修改了原始列表并返回None,因此不能将其结果分配给变量。它也不支持对其他类型的数据进行排序。

例如:

lst = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5]
lst.sort()
print(lst)  # [1, 1, 2, 3, 3, 4, 5, 5, 5, 6, 9]

sorted()是一个内置函数,它接受任何可迭代对象,并返回一个新的已排序列表,而不修改原始数据。因此,它可以将其结果分配给变量。

例如:

lst = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5]
new_lst = sorted(lst)
print(new_lst)  # [1, 1, 2, 3, 3, 4, 5, 5, 5, 6, 9]

列表复制

PS:一定要注意地址对象的问题!!!

x = [1, 2, 3]
y = x
print(x == y)  # True
print(x is y)  # True
  • 这段代码创建了一个列表x,并将它赋值给变量y

  • 由于xy都指向同一个列表对象,因此x == y返回True,表示xy所引用的对象的值相等,即它们包含的元素相同。

  • 另外,由于y是通过将x赋值给它而创建的,因此xy所引用的是同一个列表对象。因此,使用is运算符比较它们的身份标识(即内存地址)时,返回True,表示xy所引用的对象是同一个对象。

    • 可以通过id()来查看其内存地址进行判断:

    • x = [1, 2, 3]
      y = x
      print(x == y)  # True
      print(x is y)  # True
      print(id(x), id(y))  # 1678989529160 1678989529160
  • 需要注意的是,==is运算符比较的是不同的内容,前者比较的是值是否相等,后者比较的是身份标识(即内存地址)是否相同。

x = [1, 2, 3]
y = x.copy()
print(x == y)  # True
print(x is y)  # False
print(id(x), id(y))  # 2605531009096 2605530170952
  • 这段代码创建了一个列表x,并将它的元素拷贝给变量y
  • 由于y是通过将x的副本拷贝给它而创建的,因此xy包含的元素相同,即它们的值相等,因此x == y返回True
  • 由于xy所引用的是两个不同的列表对象,因此它们的身份标识(即内存地址)是不同的,即x is y返回False
    • copy()方法用于创建原列表的浅拷贝,即复制原列表中的元素,并将这些元素添加到一个新列表中,返回新列表的引用。与原列表不同,浅拷贝的元素和原列表的元素具有不同的内存地址。
    • 在本例中,x.copy()复制了x列表的所有元素,并将这些元素添加到一个新的列表中。因此,新列表的引用被赋值给了变量y,此时xy引用的是两个不同的列表对象,它们具有不同的内存地址,即它们不是同一个对象。
  • 因此,总结一下:
    • x == y返回True,因为它们的值相等
    • x is y返回False,因为它们所引用的对象不同(内存地址不同)

问题一:

  • print()的返回值是?
a = list(range(4))
b = list(range(4))

print(a == b)  # ?

c = a
c[2] = 10

print(a)

print(a == b)  # ?
print(a == c)  # ?

问题二:

  • print()的返回值是?
a = list(range(4))
b = list(range(4))

print(a == b)  # ?

c = a
c[2] = 10

print(a)  # ?

print(a == b)  # ?
print(a == c)  # ?

问题三:

  • print()的返回值是?
a = list(range(4))
b = a.copy()

print(a == b)  # ?

a[2] = 10

print(a == b)  # ?

深浅拷贝

在 Python 中,对于对象的赋值,实际上只是创建了一个指向该对象的引用。如果一个对象的值发生改变,这个改变也会反映在其他指向该对象的引用上。这种引用的复制称为浅拷贝,即只拷贝对象的一层引用关系。

Python 提供了 copy 模块来实现深浅拷贝,其中 copy() 方法用于浅拷贝,deepcopy() 方法用于深拷贝。

浅拷贝:

  • shadow copy,影子拷贝,也叫浅拷贝。遇到引用数据类型,仅仅复制一个引用而已
  • 浅拷贝只是对对象的一层引用进行了复制,而对于嵌套在对象中的其他对象,只是创建了一个指向这些对象的引用,而并没有对其进行复制。因此,在浅拷贝中,源对象和拷贝对象会共享嵌套在其中的对象。

深拷贝:

  • 深拷贝,往往会递归复制一定深度
  • 深拷贝是在浅拷贝的基础上对所有嵌套在源对象中的对象进行递归复制,从而创建一个全新的对象,与源对象完全独立。

总之,浅拷贝只复制了第一层对象的引用,深拷贝则递归地复制了所有嵌套对象。

下面是一个示例,演示了浅拷贝和深拷贝的不同之处:

import copy

## 浅拷贝
a = [1, [2, 3]]
b = copy.copy(a)
print(a)    # [1, [2, 3]]
print(b)    # [1, [2, 3]]
a[1][0] = 4
print(a)    # [1, [4, 3]]
print(b)    # [1, [4, 3]],b 中嵌套的列表对象也被修改了

## 深拷贝
a = [1, [2, 3]]
b = copy.deepcopy(a)
print(a)    # [1, [2, 3]]
print(b)    # [1, [2, 3]]
a[1][0] = 4
print(a)    # [1, [4, 3]]
print(b)    # [1, [2, 3]],b 中嵌套的列表对象没有被修改
  • 在浅拷贝中,修改源对象中嵌套的列表对象也会同时修改拷贝对象中的嵌套列表对象。
  • 而在深拷贝中,源对象和拷贝对象中的嵌套列表对象是完全独立的,修改源对象中的嵌套列表对象不会影响拷贝对象中的嵌套列表对象。

范例

  • print()的返回值是?
a = [1, [2, 3, 4], 5]
b = a.copy()

print(a == b)  # ?

a[2] = 10

print(a == b)  # ?

a[2] = b[2]

print(a == b)  # ?

a[1][1] = 100

print(a == b)  # ?,False
print(a)  # ?,[1, [100, 3, 4], 10]
print(b)  # ?,[1, [2, 3, 4], 10]
  • a 是一个列表,包含三个元素:整数 1、列表 [2, 3, 4] 和整数 5。
  • b 是通过将 a 列表中的元素复制到一个新列表中得到的,即 ba 指向不同的对象。
  • 第一次比较 a == b 的结果为 True,因为它们包含相同的元素。
  • a 列表中的第三个元素从 5 改为 10,此时 a == b 的结果为 False,因为它们包含不同的元素。
  • a 列表中的第三个元素赋值为 b 列表中的第三个元素,此时 a == b 的结果再次变为 True,因为它们包含相同的元素。
  • a 列表中的第二个元素(也就是嵌套的列表)中的第二个元素从 3 改为 100,此时 a == b 的结果为 True但是 ab 中的嵌套列表指向的是同一个对象,所以它们的值同时改变了。
    • 当进行a.copy()复制操作时,浅拷贝只是复制了a列表的第一层元素,也就是整数和嵌套列表的地址(指针,内存地址),并没有创建新的嵌套列表对象,所以ab中的嵌套列表指向同一个对象,。
    • 因此,对a嵌套列表中的元素进行修改时,会改变指向同一对象的b中的对应元素。这也是浅拷贝的一个特点,只复制对象的引用,而不是对象本身。

列表推导式

当你需要根据某些规则或逻辑来创建一个新的列表时,Python 中的列表推导式就非常有用。它允许你以一种简洁的方式从一个现有的可迭代对象(比如列表、元组、集合、字典等)创建一个新的列表。

基本语法结构

new_list = [expression for item in iterable if condition]
  • expression:对 item 执行的操作或逻辑,用于生成新列表中的元素。
  • item:可迭代对象中的元素。
  • iterable:用于迭代的对象,比如列表、元组、集合等。
  • condition(可选):用于过滤元素的条件。

注意事项:

  1. 可读性:列表推导式的嵌套和复杂度可能降低代码的可读性。谨慎使用。
  2. 性能:有时列表推导式可能不如普通的循环语句高效。在处理大量数据时,可能会影响性能。

基本列表推导式

numbers = [1, 2, 3, 4, 5]
squared_numbers = [x**2 for x in numbers]
## 输出:[1, 4, 9, 16, 25]

带条件的列表推导式

numbers = [1, 2, 3, 4, 5]
even_squared = [x**2 for x in numbers if x % 2 == 0]
## 输出:[4, 16]

嵌套列表推导式

你也可以使用嵌套的列表推导式来创建更复杂的结构:

matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
flattened = [number for row in matrix for number in row]
## 输出:[1, 2, 3, 4, 5, 6, 7, 8, 9]

这个例子中,列表推导式中的两个 for 关键字让你能够遍历嵌套的数据结构。

使用列表推导式时,保持适度的复杂度和清晰的代码结构会更有帮助。

范例 - 1

#!/usr/local/bin/python3
#eth_interfaces = ["eth{}".format(i) for i in range(3)]
eth_interfaces = [f"eth{i}" for i in range(3)]
print(eth_interfaces)
'''
['eth0', 'eth1', 'eth2']
'''

范例 - 2

[r['value'] for r in results]
  • 这段列表推导式是一个简洁的方式来创建一个新的列表,其元素来自于名为results的列表对象中字典的'value'键对应的值。换句话说,它会提取results列表中每个字典的'value'键对应的值,然后将这些值组成一个新的列表返回给你。

  • r['value'] 是 Python 中访问字典元素的方式。在这里,r 是一个代表列表 results 中的元素的变量名,而 ['value'] 则是用来从字典中获取键 'value' 对应的值的语法。

    所以,r['value'] 表示访问变量 r 所指向的字典中的 'value' 键对应的值。在列表推导式中,r['value'] 这一表达式被用来从列表 results 的每个字典中提取 'value' 键对应的值。

假设有一个名为 results 的列表,其中包含了多个字典对象,每个字典都有一个 'value' 键对应着某个值。例如:

results = [
    {'value': 10},
    {'value': 20},
    {'value': 30},
    {'value': 40}
]

现在,你可以使用列表推导式来提取results列表中每个字典的'value'键对应的值,方法如下:

extracted_values = [r['value'] for r in results]

这将产生一个名为 extracted_values 的新列表,其中包含了 results 列表中每个字典的 'value' 键对应的值:

extracted_values = [10, 20, 30, 40]

这个列表包含了results中每个字典的'value'键对应的值,并以列表的形式返回。

范例 - 3

list = [[1704016800, '100.00567979015831'], [1704016810, '100.08788532772374'], [1704016820, '100.08788532772374'], [1704016830, '100.08788532772374'], [1704016840, '100.08788532772374'], [1704016850, '100.08788532772374'], [1704016860, '100.08788532772374'], [1704016870, '79.58324257655973'], [1704016880, '99.95152232849618'], [1704016890, '99.95152232849618'], [1704016900, '99.95152232849618'], [1704016910, '99.95152232849618'], [1704016920, '99.95152232849618'], [1704016930, '99.85466684881482'], [1704016940, '99.95219092571594'], [1704016950, '99.95219092571594'], [1704016960, '99.95219092571593'], [1704016970, '99.95219092571593'], [1704016980, '99.95219092571593'], [1704016990, '100.03314837340274'], [1704017000, '100.03314837340274'], [1704017010, '100.03314837340274'], [1704017020, '100.03314837340274'], [1704017030, '100.03314837340274'], [1704017040, '100.03314837340274'], [1704017050, '100.05246773833649'], [1704017060, '100.05121521006626'], [1704017070, '100.05121521006626'], [1704017080, '100.05121521006626'], [1704017090, '100.05121521006626'], [1704017100, '100.05121521006626'], [1704017110, '100.05121521006626'], [1704017120, '99.99984617160756'], [1704017130, '99.99984617160756'], [1704017140, '99.99984617160756'], [1704017150, '99.99984617160756'], [1704017160, '99.99984617160756'], [1704017170, '99.90969187089527'], [1704017180, '100.00284892552264'], [1704017190, '100.00284892552264'], [1704017200, '100.00284892552264'], [1704017210, '100.00284892552264'], [1704017220, '100.00284892552264'], [1704017230, '100.09305441070865'], [1704017240, '100.0325744977309'], [1704017250, '100.0325744977309'], [1704017260, '100.0325744977309'], [1704017270, '100.0325744977309'], [1704017280, '100.0325744977309'], [1704017290, '99.98801182345576'], [1704017300, '99.93033657110898'], [1704017310, '99.93033657110898'], [1704017320, '99.93033657110898'], [1704017330, '99.93033657110898'], [1704017340, '99.93033657110898'], [1704017350, '99.98058409530984'], [1704017360, '99.98403929743715'], [1704017370, '99.98403929743715'], [1704017380, '99.98403929743715'], [1704017390, '99.98403929743715'], [1704017400, '99.98403929743715']]

list = [[sublist[0], int(float(sublist[1]))] for sublist in list]
  • for sublist in list:这部分是循环语句,遍历原始列表 list 中的每个子列表,将每个子列表赋值给 sublist
  • [sublist[0], int(float(sublist[1]))]:这是在循环中执行的操作。对于每个 sublist
    • 首先取出 sublist 的第一个元素 [sublist[0] 作为新子列表的第一个元素
    • 然后将 sublist 的第二个元素 sublist[1],转换为浮点数 float(sublist[1]),然后再转换为整数 int(float(sublist[1])),将这个整数作为新子列表的第二个元素
  • 整个列表推导式的作用是遍历原始列表的每个子列表,并对每个子列表中的第二个元素(字符串表示的数字)进行浮点数转换和整数转换,最终生成一个新的列表 new_list,其中包含了修改过的子列表。

列表总结

列表 CRUD 效率

C

  • Create,这里指的是在列表中添加元素
  • insert(),列表中插入元素,单个元素插入,时间复杂度为 O(n) 效率非常差,因为插入元素后,其后面所有的元素都要后移,好比一个人从中间或队首插队,队伍中的所有人都要后移。
    • insert() 定位索引时效率很高
  • append(),列表末尾添加元素,时间复杂度为 O(1) 效率一般来说非常好,但如果添加的元素非常大,Python 会分配更多的内存空间(还有垃圾回收的问题),这种情况下时间复杂度为 O(n)
  • extend(),多个元素插入,将可迭代对象添加到列表的末尾,效率相对较高
  • +,将两个或多个列表合并成一个新列表,拼接
  • *,将列表中的元素重复n次合并成一个新列表(浅拷贝),效率?

R

  • Read,这里指的是查询列表中元素的索引、数量、值
  • index(),查询列表中第一个匹配元素的位置,时间复杂度为 O(n) 效率非常差,需要遍历,能不用则不用
  • count(),查询列表中匹配元素的数量,时间复杂度为 O(n) 效率非常差,需要遍历,能不用则不用
  • len(),查询列表中匹配元素的数量,时间复杂度为 O(1) ,性能很好,爱用多少次用多少次

U

  • Update,更改元素中的内容
  • my_list[index] = value,指定元素的索引然后修改其value,因为是通过索引进行定位,所以效率较高

D

  • Delete,这里指的是在列表中删除元素
  • pop(),将列表中的某个元素删除并打印,效率取决于删除元素的位置
    • 如果删除的元素位于队首或中间,则时间复杂度为 O(n) 效率非常差,因为删除元素后,其后面所有的元素都要前移,好比一个人从中间或队首离队,队伍中的所有人都要前移。
    • 如果删除的元素位于末尾,时间复杂度为 O(1) 效率非常好,因为不涉及到元素左移的问题
    • PS:如果不指定索引,则默认移除列表中的最后一个元素。
  • remove(),删除列表中的某个元素,与pop()一样,效率取决于删除元素的位置
  • del,删除列表中的某个元素或整个列表
    • 删除某个元素时,与pop()一样,效率取决于删除元素的位置
    • 删除整个列表时,时间复杂度为常数时间复杂度O(1),因为只需要将列表对象从内存中删除即可。
  • clear(),将列表中的元素全部删除,时间复杂度为 O(n) 效率非常差,因为需要将列表中的元素逐一删除
    • PS:此方法慎用

链表 CRUD 效率对比列表

C

  • Create,这里指的是在链表中添加元素
  • 链表定位数据:
    • 链表定位数据的速度不及列表,因为:
    • 因为链表在内存中存储时并不是整齐的,因此需要先问第一个元素第二个元素的位置,第二个元素再问第三个元素的位置,以此类推,知道找到最终的元素,所以链表查询元素所在位置时是比较耗时的
    • 而列表通过索引查询位置,并且数据存放在内存中是整齐的,所以只需要计算数据的偏移量就可以直接找到元素所在的位置
  • 链表添加元素:
    • 链表添加元素的效率要高于列表,因为:
    • 列表在添加元素后,后面的元素需要右移,这样在数据量较大时,效率是很低的
    • 而链表在添加元素时,只需先找到要插入元素的位置,然后左右两边的元素断开,最后左右两边的元素只要再指向新的元素即可,不需要将元素右移,因此效率很高(小朋友拉手例子。。。)
    • 并且链表在头或尾的操作效率也很高
  • 总结:
    • 链表定位数据是遍历索引,因此时间复杂度是O(n)
    • 链表找到数据后,插入数据,就是手拉手断开,而后重新拉新元素,所以高效

R

  • Read,这里指的是查询链表中元素的索引、数量、值
  • 链表查询内容的效率不及列表,因为链表是连接表,而不是顺序表,因此需要遍历才能找到要查询的元素
    • 列表查找元素是通过索引,时间复杂度为O(1)
    • 链表查找元素需要遍历,时间复杂度为O(n)

U

  • Update,更改链表中的内容
  • 链表更改内容的效率不及列表,因为链表是连接表,而不是顺序表,因此需要遍历才能找到要更改的元素
    • 列表查找元素是通过索引,时间复杂度为O(1)
    • 链表查找元素需要遍历,时间复杂度为O(n)

D

  • Delete,这里指的是在链表中删除元素
  • 表首删除数据:
    • 链表高于列表,因为:
      • 链表只需将表首元素直接删除,后面的元素无需前移
      • 而列表删除元素后还需将后面的元素前移
  • 表中间删除数据:
    • 链表高于列表,因为:
      • 链表只需将被删除元素的左右两边元素的手断开,然后左右两边再拉手就可以
      • 而列表删除元素后还需将后面的元素前移
  • 表尾删除数据:
    • 列表和链表在表尾删除数据的效率基本上一样高

总结

  • 元素规模较大,增删频繁,尽量使用链表

修改元素

  • 修改列表中第二个元素,将其精确到小数点后两位。
value = [1703418793.873, '39.171875']

## 将第二个元素转换为浮点数,再保留小数点后两位
value[1] = round(float(value[1]), 2)
print(value) # [1703418793.873, 39.17]

1

[[1703426666, ‘39.1875’], [1703426681, ‘39.1875’], [1703426696, ‘39.1875’], [1703426711, ‘39.1875’], [1703426726, ‘39.1875’], [1703426741, ‘39.1875’], [1703426756, ‘39.1875’], [1703426771, ‘39.1875’], [1703426786, ‘39.1875’], [1703426801, ‘39.1875’], [1703426816, ‘39.1875’], [1703426831, ‘39.1875’], [1703426846, ‘39.1875’], [1703426861, ‘39.1875’], [1703426876, ‘39.1875’], [1703426891, ‘39.1875’], [1703426906, ‘39.1875’], [1703426921, ‘39.1875’], [1703426936, ‘39.1875’], [1703426951, ‘39.1875’], [1703426966, ‘39.1875’], [1703426981, ‘39.1875’], [1703426996, ‘39.1875’], [1703427011, ‘39.1875’], [1703427026, ‘39.1875’], [1703427041, ‘39.1875’], [1703427056, ‘39.1875’], [1703427071, ‘39.1875’], [1703427086, ‘39.1875’], [1703427101, ‘39.1875’], [1703427116, ‘39.1875’], [1703427131, ‘39.1875’], [1703427146, ‘39.19140625’], [1703427161, ‘39.19140625’], [1703427176, ‘39.19140625’], [1703427191, ‘39.19140625’], [1703427206, ‘39.19140625’], [1703427221, ‘39.19140625’], [1703427236, ‘39.19140625’], [1703427251, ‘39.19140625’]] 如何使用python将这个列表中的每个列表的第二个元素转换为整数值,例如[1703426666, ‘39.1875’]转换为[1703426666, ‘39’],以此类推

可以使用 Python 中的 float() 函数将字符串表示的浮点数转换为整数。在这个列表中,第二个元素是一个字符串表示的浮点数,可以使用 float() 函数将其转换为浮点数,然后再转换为整数。以下是一个示例代码:

data = [
    [1703426666, '39.1875'],
    [1703426681, '39.1875'],
    # ... 其他数据
    [1703427251, '39.19140625']
]

## 遍历列表并转换第二个元素为整数值
for sublist in data:
    sublist[1] = str(int(float(sublist[1])))

print(data)

这段代码遍历了包含子列表的主列表,并将每个子列表的第二个元素从字符串转换为浮点数,然后将其转换为整数,并存储回原来的列表位置。注意,这里转换成整数后再转换回字符串的操作是因为原列表是包含整数和字符串的列表。

并且只留第二个元素呢

如果你只想保留每个子列表的第二个元素,并将其转换为整数,你可以使用列表推导式来做到这一点。以下是一个示例代码:

data = [
    [1703426666, '39.1875'],
    [1703426681, '39.1875'],
    # ... 其他数据
    [1703427251, '39.19140625']
]

## 仅保留每个子列表的第二个元素并转换为整数
result = [int(float(sublist[1])) for sublist in data]

print(result)

这段代码使用了列表推导式,在一个单行的代码中遍历了原始数据列表,并将每个子列表的第二个元素转换为浮点数,然后转换为整数,并存储到了一个新的列表中。最终输出的 result 列表包含了所有子列表第二个元素的整数值。