はじめての C

二分木 続き

二分木 2回目の tutorial では、前に 書いたことのある、木を 変質させる- degenerate - 2つの 最悪のケースを 回避するための、さまざまな 手法を 詳しく 調べることにしましょう。

先に 学んだ アルゴリズムでは、入力時に それが 十分 ランダムに なってる場合に、良好な 検索木 - good search tree - が 実現されます。

復習を かねることにして、すでに 知っている 挿入の アルゴリズムだと、こうなります ▽

int norm_insert(struct Tree **tree, int key)
{
int rc;

if (*tree == NULL) {
*tree = new_node(key);
if (*tree == NULL)
rc = 0;
else
rc = 1;
}
else if (key < (*tree)->data)
rc = norm_insert(&(*tree)->left, key);
else if (key > (*tree)->data)
rc = norm_insert(&(*tree)->right, key);
else
rc = 0;

return rc;
}

いくつか 異なった 変更が 加えられてるのに 注意してください。

再帰ダブルポインタが 使われてるので、コードは より複雑に 見えますが、それは 錯覚に すぎません。

一回、構造体への ダブルポインタの 間接参照と、メンバへのアクセス という 考え - concept - が 使えるようになれば、このコードが 簡単に たどれます。

このスタイルを 選んだのは、それが より簡潔な 傾向を もっていて、その短さ - brevity - が、この tutorial で 後で 学ぶことになる コードでは 役にたつからです。

もう一つ 違ってるのは、(関数で) いろいろと 返すかわりに、1つの 返り値を 使っていることです。

このアルゴリズムで ほとんどの用途では 十分です。 しかし、入力データが ソート順か または 交互順の どちらかで 入るとすると、次のような 最悪のケースのうちの 一つが 生じることになります。

最悪のケース 1) データの 到着が ソート順だとすると、右 (あるいは 左) の 分肢が 生成し、結果として 全く 役たたずの リンクリストとして 構築されてしまいます ▽

0
.1
..2
...3
....4
.....5
......6

最悪のケース 2) データの 到着が 交互順 - altemating order - なら、木は ジグザグに 生成し、それだと 前の 場合と 同じくらい 悪いものに なります。

問題は、常に 1方向のみの 選択と いうことに あって、それが 二分木の 働きを 無効に してしまうのです ▽

0
.6
1
.5
2
.4
3

これらの ケースと 比較するため、最良のケースを 示してみましょう ▽

...3
.1...5
0.2.4.6

変質した木は 最悪ですので、普通に 戻すため この問題を 解決する 必要が あります。

必要なのは、木を 最悪のケースから 最良のケースに 近づけるように しておける アルゴリズムです。 方法として、最悪の シナリオを 心配せずに、二分木の すべての 役にたつ 性質を 引き出せるようにします。

考え方としては 十分 シンプルに 思えるのですが、実行上は とても 難しくなります。

この tutorial では、検索時間を 最適なものに 近づけるよう 保証された 方法を もつ、5つの 例題を 追っていきます。

手順に したがって root ノードからの 木全体の つりあいを とり、random number generator を 通して ランダムな順での 挿入を ひきうけ、すべてのノードが 1つの root に むかって 集中するよう もってくることで それ自身で 編成される - self-organize - ようにし、複合 AVL アルゴリズムによる 挿入での 再均衡 - rebalance - を 行って、最終的には、ひどく 複雑な ものとならない、つりあいのとれた 二分木を もった、確率的な (見込みのある) - probabilistic - データ構造体として 再現していきます。