1、什么是数据结构
-
官方定义: 并没有…
-
民间定义:
-
“数据结构是数据对象,以及存在于该对象的实例和 组成实例的数据元素之间的各种联系。这些联系可以 通过定义相关的函数来给出。” --- 《数据结构、算法与应用》
-
“数据结构(data structure)是计算机中存储、组织数据的方式。通常情况下,精心选择的数据结构可以 带来最优效率的算法。” ---中文维基百科
-
“数据结构是ADT(抽象数据类型 Abstract Data Type)的物理实现。” --- 《数据结构与算法分析》
什么是抽象数据类型 ADT?
实际上 python 内置的 list 就可以看成一种抽象数据类型。
ADT: Abstract Data Type,抽象数据类型,我们在组合已有的数据结构来实现一种新的数据类型, ADT 定义了类型的数据和操作。
我们以抽象一个背包(Bag) 数据类型来说明,背包是一种容器类型,我们可以给它添加东西,也可以移除东西,并且我们想知道背包里有多少东西。于是我们可以定义一个新的数据类型叫做 Bag,在类中实现背包的所有功能.
class Bag: """ 背包类型 """ pass
-
2、常见的数据结构
-
比较常见的数据结构
-
栈、队列、链表、集合、字典、树。。。。
-
常见的数据结构较多, 每一种都有其对应的应用场景, 不同的数据结构的不同操作性能是不同的:
-
有的查询性能很快,有的插入速度很快,有的是插入头和尾速度很快
-
有的做范围查找很快,有的允许元素重复,有的不允许重复等等
-
在开发中如何选择,要根据具体的需求来选择
-
-
注意: 数据结构和语言无关, 基本常见的编程语言都有直接或者间接的使用上述常见的数据结构
列表
python内置列表数据结构
列表的方法
序号 | 方法 |
---|---|
1 | list.append(obj)在列表末尾添加新的对象 |
2 | list.count(obj)统计某个元素在列表中出现的次数 |
3 | list.extend(seq)在列表末尾一次性追加另一个序列中的多个值(用新列表扩展原来的列表) |
4 | list.index(obj)从列表中找出某个值第一个匹配项的索引位置 |
5 | list.insert(index, obj)将对象插入列表 |
6 | list.pop(index=-1)移除列表中的一个元素(默认最后一个元素),并且返回该元素的值 |
7 | list.remove(obj)移除列表中某个值的第一个匹配项 |
8 | list.reverse()反向列表中元素 |
9 | list.sort( key=None, reverse=False)对原列表进行排序 |
10 | list.clear()清空列表 |
11 | list.copy()复制列表 |
字典
python内置字典数据结构
字典的方法:
序号 | 函数及描述 |
---|---|
1 | dict.clear()删除字典内所有元素 |
2 | dict.copy()返回一个字典的浅复制 |
3 | dict.fromkeys()创建一个新字典,以序列seq中元素做字典的键,val为字典所有键对应的初始值 |
4 | dict.get(key, default=None)返回指定键的值,如果键不在字典中返回 default 设置的默认值 |
5 | key in dict如果键在字典dict里返回true,否则返回false |
6 | dict.items()以列表返回一个视图对象 |
7 | dict.keys()返回一个视图对象 |
8 | dict.setdefault(key, default=None)和get()类似, 但如果键不存在于字典中,将会添加键并将值设为default |
9 | dict.update(dict2)把字典dict2的键/值对更新到dict里 |
10 | dict.values()返回一个视图对象 |
11 | pop(key,default)删除字典 key(键)所对应的值,返回被删除的值。 |
12 | popitem()返回并删除字典中的最后一对键和值。 |
集合
python内置字典数据结构
集合的方法
方法 | 描述 |
---|---|
add() | 为集合添加元素 |
clear() | 移除集合中的所有元素 |
copy() | 拷贝一个集合 |
difference() | 返回多个集合的差集 |
difference_update() | 移除集合中的元素,该元素在指定的集合也存在。 |
discard() | 删除集合中指定的元素 |
intersection() | 返回集合的交集 |
intersection_update() | 返回集合的交集。 |
isdisjoint() | 判断两个集合是否包含相同的元素,如果没有返回 True,否则返回 False。 |
issubset() | 判断指定集合是否为该方法参数集合的子集。 |
issuperset() | 判断该方法的参数集合是否为指定集合的子集 |
pop() | 随机移除元素 |
remove() | 移除指定元素 |
symmetric_difference() | 返回两个集合中不重复的元素集合。 |
symmetric_difference_update() | 移除当前集合中在另外一个指定集合相同的元素,并将另外一个指定集合中不同的元素插入到当前集合中。 |
union() | 返回两个集合的并集 |
update() | 给集合添加元素 |
len() | 计算集合元素个数 |
数组
数组是最常用到的一种线性结构,其实 python 内置了一个 array 模块,但是大部人甚至从来没用过它。 Python 的 array 是内存连续、存储的都是同一数据类型的结构,而且只能存数值和字符。
我建议你课下看下 array 的文档:array — Efficient arrays of numeric values — Python 3.12.4 documentation
你可能很少会使用到它(推荐用 numpy.array)
数组提供了高效的、固定类型的一维数组。相比于列表,array
对象节省了空间且在处理大量同类型数据时速度更快。要使用 array
模块,你需要首先导入它,然后通过 array.array()
函数来创建数组。以下是基本的使用方法:
import array # 创建一个整数数组 int_array = array.array('i', [1, 2, 3, 4, 5]) # 创建一个无符号字符数组 char_array = array.array('B', [65, 66, 67]) # 这里实际上是ASCII码值 # 对数组进行操作的例子 int_array.append(6) # 添加元素 int_array.pop(3) # 删除索引为3的元素 value = int_array[2] # 获取索引为2的元素 int_array[1] = 10 # 修改元素 length = len(int_array) # 获取数组长度 # 遍历数组 for num in int_array: print(num) # 注意:array模块不支持多维数组,只有一维数组功能
可用的类型码:
'b' 代表 signed char 'B' 代表 unsigned char 'u' 代表 Py_UNICODE (Unicode character) 'h' 代表 signed short 'H' 代表 unsigned short 'i' 代表 signed int 'I' 代表 unsigned int 'l' 代表 signed long 'L' 代表 unsigned long 'q' 代表 signed long long 'Q' 代表 unsigned long long 'f' 代表 float 'd' 代表 double
数组方法:
append()——在数组末尾添加一个新项 buffer_info()——返回给出当前内存信息的信息 byteswap()——对数组的所有项进行Byteswap count()——返回对象出现的次数 extend()——通过从可迭代对象中附加多个元素来扩展数组 fromfile()——从文件对象中读取项 fromlist()——从列表中追加项目 frombytes()——从字符串中附加项 index()——返回对象第一次出现的索引 insert()——在数组中指定的位置插入一个新项 pop()——删除并返回项目(默认最后一个) remove()——删除对象的第一次出现 reverse()——反转数组中元素的顺序 tofile()——将所有项写入一个文件对象 tolist()——返回转换为普通列表的数组 tobytes()——返回转换为字符串的数组
栈
-
栈(stack),它是一种运算受限的线性表,后进先出(LIFO)
-
LIFO(last in first out)表示就是后进入的元素, 第一个弹出栈空间. 类似于自动餐托盘, 最后放上的托盘, 往往先把拿出去使用.
-
其限制是仅允许在表的一端进行插入和删除运算。这一端被称为栈顶,相对地,把另一端称为栈底。
-
向一个栈插入新元素又称作进栈、入栈或压栈,它是把新元素放到栈顶元素的上面,使之成为新的栈顶元素;
-
从一个栈删除元素又称作出栈或退栈,它是把栈顶元素删除掉,使其相邻的元素成为新的栈顶元素。
-
-
生活中类似于栈的
-
自助餐的托盘, 最新放上去的, 最先被客人拿走使用.
-
收到很多的邮件(实体的), 从上往下依次处理这些邮件. (最新到的邮件, 最先处理)
-
注意: 不允许改变邮件的次序, 比如从最小开始, 或者处于最紧急的邮件, 否则就不再是栈结构了. 而是队列或者优先级队列结构.
-
-
栈常见的操作
-
push(element)
: 添加一个新元素到栈顶位置. -
pop()
:移除栈顶的元素,同时返回被移除的元素。 -
peek()
:返回栈顶的元素,不对栈做任何修改(这个方法不会移除栈顶的元素,仅仅返回它)。 -
isEmpty()
:如果栈里没有任何元素就返回true
,否则返回false
。 -
clear()
:移除栈里的所有元素。 -
size()
:返回栈里的元素个数。这个方法和数组的length
属性很类似。
实现:
-
代码实现:
队列
-
队列(Queue),它是一种运算受限的线性表,先进先出(FIFO First In First Out)
-
队列是一种受限的线性结构
-
受限之处在于它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作
-
-
生活中类似的队列结构
-
生活中类似队列的场景就是非常多了, 比如在电影院, 商场, 甚至是厕所排队.
-
优先排队的人, 优先处理. (买票, 结账, WC)
-
-
队列常见的操作
-
enqueue(element)
:向队列尾部添加一个(或多个)新的项。 -
dequeue()
:移除队列的第一(即排在队列最前面的)项,并返回被移除的元素。 -
front()
:返回当前队列中第一个元素,也将是最先被移除的元素。队列不做任何变动(不移除元素,只返回元素信息——与Stack
类的peek
方法非常类似)。 -
isEmpty()
:如果队列中不包含任何元素,返回true
,否则返回false
。 -
size()
:返回队列包含的元素个数,与数组的length
属性类似.
-
1、普通的队列实现
class Queue: def __init__(self): self.items = [] # 初始化一个空列表用于存储队列中的元素 self._size = 0 # 使用一个私有变量来跟踪队列的大小 def enqueue(self, el): # 向队列的末尾添加一个元素 self.items.append(el) self._size += 1 # 更新队列的大小 def dequeue(self): # 从队列的开头移除一个元素并返回它 if self._size == 0: return None # 如果队列为空,则返回None else: self._size -= 1 # 更新队列的大小 return self.items.pop(0) # 移除并返回列表的第一个元素 def front(self): # 返回队列开头的元素,但不移除它 if self._size == 0: return None # 如果队列为空,则返回None else: return self.items[0] # 返回列表的第一个元素 def is_empty(self): # 检查队列是否为空 return self._size == 0 # 如果队列的大小为0,则队列为空 def size(self): # 返回队列中的元素数量 return self._size # 直接返回队列的大小 q1=Queue() q1.enqueue(10) q1.enqueue(20) q1.enqueue(30) q1.enqueue(40) print("即将访问的队列中排第一个的元素:\n",q1.front()) print("将访问的队列中排第一个的元素,移除:\n",q1.dequeue()) print("将访问的队列中排第一个的元素,移除:\n",q1.dequeue())
2、优先级队列的介绍
-
优先级队列的特点:
-
我们知道, 普通的队列插入一个元素, 数据会被放在后端. 并且需要前面所有的元素都处理完成后才会处理前面的数据.
-
但是优先级队列, 在插入一个元素的时候会考虑该数据的优先级.(和其他数据优先级进行比较)
-
比较完成后, 可以得出这个元素正确的队列中的位置. 其他处理方式, 和队列的处理方式一样.
-
也就是说, 如果我们要实现优先级队列, 最主要是要修改添加方法. (当然, 还需要以某种方式来保存元素的优先级)
-
3、优先级队列的实现
-
实现优先级队列相对队列主要有两方面需要考虑:
-
-
封装元素和优先级放在一起(可以封装一个新的类)
-
-
-
添加元素时, 将当前的优先级和队列中已经存在的元素优先级进行比较, 以获得自己正确的位置.
-
-
-
优先级队列代码实现:
class Node: def __init__(self, el, rank): self.el = el # 节点存储的元素 self.rank = rank # 元素的排名或优先级 class mysize: def __init__(self): self.items = [] # 存储Node对象的列表 self.size = 0 # 队列中元素的数量 def enqueue(self, el, rank): # 将一个元素及其排名添加到队列中,保持元素的排名顺序 n1 = Node(el, rank) # 尝试在正确的位置插入新节点以保持排名顺序 inserted = False # 标记是否已插入节点 for i in range(self.size): if n1.rank <= self.items[i].rank: self.items.insert(i, n1) inserted = True break # 如果所有元素的排名都低于新元素,则将其添加到列表末尾 if not inserted: self.items.append(n1) # 更新队列大小 self.size += 1 def is_empty(self): # 检查队列是否为空 return self.size == 0 def dequeue(self): # 从队列中移除并返回最高排名的元素(即列表的第一个元素) if self.is_empty(): # 如果队列为空,则没有元素可以返回 return None else: # 移除并返回列表的第一个元素(即最高排名的元素) self.size -= 1 return self.items.pop(0)
链表
-
链表是链式的存储多个元素.
-
但不同于列表, 链表中的元素在内存中不必是连续的空间
-
链表的每个元素由一个存储元素本身的节点和一个指向下一个元素的引用(有些语言称为指针或者链接)组成
-
链表访问任何一个位置的元素时, 都需要从头开始访问.(无法跳过第一个元素访问任何一个元素)
-
-
生活中类似于链表的
-
链表类似于火车: 有一个火车头, 火车头会连接一个节点, 节点上有乘客, 并且这个节点会连接下一个节点, 以此类推
-
-
链表常见的操作
-
append(element)
:向列表尾部添加一个新的项 -
insert(position, element)
:向列表的特定位置插入一个新的项。 -
remove(element)
:从列表中移除一项。 -
indexOf(element)
:返回元素在链表中的索引。如果列表中没有该元素则返回-1
。 -
removeAt(position)
:从列表的特定位置移除一项。 -
isEmpty()
:如果链表中不包含任何元素,返回true
,如果链表长度大于0则返回false
。 -
size()
:返回链表包含的元素个数。与数组的length
属性类似。
-
单向链表实现
class Node(): def __init__(self, el): self.el = el # 节点存储的元素 self.next = None # 指向下一个节点的指针 class Linklist(): def __init__(self): self.head = None # 链表的头节点 self.size = 0 # 链表的大小 def append(self, el): # 在链表末尾添加一个新元素 node1 = Node(el) if self.head is None: self.head = node1 else: current = self.head while current.next is not None: current = current.next current.next = node1 self.size += 1 def insert(self, position, el): # 在指定位置插入一个新元素 # 如果位置无效,返回False if position < 0 or position > self.size: return False node1 = Node(el) if position == 0: node1.next = self.head self.head = node1 else: current = self.head previous = None for _ in range(position): previous = current current = current.next previous.next = node1 node1.next = current self.size += 1 return True def removeAt(self, position): # 移除并返回指定位置的元素 # 如果位置无效,返回None if position < 0 or position >= self.size: return None current = self.head previous = None if position == 0: self.head = current.next else: for _ in range(position): previous = current current = current.next previous.next = current.next self.size -= 1 return current def indexOf(self, el): # 返回元素在链表中的索引,如果未找到则返回-1 current = self.head index = 0 while current: if current.el == el: return index index += 1 current = current.next return -1 def remove(self, el): # 移除链表中第一个匹配的元素并返回它 # 如果未找到,则返回None index = self.indexOf(el) return self.removeAt(index) def is_empty(self): # 检查链表是否为空 return self.size == 0 def get_size(self): # 返回链表的大小 return self.size l1 = Linklist() for i in range(8): l1.append(i) print(l1.indexOf("hello")) print(l1.indexOf(3)) print(l1.remove(4).el) print(l1.get_size())
代码实现: