数组
什么是数组
数组是一种数据结构,用一堆连续的内存空间,用于存储固定数量的相同类型的元素。数组中的每个元素可以通过一个唯一的索引来识别和访问,索引通常是一个整数,从0开始,依次递增
比如:
ls = [1,2,3,4,5,6,7,8,9]
其中第一个元素(索引为0)是1,第二个元素(索引为1)是2,以此类推。数组通常用于表示一组相关的数据,例如存储学生的成绩、员工的工资等。
需要注意的是
python实际上没有数组这个概念
但是python提供了list,它和数组非常类似。我们可以把list视为 Python 的“数组”。
实际上的数组如果按照维度来分的话,可以分为一维数组和多维数组
比如:
ls = [1,2,3, # 一维数组
[1,23,4,5], # 二维数组
56,
[1,2,3,4, # 一维数组
[4,6,7, # 二维数组
[8]] # 三维数组
]
]
按照存储方式来分:可以分为静态数组和动态数组
- 静态数组 数组长度不变,不能增长,如果真要使用类似于静态数组的结构,可以使用NumPy库提供的ndarray对象
- 动态数组 数组长度可以自动增长
需要注意的是,python没有动态数组和静态数组的概念,因为Python的列表(list)就是一种动态数组,它能够自动扩展和收缩以适应存储数据的需求。在Python中,列表的大小可以在运行时动态地增加或减小,因此,Python列表可以自由地添加、删除或修改元素,并且不需要预先指定列表的大小
数组的随机读写性能相当的好
- 数组元素的存储是连续的内存地址。这意味着元素之间的距离是固定的
- 要访问数组的任何元素,只需要基础地址加上偏移量就能定位到该元素。这些操作都很高效
- 由于元素的连续性,CPU 可以更有效地缓存数组。这提高了随机访问的效率
- 因此,访问数组的任意元素都需要固定的时间。不管访问哪个元素,时间消耗都是一样的
- 这意味着数组的随机读写性能很高,时间复杂度都是 O(1)
数组的主要操作有:
- 初始化一个数组。
- 访问数组的某个索引。
- 修改数组某个索引的值。
- 获取数组的长度。
- 向数组追加元素。
- 扩展数组。
- 切片数组。
我用array模块举个例子:
import array as arr
# 初始化数组
numbers = arr.array("i", [1, 2, 3])
# 访问数组索引 2
print(numbers[2]) # 3
# 修改索引 1 的值
numbers[1] = 5
# 打印数组
print(numbers) # array('i', [1, 5, 3])
链表
什么是链表
链表是一种有序的线性数据集合,它由一系列节点组成,每个节点包含数据和指向下一个节点的指针。与数组不同,链表的节点在内存中不一定是连续存储的.
为什么需要链表
可能很多人就会问了,有了数组,可以很灵活的操作数据,为什么还需要链表?
这是因为链表有几个好处
- 灵活的内存使用。链表可以动态增加和减少节点,不需要预分配固定长度的空间。而数组需要预分配固定长度的空间
- 处理变长数据。链表天然适用于需要存储变长数据的场景,而数组适用于存储固定长度的数据
- 便于插入和删除。在链表中插入和删除节点的代价很低,只需要重新连接指针,不需要移动大量的数据。而数组的插入和删除需要移动大量数据,在插入或删除节点时,只需要修改相邻节点的指针,时间复杂度为O(1),而在数组中需要移动元素,时间复杂度可能为O(n)。因此,在插入和删除操作频繁的场景下,链表比数组更适合
- 链表使用指针连接,不需要连续的内存空间。而数组需要连续的内存空间。这使得链表也可用于内存分散的情况
- 链表可以被用于实现其它的数据结构,如栈、队列、哈希表等
- 在多线程编程中,链表的插入和删除操作可以通过锁来实现线程安全。而数组需要在插入和删除时移动元素,可能会导致数据不一致的问题
链表有多种类型,常见的有以下几种:
- 单向链表:每个节点只有一个指针,指向下一个节点。单向链表可以实现基本的插入、删除和遍历操作,但无法实现快速反转和回文判断等操作
- 双向链表:每个节点有两个指针,分别指向前一个节点和后一个节点。双向链表可以实现基本的插入、删除和遍历操作,而且可以快速反转和回文判断等操作
- 循环链表:链表的最后一个节点指向第一个节点,形成一个环。循环链表可以实现基本的插入、删除和遍历操作,而且可以用来实现循环队列等数据结构
- 带头结点链表:在链表的开头添加一个额外的节点作为头结点,头结点不存储数据,只是用来简化链表的操作。带头结点链表可以避免在插入和删除节点时需要特殊处理头节点的情况
- 带尾结点链表:在链表的结尾添加一个额外的节点作为尾结点,尾结点的指针指向空值。带尾结点链表可以快速插入和删除节点,而且可以避免在插入和删除节点时需要遍历整个链表的情况。
需要注意的是,python没有链表这个概念,但是可以用自定义类来实现一个链表
class Node:
def __init__(self, data):
self.data = data
self.next = None
# Node节点类包含两个属性:一个是存储数据值的data属性,另一个是指向下一个节点的next属性
class LinkedList:
def __init__(self):
self.head = None
def add_node(self, data):
new_node = Node(data)
if self.head is None:
self.head = new_node
else:
current_node = self.head
while current_node.next is not None:
current_node = current_node.next
current_node.next = new_node
def remove_node(self, data):
if self.head.data == data:
self.head = self.head.next
else:
current_node = self.head
while current_node.next is not None:
if current_node.next.data == data:
current_node.next = current_node.next.next
return
current_node = current_node.next
def search_node(self, data):
current_node = self.head
while current_node is not None:
if current_node.data == data:
return True
current_node = current_node.next
return False
def print_list(self):
current_node = self.head
while current_node is not None:
print(current_node.data)
current_node = current_node.next
# LinkedList类则包含一个指向链表头部的head属性,并提供了一些方法来操作链表,例如添加、删除、查找、遍历等
# 创建链表
linked_list = LinkedList()
# 添加节点
linked_list.add_node(1)
linked_list.add_node(2)
linked_list.add_node(3)
# 打印链表
linked_list.print_list() # 输出:1 2 3
# 删除节点
linked_list.remove_node(2)
# 打印链表
linked_list.print_list() # 输出:1 3
# 查找节点
print(linked_list.search_node(1)) # 输出:True
print(linked_list.search_node(2)) # 输出:False
链表和数组的比较
链表的优点:
-
插入和删除操作效率高:链表在插入和删除操作时只需要修改指针,不需要移动其他元素,因此效率比数组高
-
动态扩展:链表可以根据需要动态增加或减少元素,而数组的长度固定
-
不需要连续的内存空间:链表中的元素可以存储在不连续的内存空间中,不像数组需要一段连续的内存空间
链表的缺点:
-
随机访问效率低:链表中的元素不是按照位置顺序存储的,因此随机访问元素的效率很低
-
需要额外的存储空间:链表需要额外的指针来存储元素之间的关系,因此存储空间比数组要大
-
不支持快速查找:链表中的元素不是按照位置顺序存储的,因此查找一个元素需要遍历整个链表,效率很低
数组的优点:
-
随机访问效率高:数组中的元素按照位置顺序存储,因此随机访问元素的效率很高
-
存储空间小:数组只需要存储元素本身,不需要额外的指针来存储元素之间的关系,因此存储空间比链表小
-
支持快速查找:数组中的元素按照位置顺序存储,因此可以使用二分查找等算法快速查找一个元素
数组的缺点:
-
插入和删除操作效率低:数组在插入和删除操作时需要移动其他元素,效率比链表低
-
长度固定:数组的长度固定,不能动态扩展或减少
-
需要连续的内存空间:数组需要一段连续的内存空间来存储元素,因此在内存空间不足的情况下可能无法存储更多的元素