DATA STRUCTURES

构建数字世界的基石 · 核心原理与可视化解析

1. 数组 Array

概念定义

数组是一种线性数据结构,它用一组连续的内存空间,来存储一组具有相同类型的数据。它是最基础、最常用的数据结构。内存地址计算公式:addr[i] = base + i * size

核心特点

  • 随机访问: 支持通过下标 O(1) 时间复杂度访问元素,这是数组最大的优势。
  • 内存连续: 对 CPU 缓存友好,利用空间局部性原理(Spatial Locality)提高访问效率。
  • 大小固定: 静态数组创建时需指定容量,扩容成本较高(需复制所有数据 O(n))。
  • 动态数组: Python list、Java ArrayList 等实现自动扩容,通常扩容为 1.5~2 倍,摊销复杂度仍为 O(1)。
  • 多维数组: 支持二维、三维等多维结构,常用于矩阵运算、图像处理等场景。

适用场景

  • 需要频繁随机访问元素的场景(如索引查找)。
  • 数据量相对固定,不频繁插入删除。
  • 实现其他数据结构的底层存储(如堆、哈希表)。
  • 缓冲区、环形队列的实现。

基本操作

  • Access(i): 读取索引 i 处的元素。
  • Update(i, val): 修改索引 i 处的元素。
  • Insert/Delete: 需移动后续元素,平均复杂度 O(n)。
// 连续内存空间与索引映射
10 idx:0 25 1 42 2 8 3 99 4 Target
Python Example
# 初始化数组
arr = [10, 25, 42, 8, 99]

# 随机访问 O(1)
print(arr[2])  # Output: 42

# 修改元素 O(1)
arr[0] = 5

# 插入元素 (需移动后续元素) O(n)
arr.insert(1, 15)  # [5, 15, 25, 42, 8, 99]

# 删除元素 O(n)
arr.pop(2)  # [5, 15, 42, 8, 99]

# 二维数组(矩阵)
matrix = [
    [1, 2, 3],
    [4, 5, 6],
    [7, 8, 9]
]
print(matrix[1][2])  # 6

2. 链表 Linked List

概念定义

链表由一系列节点组成,每个节点包含数据域和指针域(指向下一个节点)。节点在内存中可以不连续存储。

链表类型

  • 单向链表: 每个节点只有一个 next 指针,内存开销小。
  • 双向链表: 节点含 prev 和 next 指针,可双向遍历,删除操作更高效。
  • 循环链表: 尾节点指向头节点形成环,适合轮询调度。
  • 跳表 Skip List: 多层索引的链表,查找效率达 O(log n),Redis 的有序集合实现。

核心特点

  • 动态扩容: 天然支持动态大小,无需预分配内存,适合数据量不确定的场景。
  • 插入删除快: 已知位置时,仅需修改指针,操作复杂度为 O(1)。
  • 随机访问慢: 必须从头遍历,查找复杂度 O(n),不支持索引访问。
  • 内存碎片: 节点分散存储,不利于 CPU 缓存,但避免了内存浪费。
  • 额外空间: 每个节点需存储指针,空间开销比数组大。

适用场景

  • 频繁插入删除操作的场景(如任务队列)。
  • LRU 缓存实现(双向链表 + 哈希表)。
  • 多项式、大数运算。
  • 图的邻接表表示。

基本操作

  • Insert(node): 修改指针指向即可。
  • Delete(node): 断开并重新连接指针。
  • Search(val): 线性遍历查找。
// 节点通过指针串联,非连续存储
A B C NULL
Python Example (Node Class)
class Node:
    def __init__(self, data):
        self.data = data
        self.next = None

# 构建链表 A -> B -> C
head = Node("A")
node_b = Node("B")
node_c = Node("C")

head.next = node_b
node_b.next = node_c

# 遍历
current = head
while current:
    print(current.data)
    current = current.next

# 在头部插入 O(1)
def insert_at_head(head, data):
    new_node = Node(data)
    new_node.next = head
    return new_node

