把常见结构与算法用 Python 写一遍,并配上可逐步播放的示意(静态博客里用轻量 SVG + 原生 JS,赤霞珠配色)。演示块带 data-pagefind-ignore,不污染全文搜索。
交互控件:每个示意下方有 播放 / 暂停 / 单步 / 重置(栈为 Push/Pop)。若脚本未加载,请硬刷新。
学习路径
- 数组与复杂度直觉
- 链表与指针改写
- 栈 / 队列
- 树与遍历
- 排序(冒泡、插入,及对比)
- BFS / DFS
每个专题先写「干什么」,再给最小代码,最后给动画(若适用)。
数组(Array / List)
Python list 是动态数组:按下标 $O(1)$ 读写,末尾追加摊销 $O(1)$,中间插入/删除 $O(n)$。
a = [5, 3, 8, 4]
a.append(2) # 尾部
a.insert(1, 9) # 下标 1 插入,后面元素后移
x = a.pop() # 弹出尾部
y = a.pop(0) # 弹出头部,O(n)
适用:随机访问、紧凑内存、双指针 / 滑动窗口。
慎用:头部频繁插入删除 → 看 collections.deque。
前缀和模板:
def prefix_sums(nums: list[int]) -> list[int]:
ps = [0]
for x in nums:
ps.append(ps[-1] + x)
return ps # sum(nums[l:r]) == ps[r] - ps[l]
链表(Linked List)
节点存值与后继。插入删除在「已知前驱」时 $O(1)$,随机访问 $O(n)$。
from __future__ import annotations
from dataclasses import dataclass
@dataclass
class Node:
val: int
next: Node | None = None
def insert_after(prev: Node, val: int) -> Node:
nxt = Node(val, prev.next)
prev.next = nxt
return nxt
哨兵头简化边界:
def remove_values(head: Node | None, target: int) -> Node | None:
dummy = Node(0, head)
cur = dummy
while cur.next:
if cur.next.val == target:
cur.next = cur.next.next
else:
cur = cur.next
return dummy.next
动画:在节点后插入
下面演示在 2 → 5 → 9 的 5 之后插入 7。
要点:改的是指针,不是「数组搬移」。找前驱仍可能 $O(n)$。
栈与队列(Stack / Queue)
栈:LIFO
用 list 即可:append / pop。
stack: list[int] = []
stack.append(3)
stack.append(1)
stack.append(4)
top = stack.pop() # 4
经典:括号匹配、单调栈、DFS 显式化、表达式求值。
队列:FIFO
from collections import deque
q: deque[int] = deque()
q.append(1) # enqueue
q.append(2)
x = q.popleft() # dequeue → 1
不要用 list.pop(0) 当队列。双端队列还可 appendleft / pop。
树(Binary Tree)
@dataclass
class TreeNode:
val: int
left: TreeNode | None = None
right: TreeNode | None = None
建一棵完全二叉树示例:
def demo_tree() -> TreeNode:
# 1
# / \
# 2 3
# / \ / \
# 4 5 6 7
return TreeNode(
1,
TreeNode(2, TreeNode(4), TreeNode(5)),
TreeNode(3, TreeNode(6), TreeNode(7)),
)
深度 ≈ 递归层数;平衡时常见操作 $O(\log n)$,退化成链则 $O(n)$。
排序:冒泡与插入
先吃透「交换 / 插入」的局部不变量,再记 $O(n\log n)$ 的快排归并堆排。
冒泡排序
反复比较相邻元素,大的后移;每轮把最大值「冒」到未排序后缀。
def bubble_sort(a: list[int]) -> list[int]:
a = a[:]
n = len(a)
for end in range(n, 1, -1):
swapped = False
for j in range(end - 1):
if a[j] > a[j + 1]:
a[j], a[j + 1] = a[j + 1], a[j]
swapped = True
if not swapped:
break
return a
时间 $O(n^2)$,稳定;教学友好。
插入排序
维护左侧有序前缀,将下一个元素插入正确位置。
def insertion_sort(a: list[int]) -> list[int]:
a = a[:]
for i in range(1, len(a)):
key = a[i]
j = i - 1
while j >= 0 and a[j] > key:
a[j + 1] = a[j]
j -= 1
a[j + 1] = key
return a
近乎有序时接近 $O(n)$;稳定。
怎么选
| 算法 | 最好 | 平均 | 最坏 | 稳定 | 备注 |
|---|---|---|---|---|---|
| 冒泡 | $O(n)$ | $O(n^2)$ | $O(n^2)$ | 是 | 教学 |
| 插入 | $O(n)$ | $O(n^2)$ | $O(n^2)$ | 是 | 小数组 / 近有序 |
| 快排 | $O(n\log n)$ | $O(n\log n)$ | $O(n^2)$ | 否 | 实用默认 |
| 归并 | $O(n\log n)$ | $O(n\log n)$ | $O(n\log n)$ | 是 | 要稳定性 |
| 堆排 | $O(n\log n)$ | $O(n\log n)$ | $O(n\log n)$ | 否 | 原地 |
Python 的 list.sort / sorted 是 Timsort(归并+插入混合),实战优先。
BFS 与 DFS
图或树的两种基础遍历。树可看作无环图。
DFS(深度优先)
递归或显式栈。先走一条路走到底。
def dfs_preorder(root: TreeNode | None) -> list[int]:
out: list[int] = []
def go(n: TreeNode | None) -> None:
if not n:
return
out.append(n.val)
go(n.left)
go(n.right)
go(root)
return out
BFS(广度优先 / 层序)
队列:先访问离起点近的。
from collections import deque
def bfs_level(root: TreeNode | None) -> list[int]:
if not root:
return []
q: deque[TreeNode] = deque([root])
order: list[int] = []
while q:
n = q.popleft()
order.append(n.val)
if n.left:
q.append(n.left)
if n.right:
q.append(n.right)
return order
动画:二叉树 BFS
高亮当前节点、队列与产出顺序。
层序模板(按层分组):
def level_order(root: TreeNode | None) -> list[list[int]]:
if not root:
return []
q: deque[TreeNode] = deque([root])
ans: list[list[int]] = []
while q:
size = len(q)
level: list[int] = []
for _ in range(size):
n = q.popleft()
level.append(n.val)
if n.left: q.append(n.left)
if n.right: q.append(n.right)
ans.append(level)
return ans
对比
| DFS | BFS | |
|---|---|---|
| 结构 | 栈 / 递归 | 队列 |
| 擅长 | 路径、连通、拓扑、记忆化搜索 | 最短路(无权)、层序 |
| 空间 | 视深度 | 视一层宽度 |
复杂度速记
- 写循环前先问:最坏会扫几遍数据?
- 额外数组 / 哈希往往换时间。
- 递归深度过大改迭代,或抬
sys.setrecursionlimit(治标)。
# 计时小抄
import time
def timed(fn, *args):
t0 = time.perf_counter()
r = fn(*args)
print(f'{fn.__name__}: {time.perf_counter() - t0:.4f}s')
return r
练习题建议
- 反转链表;检测环(快慢指针)。
- 用栈判括号有效;用单调栈每日温度。
- 二叉树最大深度;对称树;层序平均值。
- 手写插入排序后对比
sorted。 - 网格
m×n上 BFS 最短步数(障碍物)。
本页技术说明(博客实现)
- 示意挂载点:
<div data-dsa-demo="..."></div>。 - 样式 / 脚本:
/scripts/dsa-demos.css、/scripts/dsa-demos.js(文章含 mount 时由布局注入)。 - 无 React/D3;播放逻辑为原生 JS,颜色对齐赤霞珠变量。
- 标记
data-pagefind-ignore,搜索仍以正文与代码为主。
算法要「看得见」:先动画建立不变量,再默写代码。
评论