Toptree 導入編
みんな日本語記事を待っていたはず….!
toptreeがどんな感じで動いているのかを書いてみます
実装はここにあります
https://github.com/niuez/toptree-rust
# 0. toptree is なに
toptreeはlink-cut treeの上位互換です. 木を切ったりつなげたり, パスのクエリを処理したり, 木上の二分探索ができたりします
今回はそのベースとなる構造の話です
# 1. Compress Rake
# 木をまとめる
ここで言う木は, toptreeが表す木のことです. 曖昧にならないようにこのことをreal tree
と呼ぶことにします. (木を木で表現するの文章が曖昧になりがち)
toptreeでは, つながっている2つの辺をまとめる操作を繰り返したものを表現した木です. 1つの辺にまとめ上げることでパスを表現します.
まとめる操作は2つあり, それぞれCompress, Rakeといいます.
# 具体的に
こんな感じでまとめていきます
このまとめていく操作を木で表現するのがtoptreeです.
# Compress Tree
例えば, a - c - b
という一直線のreal tree
を扱う時, 辺ac
とcb
をcompressをします.
これをtoptreeで表現すると
四角は辺を表すノードです. Edge Nodeといいます. toptreeでは, Edge Nodeを葉にします.
丸はcompressした後の辺を表すノードです. Compress Nodeといいます.
Compress Nodeが節, Edge Nodeが葉のこの木をCompress Treeといいます.
重要なのは, Compress Treeの根がパスの端点を結ぶ, Compressされた辺を表しているということです. またそのcompressされた辺の端点は必ず次数が1です.
0 - 1 - 2 - 3 - 4 - 5
というreal tree
をtoptreeで表すと, 一例としては以下のようになります.
# 辺の向き付け
ここで葉のEdge Nodeの順番がパスの辺の順番になっている点に注意してください.
toptreeでは辺の向きに注意して操作しないとダメです.
僕の実装では, 0 - 1 - 2 - 3 - 4 - 5
のtoptreeを0 -> 1 -> 2 -> 3 -> 4 -> 5
と向き付けると解釈しています. 以後のreal tree
の図では向き付けしたものを用います.
compress, rakeを, 同じ向きのものをまとめる操作と解釈することにしましょう. すると, compress, rakeを向き付けたreal tree
について改めて考えると以下のようになります.
上の具体的に
で示したreal tree
はこんな感じで向き付けすると同じようにまとめる操作ができるはずです.
# Rake Tree
では一直線ではないreal tree
, 例えばこれはどうやってtoptreeにするのでしょうか.
ここでrakeを使います.
?????????????????????
辺14
を追加したreal tree
をtoptreeにすると
ひし形のノードはcompressと同じように察せるはずです. 31
と41
をrakeしたものを表現しており, ひし形のノードをRake Nodeといいます.
また, Rake Nodeが節, Compress Treeの根が葉の木をRake Treeといいます.
図では, Compress Nodeに今までの左右の子と, 赤の線でつながった子があります.
赤の線でつながった子は, Compress Nodeの右の子とrakeされるノードです.
このように3分木にしてreal tree
の情報を持ちます.
# 具体例のtoptree
こんな感じになります
四角はEdge Node, 丸はCompress Node, ひし形はRakeNodeです.
Compress Tree(青の点線で囲った部分), Rake Tree(赤の点線で囲った部分)はそれぞれここです
# Splice
重要なのは, Compress Treeの根がパスの端点を結ぶ, Compressされた辺を表しているということです.
この木のtoptreeをもう一度見てみます.
0 - 1 - 2
がこのtoptreeの主役のパスになっています.
でも, 3 - 1 - 2
を主役にしたいときもあるはずです. それは, 31
と01
を入れ替えることで達成できます.
0 - 1 - 3
にを主役にしたいときもあるはずです. それは, 31
と12
の向きを反転させてから入れ替えることで達成できます.
この, Rake Treeの葉のノードと, Compress nodeの子を入れ替えてCompress Nodeの表すパスを変える操作をsplice
といいます.
# Splay
Splay Treeを知っていますか? wikipediaを見て
Splay Treeでは, splay
という木を回転させてノードを根に持ってくるという操作をします. まあwikipediaみて
toptreeで扱っている木は, 葉木です. 葉はsplay
できないことに注意しましょう.
# Handle
splice
をするとパスを変形できることはわかりましたが, 具体的にどのノードをsplice
すると良いのでしょうか?
それを示すのがhandle
という概念です. handle
は各頂点に対して割り振られるもので, toptree上のCompress/Edge Node
が割り振られます.
具体的には, 下のルールで構成します.
- Compress Node
ab
の左右の子がac
,cb
のとき, 頂点c
のhandle
はCompress Nodeab
- Compress/Edge Node
ab
の親がいない(toptreeの根): 頂点a
,b
のhandleはCompress Nodeab
- それ以外(Rake Treeの葉になっている): 頂点
a
のhandle
はCompress Nodeab
具体例を見たほうが早い気がします.
頂点0, 5
はルール2.
, 8, a, b, c
はルール3.
, それ以外は1.
です.
今はとりあえずこういうものとしておくのがいいと思います.(あとで大活躍します.)
また頂点v
に対して, N_v
をv
のhandle
のNodeとします. (上の図で言えばN_2
はtoptreeの根です)
# Expose
expose
という操作を導入したいと思います. (これが超本質)
任意の頂点v
のhandle
をtoptreeの根にするのがexpose
です.
先にどうやってexpose
するか書いてしまいます.
expose(v)
N_v
をCompress Tree上でsplay
するN_v
の親が
- いない:
N_v
はtoptreeの根になったのでexpose
終了 - Compress Node: そのCompress Nodeを
n
とおく - Rake Node: そのRake Nodeを
r
とおく,r
をRake Tree上でsplay
し,r
の親をn
とおく(n
はCompress Nodeになります)
n
をCompress Tree上でsplay
n
の左のノードとN_v
を入れ替えるN_v
がEdge Nodeのとき,N_v
をn
にする- 1に戻る.
# 1. splay(N_v)
N_v
の属しているCompress Tree上でN_v
を根にします.
# 2.
これがちょっとむずかしいです.
親がいない場合は目的達成なので終了です.
Compress Nodeの場合, こんな状態です.
Rake Nodeの場合, 例えばこんな状態です.
splay(r)
をすると, こうなります.
# 3. splay(n)
4で行う操作を簡単にするために行います. なんで簡単になるかはsoft_expose
で解説したいと思います.
# 4. splice(N_v)
入れ替えます.
# 5.
これは何かというと, N_v
がEdge Nodeのとき, splice
するとv
のhandle
の位置が変わります. これに対応するためです.
# 6.
これで, N_v
をtoptreeの根にすることができます.
# Soft Expose
soft_expose
は任意の頂点v
, w
間のパスのCompress Node vw
を作る操作です!(やっとここまできた)
こんな形にtoptreeを変形します.
(8/5 なんか頭悪い画像になっていたので修正しました)
手順を先に言ってしまいます
soft_expose(v, w)
expose(v)
N_v
とN_w
が
- 同じ: toptreeの根は
vw
かwv
なので,wv
であれば反転する.soft_expose
おわり - 違う: 続く
N_v
をguardする(????)- expose(w)
N_v
のguardを外すN_v
から見てN_w
が右側なら, 反転させる.- おわり
toptreeの根をguardするとは, splay
操作があってもtoptreeの根を変えさせないようにすることです.
これは, N_v
をtoptreeの根にした後, N_v
の左側にN_w
を持ってくる必要があり, N_v
が根であり続ける必要があるからです.
guardされているときのsplice
の操作が少し違います.
n
の親がguardされていて, 親から見て左側にある場合, spliceはn
の左の子と交換しないといけません.
しかし, 親から見て右側にある場合, spliceはn
の左の子と交換しないといけません.
これはtoptreeの, 葉がパスの辺の順番になっているルールに違反するからです.(toptree壊れる)
# Path Query
soft_expose
ができるようになると, パスに関するクエリを処理することができます.
パスの長さとか, パス中の辺の長さの最大値とかです.
各ノードに情報をもたせて, セグ木みたいに左の子の情報と右の子の情報を演算するみたいな感じです. これをすると, soft_expose(v, w)
をしてvw
を見た時にパスv-w
についての演算結果が求まるはずです. やったね.
# ひとまず終了…
link
, cut
, select
, 各種クエリとかは後日にします… 疲れた…
Toptree - Link & Cut編 - niuez’s diary link cutかいた
僕が書くのサボった厳密な話をnoshi91さんが書いています こちらも読んでください