Alpha内测版本警告:此为早期内部构建版本,尚不完整且可能存在错误,欢迎大家提Issue反馈问题或建议。
Skip to content

第二部分:基础数据结构——组织数据的方式

词条3:哈希表(字典)——快速查找的魔法

官方解释

哈希表(Hash Table)是一种通过键(key)直接访问值(value)的数据结构。在Python中,字典(dict)就是哈希表的实现。核心原理:通过哈希函数将键映射到数组的索引,实现O(1)的平均查找时间。

兔狲老师解释

哈希表就像'智能电话簿':知道名字(键),直接找到电话号码(值)。

小小猪的比喻:

  • 哈希函数:把名字变成数字编号的机器
  • 哈希冲突:两个名字得到相同编号(需要解决)
  • 负载因子:电话簿使用率(太满需要扩容)

Python字典的特性:

  • 键必须是不可变类型(字符串、数字、元组)
  • 值可以是任意类型
  • 无序(Python 3.7+保持插入顺序)

思考题5:动手题

问题:实现一个简单的哈希表并理解其原理:

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)

思考题6:动脑题

问题:哈希表为什么能实现快速查找?有什么局限性?

思考方向:

  • 哈希函数的理想特性是什么?(确定性、均匀分布、快速计算)
  • 什么是哈希冲突?有哪些解决方法?(分离链接法、开放寻址法)
  • 为什么Python字典的键必须是不可变类型?
  • 哈希表的时间复杂度是多少?最坏情况是什么?
  • 在实际应用中,如何设计好的哈希函数?

词条4:链表——灵活的动态序列

官方解释

链表(Linked List)是一种线性数据结构,由一系列节点组成,每个节点包含数据和指向下一个节点的指针。与数组不同,链表在内存中不必连续存储,插入和删除操作更高效(O(1)),但随机访问较慢(O(n))。

链表类型:

  • 单向链表:每个节点指向下一个节点
  • 双向链表:每个节点指向前一个和后一个节点
  • 循环链表:尾节点指向头节点

兔狲老师解释

链表就像'火车车厢':每节车厢(节点)连接着下一节,可以轻松添加或移除车厢。

小小猪的比喻:

  • 节点:车厢(数据 + 连接)
  • 头指针:火车头
  • 尾指针:最后一节车厢
  • 空链表:没有车厢的火车

与数组(列表)比较:

  • 数组:连续内存,快速访问,插入删除慢
  • 链表:分散内存,访问慢,插入删除快

思考题7:动手题

问题:实现单向链表和双向链表:

python
# 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)

思考题8:动脑题

问题:链表和数组(列表)各有什么优缺点?如何选择?

思考方向:

  • 在什么情况下应该使用链表而不是数组?
  • 链表的插入删除为什么是O(1)?数组为什么是O(n)?
  • 链表的随机访问为什么是O(n)?数组为什么是O(1)?
  • 内存局部性对性能有什么影响?
  • 实际应用中,哪些数据结构基于链表实现?(栈、队列、哈希表的冲突解决)

词条5:树——层次化数据结构

官方解释

(Tree)是一种层次化的非线性数据结构,由节点和边组成。每个树有一个根节点,每个节点可以有零个或多个子节点,没有子节点的节点称为叶节点。

常见树类型:

  • 二叉树:每个节点最多有两个子节点(左子节点、右子节点)
  • 二叉搜索树(BST):左子树所有节点值小于根节点,右子树所有节点值大于根节点
  • 平衡二叉树(AVL树、红黑树):保持树的高度平衡
  • 堆:完全二叉树,满足堆属性(最大堆、最小堆)

兔狲老师解释

树就像'家族族谱'或"公司组织结构"。

小小猪的比喻:

  • 根节点:家族的祖先或公司CEO
  • 子节点:后代或下属
  • 叶节点:没有后代的人或基层员工
  • 深度:从根到节点的边数
  • 高度:从节点到最深叶节点的边数

树的遍历方式:

  • 前序遍历:根→左→右
  • 中序遍历:左→根→右(对BST得到有序序列)
  • 后序遍历:左→右→根
  • 层序遍历:按层次从上到下、从左到右

思考题9:动手题

问题:实现二叉树和二叉搜索树:

python
# 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())

思考题10:动脑题

问题:树结构在计算机科学中有哪些重要应用?

思考方向:

  • 文件系统如何用树结构组织?
  • 数据库索引为什么常用B树、B+树?
  • HTML/XML文档为什么是树结构?
  • 决策树在机器学习中如何工作?
  • 游戏中的AI决策树是什么?

词条6:图——复杂关系的网络

官方解释

(Graph)是由顶点(Vertex)和边(Edge)组成的非线性数据结构,用于表示对象之间的关系。图是树的一般化形式(树是无环连通图)。

图的分类:

  • 无向图:边没有方向
  • 有向图:边有方向
  • 加权图:边有权重
  • 连通图:任意两个顶点都有路径相连
  • 完全图:每对顶点之间都有边

图的表示方法:

  • 邻接矩阵:二维数组,matrix[i][j]表示顶点i到j的边
  • 邻接表:数组的数组,每个顶点有一个邻居列表

兔狲老师解释

图就像'社交网络'或"交通网络"。

小小猪的比喻:

  • 顶点:人(社交网络)或城市(交通网络)
  • 边:朋友关系或道路
  • 权重:亲密度或距离
  • 路径:从一个人到另一个人的朋友链

图算法应用:

  • 最短路径:导航软件找最短路线
  • 最小生成树:电网布线最省材料
  • 拓扑排序:课程安排、任务调度
  • 连通分量:社交网络中的朋友圈

思考题11:动手题

问题:实现图的基本结构和算法:

python
# 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)}")

思考题12:动脑题

问题:图论在现实世界中有哪些重要应用?

思考方向:

  • 社交网络分析如何用图论?
  • 网页排名算法(PageRank)如何用图?
  • 推荐系统如何用图表示用户-物品关系?
  • 物流配送如何用图优化路线?
  • 电路设计如何用图表示连接关系?