常见数据结构的python实现
数据结构是计算机存储和组织数据的方式,它对于算法的设计和优化有着重要的作用。Python 提供了多种数据结构的内置实现,例如列表、元组、集合、字典等,可以直接使用这些内置数据结构来编写数据结构的程序。同时,Python 还支持面向对象编程,可以使用类和对象来实现自定义的数据结构。
本文将介绍以下几种常见的数据结构,并给出它们的 Python 实现:
- 数组
- 栈
- 队列
- 链表
- 二叉树
- 堆
每个给出的例子中的第一个实现方法均为无第三方库的方法
数组
数组(Array):一种顺序存储的线性表,所有元素的类型相同,每个元素占用相同大小的存储单元。
数组在Python中可以用numpy库的ndarray来实现,也可以用列表(list)来模拟
# 使用列表创建数组
array = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
# 使用numpy库创建数组
import numpy as np
array = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
numpy库和列表有以下几个区别:
- numpy数组中的元素类型必须相同,而列表中的元素类型可以不同。
- numpy数组支持更多的数学运算和函数,而列表只能进行简单的加减乘除等操作。
- numpy数组占用的内存空间更小,而列表占用的内存空间更大。
- numpy数组的访问速度更快,而列表的访问速度更慢。
但数组更简单也更好上手更适合快速实现简单的功能
栈
栈(Stack):一种只能在一端进行插入和删除操作的线性表,遵循后进先出(LIFO)的原则。
栈在Python中可以用以下几种方法实现:
- 用列表(list)来模拟栈,用append()方法向栈顶添加元素,用pop()方法弹出栈顶元素。
- 用双端队列(deque)来模拟栈,用append()方法向栈顶添加元素,用pop()方法弹出栈顶元素。
- 用自定义的类来封装列表或双端队列,实现push()、pop()、peek()、is_empty()、size()等栈的基本操作。
例如:
使用列表创建栈
stack = []
stack.append(1) # 向栈顶添加元素1
stack.append(2) # 向栈顶添加元素2
stack.pop() # 弹出栈顶元素2
使用双端队列创建栈
from collections import deque
stack = deque()
stack.append(1) # 向栈顶添加元素1
stack.append(2) # 向栈顶添加元素2
stack.pop() # 弹出栈顶元素2
使用自定义的类创建栈
class Stack:
def __init__(self):
self.__items = [] # 使用列表存储数据
def push(self, item):
self.__items.append(item) # 向栈顶添加元素
def pop(self):
return self.__items.pop() # 弹出栈顶元素
def peek(self):
return self.__items[-1] # 返回栈顶元素
def is_empty(self):
return len(self.__items) == 0 # 判断是否为空
def size(self):
return len(self.__items) # 返回大小
stack = Stack()
stack.push(1) # 向栈顶添加元素1
stack.push(2) # 向栈顶添加元素2
stack.pop() # 弹出栈顶元素2
队列
队列(Queue):一种只能在一端插入,另一端删除的线性表,遵循先进先出(FIFO)的原则。
队列在Python中可以用以下几种方法实现:
用列表(list)来模拟队列,用insert(0, item)方法向队首添加元素,用pop()方法弹出队尾元素。
# 使用列表创建队列
queue = []
queue.insert(0, 1) # 向队首添加元素1
queue.insert(0, 2) # 向队首添加元素2
queue.pop() # 弹出队尾元素1
用双端队列(deque)来模拟队列,用appendleft(item)方法向队首添加元素,用pop()方法弹出队尾元素。
from collections import deque
queue = deque()
queue.appendleft(1) # 向对首添加元素1
queue.appendleft(2) # 向对首添加元素2
queue.pop() # 弹出对尾元素1
用queue模块中的Queue类来创建队列,用put(item)方法向队尾添加元素,用get()方法弹出队首元素。
import queue
q = queue.Queue(maxsize=5) # 创建一个最大长度为5的FIFO(先进先出)的有界阻塞式同步普通对列对象q。maxsize为0表示无界对列。LifoQueue表示后进先出。PriorityQueue表示优先级对列。
q.put(1) # 向对尾添加一个数据项。如果maxsize不为0且已满,则阻塞直到有空位或超时。timeout参数可设置超时时间。
q.put(2)
q.get() # 移除并返回一个数据项。如果为空,则阻塞直到有数据或超时。
用asyncio模块中的Queue类来创建异步的队列,用put(item)和get()方法进行入队和出队操作。
import asyncio
async def producer(q):
for i in range(10):
await q.put(i)
print(f'Produced {i}')
async def consumer(q):
while True:
item = await q.get()
print(f'Consumed {item}')
q.task_done()
async def main():
q = asyncio.Queue()
producer_task = asyncio.create_task(producer(q))
consumer_task = asyncio.create_task(consumer(q))
await producer_task
await q.join()
consumer_task.cancel()
asyncio.run(main())
用multiprocessing模块中的
链表
链表(Linked List):一种由节点组成的线性表,每个节点包含数据域和指针域,通过指针连接起来。
链表在Python中可以用“引用+类”来实现。具体来说,可以定义一个节点类,包含数据域和指针域,然后用节点对象来构造链表对象,例如:
# 定义节点类
class Node:
def __init__(self, data, next=None):
self.data = data # 数据域
self.next = next # 指针域
# 定义链表类
class LinkedList:
def __init__(self):
self.head = None # 链表头节点
# 在链表尾部插入一个节点
def append(self, data):
new_node = Node(data) # 创建新节点
if self.head is None: # 如果链表为空
self.head = new_node # 新节点作为头节点
else: # 如果链表不为空
cur = self.head # 从头节点开始遍历
while cur.next: # 直到找到尾节点
cur = cur.next
cur.next = new_node # 新节点作为尾节点的后继
# 打印链表中的所有数据
def print(self):
cur = self.head # 从头节点开始遍历
while cur:
print(cur.data) # 打印当前节点的数据
cur = cur.next # 移动到下一个节点
# 创建一个空的链表对象
llist = LinkedList()
# 在链表尾部插入一些数据
llist.append(1)
llist.append(2)
llist.append(3)
# 打印链表中的所有数据
llist.print()
树
树(Tree):一种非线性的层次结构,由一个根节点和若干子树组成,每
个子树又是一棵树。
树在Python中可以用链表来实现,与前文类似。具体来说,可以定义一个节点类,包含数据域和左右子树的引用,然后用节点对象来构造树对象,例如:
# 定义一个二叉链表节点类
class TreeNode:
# 初始化方法
def __init__(self, data):
self.data = data # 数据域
self.left = None # 左子节点域
self.right = None # 右子节点域
# 创建一个二叉树
root = TreeNode(1) # 根节点
root.left = TreeNode(2) # 根节点的左子节点
root.right = TreeNode(3) # 根节点的右子节点
root.left.left = TreeNode(4) # 根节点的左子节点的左子节点
root.left.right = TreeNode(5) # 根节点的左子节点的右子节点
# 二叉树结构如下:
# 1
# / \
# 2 3
# / \
# 4 5
图
图(Graph):一种非线性的结构,由顶点集合和边集合组成,边表示顶点之间的关系。图可以用邻接矩阵或邻接表来存储,其中邻接表更适合稀疏图。在python中,可以用字典来实现邻接表,其中键是顶点,值是相邻顶点的列表,例如:
# 定义一个字典表示邻接表
graph = {
"A": ["B", "C"],
"B": ["A", "D"],
"C": ["A", "E"],
"D": ["B", "F"],
"E": ["C", "F"],
"F": ["D", "E"]
}
# 打印图中的顶点和边
print("Vertices:", list(graph.keys()))
print("Edges:")
for v in graph:
for u in graph[v]:
print(v, "-", u)
散列表/哈希表
散列表/哈希表(Hash Table):一种根据关键码值直接进行访问的数据结构,通过散列函数将关键码映射到一个位置,并在该位置存储数据。
在python中,字典和集合就是基于哈希表实现的,它们的键必须是可哈希的,即不可变的数据类型。
堆
堆(Heap):一种特殊的完全二叉树或近似完全二叉树,满足堆序性质,即父节点的值大于或等于其子节点的值。堆是一种二叉树的数据结构,它的每个父节点的值都小于或等于(最小堆)或大于或等于(最大堆)它的子节点的值。堆可以用数组来实现,利用索引位置来表示父子关系。
在python中,有一个标准库模块heapq,它提供了一些函数来操作最小堆。例如,heapq.heappush(heap, item)可以将一个元素压入堆中,并保持堆的性质2;heapq.heappop(heap)可以弹出并返回堆中最小的元素,并保持堆的性质。
如果需要自己实现一个最小堆,这里提供参考的代码:
# 定义一个最小堆类
class MinHeap:
# 初始化一个空列表作为存储
def __init__(self):
self.data = []
# 定义一个辅助函数,交换列表中两个元素的位置
def swap(self, i, j):
self.data[i], self.data[j] = self.data[j], self.data[i]
# 定义一个辅助函数,返回父节点的索引
def parent(self, i):
return (i - 1) // 2
# 定义一个辅助函数,返回左子节点的索引
def left(self, i):
return 2 * i + 1
# 定义一个辅助函数,返回右子节点的索引
def right(self, i):
return 2 * i + 2
# 定义一个插入操作,将元素添加到列表末尾,并向上调整位置
def insert(self, item):
self.data.append(item)
i = len(self.data) - 1
while i > 0 and self.data[i] < self.data[self.parent(i)]:
self.swap(i, self.parent(i))
i = self.parent(i)
# 定义一个删除操作,将列表首元素弹出并返回,并将末尾元素移到首位,并向下调整位置
def delete(self):
if len(self.data) == 0:
return None
elif len(self.data) == 1:
return self.data.pop()
else:
item = self.data[0]
self.data[0] = self.data.pop()
i = 0
while (self.left(i) < len(self.data) and
self.data[i] > self.data[self.left(i)]) or \
(self.right(i) < len(self.data) and
self.data[i] > self.data[self.right(i)]):
if (self.right(i) < len(self.data) and
self.data[self.left(i)] >
self.data[self.right(i)]):
j = self.right(i)
else:
j = self.left(i)
self.swap(i, j)
i = j
return item