登場人物紹介
僕:数学が好きな高校生。
テトラちゃん:僕の後輩。 好奇心旺盛で根気強い《元気少女》。言葉が大好き。
双倉図書館の一般講座《リンクとストラクチャー》でアルゴリズムを学んできたテトラちゃんは、 自分の理解のため、学んだことを僕に向けて《講義》している。 僕たちは、線形リスト、双方向リスト、そしてスタックとキューについて議論してきた。
先日までは、学校の図書室の一角で話をしていたけれど、 今日の放課後は階段教室で話をすることになった。
テトラちゃんの新たな《講義》が始まる。
テトラ「先輩、それでは今日は二分探索木のお話をしたいと思いますっ!」
僕「階段教室は広い黒板が使えるからいいね。 図もたくさん書いて議論できそうだ」
テトラ「今回は、お願いしてリサちゃんにも来ていただきました」
リサ「《ちゃん》は不要」
登場人物紹介(追加)
リサ:自在にプログラミングを行う無口な女子。赤い髪の《コンピュータ少女》。
テトラ「リサちゃ……リサには、 あたしが変なことを言ったら注意してもらおうと思って声を掛けたんです。 よろしくお願いします」
僕「なるほど」
リサ「……」
テトラちゃんの言葉にも、リサは特に返事せず、 いつもと同じように真っ赤なノートブック・コンピュータを開いて何かをタイピングしている。
我関せず……と見えるけれど、僕は知ってる。 彼女は聞いていないようで聞いているし、 おもしろいことがあったらちゃんとコメントしてくれるはずだ。
だから、特に話しかけなくてもいいんだろうな、きっと。
僕「それで、今日のお題は《二分探索木》なんですね、テトラ教授!」
テトラ「いつのまにか教授になってますよ……はい。 《二分探索木》……Binary Search Treeというデータ構造です。略してBSTと呼ぶこともあります」
僕「バイナリ・サーチ・ツリー」
テトラ「これまではノードが一次元に並ぶデータ構造だけを考えてきましたが、二分探索木は違います」
僕「そういえばそうだね。スタックも、キューも、ぜんぶ一次元といえば一次元か。 要素を《横》から取り出したりはしないから?」
テトラ「はい。線形リスト、双方向リスト、スタック、キュー……すべてノードが一列に並んでいました。 今日お話する《二分探索木》も、情報を格納するノードが繋がっていますが、 こんなふうになっています。これは10から70までの情報を格納した二分探索木の例です。これをExample Treeと呼ぶことにします」
Example Tree(二分探索木の例)
僕「樹形図みたいに枝分かれしているね」
テトラ「二分探索木は、全体としてはこのような形をしているんですが、 細かく見ますと、ノードがリンクでつながりあっていることがわかります」
僕「そうだね。そこは線形リストや双方向リストと同じ仕組みになってる」
テトラ「二分探索木にもさまざまな実装方法がありますが、ここでは一つのノードが三つのフィールドを持つ実装にしています。key、left、rightというフィールドです」
二分探索木のノード
僕「keyと、leftと、right。このノードにはvalueはないんだね」
テトラ「はい。 ノードが持つ値はkeyというフィールドに収めることにしています。 理由はまた後ほどお話ししますね」
僕「はい、わかりました。お待ちします、先生!」
僕は、テトラちゃんの教え方が上手くなっていることに気付いた。
双倉図書館の一般講座で学んだことをまとめただけじゃなくて、 僕に《講義》する順番をよく考えてきたのかもしれないな。
二分探索木。さっきのExample Treeの例でやりたいことは想像できるけれど、 テトラちゃんの《講義》に耳を傾けよう。
テトラ「一つのノードにはkeyとleftとrightというフィールドがあります」
僕「さっきのExample Treeには $-\infty$ というノードがあったよね。これはリストヘッドみたいなもの?」
テトラ「はい、そうです。これは実装の都合で用意したノードです。 この二分探索木のどのノードが持つキーよりも小さな値であればかまわないのですが、便宜上、 $-\infty$ というキーとしています。そして、 $-\infty$ のノードのrightフィールドが指しているノードを、二分探索木の根(ね)と呼びます」
僕「ね……というのは根っこの根?」
テトラ「ですです。二分探索木を木に見立てたときの根です。Example Treeでは《ノード40》が根になります」
Example Tree(二分探索木の例、再掲)
僕「それから、leftとrightに保持されているリンクは、メモリ上のアドレスだよね?」
テトラ「はいはい、そう考えてくださってかまいません。 要するに左や右のノードに《たどり着く》ための情報ならばいいので、 アドレスでなければいけないというものではありませんけれど」
僕「うん、わかったよ。リンクをたどる話は、これまでもたくさんしてきたからね。これまではnextやprevでたどったけれど、 こんどはleftやrightでたどる。大丈夫、イメージできるよ」
テトラ「リンクの中には《どこも指していない》ことを表すnilというリンクもあります。図では斜線で表すこともあります。 これもすでにお話ししましたよね。 ですから、二分探索木のノードは、子の存在に関して四通りのパターンがあることになります」
二分探索木のノード
僕「そうだね。ここまでの話は、線形リストや双方向リストとだいたい同じだ」
テトラ「はい。ここからが《二分探索木》ならではのお話になります」
テトラ「二分探索木には満たすべき条件があります。二分探索木条件という条件です」
二分探索木条件
二分探索木の任意のノードをmとします。
僕「……なるほど! これはおもしろいね。テトラちゃんが言う《二分探索木条件》というのは、 Example Treeでいえば、具体的にこういうことだよね。キーの値が40になっているノードより左側にあるどのノードを見ても、キーの値は40よりも小さくなっている。 そして、右側にあるどのノードを見ても、キーの値は40よりも大きくなる」
Example Treeで二分探索木条件を確かめる
テトラ「そうです。そうです。先輩はやっぱりすぐに例で確かめるんですね」
僕「《例示は理解の試金石》だからね。 しかもテトラちゃんが最初に例を出してくれたから、それを使いたくなる」
テトラ「先輩はExample Treeの一つのノードについて二分探索木条件を確かめましたが、 この条件は二分探索木のどのノードについても成り立たなければいけません」
僕「ああ、なるほど。こういうことだね。ノード20の左右についても、ノード60の左右についてもそれぞれ二分探索木条件が成り立つ」
Example Treeで二分探索木条件を確かめる(続き)
テトラ「ではここでクイズをお出しします。 たとえば、これは二分探索木ではありません。なぜでしょうっ!……って簡単ですね」
クイズ
これは二分探索木ではありません。なぜでしょうか。
僕「……これはもちろん《二分探索木条件》を満たさないからだよね。ここと、ここ」
クイズの答え
テトラ「はい、正解です!」
僕「《キー32のノード》の《右の子》に、 《キー28のノード》があるけど、これはおかしい。 《二分探索木条件》によれば、 右部分木にあるどのノードも、32より大きなキーじゃなくてはいけないはずだから」
テトラ「ですね」
僕「それから、《キー61のノード》の《右の子》の《左の子》に、 《キー59のノード》があるけれど、これもおかしい。 なぜかというと、これじゃあ61の右部分木の中に61よりも小さなノードがあることになってしまう。 これも《二分探索木条件》に反する」
テトラ「はい……あたし、これと同じ問題が出たときに28がおかしいことはわかったんですが、 59がおかしいことには気付きませんでした」
僕「へえ……」
テトラ「それはですね、61と63を比べたら正しく63の方が大きくて、 63と59とを比べたら正しく59の方が小さかったからです」
僕「なるほどね。大小関係を局所的に見ちゃったんだね」
テトラ「局所的……」
僕「ノード一つ分、親子関係一個分だけみて判断しちゃったという意味だよ」
テトラ「そうです、そうです」
僕「じゃあ、これで二分探索木のプログラムを書くのかな?」
テトラ「そうですね。List 40が、二分探索木のノードを作る手続きNEW-NODEです。 本来なら、双方向リストと違う名前にした方がいいんですが、NEW-BINARY-SEARCH-TREE-NODEだと長すぎますのでNEW-NODEとします」
List 40: NEW-NODE
僕「List 40は特に不思議なことはないね。ノードを確保して、 left, right, keyというフィールドに初期値を入れる」
テトラ「そして、List 41が、空っぽの二分探索木を作る手続きNEW-TREEです。 リストヘッドに相当するノードを確保します。キーの値は便宜的に $-\infty$ とします」
List 41: NEW-TREE
僕「いいよ」
テトラ「では、ここでクイズです。NEW-TREEとNEW-NODEを使って、 Example Treeを組み立てる手続きNEW-EXAMPLE-TREEを作ってください!」
僕「おお、なるほど! これは、10から70までのキーを持つノードをNEW-NODEで作って、 フィールドを適切にリンクすればいいというクイズだよね?」
テトラ「そうです、そうです。そういうことです。 NEW-EXAMPLE-TREEの概略はList 42のようになります。 この3行目を具体的に書くことになります」
List 42: NEW-EXAMPLE-TREEの概略
僕「この3行目って、たった一行になっているけど、ものすごい重みがあるなあ! ここを詳細化したら何行になるんだろう!」
テトラ「いかがでしょう」
僕「うん、地道につないでいけばできるね。List 43で完成かな」
List 43: NEW-EXAMPLE-TREE(僕の解答)
テトラ「ああ! なるほどです」
僕「えっ! 何かもっとすごい方法があるの?」
テトラ「いえいえ、違います。そうですよね。一つ一つ作ってから組み上げればいいんですよね……あたしは、 List 44のようにすごいことになってしまいました」
List 44: NEW-EXAMPLE-TREE(テトラちゃんの解答)
僕は、一瞬ぎょっとしたけれど、List 44を一行一行じっくり読んでみた。なるほどなあ……。
僕「僕は下から上に作っていったけど、テトラちゃんは上から下に作っていったんだね。いろんな方法があるんだ」
テトラ「できたものは同じくExample Treeになりますね」
僕「次はどういう話になるの?」
テトラ「はい、ではいよいよ二分探索木を使った探索のお話をします。Example Treeを使って説明しますね」
僕「うん」
テトラ「Example Treeには10から70までのキーがありますが、 この二分探索木の中に50というキーがあるかどうかを探索したいとします」
僕「……いいよ」
テトラ「この二分探索木の根となっている《ノード40》に注目してスタートして、次のように考えます。ステップ1です」
50というキーがあるかどうかを探索(ステップ1)
いま注目しているノードが持つキーは40です。
でも、いま探しているキーは50です。
40よりも50は大きいので、 探しているキーを持つノードがもしあるとするならば、右部分木にあるはずです!
僕「うんうん、それを繰り返すんだね!」
テトラ「はいはい、そうです。《ノード40》のrightフィールドをたどった先にあるノードに注目します。 これは右部分木の根にあたります。 ステップ2です!」
無料で「試し読み」できるのはここまでです。 この続きをお読みになるには「読み放題プラン」へのご参加が必要です。
ひと月500円で「読み放題プラン」へご参加いただきますと、 440本すべての記事が読み放題になりますので、 ぜひ、ご参加ください。
参加済みの方/すぐに参加したい方はこちら
結城浩のメンバーシップで参加 結城浩のpixivFANBOXで参加(2021年6月11日)