# 删除节点 O(1) (已知前一节点)
def delete_node(prev_node):
    if prev_node and prev_node.next:
        prev_node.next = prev_node.next.next

3. 栈 Stack

概念定义

栈是一种遵循“后进先出”(LIFO, Last In First Out)原则的线性数据结构。就像叠盘子一样,最后放上去的盘子最先被取走。

适用场景

  • 函数调用栈(递归、保存返回地址)。
  • 浏览器历史记录(后退功能)。
  • 括号匹配检查、表达式求值。
  • 撤销/重做功能(Undo/Redo)。
  • 深度优先搜索(DFS)算法。
  • 反转字符串、回文检测。

核心特点

  • 访问限制: 只能访问栈顶元素,受限的线性结构。
  • 实现方式: 可用数组或链表实现,数组实现更常见。
  • 函数栈: 每次函数调用压栈(局部变量、返回地址),返回时出栈。

基本操作

  • Push(val): 入栈,元素加到栈顶。
  • Pop(): 出栈,移除并返回栈顶元素。
  • Peek(): 查看栈顶元素。
// LIFO: 元素 3 最后进入,最先出去
Data 1 Data 2 Data 3 Push / Pop
Python Example (List as Stack)
stack = []

# 入栈 Push
stack.append(1)
stack.append(2)
stack.append(3)

# 查看栈顶 Peek
print(stack[-1])  # 3

# 出栈 Pop
top = stack.pop() # 3
print(stack)      # [1, 2]

# 括号匹配示例
def is_valid_parentheses(s):
    stack = []
    pairs = {'(': ')', '[': ']', '{': '}'}
    for char in s:
        if char in pairs:  # 左括号
            stack.append(char)
        else:  # 右括号
            if not stack or pairs[stack.pop()] != char:
                return False
    return len(stack) == 0

print(is_valid_parentheses("({[]})"))  # True

4. 队列 Queue

概念定义

队列是一种遵循“先进先出”(FIFO, First In First Out)原则的线性数据结构。就像排队买票,先来的人先服务。

适用场景

  • 任务调度(打印机队列、CPU 调度)。
  • 消息队列(RabbitMQ, Kafka, Redis Pub/Sub)。
  • 广度优先搜索(BFS)算法。
  • 缓冲区管理(IO 请求队列)。
  • 事件循环(JavaScript Event Loop)。
  • 多线程同步(生产者-消费者模式)。

核心特点

  • 公平性: 先到先服务(FCFS),保证顺序处理。
  • 实现方式: 数组实现需要移动元素(可用循环队列优化),链表实现更高效。
  • 优先队列: 变体,元素按优先级出队,通常用堆实现。

基本操作

  • Enqueue(val): 入队,加到队尾。
  • Dequeue(): 出队,移除队头元素。
// FIFO: 从 Tail 进入,从 Head 离开
In 2 1 Out Enqueue Dequeue
Python Example (collections.deque)
from collections import deque

queue = deque()

# 入队 Enqueue
queue.append("A")
queue.append("B")
queue.append("C")

# 出队 Dequeue
first = queue.popleft() # "A"
print(queue)            # deque(['B', 'C'])

# BFS 示例(层序遍历)
def bfs(graph, start):
    visited = set([start])
    queue = deque([start])
    
    while queue:
        node = queue.popleft()
        print(node)
        
        for neighbor in graph[node]:
            if neighbor not in visited:
                visited.add(neighbor)
                queue.append(neighbor)

5. 哈希表 Hash Table

概念定义

哈希表(散列表)通过哈希函数 Hash(key) 将键映射到存储桶(Bucket)的索引,从而实现快速查找。好的哈希函数应该分布均匀、计算高效。

