この記事は IQ1アドベントカレンダー2019 の 5日目の記事です。
今回はちょっとした小ネタ記事として、splay treeのvisualizationを簡易的に作った話を書きます。
はじめに
最近、Splay Treeがぬるぬる動く様子を眺めて癒やされたい(?)、という気持ちになり自作したくなったのでさくっと作りました。
作成期間はこのアドベントカレンダー記事を書こうとしてからの直近1週間です。
Splay Treeとは?
スプレー木 - Wikipedia
Splay tree - Wikipedia
平衝二分探索木の1つであり、参照したノードを回転しながらrootに持ってくるsplay操作によって平衝を保つ。
splay操作では zig, zig-zig, zig-zag の操作を組み合わせて行う。
Splay Tree上の操作は ならし計算量で1回あたり になる。
Splay Treeの操作実現方法としてbottom-up方式とtop-down方式がある。
bottom-up
目的のノードまで探索してからrootに向かってsplay操作を行う。
各操作は以下のイメージのように行う。
- zig step
下図のノードyがrootの場合の操作
- zig-zig step
- zig-zag step
top-down
rootから目的のノードまで探索するのに合わせ、splay操作も行う。
splay操作中は3つの木を持ち、現在探索している木(middle tree)の他に探索対象のkey より小さいノードを含む木(left tree)と大きいノードを含む木(right tree)を管理する。
そして、木に含まれるノードをkey との大小の関係から2つの木(left tree, right tree)に移動させ、最後に3つの木をマージして完了させる。
各操作と最後のマージは以下のイメージのように行う。
- zig step
子ノード(下図のy)が参照するノードの場合の操作
- zig-zig step
- zig-zag step
- 最後のマージ
middle treeの根ノードと左右ノードの間にleft tree, right treeを差し込む感じでマージする。
visualizationの実装
ぬるぬる動くようにしたいのですが自前で動きをつけるのはめっちゃ面倒なので、アニメーションを実現できるJavaScriptライブラリを使うことにしました。
今回は anime.js を使いました。
animejs.com
各アニメーションは、移動後の座標と移動時間を設定するだけで動いてくれるので記述が楽でした。
また、Timelineを用いて連続したアニメーションを簡単に記述できたのもよさげでした。
// tl = anime.timeline() function translate_obj(result, tl) { tl.add({ // 各ノードのアニメーション設定 (svgのgタグ) targets: ['g.node'], translateX: (el) => { // 移動後のX座標を返す }, translateY: (el) => { // 移動後のY座標を返す }, duration: 1000, // 移動にかける時間 easing: 'linear', }).add({ // 各辺のアニメーション設定 (svgのpathタグ) targets: ['path.edge-l', 'path.edge-r'], d: [{value: function(el) { // 辺の始点(A, B)と終点(C, D)を "M<A>,<B>L<C>,<D>" という形式で返す }}], offset: '-=1000', // 直前のノードのアニメーションの開始時刻と合わせる duration: 1000, easing: 'linear', }); }
あとは、JavaScript上でSplay Treeを自分で実装し、splay操作の各stepごとに各ノードと辺に対しアニメーションの設定を行うように実装しました。
今回はkeyの追加と検索はできるようにしてあります。
ページは以下です。環境によっては動かなかったり崩れたりする可能性はあります。一応ChromeとFirefoxあたりでは正しく動くと思います。
- Bottom-Up方式: Visualization (Bottom-Up Splay Tree)
- Top-Down方式: Visualization (Top-Down Splay Tree)
とりあえず、短期間の雑な実装で自分が思う程度にぬるぬる動いてくれてると思います。
実装が雑なので、見た目や足りない操作は今後追加・調整していこうと思います。