/*
B木(B-tree)は完全平衡木である。
大量のレコードを二次記憶装置に蓄えて、その上で探索を行う用途に適している。
B木は多分木(multiway tree)である。
次の3つの制約を持つ。
(1) 内点の子供の数の最大はmである。mは3以上の正の定数とする。
(2) 内点の子供の数の最小はm/2の切り上げた値である(仮にmが3ならば最小は2)。
例外として、根の子供の数の最小は2である。
(3) 根から端点までの深さはどの端点についても同じである。
B木は端点(葉)と内点(葉以外の頂点)をまったく別の種類のものとして扱う。
内点はレコードを持たず、辺と辺の境目を示すキーの値だけを(最大でm-1個)持つ。
端点は二次記憶装置上のレコードの位置を持つ。
(このプログラムでは単に構造体recordのポインタを持たせている)
B木の操作の計算量はいずれもO(log2(n))だが、これにかかる係数は、
内点における配列操作などでO(m)かかるために、AVL木などに比べて大きくなる。
しかし主記憶上での操作に少々時間がかかろうと、二次記憶装置へのアクセス回数を
減らすことができるなら、それは問題ではない。
*/
package main
import (
f "fmt"
)
// 何分木のB木にするか
const (
MaxChild = 6
MinChild = 3
)
// 木に格納するレコード。
type record struct {
key int
value string
}
// 内点が持つ辺。
type edge struct {
ptr *vertex // 内点から子への参照
bound int // 辺と辺の境界値
}
/*
B木の頂点。
内点はB木の制約により必ず複数の子を持つ。
childは常にboundの昇順で整列していなければならない。
境界値child[i].boundに設定する値をbとすると、bは
child[i-1]が持つ子の中で最大のkey < b <= child[i]が持つ子の中で最小のkey
の範囲内で自由に決めて良い。この実装ではchild[i]が持つ子の中で最小のkeyにしている。
*/
type vertex struct {
tag int // 内点か端点か
child []edge // 内点の場合に持つ子への辺
data *record // 端点の場合にもつレコード。
}
// vertex.tagに設定する値
const (
Internal = iota // 内点
External // 端点
)
// 新しい内点を生成し、そのポインタを返す。
func newIntVtx() *vertex {
return &vertex{
tag: Internal,
child: make([]edge, 0),
data: nil,
}
}
// データkey,valueを格納する新しい端点を生成し、そのポインタを返す。
func newExtVtx(key int, value string) *vertex {
return &vertex{
tag: External,
child: nil,
data: &record{key: key, value: value},
}
}
// 内点tのフィールドchildに子cを挿入する。O(M)。
func (t *vertex) insertChild(c *vertex) {
var bound int
if c.tag == External {
bound = c.data.key
} else {
bound = c.child[0].bound
}
p := 0 // 子cを挿入する位置p
for ; p < len(t.child) && t.child[p].bound < bound; p++ {
}
nc := make([]edge, 0, MaxChild)
nc = append(nc, t.child[:p]...)
nc = append(nc, edge{c, bound})
t.child = append(nc, t.child[p:]...)
}
// 内点tの子の中で、キーkeyを持つ端点につながる子の添字を返す。O(log2(M))。
func (t *vertex) locateChild(key int) int {
l := 1
h := len(t.child) - 1
for l <= h {
m := (l + h) / 2
if key < t.child[m].bound {
h = m - 1
} else {
l = m + 1
}
}
return h
}
// 内点tのi番目の子の境界値を更新する。O(1)。
func (t *vertex) updateBound(i int) {
c := t.child[i].ptr
if c.tag == External {
t.child[i].bound = c.data.key
} else {
t.child[i].bound = c.child[0].bound
}
}
// 内点tのi番目の子を削除する。O(M)。
func (t *vertex) removeChild(i int) {
nc := make([]edge, 0, MaxChild)
nc = append(nc, t.child[:i]...)
t.child = append(nc, t.child[i+1:]...)
}
// 木tの探索。キーがkeyに等しいレコードのポインタを返す。
// そのようなレコードが見つからなかった場合はnilを返す。
func search(t *vertex, key int) *record {
if t == nil {
return nil
}
// B木の探索は二分探索木と違って必ず端点まで降りる。
for t.tag == Internal {
i := t.locateChild(key)
t = t.child[i].ptr
}
// 端点が目的のレコードを持っているとは限らない。
if t.data.key != key {
return nil
}
return t.data
}
// 木tにデータkey,valueを挿入した木を返す。
func insert(t *vertex, key int, value string) *vertex {
if t == nil {
// 最小のB木は端点1つから成る。
return newExtVtx(key, value)
}
bro := insertAux(t, key, value)
if bro == nil {
return t
}
// 根tと兄弟になる頂点broが出来たときが、木の高さが増える唯一のケース。
root := newIntVtx()
root.insertChild(t)
root.insertChild(bro)
return root
}
// 関数insertの補助関数。まず探索と同様に端点まで降りて、新しい端点を追加し、
// それに伴う内点の調整を根方向に必要なだけ繰り返す。
// 問題なく挿入が完了した場合、nilを返す。
// 頂点tに子を追加する余裕が無かった場合、新しい頂点を作って返す。
func insertAux(t *vertex, key int, value string) *vertex {
if t.tag == External {
if t.data.key == key {
return nil // 二重登録の場合は何もしない
}
// 親に端点を追加したよと伝える
return newExtVtx(key, value)
}
i := t.locateChild(key)
newborn := insertAux(t.child[i].ptr, key, value)
t.updateBound(i)
if newborn != nil {
t.insertChild(newborn)
if MaxChild < len(t.child) {
// 新しい内点nと子供達を分け合い、親にnが出来たよと伝える
n := newIntVtx()
n.child = t.child[MinChild:]
t.child = t.child[:MinChild]
return n
}
}
return nil
}
// 関数removeAuxが関数呼び出し元に返すメッセージ
const (
Succeeded = iota // 正常終了
Removed // この頂点を削除したので辺を破棄してください
Shrunk // この頂点の子の数が少なくなりました
)
// 木tからキーがkeyに等しいレコードを持つ端点を削除して返す。
func
remove(t
*vertex
, key
int) *vertex
{ if t == nil {
return nil
}
switch removeAux(t, key) {
case Removed:
return nil
case Shrunk:
if len(t.child) < 2 {
// 1個だけになった子を新たな根とする。木の高さが減る唯一のケース。
return t.child[0].ptr
}
}
return t
}
// 関数removeの補助関数。まず探索と同様に端点まで降りて、端点を削除し、
// それに伴う内点の調整を根方向に必要なだけ繰り返す。
func removeAux(t *vertex, key int) int {
if t.tag == External {
if t.data.key == key {
return Removed
}
return Succeeded // keyをもつレコードがなかったけどまあいい
}
ai := t.locateChild(key)
a := t.child[ai].ptr
switch removeAux(a, key) {
case Removed:
t.removeChild(ai)
return Shrunk
case Shrunk:
if len(a.child) < MinChild {
bi := next(ai) // aの兄弟bの添字
needToRemoveB := reallotChild(a, t.child[bi].ptr)
t.updateBound(ai)
if needToRemoveB {
t.removeChild(bi)
return Shrunk
}
t.updateBound(bi)
}
}
return Succeeded
}
// 列の添字iの隣の添字を決める
func next(i int) int {
if i == 0 {
return 1
}
return i - 1
}
// 子供が足りない内点aに、内点bの子供を移して、a,b共に制約を満たすようにする。
// 共に満たせるほど子供がいなかったら、aに全子供を持たせてtrueを返す。
func reallotChild(a, b *vertex) bool {
acl := len(a.child)
bcl := len(b.child)
astb := a.child[0].bound < b.child[0].bound
if acl+bcl < MinChild*2 {
if astb {
a.child = append(a.child, b.child...)
} else {
a.child = append(b.child, a.child...)
}
return true // bはその役目を終えた
}
p := (acl + bcl) / 2
if astb {
p = bcl - p
a.child = append(a.child, b.child[:p]...)
b.child = b.child[p:]
} else {
nc := make([]edge, 0, MaxChild)
nc = append(nc, b.child[p:]...)
a.child = append(nc, a.child...)
b.child = b.child[:p]
}
return false
}
// 木tの各頂点を深さ毎に表示する。内点は {境界値リスト} 端点は [キー] の形で表す。
func printTree(t *vertex) {
if t == nil {
f.Println(t)
return
}
a := traversal(t, 0, [][]*vertex{})
for depth, vs := range a {
f.Printf("depth %v: ", depth)
for _, v := range vs {
if v.tag == Internal {
printIntVtx(v)
} else {
f.Printf("[%v]", v.data.key)
}
}
f.Println()
}
f.Println()
}
// 内点vを表示する。
func printIntVtx(v *vertex) {
f.Print("{")
for i := 0; i < len(v.child); i++ {
f.Printf("%v", v.child[i].bound)
if i < len(v.child)-1 {
f.Print(" ")
}
}
f.Print("} ")
}
// 木tの全頂点を深さ毎にまとめた二次元列を返す。例えばa[1][3]は深さ1の3番目の頂点。
func traversal(t *vertex, depth int, a [][]*vertex) [][]*vertex {
if len(a) == depth {
a = append(a, []*vertex{})
}
a[depth] = append(a[depth], t)
if t.tag == Internal {
for _, c := range t.child {
a = traversal(c.ptr, depth+1, a)
}
}
return a
}
func main() {
var t *vertex
f.Println("/* 最小のB木は端点1つだけから成る */")
t = insert(t, 1, "A")
printTree(t)
f.Println("/* データが2件になると内点1つと端点2つになる */")
t = insert(t, 2, "B")
printTree(t)
f.Println("/* key=1のデータを削除すると端点1つに戻る */")
printTree(t)
f.Println("/* 5件のデータ[1,3,4,5,6]を追加して */")
for _, x := range []int{1, 3, 4, 5, 6} {
t = insert(t, x, string(x+64))
}
printTree(t)
f.Println("/* key=7のデータを追加して内点の分割と木の高さの増加を観察する */")
t = insert(t, 7, "G")
printTree(t)
f.Println("/* 3件のデータ[2,4,6]を削除して変化を観察する */")
for _, x := range []int{2, 4, 6} {
printTree(t)
}
f.Println("/* 昇順ソート済のデータ24件を順に挿入すると */")
t = nil
for i := 1; i <= 24; i++ {
t = insert(t, i, string(i+64))
}
printTree(t)
f.Println("/* 降順ソート済のデータ24件を順に挿入すると */")
t = nil
for i := 24; 1 <= i; i-- {
t = insert(t, i, string(i+64))
}
printTree(t)
f.Println("/* てきとーに探索してみる */")
f.Printf("0を探索: %v \n", search(t, 0))
f.Printf("1を探索: %v \n", search(t, 1))
f.Printf("5を探索: %v \n", search(t, 5))
f.Printf("7を探索: %v \n", search(t, 7))
f.Printf("17を探索: %v \n", search(t, 17))
f.Printf("20を探索: %v \n", search(t, 20))
f.Printf("24を探索: %v \n", search(t, 24))
f.Printf("25を探索: %v \n", search(t, 25))
}