冲突解决方法

  • 链地址法(Separate Chaining): 每个桶维护链表或红黑树(Java 8+),冲突元素追加。
  • 开放寻址法(Open Addressing): 发生冲突时探测下一位置(线性探测、二次探测、双重哈希)。
  • 再哈希法: 使用多个哈希函数计算新位置。
  • 建立公共溢出区: 将冲突元素存储到单独的溢出表。

核心特点

  • 极速查找: 平均时间复杂度 O(1),最坏情况 O(n)(所有元素冲突)。
  • 键值对存储: 适合存储映射关系,快速查找。
  • 哈希冲突: 不同的 Key 可能映射到同一个 Index,负载因子(Load Factor)影响性能。
  • 动态扩容: 负载因子超过阈值(如 0.75)时自动扩容,rehash 所有元素。
  • 无序性: 元素存储无序,LinkedHashMap 可保持插入顺序。

适用场景

  • 快速查找、去重(如统计单词频率)。
  • 数据库索引(哈希索引)。
  • 缓存实现(LRU、LFU)。
  • 分布式系统(一致性哈希)。

基本操作

  • Put(key, value): 插入或更新键值对。
  • Get(key): 获取值。
  • Remove(key): 删除键值对。
// Key 经过 Hash 函数计算出 Index
"Apple" Hash() 0: 1: ("Apple", $5) 2: 3: ("Banana", $3)
Python Example (Dictionary)
# 创建哈希表
prices = {}

# 插入
prices["Apple"] = 5
prices["Banana"] = 3

# 查找 O(1)
if "Apple" in prices:
    print(prices["Apple"])  # 5

# 删除
del prices["Banana"]

# 单词频率统计
def word_frequency(text):
    freq = {}
    for word in text.split():
        freq[word] = freq.get(word, 0) + 1
    return freq

text = "hello world hello python"
print(word_frequency(text))
# {'hello': 2, 'world': 1, 'python': 1}

6. 树 Tree

概念定义

树是一种分层数据的抽象模型。二叉树(Binary Tree)是每个节点最多有两个子节点的树结构。

树的类型

  • 二叉搜索树 BST: 左子节点 < 根节点 < 右子节点,平均 O(log n) 查找,最坏退化为 O(n)。
  • AVL 树: 严格平衡的二叉搜索树,任意节点左右子树高度差 ≤ 1,查找最稳定。
  • 红黑树: 弱平衡树,Java TreeMap/TreeSet、Linux 内核底层实现,插入删除性能优于 AVL。
  • B 树: 多路搜索树,节点可有多个子节点,数据库索引(InnoDB)。
  • B+ 树: B 树的变体,叶子节点形成链表,MySQL 索引首选,范围查询高效。
  • 字典树 Trie: 见第 10 节。
  • 线段树: 用于区间查询(区间和、最大值),竞赛常用。
  • 树状数组(Fenwick Tree): 轻量级线段树,单点更新与前缀和查询。

核心特点

  • 层级关系: 根节点、父节点、子节点、叶子节点。
  • 递归结构: 每个子树也是一棵树。
  • 应用广泛: 文件系统、DOM 树、数据库索引(B+树)。

遍历方式

  • Pre-order: 根 -> 左 -> 右(用于复制树结构)
  • In-order: 左 -> 根 -> 右(BST 中得到有序序列)
  • Post-order: 左 -> 右 -> 根(用于删除树)
  • Level-order: 层序遍历,使用队列实现
// 二叉树结构:Root, Left Child, Right Child
1 2 3 4 5
Python Example (Binary Tree Node)
class TreeNode:
    def __init__(self, val=0):
        self.val = val
        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

# 中序遍历(左-根-右)
def inorder(root):
    if root:
        inorder(root.left)
        print(root.val, end=' ')
        inorder(root.right)

inorder(root)  # 4 2 5 1 3

7. 图 Graph

概念定义

图由顶点(Vertex)和边(Edge)组成,表示多对多的关系。分为有向图和无向图,带权图和无权图。

