2.常见数据结构
2.3队列
队列(Queue)是一种常见的数据结构,用于在特定顺序下存储和访问数据。它遵循“先进先出”(First In, First Out,FIFO)的原则,即最早加入队列的元素会最先被移除。队列通常用于需要按照加入顺序处理元素的场景,例如任务调度、消息传递等。
以下是队列的一些基本特性和操作:
基本特性
- FIFO(先进先出):最早进入队列的元素最先被处理。
- 有限或无限容量:队列可以有一个固定的容量(有限队列),也可以根据需要动态扩展(无限队列)。
- 顺序访问:元素只能按照加入顺序被访问。
基本操作
- 入队(Enqueue):将一个元素添加到队列的末尾。
- 出队(Dequeue):从队列的头部移除一个元素。
- 查看队头(Front/Peek):查看队列头部的元素,但不移除它。
- 检查队列是否为空(IsEmpty):判断队列中是否包含元素。
- 检查队列是否已满(IsFull,仅对有限队列):判断队列是否已经达到其容量上限。
实现方式
队列可以用多种数据结构来实现,最常见的包括:
- 数组:
- 优点:简单直接,易于实现。
- 缺点:固定大小,当队列满时需要处理溢出,数组移动元素的操作开销较大。
- 链表:
- 优点:动态大小,插入和删除操作高效(O(1)时间复杂度)。
- 缺点:需要额外的内存来存储指针。
- 循环数组(环形缓冲区):
- 优点:固定大小,但可以通过循环数组的方式实现高效的入队和出队操作。
- 缺点:实现较为复杂,需要处理边界条件。
import queue q = queue.Queue() q.put(1) q.put(3) q.put(2) print(q.qsize()) print(q.get()) print(q.get()) print(q.get())
双端队列
双端队列(Deque,Double-Ended Queue)是一种具有队列和栈性质的数据结构,它允许我们在两端进行元素的添加(push)和移除(pop)操作。在Python中,双端队列可以通过collections模块中的deque类来实现。
deque是一个双端队列的实现,它提供了在两端快速添加和移除元素的能力。
from collections import deque q = deque() q.append(1) q.append(2) q.appendleft(3) q.appendleft(4) print(q.pop()) print(q.popleft())
当结合使用appendleft和popleft时,你实际上是在实现一个栈(Stack)的数据结构,因为栈是后进先出(LIFO)的,而这两个操作正好模拟了栈的“压栈”和“弹栈”行为。append和pop结合使用同理。
优先队列
优先队列(Priority Queue)是一种特殊的队列,其中的元素按照优先级进行排序。优先级最高的元素总是最先出队。Python 标准库中提供了 queue.PriorityQueue 和 heapq 模块来实现优先队列。queue.PriorityQueue 是 Python 标准库 queue 模块中的一个类,适用于多线程环境。它实现了线程安全的优先队列。
import queue q = queue.PriorityQueue() # 向队列中添加元素,元素是一个元组 (priority, item),其中 priority 是优先级,item 是实际的数据 q.put((1,'item1')) q.put((3,'item3')) q.put((2,'item2')) print(q.get()) print(q.get()) print(q.get())
heapq
heapq 模块是 Python 标准库中的一个模块,提供了基于堆的优先队列实现。heapq 模块不是线程安全的,适用于单线程环境。
import heapq # 创建一个列表作为堆 heap = [] # 向堆中添加元素,元素是一个元组 (priority, item) heapq.heappush(heap, (3, 'Task 3')) heapq.heappush(heap, (1, 'Task 1')) heapq.heappush(heap, (2, 'Task 2')) # 从堆中取出元素 print(heapq.heappop(heap)) # 输出: (1, 'Task 1') print(heapq.heappop(heap)) # 输出: (2, 'Task 2') print(heapq.heappop(heap)) # 输出: (3, 'Task 3')
2.4 树
树是一种非常重要的非线性数据结构,在计算机科学和日常生活中都有广泛的应用。以下是对树的详细介绍:
2.4.1定义与特点
- 定义:树是一种层次结构的数据结构,由节点(或称为顶点)和边组成。每个节点有一个父节点和零个或多个子节点,节点之间不能形成环路。
- 特点:
- 每个节点有零个或多个子节点。
- 没有父节点的节点称为根节点。
- 每一个非根节点有且只有一个父节点。
- 除了根节点外,每个子节点可以分为多个不相交的子树。
2.4.2 二叉树
定义与特点
定义:二叉树(Binary Tree)是一种特殊的树形数据结构,其中每个节点最多有两个子节点,通常称为左子节点和右子节点。这两个子节点分别被称为左子树和右子树,并且子树之间是有序的,即左子树和右子树的次序不能颠倒。
特点:
- 每个节点最多有两个子节点,即每个节点的度不大于2。
- 左子树和右子树都是二叉树,它们本身也可以是空树。
- 二叉树的节点结构包含一个数据元素和指向左右子树的指针(或链接)。
基本形态与特殊类型
基本形态:逻辑上,二叉树有五种基本形态:
- 空二叉树:没有任何节点的二叉树。
- 只有一个根节点的二叉树。
- 只有左子树的二叉树。
- 只有右子树的二叉树。
- 完全二叉树(包括满二叉树作为特例)。
特殊类型:
- 满二叉树:如果一棵二叉树只有度为0的节点和度为2的节点,并且度为0的节点在同一层上,则这棵二叉树为满二叉树。
- 完全二叉树:深度为k,有n个节点的二叉树当且仅当其每一个节点都与深度为k的满二叉树中编号从1到n的节点一一对应时,称为完全二叉树。其特点是叶子节点只可能出现在层序最大的两层上,并且某个节点的左分支下子孙的最大层序与右分支下子孙的最大层序相等或大1。
- 二叉搜索树(BST):左子树上所有节点的值均小于根节点的值,右子树上所有节点的值均大于根节点的值。二叉搜索树在搜索、插入和删除操作中具有较高的效率。
- 平衡二叉树:任意节点的左子树和右子树都是平衡二叉树,且左子树和右子树的深度之差的绝对值不超过1。常见的平衡二叉树有AVL树和红黑树等。
性质
- 二叉树的第i层上至多有2^(i-1)个节点(i≥1)。
- 深度为h的二叉树中至多含有2^h-1个节点。
- 若在任意一棵二叉树中,有n0个叶子节点,有n2个度为2的节点,则必有n0=n2+1。
- 具有n个节点的满二叉树深为log₂(n+1)。
- 若对一棵有n个节点的完全二叉树进行顺序编号(1≤i≤n),那么对于编号为i(i≥1)的节点:
- 当i=1时,该节点为根节点,它无双亲节点。
- 当i>1时,该节点的双亲节点的编号为i/2(向下取整)。
- 若2i≤n,则有编号为2i的左节点,否则没有左节点。
- 若2i+1≤n,则有编号为2i+1的右节点,否则没有右节点。
存储方式
二叉树可以采用以下两种存储方式:
- 顺序存储:通常用于完全二叉树,可以方便地通过数组下标访问节点。
- 链式存储:通过节点间的指针连接,更加灵活,适用于一般形态的二叉树。
遍历方式
二叉树的遍历是指按一定的规则和顺序走遍二叉树的所有节点,使每一个节点都被访问一次,而且只被访问一次。常见的遍历方式有:
- 前序遍历:首先访问根节点,然后递归地前序遍历左子树和右子树。遍历顺序是根-左-右。
- 中序遍历:先递归地中序遍历左子树,然后访问根节点,最后递归地中序遍历右子树。遍历顺序是左-根-右。对于二叉搜索树,中序遍历的结果是有序的。
- 后序遍历:先递归地后序遍历左子树和右子树,然后访问根节点。遍历顺序是左-右-根。
- 层序遍历:自上而下,自左而右地访问节点。这通常需要使用队列等数据结构来实现。
2.4.3 二叉查找树
二叉查找树(Binary Search Tree,简称BST),又称二叉搜索树或二叉排序树,是一种特殊的二叉树数据结构。以下是对二叉查找树的详细介绍:
定义与性质
定义:
- 二叉查找树或者是一棵空树,或者是具有下列性质的二叉树:若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值;若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值;它的左、右子树也分别为二叉排序树。
性质:
- 有序性:对于树中的任意节点X,其左子树上所有节点的值都小于X的值,而其右子树上所有节点的值都大于X的值。这一特性使得二叉查找树在查找、插入和删除操作上具有高效性。
- 递归性:二叉查找树的左子树和右子树也都是二叉查找树,这保证了整个树结构的一致性。
- 动态集合操作:二叉查找树支持动态集合上的各种操作,如插入、删除和查找等。这些操作的时间复杂度都与树的高度成正比,因此在平衡的二叉查找树中,这些操作都能保持较高的效率。
基本操作
查找:
- 通过比较键值,可以在O(log n)的时间复杂度内找到目标节点(在平衡树的情况下)。查找过程是从根节点开始,如果目标值小于当前节点的值,则递归地在左子树中查找;如果目标值大于当前节点的值,则递归地在右子树中查找;如果找到目标值,则返回该节点。
插入:
- 同样利用键值比较,可以在适当的位置插入新节点,保持树的搜索性质。插入过程是从根节点开始,根据目标值与当前节点的值进行比较,决定向左子树还是右子树递归插入,直到找到空节点作为新节点的父节点。
删除:
- 删除节点时需要重新调整树结构,以保持二叉查找树的性质。删除操作根据被删除节点的情况采取不同的策略:
- 如果节点是叶子节点,则直接删除。
- 如果节点只有一个子节点,则将其子节点移动到被删除节点的位置。
- 如果节点有两个子节点,则找到其直接前驱或直接后继节点,用该节点替换被删除节点,并删除该前驱或后继节点。
class TreeNode: def __init__(self, key): self.left = None self.right = None self.val = key class BinarySearchTree: def __init__(self): self.root = None # 插入节点 def insert(self, key): if self.root is None: self.root = TreeNode(key) else: self._insert(self.root, key) def _insert(self, node, key): if key < node.val: if node.left is None: node.left = TreeNode(key) else: self._insert(node.left, key) else: if node.right is None: node.right = TreeNode(key) else: self._insert(node.right, key) # 查找节点 def search(self, key): return self._search(self.root, key) def _search(self, node, key): if node is None or node.val == key: return node if key < node.val: return self._search(node.left, key) return self._search(node.right, key) # 删除节点 def delete(self, key): self.root = self._delete(self.root, key) def _delete(self, node, key): if node is None: return node if key < node.val: node.left = self._delete(node.left, key) elif key > node.val: node.right = self._delete(node.right, key) else: # 节点只有一个子节点或没有子节点 if node.left is None: return node.right elif node.right is None: return node.left # 节点有两个子节点,找到右子树中的最小节点(或左子树中的最大节点) temp = self._minValueNode(node.right) node.val = temp.val node.right = self._delete(node.right, temp.val) return node def _minValueNode(self, node): current = node while current.left is not None: current = current.left return current # 中序遍历(用于测试) def inorderTraversal(self, node, result=None): if result is None: result = [] if node: self.inorderTraversal(node.left, result) result.append(node.val) self.inorderTraversal(node.right, result) return result # 测试代码 if __name__ == "__main__": bst = BinarySearchTree() bst.insert(50) bst.insert(30) bst.insert(20) bst.insert(40) bst.insert(70) bst.insert(60) bst.insert(80) print("中序遍历结果:", bst.inorderTraversal(bst.root)) found = bst.search(40) if found: print("找到节点:", found.val) else: print("未找到节点") bst.delete(20) print("删除20后的中序遍历结果:", bst.inorderTraversal(bst.root)) bst.delete(30) print("删除30后的中序遍历结果:", bst.inorderTraversal(bst.root)) bst.delete(50) print("删除50后的中序遍历结果:", bst.inorderTraversal(bst.root))
在这个代码示例中,
TreeNode
类表示二叉查找树的节点,每个节点包含一个值 (val
)、一个左子节点 (left
) 和一个右子节点 (right
)。BinarySearchTree
类提供了插入 (insert
)、查找 (search
) 和删除 (delete
) 节点的方法,以及一个辅助的中序遍历方法 (inorderTraversal
) 用于测试。