Left-Leaning Red-Black Tree

以下の記事でも書いたが、もう少しだけ補足。

ARC 030: D - グラフではない (Left-Leaning Red-Black Tree) - pekempeyのブログ

join

次の 2 つの木を join するには?

f:id:pekempey:20161024195838p:plain

左の木の方が黒高さが高いので、左の木を右に降りていく。右の木と黒高さが同じ頂点が見つかったら、赤頂点を新たに作って繋ぐ。これは黒高さ条件を壊さない。

f:id:pekempey:20161024195844p:plain

赤頂点を挟んだ結果、赤頂点が隣接してしまうかもしれない。これを再帰的に修正していく。ここから先は insert/erase ベースの LLRB 木と同じ処理になる。

Algorithms with Python / 赤黒木 (red-black tree)

insert/erase のときと違い、葉ノードにデータを持たせる。内部ノードはデータを繋いでいるだけ。RBSTだと内部にデータを持たせることがあるが、こっちはセグメント木的な持ち方をしている、とも言える。

split

split の擬似コードは次の通り。

pair<node *, node *> split(node *x, int k) {
    if (k == 0) return make_pair(nullptr, x);
    if (k == x->sz) return make_pair(x, nullptr);
    x = push(x);
    if (k < x->l->sz) {
        auto p = split(x->l, k);
        return make_pair(p.first, join(p.second, x->r));
    } else {
        auto p = split(x->r, k - x->l->sz);
        return make_pair(join(x->l, p.first), p.second);
    }
}

RBST を書いたことがあれば、大体同じ処理だと分かる。ひとつ違うのが、単なるポインタの張替えでなく join で繋いでいるということ。 赤黒木条件を保持するにはこうしなければならない。

何度も join が呼び出されるので計算量が危険に見える。そこまできちんと証明していないので怪しいが、join はそもそも O(|x.rank - y.rank|) であり、根に上がるにつれて rank は単調増加であるから合計で O(log n) になる、ということだろう。

実装

以下を参考に。

ARC 030: D - グラフではない (Left-Leaning Red-Black Tree) - pekempeyのブログ

おまけ

1000 頂点くらいでビジュアライズしてみた。定義的には右側の方が低くなりそうだし、left-leaning という名前がついてるくらいなので、左に偏るかなと思った。 1000 頂点だと分かりづらいが、まあよく見れば偏ってる。本当に偏るかは知らない。

f:id:pekempey:20161024201840p:plain