核心算法

  • BFS 广度优先: 使用队列,适合无权图最短路径、层序遍历。
  • DFS 深度优先: 使用栈/递归,适合连通性检测、拓扑排序、环检测。
  • Dijkstra: 单源最短路径算法(非负权边),使用优先队列优化。
  • Bellman-Ford: 支持负权边的最短路径算法,可检测负权环。
  • Floyd-Warshall: 全源最短路径,动态规划实现。
  • Prim / Kruskal: 最小生成树算法(MST)。
  • 拓扑排序: 有向无环图(DAG)的线性排序,课程依赖、任务调度。
  • 强连通分量: Tarjan、Kosaraju 算法。

适用场景

  • 社交网络(好友关系)。
  • 地图导航(最短路径)。
  • 网络拓扑。

表示方法

  • 邻接矩阵: 二维数组,查询快,耗内存。
  • 邻接表: 链表数组,省内存,适合稀疏图。
// 复杂网络结构:节点互联
A B C D
Python Example (Adjacency List)
graph = {
    "A": ["B", "C"],
    "B": ["A", "C", "D"],
    "C": ["A", "B", "D"],
    "D": ["B", "C"]
}

# 查找 A 的邻居
print(graph["A"]) # ['B', 'C']

# DFS 递归实现
def dfs(graph, node, visited=None):
    if visited is None:
        visited = set()
    visited.add(node)
    print(node, end=' ')
    
    for neighbor in graph[node]:
        if neighbor not in visited:
            dfs(graph, neighbor, visited)

dfs(graph, "A")  # A B C D

8. 堆 Heap

概念定义

堆是一种特殊的完全二叉树,满足堆属性:父节点的值总是大于(最大堆)或小于(最小堆)其子节点的值。堆通常用数组实现。

核心特点

  • 完全二叉树: 除最后一层外所有层都被填满。
  • 堆序性质: 最大堆根节点最大,最小堆根节点最小。
  • 高效操作: 插入和删除的时间复杂度为 O(log n)。
  • 数组表示: 节点 i 的左子节点为 2i+1,右子节点为 2i+2。

适用场景

  • 优先队列的实现(任务调度)。
  • 堆排序算法。
  • Top K 问题(找最大/最小的K个元素)。
  • Dijkstra 最短路径算法。

基本操作

  • Insert(val): 插入元素并上浮调整 O(log n)。
  • Extract-Max/Min: 取出堆顶并下沉调整 O(log n)。
  • Peek(): 查看堆顶元素 O(1)。
  • Heapify: 将数组转化为堆 O(n)。
// 最大堆:父节点 >= 子节点
90 Max 80 70 50 60 40 30
Python Example (heapq - Min Heap)
import heapq

# 创建最小堆
min_heap = []
heapq.heappush(min_heap, 50)
heapq.heappush(min_heap, 30)
heapq.heappush(min_heap, 70)
heapq.heappush(min_heap, 10)

# 查看堆顶(最小值)
print(min_heap[0])  # 10

# 弹出最小值
smallest = heapq.heappop(min_heap)  # 10

# 创建最大堆(取负值技巧)
max_heap = []
heapq.heappush(max_heap, -90)
heapq.heappush(max_heap, -80)
largest = -heapq.heappop(max_heap)  # 90

9. 双端队列 Deque

概念定义

双端队列(Double-Ended Queue)是一种允许在两端进行插入和删除操作的线性数据结构。它结合了栈和队列的特点。

核心特点

  • 双向操作: 头尾均支持 O(1) 插入和删除。
  • 灵活性高: 可当作栈或队列使用。
  • 实现方式: 通常用双向链表或循环数组实现。

适用场景

  • 滑动窗口最大值/最小值问题。
  • 工作窃取调度算法。
  • 浏览器历史记录(前进/后退)。
  • 撤销/重做功能实现。

基本操作

  • push_front(val): 头部插入。
  • push_back(val): 尾部插入。
  • pop_front(): 头部删除。
  • pop_back(): 尾部删除。
// 双端操作:两端均可进出
Front A B C D Back
Python Example (collections.deque)
from collections import deque

dq = deque()

# 两端插入
dq.append("C")       # 右端插入
dq.appendleft("B")   # 左端插入
dq.append("D")
dq.appendleft("A")
# deque: ['A', 'B', 'C', 'D']

# 两端删除
left = dq.popleft()  # 'A'
right = dq.pop()     # 'D'
# deque: ['B', 'C']

# 滑动窗口最大值示例
def max_sliding_window(nums, k):
    dq = deque()  # 存储索引
    result = []
    for i, num in enumerate(nums):
        while dq and nums[dq[-1]] < num:
            dq.pop()
        dq.append(i)
        if dq[0] <= i - k:
            dq.popleft()
        if i >= k - 1:
            result.append(nums[dq[0]])
    return result

10. 前缀树 Trie

概念定义

Trie(发音 "try"),又称前缀树或字典树,是一种用于高效存储和检索字符串键的树形数据结构。每条从根到节点的路径代表一个前缀。

核心特点

  • 前缀共享: 相同前缀的单词共享节点,节省空间。
  • 快速查找: 查找时间复杂度 O(m),m 为字符串长度。
  • 有序性: 可按字典序遍历所有字符串。
  • 空间换时间: 通过多叉树结构换取查找效率。

适用场景

  • 自动补全(搜索引擎建议)。
  • 拼写检查器。
  • IP 路由表(最长前缀匹配)。
  • 词频统计。

基本操作

  • Insert(word): 插入单词 O(m)。
  • Search(word): 精确查找 O(m)。
  • StartsWith(prefix): 前缀匹配 O(m)。
// 存储: "app", "apple", "api", "bat"
root a b p a p ✓app i ✓api t ✓bat le
Python Example (Trie Implementation)
class TrieNode:
    def __init__(self):
        self.children = {}  # 子节点字典
        self.is_end = False # 标记单词结尾

class Trie:
    def __init__(self):
        self.root = TrieNode()
    
    def insert(self, word: str) -> None:
        node = self.root
        for char in word:
            if char not in node.children:
                node.children[char] = TrieNode()
            node = node.children[char]
        node.is_end = True
    
    def search(self, word: str) -> bool:
        node = self._find_node(word)
        return node is not None and node.is_end
    
    def startsWith(self, prefix: str) -> bool:
        return self._find_node(prefix) is not None
    
    def _find_node(self, prefix: str):
        node = self.root
        for char in prefix:
            if char not in node.children:
                return None
            node = node.children[char]
        return node

# 使用示例
trie = Trie()
trie.insert("apple")
trie.insert("app")
print(trie.search("app"))      # True
print(trie.startsWith("ap"))   # True

11. 复杂度对比 Complexity

时间复杂度总览

以下表格汇总了各数据结构核心操作的平均时间复杂度,帮助你在不同场景下做出最优选择。

数据结构 访问 查找 插入 删除 空间复杂度
数组 Array O(1) O(n) O(n) O(n) O(n)
链表 Linked List O(n) O(n) O(1)* O(1)* O(n)
栈 Stack O(n) O(n) O(1) O(1) O(n)
队列 Queue O(n) O(n) O(1) O(1) O(n)
哈希表 Hash Table N/A O(1) O(1) O(1) O(n)
二叉搜索树 BST O(log n) O(log n) O(log n) O(log n) O(n)
堆 Heap O(1)* O(n) O(log n) O(log n) O(n)
Trie 前缀树 N/A O(m) O(m) O(m) O(n·m)

注释说明

  • O(1)*: 链表在已知位置时为 O(1),否则需先遍历。
  • O(1)* (堆): 堆的访问指获取堆顶元素。
  • O(m): m 为字符串长度,与元素数量 n 无关。
  • BST 最坏情况: 退化为链表时复杂度变为 O(n)。
  • 颜色标识:最优 O(1) / 良好 O(log n) / 一般 O(n)