Rustでデータ構造書いてみた(B木編):後編

2024-12-11

はじめに

この記事は「Rustでデータ構造書いてみた(B木編):前編」の続きです.まだ読んでいないかたは先にそちらをご参照下さい.

概要

前編

  • B木の操作について説明したよ
      • 検索
      • 挿入

後編

B木の仕組み

削除

B木からの削除は場合分けが多くかなり複雑ですが,1つづつみていきましょう.

B木 TT からキー kk を削除するアルゴリズムは以下のとおりです.(ノード kkTT に存在することを仮定します)


アルゴリズム: B-Tree-Remove(T,k)\text{B-Tree-Remove}(T, k)

  1. xxTT の根へのポインタで初期化する

その後,以下の場合分けにしたがう.

  1. キー kk がノード xx に存在し,xx が葉であるとき,kkxx から削除する
  2. キー kk がノード xx に存在し,xx が内部ノードであるとき,
      1. xx における kk の直前の子 yy のサイズが tt 以上のとき

          yy の部分木の最大値 kk' を削除し,xx において kkkk' に置き換える

      2. xx における kk の直後の子 zz のサイズが tt 以上のとき

          zz の部分木の最小値 kk' を削除し,xx において kkkk' に置き換える

      3. yyzz のサイズがいずれも t1t-1 のとき

          y,k,zy,k,z をこの順で併合し,併合したノードから kk を削除する

  3. キー kk がノード xx に存在しないとき,キー kk を部分木に含むような xx の子 x.cix.c_i を特定する.x.cix.c_i のサイズが t1t-1 のとき,ステップ3-1, 3-2のいずれかを実行し,xx の適切な子に再帰する.
      1. x.cix.c_i のサイズが t1t-1 であり,かつ,x.ci1x.c_{i-1} または x.ci+1x.c_{i+1} のサイズが tt のとき

          xx のキーを x.cix.c_i に移す.その代わりとして,x.ci1x.c_{i-1} の最大値,または x.ci+1x.c_{i+1} の最小値を削除し,元あった場所に挿入する.x.ci1x.c_{i-1} または x.ci+1x.c_{i+1} の適切な子ポインタも x.cix.c_i に移動する.

      2. x.cix.c_i のサイズが t1t-1 であり,かつ,x.ci1x.c_{i-1}x.ci+1x.c_{i+1} のサイズがどちらも t1t-1 のとき

          x.cix.c_i をどちらかの兄弟と併合する


ノードの併合については以下で説明します.

ノードの併合

ノード xx のキー x.keyix.\mathit{key}_i の左の子 x.cix.c_i ,右の子 x.ci+1x.c_{i+1} のサイズがともに t1t-1 であるとき, kkx.ci+1x.c_{i+1} のキーをこの順に x.cix.c_i に移し,x.ci+1x.c_{i+1} を削除します.z.ci+1z.c_{i+1} 子のポインタについても適切に移動します.

例で見ていきましょう.

1. 葉からの削除

2727 を削除する場合を考えます.

  1. 2020 の右の子に進みます
  1. 3030 の左の子に進みます.
  1. 2727 を発見したので削除します.
  1. これで完了です.

2-1. 内部ノードからの削除(左右のいずれかの子のサイズが t 以上)

1515 を削除する場合を考えます.

  1. 2020 の左の子に進みます.
  1. 1515 を発見しますが,そのまま消すことはできません. 1515 の左の子のサイズが t (=3)t ~(=3) 以上なので 1515 の左の子から最大値 1212 を削除します.
  1. 1515 を削除し,その位置を先ほど取ってきた 1212 を入れて完了です.

2-2. 内部ノードからの削除(左右の両方の子のサイズが t-1 以下)

3030 を削除する場合を考えます.

  1. 2020 の右の子に進みます.
  1. 3030 を発見しますが,そのまま消すことはできません. 3030 の左の子と右の子のサイズがともに t1 (=2)t-1~(=2) なので,それらをマージします.
  1. マージされた頂点に進みます.
  1. マージされた頂点から値を削除します.
  1. これで完了です.

3-1,2. サイズが t-1 以下のノードに進む場合

2222 を削除する場合を考えます.

  1. 根ノードの2つの子のサイズがともに t1 (=2)t-1~(=2) なので,これらをマージします.(3-2)
  1. マージが完了しました.
  1. 2525 の左の子に進みますが,このノードのサイズは t1t-1 しかないのでそのままでは進めません.
  1. 2525 の右の子のサイズは t (=3)t~(=3) 以上なので,ここから最小値を削除します.
  1. 削除した最小値を左の子に移します.これで左の子のサイズが tt 以上になりました! 2727 の左の子に移動します.
  1. 2222 を削除します.
  1. これで完了です!

実装

実装はこちらにアップロードしました:https://github.com/kentakom1213/rust-btree

各操作については,

をご覧ください.

また,動かして遊べるページも作ったのでぜひ試してみてください.

https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=ad1ab8c52ba9988eb4654ba9110c04f3

後編のまとめ

ここまで,

  • B木の操作
      • 値の削除
  • B木の実装

を見てきました.この記事の内容は以上となります.

おわりに

ここまで読んでくださりありがとうございました.

今年のクリスマスツリーはB木できまりですね!

メリークリスマス🎅

参考文献

  1. 浅野哲夫.「アルゴリズムイントロダクション」第3版 第2巻,近代科学社,2012年,p106-121
  2. B木,Wikipedia,https://ja.wikipedia.org/wiki/B木,2024年12月3日閲覧

おすすめ記事