常见数据结构的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