このページでは、木構造の用語、完全2分木の公式、先行・中間・後行の走査法、2分探索木と、AVL木・B木などのバランス木がわかります。
- 一言で言うと:
- データを「親子の階層」で持つ木の話。木の名前の付け方、完全2分木の数え方、木のたどり方、探しやすい木(2分探索木)と、その偏りを防ぐ木(バランス木)が出てくる。
木の部品と種類 → きれいな木を数える(完全2分木)→ 木をたどる(走査法)→ 探しやすい木(2分探索木)→ 偏らない木(バランス木)
覚え方の軸
- 木の用語は「家系図」で覚える:
- 一番上が根、子がいないのが葉、根から下への枝の数が深さ
- 完全2分木の公式は「葉=2のH乗、葉以外=葉−1」:
- 深さHなら葉は2ᴴ個、葉以外は2ᴴ−1個
- 走査は「節を見るタイミング」で3つ:
- 先(節→左→右)、中(左→節→右)、後(左→右→節)。2分探索木を中間順でたどると昇順になる
- 2分探索木は「左は小さい、右は大きい」:
- バランスがよければ比較回数はlog₂n、片側に偏るとn
- 偏りを防ぐのがバランス木:
- 2分木ベースのAVL木(高さの差1以下)、多分木ベースのB木(外部記憶向け)
木の構造と種類:木の部品の名前
- 木(tree):
- データの親子関係など、階層的な構造を表すのに向いたデータ構造
- 木の部品
- 節(ノード、節点):
- 図の○の部分
- 枝(branch):
- 親子の節を結ぶ線。枝の上側の節が親、下側の節が子
- 根(root):
- 親がない節
- 葉(leaf):
- 子をもたない節
- 根以外の節がもつ親は1つだけ。葉以外の節がもつ子の数には制限がない
- 節の深さ:
- 根からその節に着くまでの枝の数
- 木の高さ:
- 根から一番深い節までの深さ
- 部分木:
- ある節から下の部分も木になっている(例:根の右部分木)
- 分節数:
- 1つの節から出ている枝の数(=子の数)。分節数が0の節は葉
- 節(ノード、節点):
- 子の数による分類
- 2分木(2進木):
- 各節の子の数が高々2(2以下)の木
- 多分木(n進木):
- 各節の子の数がn(n>2)の木
- 2分木(2進木):
- 順番に意味があるかによる分類
- 順序木:
- 親と子の間や、子どうしの間に何らかの順序が決まっている木。左右の子を入れ替えると別の木になる
- 非順序木:
- 順序がない木。子が左右どちらにあっても同じ(等価な)木
- 例:
- 根Aの下にB(左)とC(右)がある木と、C(左)とB(右)がある木は、BとCに順序関係があれば別の2分木、なければ同じ2分木
- 順序木:
完全2分木:葉と節の数の公式
- 完全2分木:
- 葉以外の節がすべて2つの子をもち、根から葉までの深さ(根から葉までの枝の個数)がすべて等しい木
- 葉の個数:
- 深さごとの節数は 1(2⁰)→ 2(2¹)→ 4(2²)→ 8(2³)と倍々に増える
- 木の深さがHなら、葉の個数=2ᴴ
- 葉以外の節の個数:
- 深さ0〜H−1の節の合計=2⁰+2¹+…+2ᴴ⁻¹
- これは初項1(=2⁰)、公比2の等比数列の和なので
- 式:
- 初項×(1−公比ᴴ)÷(1−公比)= 2⁰×(1−2ᴴ)÷(1−2)= 2ᴴ−1
- 等比数列:
- 隣り合う2項の比が一定の数列。例:
- 2、6、18、54、… は初項2、公比3の等比数列
- 隣り合う2項の比が一定の数列。例:
- ★試験では、完全2分木の葉の個数と葉以外の節の個数の関係が問われる
- 葉の個数=2ᴴ
- 葉以外の節の個数=2ᴴ−1
- 葉の個数がnなら、葉以外の節の個数はn−1
- 例題:
- 深さH=3の完全2分木
- 葉の個数=2³=8
- 葉以外の節の個数=2³−1=7(1+2+4=7で検算OK)
- 節の総数=8+7=15
- 例題2:
- 葉が64個の完全2分木の、葉以外の節の個数は?
- 64−1=63(64=2⁶なので深さは6)
2分木の走査法(巡回法):木をどの順にたどるか
- 走査(巡回):
- 木の各節を1つずつ調べること。系統的な方法に幅優先探索と深さ優先探索がある
- 例の木の形:
- 根A、Aの左の子B、右の子C。Bの左の子D。Cの左の子E、右の子F
- 幅優先探索(幅優先順):
- 根に近い節から順に調べる。同じ深さの節は「左→右」の順
- 例の木:
- A→B→C→D→E→F
- 配列で表すと楽:
- 先頭要素A[1]を根とし、A[i]の左の子をA[2i]、右の子をA[2i+1]とした配列を、先頭から順に調べれば幅優先探索になる
- 深さ優先探索(深さ優先順):
- 根から葉へ向かって、できるだけ分岐せずに枝をたどり、葉に着いたら1つ前の節に戻って、もう一方をたどる
- 節の値を調べるタイミングで3つに分かれる
| 方法 | 別名 | 調べる順 | 例の木の結果 |
|---|---|---|---|
| 先行順 | 行きがけ順 | 節 → 左部分木 → 右部分木 | A→B→D→C→E→F |
| 中間順 | 通りがけ順 | 左部分木 → 節 → 右部分木 | D→B→A→E→C→F |
| 後行順 | 帰りがけ順 | 左部分木 → 右部分木 → 節 | D→B→E→F→C→A |
- 3つの子だけの木(根A、左B、右C)で覚える
- 先行順:
- A→B→C(節Aを調べてから、子を順に)
- 中間順:
- B→A→C(最初の子を調べてから節A、その後に残りの子)
- 後行順:
- B→C→A(子をすべて調べてから節A)
- 先行順:
- 覚え方:
- 「先・中・後」は節(親)を見るのが、先か、真ん中か、後か
2分探索木:左は小さく、右は大きく
- 2分探索木:
- 2分木の各節に大小比較できるデータをもたせた木。どの節nから見ても、左部分木(左子孫節)のデータはすべて節nより小さく、右部分木(右子孫節)のデータはすべて節nより大きい
- 例:
- 根50、左の子30(その子が20と40)、右の子70(その右の子が80)
- 2分探索木を深さ優先探索の中間順でたどると、昇順に整列されたデータが得られる
- 例の木なら 20→30→40→50→70→80
- 2分探索木での探索:
- 根のデータと探すデータを比べ、探すデータが小さければ左部分木へ、大きければ右部分木へ進む。これを、見つかるか、進む節がなくなるまで繰り返す
- 探索の計算量:
- 枝分かれしている節の深さで決まる
- 最良(要素数nの完全2分木のように左右のバランスがよい):
- 最大比較回数は log₂n
- 最悪(片方だけに偏った木):
- 最大比較回数は n
- log₂nになる理由:
- 最大比較回数をSとすると、n個の要素を半分、さらに半分…とS回絞るので、n=2ˢ。よってS=log₂n
- ★試験では、最良と最悪の計算量の式が問われる。どんなときに最悪になるか(片側に偏ったとき)も問われる
- 例題:
- 要素数1,024(=2¹⁰)の2分探索木で、最良なら最大比較回数は log₂1024=10回。片側に偏った最悪の木なら最大1,024回
- 例(データ10を探す)
- バランスのよい木(根40、その下に20と60、さらに10・30・50・70):
- 40 → 20 → 10 と進むので、比較回数=3回
- 偏った木(70→60→50→40→30→20→10と一直線):
- 比較回数=7回
- バランスのよい木(根40、その下に20と60、さらに10・30・50・70):
- 節の挿入:
- 挿入するデータを、探索と同じ方法で根から順にたどり、たどる部分木がなくなったところに挿入する
- 挿入するデータが複数あるときは、挿入の順序によって木の形が変わる
- 例(根50、左30、右70、30の子が20と40の木に、45 → 42 の順に挿入):
- 45は 50→30→40 とたどり、40の右に部分木がないので40の右に入る。次に42は 50→30→40→45 とたどり、45の左に部分木がないので45の左に入る
- 42 → 45 の順に挿入すると、42が40の右に、45が42の右に入るので、別の形になる
- 節の削除(★削除する節の状態で処理が変わる)
- 葉の場合:
- 単純にその葉を削除する
- 左右どちらかの部分木しかもたない場合:
- 削除する節を、その子で置き換える
- 左右両方の部分木をもつ場合:
- 削除する節を、左部分木の最大値の節か、右部分木の最小値の節で置き換える
- 例(根50、左30(子20・40)、右70(左の子60)):
- 葉の40を消す → そのまま消す
- 左の子60だけをもつ70を消す → 60で置き換える
- 両方の子をもつ根50を消す → 左部分木の最大値40か、右部分木の最小値60で置き換える
- 理由:
- 左の最大値か右の最小値なら、置き換えても「左は小さい、右は大きい」のルールが崩れない
- 葉の場合:
バランス木:偏らないように組み直す木
- 2分探索木は、1回の比較で左右どちらを探せばよいかが決まる。だから左右のバランスがよいと速く、悪いと遅い
- バランス木(平衡木):
- 根から葉までの深さがほぼ一定になるように作られた木
- 要素の追加や削除でバランスが悪くなったら、バランスを保つように木を再構成する機能をもつ
- 2分木をベースにしたAVL木と、多分木をベースにしたB木がある
- AVL木:
- どの節でも、左右の部分木の高さの差が1以下の木
- 例:
- 左右部分木の高さの差が2になる節が1つでもあれば、AVL木ではない
- 再構成の方法には1重回転と2重回転がある
- 1重回転の例:
- 根50(左30(子20・40)、右60)に10を追加すると、10は20の左に入り、根50から見て左が深くなりすぎる(左の高さ2、右の高さ0)
- 左部分木の根30を中心に、木全体を右方向に回転させる → 根が30、左20(左の子10)、右50(子40・60)になり、高さの差が1以下に戻る
- B木:
- 外部記憶装置にデータを格納するために考えられた、多分木のデータ構造
- 実現方法はいろいろあり、2分探索木のように各節にデータをもたせる方法もある。ここでは、データは葉だけに入れ、葉以外の節には枝と枝の境目を示すキーの値だけをもたせるB木で説明する(上が索引部、葉がデータ部のイメージ)
- 節のデータ構造:
- p0 k1 p1 k2 p2 … kn pn
- 1つの節にキーをn個まで入れられる(nは偶数)
- p0〜pnには子へのポインタが入る
- k1〜knは、節の中で昇順に並んでいる
- ポインタが指す部分木のキーの範囲
- p0の先:
- すべてk1より小さい
- pi(1≦i<n)の先:
- すべてkiより大きく、ki+1より小さい
- pnの先:
- すべてknより大きい
- p0の先:
- キーの探索:
- 2分探索木とほぼ同じ。根から順に、節のキーと探すキーを比べてポインタをたどる
- 新しいキーの挿入(1つの節にキーが2個までの例):
- 根[50]、左の葉が[20, 35]、右の葉が[70]のB木に42を入れる
- 入れる先の[20, 35]は満杯なので、新しい節を作り、20、35、42を昇順に並べて分割し直す
- 中央のキー35を、新しい節へのポインタと一緒に親の節の適切な位置に入れる → 親は[35, 50]、葉は[20][42][70]になる
- B木の種類
- B*木:
- ①データを葉に入れ、葉以外の節にキーを入れる ②節が満杯のとき、兄弟節が空いていればそれを使い、2つの兄弟節がどちらも満杯のときに分割する
- B+木:
- B*木で、最下位の葉どうしをポインタで結んだもの。B+木を使ったインデックスをB+木インデックスという(データベースのインデックスで使われる)
- B*木:
混同しやすいペア
- 根(親がない)/葉(子がない)
- 節の深さ(根からその節までの枝の数)/木の高さ(根から一番深い節までの深さ)
- 2分木(子が2以下)/多分木(子がn個、n>2)
- 順序木(左右を入れ替えると別の木)/非順序木(入れ替えても同じ木)
- 完全2分木の葉(2ᴴ)/葉以外の節(2ᴴ−1)
- 先行順(節→左→右)/中間順(左→節→右)/後行順(左→右→節)
- 行きがけ順=先行順/通りがけ順=中間順/帰りがけ順=後行順
- 幅優先(根に近い順、キュー)/深さ優先(行けるところまで下る、スタック)
- 2分探索木の最良(log₂n)/最悪(n、片側に偏った木)
- AVL木(2分木ベース、高さの差1以下)/B木(多分木ベース、外部記憶向け)
- B*木(兄弟節の空きを使う)/B+木(葉どうしをポインタでつなぐ)