从 DFS 到推荐算法:一个 AI Native 开发者的算法思维启蒙
v026 用 Canvas 在浏览器里画图、做游戏、搞数据可视化——那是"把东西画出来"的能力。
今天换个维度:从"视觉/图形"的世界走进"逻辑/算法"的世界。 学数据结构与算法——深度优先搜索(DFS)、广度优先搜索(BFS)、列表转树,以及一个真实世界级算法案例:抖音推荐系统。
为什么 AI Native 开发者要学算法?不是去 LeetCode 刷题——是因为 AI 的底层就是算法。 向量相似度是点积运算,Transformer 里有自注意力机制,推荐系统是一个多级漏斗——理解算法思维,你才能真正理解 AI 在做什么。
v025 ──→ v026 ──→ v027 今天
前端+AI 图形/视觉 算法思维
│
视觉世界 ──→ 逻辑世界
一、为什么要学算法?
作为 AI Native 开发者,日常是用 Claude Code 写代码、调 API、搭项目。那算法有什么用?
第一,理解 AI 的原理。 当你调用一个 LLM API 时,底层是 Transformer 架构在做矩阵运算;当你刷抖音时,背后是推荐算法在做向量相似度计算。不理解算法,你对 AI 的认知就停留在"黑盒调 API"的层面。
第二,培养结构化思维。 算法本质是"解决问题的步骤"。DFS 教你"一条路走到黑"的探索策略,BFS 教你"层层推进"的搜索策略——这些思维模式迁移到项目架构、Debug、需求分析上同样适用。
第三,面试基本功。 大厂面试必考算法,这是绕不开的现实。但今天不按 LeetCode 的套路来——我们以"理解它为什么存在"的视角来学。
先理解算法的思想,再考虑代码怎么写。思想对了,代码就是翻译。
二、DFS 深度优先搜索
什么 DFS?
Depth-First Search(深度优先搜索):沿着一条分支一路遍历到底,回溯后再探索其余分支。
想象你在走迷宫:
BFS 策略:站在入口,先看看一步范围内有哪些路,再两步、三步...
DFS 策略:选一条路,一直往前走,走不通了退回来,换另一条路
[1]
/ \
[2] [5]
/ \ \
[3] [4] [6]
DFS 遍历顺序:1 → 2 → 3 → 4 → 5 → 6
(一路向左走到底,回溯,再向右)
BFS 遍历顺序:1 → 2 → 5 → 3 → 4 → 6
(一层一层往下扫)
递归实现(最简单的方式)
function dfs(root, res = []) {
if (!root) return res // 退出条件:到叶子节点了
res.push(root.val) // 处理当前节点
dfs(root.left, res) // 递归左子树——"一条路走到底"
dfs(root.right, res) // 回溯后,递归右子树
return res
}
递归版的 DFS 就是三步:处理当前 → 递归左边 → 递归右边。 代码极简,但背后是函数调用栈在帮你记住"回溯点"。
递归的核心: 函数自己调用自己,每次调用都会在调用栈上压入一个新的执行上下文。当递归到叶子节点(
!root),开始逐层返回——这就是"回溯"的本质。
迭代实现(手写栈)
递归虽简洁,但理解"栈"这个数据结构才能掌握 DFS 的本质:
// 递归的升级版:迭代实现
function dfsPreOrderIter(root) {
if (!root) return []
const res = []
const stack = [root] // 用栈模拟递归
while (stack.length) {
const node = stack.pop() // 弹出栈顶——LIFO 后进先出
res.push(node.val)
// 先右后左——这样左会先出栈
if (node.right) stack.push(node.right)
if (node.left) stack.push(node.left)
}
return res
}
栈的工作原理:后进先出(LIFO)
push(1): [1]
push(2): [1, 2]
push(3): [1, 2, 3]
pop(): 3 ← 最后进去的,最先出来
pop(): 2
pop(): 1
递归版和迭代版的本质是一样的——都是在用栈。 递归用的是 JS 引擎的调用栈(你不可见),迭代版用的是手动维护的数组栈(你完全可控)。
什么时候用迭代? 当树的深度可能超过调用栈限制(约 10000 层),递归会导致栈溢出。这时必须用迭代。
三、BFS 广度优先搜索
什么是 BFS?
Breadth-First Search(广度优先搜索):从根节点开始,一层一层地遍历。先访问距离近的节点,再访问距离远的节点。
function bfs(root) {
if (!root) return []
const res = []
const queue = [root] // 用队列——不是栈!
while (queue.length) {
const node = queue.shift() // 从队头取出——FIFO 先进先出
res.push(node.val)
if (node.left) queue.push(node.left)
if (node.right) queue.push(node.right)
}
return res
}
DFS 用栈(pop/push),BFS 用队列(shift/push)。 数据结构不同,遍历逻辑完全不同:
DFS: 栈 = 后进先出 = 一条路走到底再回头
BFS: 队列 = 先进先出 = 一层一层往外扩
[1] ← 第 0 层
/ \
[2] [3] ← 第 1 层
/ \ / \
[4] [5] [6] [7] ← 第 2 层
BFS 遍历:1 → 2 → 3 → 4 → 5 → 6 → 7
队列变化过程:
初始: [(1)]
取出1,放入2,3: [(2), (3)]
取出2,放入4,5: [(3), (4), (5)]
取出3,放入6,7: [(4), (5), (6), (7)]
...依次取出 4,5,6,7
DFS vs BFS:什么时候用哪个?
| DFS | BFS | |
|---|---|---|
| 数据结构 | 栈(Stack) | 队列(Queue) |
| 遍历方式 | 深度优先——一条路到底 | 广度优先——一层层扫 |
| 空间复杂度 | O(h),h=树的高度 | O(w),w=树的最大宽度 |
| 适用场景 | 路径探索、回溯问题、连通性 | 最短路径、层级遍历、社交关系 |
| 代码特点 | 递归简洁,迭代需手动栈 | 只能用队列,没有简洁递归写法 |
直观判断法:
- 要找"最短路径"、"第几层" → BFS
- 要"探索所有可能"、"路径是否存在" → DFS
- 树很深但很窄 → DFS(省空间)
- 树很宽但很浅 → BFS(省空间)
四、列表转树:DFS 的实际应用
前端开发中有一个高频场景:后端返回扁平数组,你需要转成树形结构渲染。
// 后端返回的扁平数据
const list = [
{ id: 1, pid: null, name: '根节点' },
{ id: 2, pid: 1, name: '子节点A' },
{ id: 3, pid: 1, name: '子节点B' },
{ id: 4, pid: 2, name: '叶子A-1' },
]
// 你需要渲染的树形结构
const tree = [
{
id: 1, name: '根节点',
children: [
{ id: 2, name: '子节点A', children: [{ id: 4, ... }] },
{ id: 3, name: '子节点B', children: [] },
]
}
]
这就是 DFS 的典型应用——先建好 Map 索引,再递归(或遍历)构建父子关系。 核心思路:
- 遍历一遍
list,把所有节点放进Map<id, node> - 再遍历一遍,根据
pid把每个节点挂到父节点的children下 - 返回
pid === null的节点(根节点)
这是一个 O(n) 的两遍扫描,不需要嵌套循环。
五、真实世界的算法:抖音推荐系统
DFS 和 BFS 是基础算法。现在来看一个工业级算法系统——它就在你每天刷的短视频里。
你在刷抖音时,抖音在干嘛?
"我们在刷手机,手机在刷我们。"
你每滑动一次、点赞一次、停留多一秒——都在为你的用户画像贡献数据。
用户画像
┌────────────────────────────────────┐
│ tag: 科技 0.85 │
│ tag: 幽默 0.05 │
│ tag: 西语 0.03 │
│ 还有 509 个维度... │
└────────────────────────────────────┘
你的画像 = 一个 512 维的向量
视频的特征 = 也是一个 512 维的向量
相似度 = cosine(你的向量, 视频向量)
结果越接近 1 → 你们越"像" → 推荐给你
这就是你刷到的每个视频背后的数学——向量点积(cosine 相似度)。 每天几千万用户 × 几千万视频的向量之间做海量计算,这就是抖音后台的算力在做的事。
第一步:多模态解构
一段视频上传到抖音,首先被拆解为:
一段 15 秒视频
│
├── 视觉特征(逐帧提取)
│ Vision Transformer 逐帧分析
│ 认出:一只猫 / 程序员写代码 / 寝室看世界杯
│ 判断情绪基调:温柔治愈 / 冷酷硬核
│ → 数据量最大的一维
│
├── 音频特征
│ ASR 技术:语音 → 文本
│ NLP 分析:关键词、语义
│ 音量、情绪
│
└── → 汇总为一个 512 维向量
这个向量 = 视频的"数字指纹"
多模态是 LLM 的主要发展方向。 一段视频 = 视觉 + 音频 + 文本,每个维度用不同的 AI 模型处理,最终融合成一个统一的向量表示。
第二步:推荐漏斗
输入:每个视频 512 维向量。输出:你屏幕上那一条视频。
1000 万条视频
│
▼ 召回:双塔模型
│ 向量点积运算,cosine 夹角
│ 从 1000 万筛选出 1000 条
│
1000 条
│
▼ 粗排:轻量 ML 模型
│ 计算内部特征
│ 从 1000 条筛选到 300 条
│
300 条
│
▼ 精排:深度模型
│ score = w₁·点击 + w₂·完播 + w₃·点赞 + w₄·评论 + w₅·分享
│ 预测你每一项行为的概率,加权算总分
│
│
▼ 重排:打散策略
│ 前十名都是 AI 视频?强制插入一条跳舞视频
│ 防止内容疲劳
│
▼
你屏幕上看到的那一条
这个漏斗的关键设计:
| 阶段 | 数据量 | 模型复杂度 | 目标 |
|---|---|---|---|
| 召回 | 1000万→1000 | 向量点积(轻量) | 快速缩小范围 |
| 粗排 | 1000→300 | 轻量 ML | 再筛一遍 |
| 精排 | 300→排序 | 深度模型 | 精确预测你的行为 |
| 重排 | 最终列表 | 规则策略 | 防疲劳 + 探索 |
Explore & Exploit:探索与利用
最精妙的设计在重排阶段:
80% Exploit(利用)
→ 给你看已知你爱看的
20% Explore(探索)
→ 给你看你不确定爱不爱看的
如果你在"野外求生"视频上多停留了几秒...
→ 一个全新的兴趣标签被写入你的用户画像
→ 下次推荐就有了新内容来源
这 20% 的探索机制决定了:不是你主动搜索才看到新东西,而是算法在试探你的潜在兴趣。 你的每一次"莫名多看了几秒",都在重新定义你是谁。
从算法视角看推荐系统
回到 DFS 和 BFS——推荐系统跟它们有关系吗?
DFS 的"回溯"思想 → 探索(Explore):在你熟悉的主路径之外,
开辟新分支,看看有没有新的兴趣点
BFS 的"层级"思想 → 漏斗结构:从 1000 万 → 1000 → 300 → 排序
层层过滤,每层用不同策略
基础算法 + 基础数据结构 = 工业级系统的种子。 向量是数组,漏斗是队列,用户画像是 Map——你刚学的基础数据结构,在工业系统中以更复杂的形式复用着。
结语
今天从基础算法到工业系统,走完了一条"算法思维升级链":
- 为什么要学算法 —— 理解 AI 底层原理、培养结构化思维、面试基本功。不刷题,先理解思想
- DFS 深度优先搜索 —— 递归版(调用栈自动回溯)+ 迭代版(手写栈,后进先出)。适合路径探索、连通性问题
- BFS 广度优先搜索 —— 队列实现(先进先出),一层一层往外扩。适合最短路径、层级遍历
- 列表转树 —— DFS 在前端开发中的实际应用,Map + 两遍扫描 = O(n)
- 抖音推荐系统 —— 多模态解构 → 512 维向量 → 推荐漏斗(召回→粗排→精排→重排)→ Explore & Exploit。你刷的不是视频,是数学
从 v026 到今天的 AI 全栈版图扩展:
视觉线 算法线
│ │
├── v026: Canvas 2D ├── v027: 算法思维 ← 今天新增!
│ 图形编程 │ DFS/BFS/推荐系统
│ │
│ "把东西画出来" │ "理解系统怎么运作"
│ │
└────────────────────┴─────────────────────────
v026 让你能用 Canvas 画图做游戏——那是"手"的能力。今天让你理解算法和推荐系统——这是"脑"的能力。 手脑并用,才是完整的 AI Native 开发者。
AI 的底层是数学,AI 的工程是代码,AI 的产品是推荐给你的那条视频。 从写 dfs() 函数到理解抖音的推荐漏斗——算法思维贯穿始终。
下篇见。