这是一篇不需要 h e a d l i n e \rm headline headline 的杂谈文章。
天下的平衡树众多,究竟该用哪一个?这个问题困扰了我很久了。
上网查资料,竟然众说纷纭。讲 Leafy Tree \text{Leafy Tree} Leafy Tree 的论文表示红黑树最快、 SBT \textit{SBT} SBT 次之,而 Treap \texttt{Treap} Treap 和 Splay \texttt{Splay} Splay 分别为倒数第二和最慢。利用在线测评网站做评估的结果相似。讲 SBT \textit{SBT} SBT 的论文则说 SBT \textit{SBT} SBT 快于 AVL \textit{AVL} AVL 。
这个较久远的帖子却说替罪羊最快,接下来依次是 Treap \texttt{Treap} Treap, AVL \textit{AVL} AVL 和 SBT \textit{SBT} SBT,与上面的结论相反。替罪羊比较快、 Splay \texttt{Splay} Splay 慢,这是共识。然而 Treap \texttt{Treap} Treap 和 SBT \textit{SBT} SBT 究竟慢不慢?
刚好该文章进行了比较后,给出了测试包。我干脆自己也测一测。反正我只会写 Treap \texttt{Treap} Treap 和 SBT \texttt{SBT} SBT 欸 🤣
表中
Splay
\texttt{Splay}
Splay 代码为测试包内原有代码,在 C++11 -O2
环境下测试,
Intel(R) Core(TM) i7-8565U
\text{Intel(R) Core(TM) i7-8565U}
Intel(R) Core(TM) i7-8565U,
64-bit Windows10
\text{64-bit Windows10}
64-bit Windows10 。
SBT \textit{SBT} SBT | Treap \texttt{Treap} Treap | Splay \texttt{Splay} Splay | |
---|---|---|---|
pushUp \texttt{pushUp} pushUp 次数 | 9436871 | 57125119 | 80662645 |
insert \texttt{insert} insert | 0.968 | 1.796 | 2.281 |
insert+erase \texttt{insert+erase} insert+erase | 0.796 | 0.953 | 1.578 |
有序数据(平均值) | 0.450 | 0.174 | 0.090 |
上表中 pushUp \texttt{pushUp} pushUp 次数对应 insert+erase \texttt{insert+erase} insert+erase 组数据。运行时间会有 0.1 s 0.1\rm s 0.1s 左右浮动。
线段树是最快的平衡树。在不需要某些动态特性时,优先选用。几乎总是快一倍。- Splay \texttt{Splay} Splay,请你只局限于 LCT \textit{LCT} LCT 和启发式合并吧!
-
Treap
\texttt{Treap}
Treap 在非随机数据上表现好(
本来它的核心思想就是使得数据随机),随机数据上则不如 SBT \textit{SBT} SBT 了。卡 Treap \text{Treap} Treap 可用 “两头往中间走” 的值(每次 split \texttt{split} split 两边大小较平均)。 - SBT \textit{SBT} SBT 不在意数据,并且 pushUp \texttt{pushUp} pushUp 次数明显较少。如果 pushUp \texttt{pushUp} pushUp 涉及乘法(比加法慢 20 20 20 倍)那可能 SBT \textit{SBT} SBT 会是很不错的选择。
- 在随机数据下
BST
\textit{BST}
BST 最快,比线段树还快。
就看你胆子够不够大。
所以最快的方法是:先用 BST,发现数据不随机就用 Treap?
出于代码熟练度的考虑,其实我会优先选择 Treap \texttt{Treap} Treap 的。把 SBT \textit{SBT} SBT 玩熟之后就不一定了 😏