第二部分:基础数据结构——组织数据的方式 词条3:哈希表(字典)——快速查找的魔法 官方解释 哈希表(Hash Table)是一种通过键(key)直接访问值(value)的数据结构。在Python中,字典(dict)就是哈希表的实现。核心原理:通过哈希函数将键映射到数组的索引,实现O(1)的平均查找时间。 兔狲老师解释 哈希表就像'智能电话簿':知道名字(键),直接找到电话号码(值)。 小小猪的比喻: 哈希函数:把名字变成数字编号的机器 哈希冲突:两个名字得到相同编号(需要解决) 负载因子:电话簿使用率(太满需要扩容) Python字典的特性: 键必须是不可变类型(字符串、数字、元组) 值可以是任意类型 无序(Python 3.
哈希表(Hash Table)是一种通过键(key)直接访问值(value)的数据结构。在Python中,字典(dict)就是哈希表的实现。核心原理:通过哈希函数将键映射到数组的索引,实现O(1)的平均查找时间。
哈希表就像'智能电话簿':知道名字(键),直接找到电话号码(值)。
小小猪的比喻:
Python字典的特性:
问题:实现一个简单的哈希表并理解其原理:
# 1. Python字典的基本操作 phonebook = {} # 创建空字典 phonebook["Alice"] = "123-4567" phonebook["Bob"] = "987-6543" phonebook["Charlie"] = "555-1234" print("电话簿:", phonebook) print("Alice的电话:", phonebook.get("Alice")) print("David的电话:", phonebook.get("David", "未找到")) # 2. 遍历字典 print("\n遍历电话簿:") for name, phone in phonebook.items(): print(f"{name}: {phone}") # 3. 字典推导式 squares = {x: x*x for x in range(1, 6)} print("\n平方字典:", squares) # 4. 模拟哈希冲突解决(分离链接法) class SimpleHashTable: """简单的哈希表实现""" def __init__(self, size=10): self.size = size self.table = [[] for _ in range(size)] # 每个桶是一个列表 def _hash(self, key): """简单的哈希函数:字符串的ASCII和取模""" if isinstance(key, str): return sum(ord(c) for c in key) % self.size return hash(key) % self.size def put(self, key, value): """插入键值对""" index = self._hash(key) bucket = self.table[index] # 检查是否已存在该键 for i, (k, v) in enumerate(bucket): if k == key: bucket[i] = (key, value) # 更新 return # 不存在,添加新项 bucket.append((key, value)) def get(self, key): """获取值""" index = self._hash(key) bucket = self.table[index] for k, v in bucket: if k == key: return v raise KeyError(f"Key '{key}' not found") def __str__(self): result = [] for i, bucket in enumerate(self.table): if bucket: result.append(f"桶[{i}]: {bucket}") return "\n".join(result) # 测试简单哈希表 print("\n" + "="*50) print("简单哈希表实现:") ht = SimpleHashTable(size=5) ht.put("apple", 1) ht.put("banana", 2) ht.put("cherry", 3) ht.put("date", 4) ht.put("elderberry", 5) print(ht) print("\n获取'banana':", ht.get("banana")) print("获取'cherry':", ht.get("cherry")) # 测试哈希冲突 ht.put("apple", 10) # 更新值 print("\n更新后的哈希表:") print(ht)
问题:哈希表为什么能实现快速查找?有什么局限性?
思考方向:
链表(Linked List)是一种线性数据结构,由一系列节点组成,每个节点包含数据和指向下一个节点的指针。与数组不同,链表在内存中不必连续存储,插入和删除操作更高效(O(1)),但随机访问较慢(O(n))。
链表类型:
链表就像'火车车厢':每节车厢(节点)连接着下一节,可以轻松添加或移除车厢。
小小猪的比喻:
与数组(列表)比较:
问题:实现单向链表和双向链表:
# 1. 单向链表节点 class SinglyNode: def __init__(self, data): self.data = data self.next = None def __str__(self): return str(self.data) # 单向链表 class SinglyLinkedList: def __init__(self): self.head = None self.size = 0 def append(self, data): """在末尾添加节点""" new_node = SinglyNode(data) if self.head is None: self.head = new_node else: current = self.head while current.next: current = current.next current.next = new_node self.size += 1 def prepend(self, data): """在开头添加节点""" new_node = SinglyNode(data) new_node.next = self.head self.head = new_node self.size += 1 def insert(self, index, data): """在指定位置插入节点""" if index < 0 or index > self.size: raise IndexError("索引超出范围") if index == 0: self.prepend(data) return new_node = SinglyNode(data) current = self.head for _ in range(index - 1): current = current.next new_node.next = current.next current.next = new_node self.size += 1 def delete(self, data): """删除第一个匹配的节点""" if self.head is None: return False # 如果要删除的是头节点 if self.head.data == data: self.head = self.head.next self.size -= 1 return True current = self.head while current.next: if current.next.data == data: current.next = current.next.next self.size -= 1 return True current = current.next return False def search(self, data): """查找节点""" current = self.head index = 0 while current: if current.data == data: return index current = current.next index += 1 return -1 def __str__(self): elements = [] current = self.head while current: elements.append(str(current.data)) current = current.next return " -> ".join(elements) if elements else "空链表" def __len__(self): return self.size # 2. 双向链表节点 class DoublyNode: def __init__(self, data): self.data = data self.prev = None self.next = None def __str__(self): return str(self.data) # 双向链表 class DoublyLinkedList: def __init__(self): self.head = None self.tail = None self.size = 0 def append(self, data): """在末尾添加节点""" new_node = DoublyNode(data) if self.head is None: # 空链表 self.head = new_node self.tail = new_node else: new_node.prev = self.tail self.tail.next = new_node self.tail = new_node self.size += 1 def prepend(self, data): """在开头添加节点""" new_node = DoublyNode(data) if self.head is None: # 空链表 self.head = new_node self.tail = new_node else: new_node.next = self.head self.head.prev = new_node self.head = new_node self.size += 1 def delete(self, data): """删除第一个匹配的节点""" current = self.head while current: if current.data == data: # 调整前后节点的指针 if current.prev: current.prev.next = current.next else: # 删除的是头节点 self.head = current.next if current.next: current.next.prev = current.prev else: # 删除的是尾节点 self.tail = current.prev self.size -= 1 return True current = current.next return False def forward_traversal(self): """前向遍历""" elements = [] current = self.head while current: elements.append(str(current.data)) current = current.next return " -> ".join(elements) if elements else "空链表" def backward_traversal(self): """后向遍历""" elements = [] current = self.tail while current: elements.append(str(current.data)) current = current.prev return " <- ".join(elements) if elements else "空链表" def __str__(self): return f"前向: {self.forward_traversal()}\n后向: {self.backward_traversal()}" def __len__(self): return self.size # 测试单向链表 print("单向链表测试:") sll = SinglyLinkedList() sll.append(1) sll.append(2) sll.append(3) sll.prepend(0) sll.insert(2, 1.5) print("链表:", sll) print("长度:", len(sll)) print("查找2的位置:", sll.search(2)) sll.delete(1.5) print("删除1.5后:", sll) print("\n" + "="*50) # 测试双向链表 print("双向链表测试:") dll = DoublyLinkedList() dll.append(1) dll.append(2) dll.append(3) dll.prepend(0) print(dll) print("长度:", len(dll)) dll.delete(2) print("\n删除2后:") print(dll)
问题:链表和数组(列表)各有什么优缺点?如何选择?
思考方向:
树(Tree)是一种层次化的非线性数据结构,由节点和边组成。每个树有一个根节点,每个节点可以有零个或多个子节点,没有子节点的节点称为叶节点。
常见树类型:
树就像'家族族谱'或"公司组织结构"。
小小猪的比喻:
树的遍历方式:
问题:实现二叉树和二叉搜索树:
# 1. 二叉树节点 class TreeNode: def __init__(self, value): self.value = value self.left = None self.right = None def __str__(self): return str(self.value) # 二叉树 class BinaryTree: def __init__(self, root_value=None): if root_value is not None: self.root = TreeNode(root_value) else: self.root = None # 遍历方法 def preorder(self, node=None, result=None): """前序遍历:根→左→右""" if result is None: result = [] if node is None: node = self.root if node: result.append(node.value) self.preorder(node.left, result) self.preorder(node.right, result) return result def inorder(self, node=None, result=None): """中序遍历:左→根→右""" if result is None: result = [] if node is None: node = self.root if node: self.inorder(node.left, result) result.append(node.value) self.inorder(node.right, result) return result def postorder(self, node=None, result=None): """后序遍历:左→右→根""" if result is None: result = [] if node is None: node = self.root if node: self.postorder(node.left, result) self.postorder(node.right, result) result.append(node.value) return result def level_order(self): """层序遍历""" if not self.root: return [] result = [] queue = [self.root] while queue: node = queue.pop(0) result.append(node.value) if node.left: queue.append(node.left) if node.right: queue.append(node.right) return result def height(self, node=None): """计算树的高度""" if node is None: node = self.root if node is None: return 0 left_height = self.height(node.left) right_height = self.height(node.right) return max(left_height, right_height) + 1 def __str__(self): return f"前序: {self.preorder()}\n中序: {self.inorder()}\n后序: {self.postorder()}\n层序: {self.level_order()}" # 2. 二叉搜索树 class BinarySearchTree(BinaryTree): def insert(self, value): """插入值到二叉搜索树""" if self.root is None: self.root = TreeNode(value) return current = self.root while True: if value < current.value: if current.left is None: current.left = TreeNode(value) break else: current = current.left elif value > current.value: if current.right is None: current.right = TreeNode(value) break else: current = current.right else: # 值已存在,不插入重复值 break def search(self, value): """在二叉搜索树中查找值""" current = self.root while current: if value == current.value: return True elif value < current.value: current = current.left else: current = current.right return False def find_min(self, node=None): """找到最小值节点""" if node is None: node = self.root while node and node.left: node = node.left return node.value if node else None def find_max(self, node=None): """找到最大值节点""" if node is None: node = self.root while node and node.right: node = node.right return node.value if node else None # 测试二叉树 print("二叉树测试:") bt = BinaryTree(1) bt.root.left = TreeNode(2) bt.root.right = TreeNode(3) bt.root.left.left = TreeNode(4) bt.root.left.right = TreeNode(5) bt.root.right.left = TreeNode(6) bt.root.right.right = TreeNode(7) print(bt) print("树的高度:", bt.height()) print("\n" + "="*50) # 测试二叉搜索树 print("二叉搜索树测试:") bst = BinarySearchTree() values = [50, 30, 70, 20, 40, 60, 80] for v in values: bst.insert(v) print(bst) print("查找40:", bst.search(40)) print("查找90:", bst.search(90)) print("最小值:", bst.find_min()) print("最大值:", bst.find_max())
问题:树结构在计算机科学中有哪些重要应用?
思考方向:
图(Graph)是由顶点(Vertex)和边(Edge)组成的非线性数据结构,用于表示对象之间的关系。图是树的一般化形式(树是无环连通图)。
图的分类:
图的表示方法:
图就像'社交网络'或"交通网络"。
小小猪的比喻:
图算法应用:
问题:实现图的基本结构和算法:
# 1. 图的邻接表表示 class Graph: def __init__(self, directed=False): self.vertices = {} # 顶点字典:顶点名 -> 顶点对象 self.directed = directed # 是否是有向图 def add_vertex(self, name): """添加顶点""" if name not in self.vertices: self.vertices[name] = Vertex(name) def add_edge(self, from_vertex, to_vertex, weight=1): """添加边""" if from_vertex not in self.vertices: self.add_vertex(from_vertex) if to_vertex not in self.vertices: self.add_vertex(to_vertex) self.vertices[from_vertex].add_neighbor(to_vertex, weight) if not self.directed: # 无向图需要添加反向边 self.vertices[to_vertex].add_neighbor(from_vertex, weight) def get_vertices(self): """获取所有顶点""" return list(self.vertices.keys()) def get_edges(self): """获取所有边""" edges = [] for from_vertex in self.vertices: for to_vertex, weight in self.vertices[from_vertex].neighbors.items(): edges.append((from_vertex, to_vertex, weight)) return edges def __str__(self): result = [] for vertex_name, vertex in self.vertices.items(): neighbors = ", ".join([f"{n}({w})" for n, w in vertex.neighbors.items()]) result.append(f"{vertex_name}: {neighbors}") return "\n".join(result) class Vertex: def __init__(self, name): self.name = name self.neighbors = {} # 邻居字典:邻居名 -> 权重 def add_neighbor(self, neighbor, weight=1): """添加邻居""" self.neighbors[neighbor] = weight def __str__(self): return self.name # 2. 图的遍历算法 def bfs(graph, start): """广度优先搜索""" if start not in graph.vertices: return [] visited = set() queue = [start] result = [] while queue: vertex = queue.pop(0) if vertex not in visited: visited.add(vertex) result.append(vertex) # 添加所有未访问的邻居 for neighbor in graph.vertices[vertex].neighbors: if neighbor not in visited: queue.append(neighbor) return result def dfs(graph, start): """深度优先搜索(递归)""" if start not in graph.vertices: return [] visited = set() result = [] def dfs_recursive(vertex): visited.add(vertex) result.append(vertex) for neighbor in graph.vertices[vertex].neighbors: if neighbor not in visited: dfs_recursive(neighbor) dfs_recursive(start) return result # 3. 最短路径算法(Dijkstra) import heapq def dijkstra(graph, start, end): """Dijkstra算法求最短路径""" if start not in graph.vertices or end not in graph.vertices: return float('inf'), [] # 初始化距离字典 distances = {vertex: float('inf') for vertex in graph.vertices} distances[start] = 0 # 初始化前驱字典 predecessors = {vertex: None for vertex in graph.vertices} # 优先队列 pq = [(0, start)] while pq: current_distance, current_vertex = heapq.heappop(pq) # 如果找到更短的路径,跳过 if current_distance > distances[current_vertex]: continue # 遍历邻居 for neighbor, weight in graph.vertices[current_vertex].neighbors.items(): distance = current_distance + weight if distance < distances[neighbor]: distances[neighbor] = distance predecessors[neighbor] = current_vertex heapq.heappush(pq, (distance, neighbor)) # 重建路径 path = [] current = end while current is not None: path.append(current) current = predecessors[current] path.reverse() return distances[end], path if distances[end] != float('inf') else [] # 测试图 print("图结构测试:") g = Graph(directed=False) # 添加边(模拟城市交通) g.add_edge("北京", "上海", 1000) g.add_edge("北京", "广州", 2000) g.add_edge("上海", "广州", 1500) g.add_edge("上海", "成都", 1800) g.add_edge("广州", "成都", 1200) g.add_edge("成都", "西安", 800) g.add_edge("北京", "西安", 1100) print("图结构:") print(g) print("\n所有顶点:", g.get_vertices()) print("所有边:", g.get_edges()) print("\n" + "="*50) # 测试遍历算法 print("遍历算法测试:") print("BFS从北京开始:", bfs(g, "北京")) print("DFS从北京开始:", dfs(g, "北京")) print("\n" + "="*50) # 测试最短路径 print("最短路径测试:") distance, path = dijkstra(g, "北京", "成都") print(f"北京到成都的最短距离: {distance} km") print(f"路径: {' -> '.join(path)}") distance, path = dijkstra(g, "上海", "西安") print(f"\n上海到西安的最短距离: {distance} km") print(f"路径: {' -> '.join(path)}")
问题:图论在现实世界中有哪些重要应用?
思考方向: