数据结构算法总结

一、时间复杂度入门

复杂度 常见叫法 例子
O(1) 常数 读取列表指定位置 nums[0]
O(n) 线性 遍历长度为 n 的列表
O(n²) 平方 两层循环都遍历 n 次
O(log n) 对数 每次把搜索范围减半

怎么看循环?

遍历一次列表:

复制代码
for x in nums:
    print(x)

如果列表有 n 个元素,循环约执行 n 次,是 O(n)。

两层循环都遍历同一列表:

复制代码
for x in nums:
    for y in nums:
        print(x, y)

总次数约为 n × n,是 O(n²)。

每轮把 i 加倍:

复制代码
i = 1
while i < n:
    i *= 2

大约执行 log₂(n) 次,是 O(log n)。

  • nums[i]:列表按下标读取,通常是 O(1)
  • x in nums:列表查找,通常是 O(n)
  • x in some_set:集合查找,平均通常是 O(1)
  • 排序:常见排序算法通常是 O(n log n)

因此,如果需要反复检查元素是否出现,把元素放进集合后再查,通常比反复扫描列表高效。

二、哈希表、栈与队列

1. 哈希表:字典和集合的底层思路

字典和集合通常用哈希表实现。哈希函数会把键转换成位置,帮助快速查找。

复制代码
user = {"id": 101, "name": "小林"}
  • 字典的键不能重复;重复赋值会覆盖旧值。
  • 字典键和集合元素通常需要是可哈希的,例如整数、字符串、不可变元组。
  • 不同键可能得到相同位置,这叫哈希冲突;实现会处理冲突,所以查找通常快,但并非任何情况下都保证 O(1)。

2. 栈:后进先出

栈的英文是 Stack,特点是 LIFO:最后放进去的,最先取出来。

复制代码
stack = []
stack.append("A")  # 入栈
stack.append("B")
top = stack.pop()  # 出栈,得到 "B"

常见用途:

  • 括号匹配
  • 撤销操作
  • 深度优先搜索(DFS)
  • 函数调用过程

3. 队列:先进先出

队列的英文是 Queue,特点是 FIFO:先放进去的,先取出来。

Python 中可以使用 deque:

复制代码
from collections import deque

queue = deque()
queue.append("A")       # 入队
queue.append("B")
first = queue.popleft() # 出队,得到 "A"

不建议频繁用 list.pop(0) 做出队操作,因为它需要移动后面的元素,通常是 O(n);deque.popleft() 通常是 O(1)。

队列常用于广度优先搜索(BFS)。

4. 列表与链表的直观区别

  • 数组 / Python 列表:按下标读取快,通常 O(1);在开头插入或删除通常较慢,O(n)。
  • 链表:节点之间通过引用连接;已知插入位置时,插入操作可以很快,但要先按位置查找时通常需要 O(n)。

数组随机访问快;链表插入删除灵活,但查找指定位置较慢。

三、树、堆与图搜索

1. 树的基本概念

树是一种分层结构,常见术语:

  • 根节点:树的起点
  • 叶子节点:没有子节点的节点
  • 深度:从根到该节点经过的边数
  • 二叉树:每个节点最多有两个子节点

二叉搜索树(BST)满足:左子树的值小于节点,右子树的值大于节点。查找平均较快,但树严重偏斜时会退化到 O(n)。

2. 树的遍历

对节点 A,左子树为 B、右子树为 C:

  • 前序:根 → 左 → 右:A, B, C
  • 中序:左 → 根 → 右:B, A, C
  • 后序:左 → 右 → 根:B, C, A
  • 层序:从上到下、从左到右,通常用队列

几种树的区别:

先按用途分组,容易记:

  • 内存中的有序查找:二叉搜索树、AVL 树、红黑树
  • 磁盘 / 数据库索引:B 树、B+ 树
  • 数据完整性校验:哈希树(通常指 Merkle Tree)
  • 其他常见结构:堆、Trie(字典树)
结构 核心特点 常见用途
二叉搜索树(BST) 左子树值较小,右子树值较大;可能退化成单链 有序查找的基础结构
AVL 树 严格控制左右子树高度差,保持平衡 查找频繁、希望树较平衡
红黑树 用颜色规则维持近似平衡,查找、插入、删除都是 O(log n) 内存中的有序 Map / Set
B 树 多路平衡搜索树,一个节点可存多个键;所有叶子深度相同 数据库、文件系统索引
B+ 树 内部节点主要存索引键,数据集中在叶子;叶子通常按序链接 数据库范围查询、磁盘索引
哈希树 / Merkle Tree 叶子存数据块的哈希,父节点由子节点哈希计算得出 * 校验数据完整性、快速证明数据属于某集合

红黑树:记住"近似平衡"

红黑树通过节点颜色规则限制树高。重点性质包括:

  • 节点为红色或黑色,根节点为黑色
  • 红节点不能有红色子节点
  • 从任一节点到后代叶子的路径,黑节点数量相同

因此它能保证查找、插入和删除为 O(log n)。与 AVL 树相比,红黑树的平衡约束较宽松,插入删除时通常更灵活;两者都是平衡二叉搜索树。

B 树与 B+ 树:记住"减少磁盘读取"

它们都让一个节点容纳多个键,降低树的高度,适合以页为单位读写数据的存储设备。

  • B 树:键和对应数据可以分布在内部节点与叶子节点。
  • B+ 树:数据记录集中在叶子节点,内部节点负责导航;叶子节点通常连成有序链表,因此范围查询效率高。

为什么数据库索引常用 B+ 树?因为扇出大、树高较低,能减少磁盘访问;叶子有序相连,也便于范围扫描。

哈希树:注意它不是哈希表

这里的哈希树通常指 Merkle Tree :把数据块分别做哈希,再逐层合并哈希,最终得到根哈希。数据一旦变化,根哈希也会变化,所以可用于检查数据是否被篡改。它主要用于完整性验证,不是为了按键查找。

3. 堆

堆是一种便于取出最大值或最小值的结构:

  • 最小堆:堆顶是最小值
  • 最大堆:堆顶是最大值
  • 查看堆顶通常是 O(1);插入、删除堆顶通常是 O(log n)

常用于优先队列和 Top-K 问题。例如,从大量数字中找最大的 10 个,通常可以用堆维护候选值。

4. 图与 BFS、DFS

图由节点(顶点)和边组成,可以是有向或无向的。

BFS(广度优先搜索)一层层向外访问,常用队列;在无权图中,可以用来找最少经过几条边的路径。

DFS(深度优先搜索)沿一条路线尽可能深入,常用递归或栈;常用于遍历连通区域、检查路径等。

简单记忆:

  • BFS:队列,先发现的先处理
  • DFS:栈或递归,沿一条路先走到底

(1)递归:函数调用自己

递归需要两部分:

  1. 终止条件:什么时候停止调用自己。

  2. 递归步骤:问题如何缩小,再调用自己。

    def factorial(n):
    if n <= 1: # 终止条件
    return 1
    return n * factorial(n - 1) # 递归步骤

factorial(4) 会依次计算 4 × 3 × 2 × 1,结果是 24。

易错点:没有终止条件,或问题没有逐步缩小,递归就可能不断调用,最终触发递归深度错误。

(2)回溯

回溯可以理解为:尝试一个选择,继续搜索;如果这条路走完,就撤销选择,再试下一种。它通常通过递归实现。

典型结构:

  1. 记录当前结果
  2. 逐个尝试可选项
  3. 做选择并递归
  4. 撤销选择

例如,生成 [1, 2] 的所有子集:

复制代码
def subsets(nums):
    result = []
    path = []

    def dfs(start):
        result.append(path.copy())

        for i in range(start, len(nums)):
            path.append(nums[i])  # 做选择
            dfs(i + 1)            # 继续搜索
            path.pop()             # 撤销选择

    dfs(0)
    return result

结果是:

复制代码
[[], [1], [1, 2], [2]]

path.copy() 很重要:如果直接把 path 放进结果,之后回溯时修改 path,之前保存的结果也会跟着变化。

有 n 个元素时,子集总数是 2ⁿ。因为还要复制并保存每个子集,时间复杂度通常写作 O(n×2ⁿ)。

(3)DFS:沿一条路尽量走深

DFS 可以用递归实现。图可能有环,所以要用 visited 防止重复访问:

复制代码
def dfs(graph, node, visited=None):
    if visited is None:
        visited = set()

    if node in visited:
        return

    visited.add(node)
    print(node)

    for neighbor in graph[node]:
        dfs(graph, neighbor, visited)

这个写法用 None 作为默认参数,避免不同调用意外共享同一个集合。

(4)BFS:一层一层访问

BFS 用队列。把节点加入队列时就标记为已访问,可以避免重复入队:

复制代码
from collections import deque

def bfs(graph, start):
    visited = {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)

以邻居顺序为准,示例图如下:

复制代码
graph = {
    "A": ["B", "C"],
    "B": ["D"],
    "C": ["D"],
    "D": []
}

从 A 开始:

  • BFS 顺序:A → B → C → D
  • DFS 顺序:A → B → D → C

DFS 的遍历顺序会受邻居排列顺序影响,因此不同邻居顺序可能得到不同的有效遍历结果。

(5)复杂度与用途

用邻接表表示图时,完整遍历 BFS 或 DFS 的时间复杂度通常是 O(V + E) ,其中 V 是节点数,E 是边数。

  • 找无权图的最短路径:常用 BFS
  • 检查能否到达、遍历连通区域:可用 DFS 或 BFS
  • 需要探索所有可能路径:常用 DFS 配合回溯

四、排序与二分查找

1. 常见排序算法

先记住复杂度和特点:

排序 核心思路 平均时间 最坏时间 额外空间 稳定
冒泡排序 比较相邻元素,把较大值逐步移到后面 O(n²) O(n²) O(1) 是
选择排序 每轮找未排序部分的最小值,放到前面 O(n²) O(n²) O(1) 否
插入排序 把新元素插入前面已排好序的部分 O(n²) O(n²) O(1) 是
希尔排序 按间隔分组,先粗略排序,再逐步缩小间隔 取决于间隔序列 常见实现 O(n²) O(1) 否
归并排序 分治:拆成小段排序,再合并 O(n log n) O(n log n) O(n) 是
快速排序 选基准值,把数据分到基准两侧,再递归排序 O(n log n) O(n²) 平均 O(log n) 否
堆排序 建堆,反复取出堆顶元素 O(n log n) O(n log n) O(1) 否
基数排序 按个位、十位等位数依次分配和收集 O(d(n+k)) O(d(n+k)) O(n+k) 是

n 是元素数量;基数排序中的 d 是处理的位数,k 是每一位可能的取值范围。基数排序适合整数、固定长度字符串等能按位处理的数据,不适合所有类型的元素。

稳定"是指:两个相等元素排序后,原来的先后顺序仍然保留。基数排序要通过稳定的分配过程实现稳定性。

快速记忆

  • 简单直接但通常慢:冒泡、选择、插入,平均 O(n²)
  • 平均效率较高:希尔、归并、快速、堆
  • 不是基于元素两两比较:基数排序
  • 稳定排序:冒泡、插入、归并、基数
  • 原地排序且额外空间较少:冒泡、选择、插入、希尔、堆;快速排序还需要递归栈空间

2. 二分查找

二分查找每次排除一半候选范围,因此时间复杂度是 O(log n)。前提是数据已经排好序。

复制代码
def binary_search(nums, target):
    left = 0
    right = len(nums) - 1

    while left <= right:
        mid = (left + right) // 2

        if nums[mid] == target:
            return mid
        elif nums[mid] < target:
            left = mid + 1
        else:
            right = mid - 1

    return -1

例如:

复制代码
binary_search([1, 3, 5, 7, 9], 7)  # 返回 3
binary_search([1, 3, 5, 7, 9], 4)  # 返回 -1

常见易错点:

  • 输入必须有序。
  • right = len(nums) - 1,因为下标从 0 开始。
  • while left <= right 才能检查左右边界重合的情况。
  • 没找到时通常返回 -1。
相关推荐
信誓旦旦的程序猿2 小时前
【Python量化实战宝典 #18】MACD 金叉死叉手册:DIF、DEA、柱体全拆解(Python 可跑)
java·python·股票数据api·股票数据api接口·股票api数据接口·股票量化数据api·股票量化数据接口
Patrick在香港2 小时前
一个加 1 的动作,把最大值变成了最小值
python·numpy·数据类型·整数溢出·数据精度
Dageno_AI2 小时前
ChatGPT 不再引用 Reddit 之后,还值得在 Reddit 上做 AI 可见度吗?
算法
hetao17338372 小时前
2026-10-25 hetao1733837 的刷题记录
c++·算法
weixin_BYSJ19872 小时前
电竞先锋网站---附源码02032
java·vue.js·spring boot·python·django·tomcat·php
Jsundoku3 小时前
第七章 机器学习 -- 其他经典算法
人工智能·算法·机器学习
不脱发的程序猿3 小时前
红利ETF量化策略
人工智能·python·ai·程序员·量化交易·etf·策略回测
Apok'3 小时前
零基础学Python量化(1):研究大纲与流程
python·机器学习·金融·数据挖掘·数据分析
鬓戈3 小时前
llmproxy 系统开发目的和基本功能
人工智能·python