从一棵树到一亿人:算法世界的"深搜"哲学
今天的学习,一边是最朴素的递归遍历,一边是支撑数亿人刷视频的推荐引擎。看似天差地别,实则一脉相承。
一、树的浪漫:DFS,一场说走就走的深度旅行
先来看看今天的"开胃菜"------深度优先搜索(Depth-First Search)。
想象你走进了一座迷宫。普通人可能会在每个路口都探头探脑、犹豫不决;而 DFS 的作风是------认准一条路,走到黑再说,撞了南墙再回头。这就是"深度优先"的精髓。
我们以一棵简单的树为例:
css
a
/ \
b c
/
d
DFS 的遍历顺序是 a → b → d → c------它像一只执着的小蚂蚁,从根节点 a 出发,一头扎进左子树 b,发现 b 下面还有个 d,不管不顾继续往下钻,直到 d 无路可走了,才恋恋不舍地回头,去拜访被冷落许久的 c。
来看看代码实现。最经典的写法是递归:
javascript
function dfs(root, res = []) {
if (!root) {
return res; // 撞了南墙,回头!
}
res.push(root.val);
dfs(root.left, res); // 一条路走到黑
dfs(root.right, res); // 左边走完了,换右边
return res;
}
递归的优雅之处在于------它让你用不到十行代码,就完成了一次完整的树探索。你甚至不需要亲手管理"回头路",函数的调用栈天然帮你记住了"我在哪、我来过哪、我该回哪"。
但递归也有软肋:当树的深度过大,调用栈会溢出,程序直接崩溃。这时候,我们就需要手动用栈(stack)模拟递归,来一次"递归的升级":
javascript
function dfsPreOrderIter(root) {
if (!root) return [];
const stack = [root];
const res = [];
while (stack.length) {
const node = stack.pop(); // 弹出一个节点
res.push(node.val); // 访问它
if (node.right) stack.push(node.right); // 右孩子先压栈
if (node.left) stack.push(node.left); // 左孩子后压栈(这样左孩子会先被弹出)
}
return res;
}
教学小贴士 :注意这里
right先入栈、left后入栈------因为栈是"后进先出"(LIFO),后入栈的左孩子会先被处理,从而保证了"先左后右"的前序遍历顺序。这个小细节,是很多初学者的绊脚石。
这就是今天的第一个收获:递归是算法的诗,迭代是工程的事。 两者同根同源,只是换了种语言表达。
二、当一个 App 开始"刷"你:抖音推荐系统的算法全景
如果说 DFS 是算法世界里的小桥流水,那抖音的推荐系统就是一座精密运转的巨型工厂。今天的学习让我看到了冰山一角------但这一角,已经足够震撼。
2.1 "我们在刷手机,手机在刷我们"
这句话是今天的灵魂金句。你以为你在刷抖音?不,抖音也在"刷"你------你的每一次滑动、每一次停留、每一次点赞,都在给一个庞大的数据模型喂料。
每天几千万用户产生的视频行为数据,汇聚成每个人的用户画像 。这些画像不是一个简单的标签列表,而是一个512维的向量:
csharp
[0.85 科技, 0.05 幽默, 0.03 其他, ...]
512 维是什么概念?我们生活在三维空间,最多能想象四维时空。而抖音用 512 个维度来描述你------你是一个活在 512 维超空间里的"数据幽灵",比你妈还了解你。
2.2 第一步:多模态解构------让 AI "看懂"视频
一段视频在抖音眼里不是画面加声音,而是可以被数学运算的数据。
| 模态 | 技术路径 | 提取内容 |
|---|---|---|
| 视觉 | Vision Transformer 逐帧提取 Tensor | 对象、场景、动作、色彩情绪(温暖治愈还是冷酷硬核) |
| 音频 | ASR 语音识别 → NLP 语义分析 | 文本内容、声音情绪、音量大小 |
这两股信息流最终汇成一个 512 维向量------这就是视频的"数字指纹"。相似的内容,在 512 维空间中距离更近;风马牛不相及的内容,则天各一方。
2.3 第二步:推荐漏斗------从 1000 万到你的屏幕
这是整个推荐系统的心脏,一条精密的多级漏斗:
ini
1000万条视频
│
▼ ① 召回(双塔模型)
│ 向量点积 × Cosine Similarity
│ 从千万到千:1000条候选
│
▼ ② 粗排(轻量ML模型)
│ 从千到三百:300条候选
│
▼ ③ 精排(深度模型预测)
│ Score = w₁×点击 + w₂×完播 + w₃×点赞 + w₄×评论 + w₅×分享
│ 从三百到几十:精准排序
│
▼ ④ 重排(打散 & 探索)
│ 去重、多样性注入、Explore & Exploit
│
▼
你的"首页推荐"
每一步都在做减法,每一步都在逼近你的真实偏好。
2.4 重排中最精妙的设计:Explore vs. Exploit
如果前十个推荐全是 AI 视频(你的最爱),你会审美疲劳。所以重排机制会强制打散------每两个硬核内容之间,插入一个跳舞视频,防止你刷到怀疑人生。
更精彩的是 80/20 探索机制:
- 80% 的内容来自你已知的爱好(Exploit,利用)
- 20% 的内容是随机探索(Explore,勘探)
你某天在一条野外求生视频上多停留了 2 秒------bang!一个新的兴趣标签就此被记录下来。这不是巧合,是算法在刻意试探你的边界。抖音比你更想知道你还喜欢什么。
三、殊途同归:从 DFS 到推荐系统,算法思维的一以贯之
回到文章开头的问题------DFS 和抖音推荐,一个遍历一棵树,一个服务一亿人,它们之间有什么关系?
3.1 本质都是"搜索"
DFS 在确定的树结构 中搜索路径;推荐系统在高维向量空间 中搜索与你最匹配的内容。前者搜索的是节点,后者搜索的是相似度。核心逻辑一模一样:给定一个起点,找到一条(或若干条)"最优"路径。
3.2 都需要"回溯"
DFS 走到叶子节点后回溯;推荐系统的 Explore 机制本质上也是一种"跳出局部最优"的回溯------不能因为你喜欢 AI 视频就只喂 AI 视频,那样你会被困在信息茧房里。偶尔回头看看、侧头探探,才能发现更广阔的世界。
3.3 都用到了"栈"的思想
迭代版 DFS 显式维护了一个栈;而抖音的推荐漏斗------召回 → 粗排 → 精排 → 重排------不就像一个精心设计的"处理栈"吗?每一层都弹出一些候选、压入一些筛选,层层递进,最终输出你屏幕上那一个视频。
3.4 递归思维的终极隐喻
还记得递归的两个要素吗?
- 基准条件(Base Case) :
if (!root) return res;------停止递归的底线。 - 递归条件(Recursive Case) :
dfs(root.left, res)------将大问题拆解为更小的子问题。
抖音的推荐系统,本质上也是一个巨大的递归:把"找到你爱看的视频"这个大问题,逐层拆解成召回、粗排、精排、重排四个子问题,每个子问题再用更小的模型去解决,直到最终输出那一个视频。 这不就是"分而治之"的递归精神在工业级系统里的终极体现吗?
四、结语:算法不是冰冷的公式,而是理解世界的思维方式
今天的收获,远不止两个算法知识点。
DFS 教会我们:复杂问题的解,往往藏在一条走到黑的路上。 不要怕深入,不要怕回溯,每一次"撞南墙"都是在为正确答案排除错误选项。
抖音推荐系统教会我们:理论上的优雅,在工程中必须经历海量数据的淬炼。 一个 512 维的向量点积,背后是数千万用户的实时行为、数百台 GPU 集群的算力支撑。算法的浪漫在于它的简洁;算法的力量在于它能落地。
用一首打油诗总结今天:
递归深搜一条路,撞墙回头不认输。 栈中存下回头路,迭代代替调用栈。 抖音背后千亿算,五百一十二维向量看。 漏斗层层筛千万,探索利用八二分。 算法之美在何处?化繁为简见真章。