一支由清华大学计算机系学生在 2013 年组建的校园摇滚乐队,因为名为 Splay,再次进入公众视野。这个名字来自数据结构中的伸展树(Splay Tree):它不严格维持全局平衡,却会把刚访问过的节点旋转到根部。音乐与算法看似毫不相干,但放在一起看,恰好说明了一件事:很多当下无法量化的投入,会在表达能力、协作方式和个人辨识度上留下长期回报。
“有用”不是兴趣的唯一衡量单位
从结果看,校园乐队可能只留下几首歌、一次比赛成绩和一些旧录音。若只用职业晋升、论文数量或商业收入衡量,它很容易被归入“没什么用”。
但乐队排练包含一组工程师熟悉的能力:在约束下创作、快速暴露错误、与他人同步节奏,以及把不完整的想法打磨成可以公开呈现的作品。代码评审、系统联调和现场演出并不等价,却共享一种工作方式:个人能力只有进入协作系统,才会形成完整输出。
这也解释了为什么技术人的非技术经历常常具有延迟价值。它未必直接生成一项职业技能,却可能改善一个人如何讲述复杂问题、如何接受反馈,以及如何在不确定性中持续完成作品。
Splay 这个名字为什么贴切
伸展树是一种二叉搜索树。每次查询、插入或删除后,它都会通过旋转把相关节点移动到根附近。它不保存红黑树那样明确的平衡标记,也不能保证单次操作一定是 O(log n);极端情况下,单次操作可能达到 O(n)。但在一系列操作上,它具有均摊 O(log n) 的性能。
更重要的是,它会偏爱近期访问过的数据。如果访问具有局部性,热点节点会逐渐靠近根部,后续查询因而更快。这种结构没有追求“每一刻都绝对整齐”,而是根据真实访问不断调整自己。
把乐队命名为 Splay,可以读出一种计算机系学生式的幽默。进一步说,它也提供了一个理解兴趣投入的隐喻:一次排练或一首歌未必立刻改变什么,但反复访问某种能力,会让它逐渐靠近个人能力结构的“根”。
动手观察伸展树如何记住热点
下面是一个可直接运行的最小 Python 示例。它实现插入、查询和伸展操作,并连续访问同一个键,观察热点节点如何移动到根部。将代码保存为 splay_demo.py,使用 Python 3 运行即可。
from dataclasses import dataclass
from typing import Optional
@dataclass
class Node:
key: int
left: Optional["Node"] = None
right: Optional["Node"] = None
def rotate_right(root: Node) -> Node:
new_root = root.left
assert new_root is not None
root.left = new_root.right
new_root.right = root
return new_root
def rotate_left(root: Node) -> Node:
new_root = root.right
assert new_root is not None
root.right = new_root.left
new_root.left = root
return new_root
def splay(root: Optional[Node], key: int) -> Optional[Node]:
if root is None or root.key == key:
return root
if key < root.key:
if root.left is None:
return root
if key < root.left.key:
root.left.left = splay(root.left.left, key)
root = rotate_right(root)
elif key > root.left.key:
root.left.right = splay(root.left.right, key)
if root.left.right is not None:
root.left = rotate_left(root.left)
return rotate_right(root) if root.left is not None else root
if root.right is None:
return root
if key > root.right.key:
root.right.right = splay(root.right.right, key)
root = rotate_left(root)
elif key < root.right.key:
root.right.left = splay(root.right.left, key)
if root.right.left is not None:
root.right = rotate_right(root.right)
return rotate_left(root) if root.right is not None else root
def insert(root: Optional[Node], key: int) -> Node:
if root is None:
return Node(key)
root = splay(root, key)
assert root is not None
if root.key == key:
return root
node = Node(key)
if key < root.key:
node.left, node.right = root.left, root
root.left = None
else:
node.left, node.right = root, root.right
root.right = None
return node
root = None
for value in [40, 20, 60, 10, 30, 50, 70]:
root = insert(root, value)
for query in [20, 60, 20, 20]:
root = splay(root, query)
print(f"访问 {query:>2} 后,根节点是 {root.key}")
运行命令:
python3 splay_demo.py
每次成功查询后,被访问的键都会成为根节点。真实实现还需要补充删除、重复键策略、测试和性能基准;这里的代码只用于展示伸展过程。对于要求稳定尾延迟的服务,伸展树的单次最坏复杂度需要谨慎评估,不能仅凭均摊复杂度做选型。
把“无用”爱好经营成可复用资产
技术人不必强行把每个爱好包装成生产力工具。更实际的做法,是保留兴趣本身,同时让过程产生可复用的结果:完成一首歌,而不只是练习片段;组织一次演出,而不只是讨论创意;为作品留下版本、说明和可公开访问的归档。
判断一项长期投入是否值得,可以检查四件事:
- 它是否促使你持续完成作品,而非只消费内容;
- 它是否让你与不同能力的人协作;
- 它是否提供真实、及时且无法回避的反馈;
- 即使没有职业回报,你是否仍愿意承担时间成本。
摇滚未必需要证明自己“有用”。但当一段校园经历多年后仍能让人记住一个名字、一首作品和一个数据结构时,它已经产生了比短期指标更复杂的价值。像伸展树一样,人的能力也会被一次次访问重新排列;真正影响长期路径的,往往正是那些被持续调用的兴趣。