特集・プログラミング入門 図 11 子と弟の表現をニ分木としてみる さきはどの定義を使うと、プログラムは次のようになり ます。 tree search(tree t , etype key) 1 2 3 5 int ; if (t = = NULL) return NULL ; if ((i t—>elem ー key) return t ; if (i > 0 ) return search(t—>left , key) ; else return search(t—>right , key) ; 二分木のデータ構造は再帰的データ橢匿なので、この関 数も再帰関数として実現しています。より効率のよい探索 関数を作ることも可能ですが、これについてはもうすこし あとて紹介します。 二分探索では酉冽カイ吏えるため、探索の対象となるレコ ード以外のよけいな領域は必要ありませんでした。ところ が、こ : 分探索木を利用する場合は明カ勺に冓造を作らな ければなりません。したがって、リンク尉寺するための 領域が必要になり、実際にリンクにするデータも代入して おかなければなりません。このように書くと、二分探索木 を利用するガ去は二分探索とくらべてまったく利点がなさ そうに思えますが、そんなことはありません。二分探索で 大きな間題になるデータの j 助日は、二分探索木を利用する 舌を探索に戻しましよう。ここまでみてきたように、図 場合にはあまり間題にならないのです。 3 で示した探索が進められる様子は二分木になっていま す。この木には 1 つ特徴があります。この木を通りがけ順 二分探索でデータの追加が大変なのは、追加後のデータ で走査すると、データか」しているのです。見方を変え を整列しなおす必要があるからです。一方、二分探索木に ると、あるノードに注目したとき、その左部分木に含まれ データを追加する場合は、冓造の適当な位置をみつけ、 るノードは親のノードのもっデータよりも小さな値をもっ そこにデータを自由に追加することかできます。この挿入 ていますし、右部分木に含まれるノードは親のノードのも する際のコストの低さが、二分探索本を使うガ去の大きな つデータよりも大きな値をもっています。 利点です。 二分探索では、あらかしめ整列した、、配列 " を準備しま こで、 1 ~ 7 の整数をノードとする二分探索木を考え したが、この二分木を直接準備していたらどうでしよう てみましよう。おそらく、図 12 のような冓造がい浮 か。探索する値と本比 ) ノードの値とを上交し、一致してい かぶのではないでしようか。 れは探索は終了です。一致しない場合は、探索する値と根 たしかに、この牙冓造は二分探索木の条件を満たしてい の値とを上交し、探索する値のほうが小さければ左部分木 ます。どのノードをとっても、その左部分木にあるノード の処理に、探索する値のはうが大きけれは省部分木の処理 より大きな値であり、右部分木にあるノードよりも小さな に秘します。それぞれの部分木の処理は、根のノードの 値になっています。ところが、これらのデータから構築で 処理と同しです。 きる二分探索木はこれ 1 つだけではありません。たとえ 7 = 0 ) 8 クをもっているため、実際に作られる橢匿は二分木とは異 なります。しかし、図 10 から親へのリンクを取り除いて ノードの位置をーれば、できあがる構造は二分木とみ なすこともできます ( 図 11 ) 。 この二分木を行きがげ頂て処理すると、 1 、 2 、 3 、 4 、 5 、 6 、 7 、 8 となるのが分かるでしよう。これは、もともとの 木での行きがげ頂でもあります。また、新たに作られた一 分木を通りがげ頂て処理すると、 3 、 4 、 2 、 6 、 7 、 8 、 5 、 1 となります。これは、もともとの木では帰りがげ頂になり ます。二分木の行きがけ順や通りがけ順での処理方法はす でにみましたが、それと同様なガ去で処理するだけで、木 の行きがけ順や帰りがけ順での処理が可能になります。 ニ分探索木 一三ロ 110 UNIX MAGAZINE 2003.2
特集・プログラミング入門 図 27 削除にともなう一重回車云 1 3 2 図 28 削除にともなうニ重回車云 1 1 1 2 3 2 4 3 2 AV し木を扱うコード typedef enum { てみました。 す。今回は、次のような列挙 (enum) 型を定義して使っ 左右の部分木の高さの差を表す値をイ尉寺するだけで一ト分で 持する必要があるように思えるかもしれませんが、実際は なけれはならないので、すべての部分木について高さを保 ます、データ構造です。左右の部分木の高さを上交し ました。 す。そこで、今回は工ッセンスだけをとりあげることにし せんが、さきはどの二分探索木のプログラムの約 3 倍で テスト用のコードも入っているので正確な上師交にはなりま たためか、プログラムが巨大になってしまったからです。 ます。というのも、アルゴリズムを忠実に実現しようとし ・・と書きたいところですが、部分的な紹介に留めておき それでは、 AVL 木を扱うコードをみていきましよう、 tooleft left even = 0 , r i ght tooright } balanced ; 118 2 3 ノードを表すデータ構造では、この値尉寺するため 4 UNIX MAGAZINE 2003.2 q—>balanced— q—>left = p; if (i > 0 ) { たに追加するノードです。 次はそのコードの部分ですが、 q か親のノード、 p カ噺 anced の値を各します。 加したあと、追加した位置にもとづいて親のノードの b 心 の場合と同様です。問題は挿入後の卩髜各です。ノードを追 挿入の操作については、ノードの挿入自体は二う対架索木 く同じコードが使えます。 されてはいますが、基本的には二 . 分探索木なので、まった す。 AVL 木は、罸造が適度にバランスするように工夫 探索の操作自体は、二分探索木を用いた探索と同一で ら冓造を操作するようにしました。 す。そこで、親を孑一リンクを設けて、これも管理しなが 親を効率的に参照できたほうカワ。ログラムが高速になりま AVL 木では親の様子を調べる機会がたくさんあるので、 もう 1 つ加えたのは、親を表すリンクの parent です。 まった場合は、整か必要なことを表します。 right か言午容示巨囲内で、 tooleft や tooright になってし のフィールド balanced を追加しています。 left 、 even 、
特集・プログラミング入門 図 24 1 図 25 1 一重回車云の好 2 図 26 1 ニ重云の好 2 日 3 3 ニ重回車訒要な告 3 2L 2R すれも同し高さのものです。 b の下にある部分木の高さが 揃っています。このどちらかが 1 つ高く、そちらに新た なノードカ功日えられた場合、高さが 2 変わることになりま す。このとき、図の a に b が対応し、高いほうの部分木 の根が b に対応するとみると、この図と同し形式になるは すです。 一重回転をおこなうのは、この図の左側のケースです。 この場合、図 24 のようなかたちにノードを回転します。 この回転は簡単です。また、回転後の本の高さはもと ここには描かれていない もとの料冓造と変わらないため、 殞則のヨ寸冓 j を、、景か被及することもありません。 難しいのは、右側のようなかたちになってしまったと きです。この場合は、二重回転をしなければなりません。 さきはど、二重回転には 3 つのノードか闕与すると書きま したが、この図では a と b の 2 つのノードしかありませ ん。ノードをもう 1 つ追加した絵を描いてみましよう ( 図 25 ) 。 この図では、新たに加えたノードを 2L の下に付けてい ますが、これが 2R の下にあってもアルゴリズムとしては 基本的に変わりません。重要なのは、高さの等しい 2L と 2R のどちらかにノードが 1 つ付くため、それらが 1 や 3 の部分木と同し高さになる点です。ただし、 d 自身が」砌日 されたノードという場合もあります。このようなときは、 UNIX MAGAZINE 2003.2 左右の部分木は高さ 0 て等しいことになるので注意が必要 です。また、 1 や 3 の部分木は空になりますから、 d の左 右の部分木はどちらも 1 や 3 と同し高さになることにな ります。 さて、これを二重回転すると図 26 のようになります。 この場合も、回転した結果、本の部分木の高さはノー ドの j 助日前と変わらないので、さらに外部について景を 調べる必要はなくなります。 削除についても考え方はです。ます、二分探索木の 場合とに削除を実行します。たとえば、左右両方の子 をもつノード x を削除するときは、代わりになるノード Y をみつけ、これとノード X とを交換し、簡単に削除で きる位置に移動させたノード X を実際に削除するのです。 もちろん、木のバランスか削巣作によって崩れるので、 この場合にも調整が必要になります。これにも一重回転と 二重回転の 2 不頁があります。 一重回転は、挿入のときの一重回転の反対のような操作 になります ( 図 27 ) 。 この図は、薄く描いた c のノードを削除した様子を表し ています。この変換では、処理前と処理後とで部分木全体 の高さか変わってしまうため、この変更がほかに景界些を与 えるかどうかをヾる必要があります。なお、 2 の部分木 はもう 1 つ高いものでも同し処理をおこないます。その場 合は、変更後の部分木の高さは変わらないので、はカの 景斧些を調べる必要はありません。 削除にともなう二重回転も、やはり挿入における二重回 転の反対のような動きになります ( 図 28 ) 。 2 や 3 の部分木のいすれか片方は、図よりも 1 つ短い 可能性があります。しかし、いすれにせよ部分木全体は 1 つ短くなるため、この場合もやはりはかの部分での景グを 調べる必喫があります。 117
特集・プログラミング入門 両方の子をもつ / ードの削除 図 18 図 16 子をもたないノードの削除 図 17 片方・をもつノードの削除 ノード " を満たしています。もちろん、右部分木の一番左 のノードについてもまったく同様です。 代わりに根とすべきノードをみつけたら、そのノードの 子を親に耐妾つないだあと、みつけたノードを根に移動し ます ( 図 18 ) 。 置すれば、二分探索木の生質尉寺できます。したがって、 それでは、実際に削除を実行する関数をみてみましょ 親のノードのリンクに、削除したいノードがもっ子を代入 う。作成した関数の前半では、目的の値をもつノードを検 すれはいいでしよう ( 図 17 ) 。 索しています。この部分は、挿入をおこなう関数とたいへ この図では、左の子だけをもつノードを削除しています んよく似ています。 が、右の子だけをもつノードについてもまったく同様に削 除できます。 delete(tree t , int key) 削除したいノードが両方の子をもっている場合は厄介で す。このノードを削除すると 2 つの子に対するリンクか鞘 えてしまうため、親からのリンクだけで両方を一尉寺するこ とはできません。このようなケースでは、削除するノード を根とする部分木のなカ功、ら、削除するノードの代わりに 根となることができ、かっ削除か容易な位置にあるノード をみつけ、そのノードと根のノードを交換してから目的の ノードを削除する処理が必要になります。 こで間題になるのは、新たに根となるノードのみつけ 方です。これは、左部分木のなかでもっとも右に位置する re turn NULL ; ノードか、右部分木のなかでもっとも左に位置するノード ノードをみつけるだけなら探索の関数と同様にしてもよ をみつければいいでしよう。左部分木にあるすべてのノー さそうですが、あとでノードを削除することを考えると、 ドの値は、根のノードの値よりも小さいはすです。そのな そのノードを指しているリンクも記慮しておきたくなりま かで一番右にあるノードは最大の値をもっているはすです す。だからこそ、探索よりも挿入の関数によく似たものと から、左部分木のはかのどのノードよりも大きな値をもつ なっているわけです。 はすです。さらに、このノードのもつ値は右部分木のどの この関数は、指定したノードを削除するためのものなの ノードのもつ値よりも小さいはすですから、ちょうど、根 で、削除すべきデータがみつからなかったらそれで終了し のノードとしての条件を満たすことになります。さらに、 左部分木の一番右の子ということは、それよりも右側に子 ます。その場合には NULL を返し、削除していないこと がいないことを意味します。つまり、すくなくとも右の子 を示します。 は存しないことが分かっているわけです。すでにみてき 削除すべきデータをみつけたときは、 goto 文を使って たように、これはもう 1 つの条件である、、削除が容易な ラベル found までジャンプします。ラベル found の部分 Q) Q) V V ・ 1 while (p ! = NULL) { if ( (i = p—>val ー key) goto found ; else if (i > 0 ) else = 0 ) 114 UNIX MAGAZINE 2003.2
特集・プログラミング入門 } else { q->right = p; q—>balanced + + ; i は追加するノードの位置を調べる際に利用している変 数で、正なら左部分木に、負なら右部分木に追加します。 ノードを追加したあとは、 j 助日した部分木の高さが 1 増え るため、親のノードの balanced の値を訓整しています。 この訓整では、列挙型の定数ではなく、整直であるか のように扱います。左部分木に追加したときはデクリメン トし、以前に even であったなら left に、以前に right であったなら even に変更されることになります。 バランスの訓整は、いま balanced の値を修正した親の ノードから順に、根のノードに向かっておこないます。 for (p = q; p—>parent & & p->balanced ! = even;=> p = p¯>parent) { さきほど、図を示しながら説明したときに外部への景グ の有無について触れましたが、外部に対して景グがなけれ ばこの検査を終了します。 親が left か right の場合、いまおこなった追加によっ て部分本か高くなったことが分かります。そのため、この 変史をさらにその親 ( 祖父 ) に伝播するために、値の調整 をおこないます。 if (p—>balanced left Ⅱ right) { p—>balanced if (p->parent—>right p¯>parent—>balanced + + ; else p¯>parent—>balanced— この場合、親は大丈夫でも、その祖先のなかにはバラン スが崩れてしまうノードがあるかもしれません。そこで、 ループに戻ってチェックを続けます。 親が tooleft の場合には、回転処理か必要です。このと きの左部分木を q とすると、 q が le 仕状態であれは一重 回転で対応できます。 } else if (p—>balanced q = p¯>left ; if (q—>balanced = left) { / * 一重回転で OK * / この処理をおこなう部分は省略しますが、一重回転を実 現するコードです。左部分木が right であれは、二重回転 tooleft) { UNIX MAGAZINE 2003.2 が必要になります。 } else if (q—>balanced = right) { / * ニ重回転が必要 * / もちろん、 こにも二重回転をおこなうコードがありま すが、これも省略します。 これら以外、つまり左部分木が even ということはあり えません。図 23 において、左側であれは・ 2 の部分木が、 右側であれば 1 の部分木が 1 つ高い場合になります。と ころが、このような状態だとすると、ノード c を追加する 前にノード a において左右の部分木の高さが 2 以上異な るので、調整されていることになります。よって、 even にはなりえないわけです。そのため、もしこのような状態 になったら、その旨を示す工ラーメッセージを出力するよ うにします。 } else { fprintf (stderr, break; "This can' t happen\n" ) ; また、これらの処理により、部分木全体の高さがノー ドを追加する前と同しになるため、さらに祖先を調べる必 要はなくなります。そこで、 break を用いて祖先をたど る for ループを抜けています。このあと、はは 1 司じ内容で right と left を入れ替えたコードか読き、 for ループを終 了します。 一方の削ド作のほうも処理は同様です。ます、二分探 索木における削除をおこない、削除したノードの親からバ ランスを調整します。挿入の場合には、次のようなコード を用いて自分カ襯の右の子なのか左の子なのかを判断する ことができました。 p¯>parent—>right ところが、ノードを削除してしまうと、削除したノー ドがどこに付いていたのかカ喇断できなくなります。そこ で、どちらの子を削除したのかが分かるように新たな変数 を設けました。さらに、削除したときに注目している部分 木の長さが処理によって短くなったかどうかを示す変数 (shorten) も設けています。これは、挿入の場合とは異な り、削作ではイイ系で実行した削除の景グが、回転処理 をおこなっても祖先まて伝播することがあるためです。 バランスをとるためのコードでは、やはり親ノードと子 ノードの balanced の値にもとづいて場合分けをおこない ます。それぞれの場合に一重回転や二重回転を実行し、さ 119
特集・プログラミング入門 図 20 1 を挿入する前の豈 5 3 7 6 2 ただし、 4 図 21 1 図 23 図 22 0 1 1 の挿入後の告 5 0 を挿入し、回転したあとの告 6 7 5 3 2 1 4 このガ去ではさまざまなキーのデータの」助日・削 除を繰り返していると、死んでいるノードか彳余々に増え、 探索の手間もよけいにかかります。この点には注意が必要 です。 AVL 木 二分探索木を使うと、平均的には効率よく探索がおこ なえますが、都合の悪い入力が与えられたときはれに比 例する手間がかかってしまいます。最悪の場合もこれほ ど手間がかからないように、 : 冓造をある程度バランスさ せるようなイ督はみも考えられています。これを、、平衡化 " と呼び、このような仕組みを採り入れた冓造を、、平衡木 (balanced tree)" と呼びます。二分探索木に対して、、回 転 " という操作を定義し、ある程度バランスさせるように した平衡木が AVL 木です。 この名称は、考案者である G. M. AdeI'son-VeI'skii と E. M. Landis の名前にちなんだものです。 AVL 木は、 2 つの兄弟を根とする部分木の高さがたかだか 1 しか異なっ ていないという性質をもっています。 : 冓造をバランスさせるのであれば、つねに木を一に しておくのがもっとも効率的です。しかし、そのための手 間は無視できるはど小さくはありません。図 20 を見てく ださい。 この図のイ冓造に値が 1 のノードを追加したとき、木 を一杯にするには、図 12 のような冓造に変更する必要 があります。このとき、 2 ~ 7 の既存のすべてのノードで は、親となるノードか変更されています。つまり、この処 理はどうがんばってもれに上 tf 列する手間がかかってしま うのです。 AVL 木では、兄弟を根とする部分木の高さが 1 違っていてもよいので、値が 1 であるノードを追加し て、図 21 のように少々いびつな構造になっても許容され ます。 この料冓造に対し、さらに 0 を値とするノードを追加 116 3 2 4 7 6 ノードの挿入によってパランスが覇れる本好 3 2 1 3 2 すると、 2 のノードの左右の部分木において左側は高さが 2 、右側は高さが 0 となり、高さの差が 2 になります。そ こで、回転操作をおこないます。こ巣作は、根のノード の左の子を新しい根とし、もとの根のノードをその右の子 とします。さらに、新たに根となったノードのもとの右の 子は、もとの根のノードの左の子とします。この操作によ り、冓造は図 22 のようになります。 回転には、 2 つのノードカ与する、、一重回転 " と 3 つ のノードが関与する、、二重回転 " があります。上記の、 0 を挿入したときにおこなわれた回転は一重回転です。一重 回転がおこなわれる様子をみてみましよう。 AVL 木として羽厮している : イ冓造にノードが追加され てバランスカ繃れる場合には、図 23 の 2 通りがあります ( もちろん、これの左右対称の状態になることもあります が、それは反転して考えてください ) 。 この図のなかで三角形て表している 3 つの部分木は、い UNIX MAGAZINE 2003.2
図 12 図 15 探索の手間かれに上錮」するニ分探索木 1 図 13 1 図 14 1 特集・プログラミング入門 1 ~ 7 のニ分探索木 4 2 3 6 7 6 5 5 7 もう 1 つのニ分探索木 3 2 6 4 5 7 造らしくないニ分探索木 7 4 5 3 6 2 UNIX MAGAZINE 2003.2 を見ると、探索の手間がれに比例する理由もすぐに分かる かりやすいのは、図 15 のような冓造でしよう。この図 れに上 tf 列する手間がかかってしまいます。図 14 よりも分 きはどの図 14 に示したようなオ冓造では、探索するのに まったく意味をなさない場合があるのです。たとえは、さ 悲参な結果になります。一所懸命に冓造を構築しても、 一方、最悪のケースでは、二分探索木を用いた探索では 手間は二分探索と同様に 1 。 g れに上第」します。 分探索における探索経路とまったく同しですから、探索の このとき、二分探索木を用いた探索で採られる糸各は、 いのは、図 12 のような完全二分木になっている場合です。 カ躱索の手間に大きく関係してきます。もっとも効率がよ ここ分探索木を用いた探索の場合には、二 . 分探索木の構造 ニ分探索木を用いた探索の手間 と冓造とはかけ離れたものになってしまいます。 この程度なら、また冓造といえますが、図 14 になる ば、図 13 のようなものも考えられます。 4 3 2 1 111 ばなりません。そこで、リンクされたリストのときと同様 ますが、今回は根のノードの処理だけ特別扱いにしなけれ 根のノードは木を指すポインタで直孑旨し示すこともでき タをもたない葉のノードは、 NULL て表すことにします。 と、根のノードの扱いを決めておかねばなりません。デー いことがあります。木の表現ガ去です。葉のノードの扱い もう 1 つ、プログラミングを始める前に決めておきた も、コードの基本部分はそれほど変わりません。 法で実現してみましよう。親へのポインタを利用する場合 と削幇です。この場合は、親へのポインタを使わない方 こで、親へのポインタが必喫になるのはデータの挿入時 ら、二分木のデータ構造をそのまま使うことができます。 探索木はノードに値をイ尉寺することができる二分木ですか 最初に、木の表現を決定しなければなりません。二分 ましよう。 それでは、ここ分探索木を扱うコードをみていくことにし ニ分探索木を扱うコ - ド れでも、一杯に近いバランスのとれた木になるのです。 ーーなな二分木になることはますないと思いますが、そ 与えられたとき、できあがるヨ冓造か完全二分木になった 手間にかかる定数の部分が異なります。入力がランダムに ます。ただし、最良の場合とまったく同じわけではなく、 きは、最良の場合といに log れに出列する手間がかかり 雑になるので省きますが、ランダムな入力が与えられたと かる手間はどのくらいになるのでしようか。計算はやや複 最良と最悪の場合の手間は分かりましたが、爿」的にか 悪の場合にはれに上 tf 列する手間がかかってしまうのです。 のよいときは 1 。 g れに上ける手間でおこなえますが、最 このように、二分探索を用いオ酩は、もっとも効率 のではないでしようか。
写真 1 PDA と携帯舌合 Handspring Treo 270 Communicator ( 左 ) と T Mobile Pocket PC Phone Edition ( 右 ) 連載 UNIX Communication Notes—@ になる PC と情報を同期させる機能 ) はよくできている ので、たとえばふだんは PC で管理している住戸形求など も、手軽に期させることができる。また、ポイスメール や FAX なども、自宅やオフィスの PC で受け取ったも のを PDA にコピーして再生するといった使い方も十分に 考えられる。こうなってくると、真の意味でのモバイル環 境とオフィス ( またはホーム ) 竟の融合か期待できる。 ところが、残念なことに、日本ではこの種の製品がまっ たくない。たんなる擱則だが、なんらかの規制があって携 帯電話端末として認定されないとか、携帯電言末のメー カーからの圧力があるとか、あるいは、携帯電話サービス を提供する企業は本当は PDA が大嫌いであるとか、そう はつらいのも事実である。その理由は、次の 3 点に集約で いった理由でもあるのだろうか。技勺にはとくに困難な きる。 部分はなく、十分に実現可能と思えるだけに、なせ登場し ・ 2kg 前後と重い。 ないのかか不思議でならない。 ・バッテリーでは、せいぜい獅間程度しカ硬えない。 NTT DoCoMo や au 、 J- フォンなど秀帯電話サー 携帯電話端末などとくらべると手軽には使えない。 ビス会社が、本当に第 3 世イ秀帯電話を普及させたいと 願っているのであれば、コンピュータとの本格的な連まや 携帯電話端末だけでは機能不足だからラップトップ PC データ通信を中心とした利用形態をもっと強く打ち出すべ も持ち歩く、という考え方は分かるし、私自身も日頃から きであろう。ぜひとも、 PDA と携帯電話端末との融合を 実践している。とはいえ、携帯電話の軽央感を失うのはや 真剣に検討してほしいものである。 はり残念である。 そう思っている人はかなり多いらしく、このジレンマを セキュアな WWW サービス 鮹夬する新たな製品か登場し始めている。残念ながら日本 では販売されていないが、 PDA と携帯電話を合体させた 前回は、 X. 509 証明書とその利用力法の各を述べた。 製品である。丐点では PDA と GSM 携帯電話を組み 私たちが入手できる X. 509 証明書は、次の 2 種類で 合わせたものが大部分で、携帯電話としての機能は第 2 世 ある。 イ秀帯電話、すなわち狭帯域サーピスしか使えない点か残 念ではある。 ・ WWW サイト側の証明書 写真 1 を見れば分かるように、これらの製品では PaIm これは WWW サーバーの素生を明らかにするための OS や Pocket PC を載せた PDA に携帯電話のダイヤラ 証明書である。 WWW サーバー側で SSL/TLS サー ーカ咐いており、通常の音声通話もできる。則の Hand- ピスを提供するときに必喫となる。 spring 製の端末では、イヤスピーカーとマイクカ埔ⅱ面の ・個人用証明書 カバーに組み込まれていて、通話中もボタンやタッチセ こちらはネットワーク・サービスを利用するユーザー ンサーの部分に身体の一黯にが触れないように工夫されてい の、、身分証 " である。こ刎固人用証明書を使ってアクセ る。インターネットへのアクセスについては、 GPRS と ス制御をおこなったり、あるいは、電子メールに付ける 呼ばれる GSM 携帯電話システムのバケットサービスを 電イ署名を生成したりすることかできる。 利用する。 今回は、最初にセキュアな www サーピスの構築方 第 3 世秀帯電話にもこういう製品が登場すれば、本 法について述べる。つまり、 WWW サーバーとクライア 格的な情報処理と携帯電話の利点を活かした使い方ができ ントとのあいだの通信が SSL/TLS によって暗号化さ るはすである。現在の多くの PDA の同期機能 ( べース ← ( 415 ) 555 ・ す 2 を 3 をいル 42 UNIX MAGAZINE 2003.2
特集・プログラミング入門 に、地頁にダミーのノードを追加することにします。変数 として木尉寺する場合は、このダミーノードを指し示し ます。これによって、木の根のノードを変更する場合も、 もともとの変数を変更する必要がなくなります。 これらの点を考慮してプログラムを書くと、 : 冓造のデ ータを匆祺月化するコードは次のようになります。 typedef struct —node { struct _node *1eft ; struct _node *right ; int va1 ; } node, *tree; initialize 関数でネ川月化された冓造を返しますが、ノ ードを 1 っ作り、右の子 (right) に NULL を代入するこ とで冓造が空であることを示しています。左の子 (left) と値 val は、利用することはありませんが、一応 NULL や 0 で期化しておきます。 次はデータを追加するコードを・・・・・・といきたいところ ですが、ぐっとこらえて、データを探索するコードをさき にみておきましよう。さきはど示した探索のためのコード では、再帰関数として関数を実現していました。関数をよ くみていくと分かりますが、探索をおこなうのは末尾再帰 の関数です。したがって、簡単に区し関数に変換するこ t , initialize(void) tree t; (tree)malloc(sizeof(node)) ; t t->left = t—>right = NULL; t—>val = 0 ; return t ; 初のダミーノードの扱いを加えると、関数の部分は次のよ とカきます。再帰関数を縄区し関数に変換し、さらに最 うになります。 search(tree tree p; int int key) p = t—>right, while (p ! = NULL) if ()i = p—>val ー key) return p ; else if (i > 0 ) 112 p = p¯>left ; else p = p->right; return NULL ; ます、根のノードを指すために right をたどっていま す。これによって、ダミーのノードを読み飛ばしていま す。その次は、再帰関数を縄区し関数に変換した部分で す。その終了条件は、注目するノードが NULL になら ない、つまりデータか存在するあいだという系お区しです。 注目するノードが NULL になるようなときは、目的のデ ータがないことが分かるため、すぐに探索の失敗を示す値 (NULL) を返せます。一方、ノードにデータがある場合 は、その値と探索する値とを上交します。これらカしけ れば注目しているイゞ目的の値になりますから、目的の値 がみつかったことを示すために、そのノードのアドレスを 返します。両者カしくなければ、 2 つの値の大小関係を ヾて次に探索すべきノードを決定します。 : 冓造のノー ドのほうが大きな値であれは方の子を、小さな値であれば 右の子を次に探索します。 この関数では、ループを 1 度まわるたびに right か left のリンクを 1 回たどるため、対象とするノードのレベルが かならす 1 すっ増えていきます。二分探索木の高さは有 限ですから、このプログラムはいつかは停止することにな ります。木構造か適度にバラけていれは、この木の高さが 1 。 g れに比例する値となるので、探索も 1 。 g れに上 tf 列する 手間でおこなえるわけです。 次は、レコードを追加するための関数をみてみましょ う。二分探索木にデータを追加する場合は、ます、追加し UNIX MAGAZINE 2003.2 これらの処理をおこなうための関数は、次のように作れ にすることができます。 加して値を登録すれば、二分探索木の条件を満た、・すを冓造 の場合は、ちょうど注目している位置に新たなノードを追 に葉に到達していて、 NULL となっているはすです。 データが求されていなければ、注目するノードはすで 録されているデータのノードのアドレスを返します。 重登録はエラーとして扱っているため、関数ではすでに登 ていることになり、 . : : 重疆こなります。この例では、 るデータがみつかったら、同しキーの値がすでにイ求され たいデータをキーとして探索を実行します。これで対応す
特集・プログラミング入門 図 5 根を上に描いた木 図 6 造の用語 ( 1 ) ・ノード ( 頂点、節点 ) 糸目系冓造を表す図などにも似ています。図 5 では、ある点 から 3 本の枝カ咄ているものもあるのでトーナメント表に たとえるのはやや無理がありますが、組であれば 1 点 から 3 本以ーヒの枝カ咄ていても不思議ではありません。 : 冓造の構成要素を示すために使われる用語には、糸目織 図と似た構造をもつ家系図 (family tree) から借用したも のがたくさんあります。 : イ冓造の用語について、簡単に説明しておきましよう ( 図 6 ~ 7 も見てください ) 。ます、さきはど冓造におけ る、、点 " と呼んだ部分は、ノード、項点、節点などといい ます。ノードどうしを結ぶ線は、リンク、辺、枝などとい われます。冓造のノードのなかで、、根 " と呼ばれるもの が 1 つだけあり、これはその他のノードとはすこし異なる 性質をもっています。 討冓造として表現する場合、根を上に配置したときにリ ンクの上側にくるノードを親ノード、一ド側にくるノードを 子ノードと呼びます。 : 冓造中のすべてのノードは、根の ノードを除き、かならず 1 つの親ノードをもっています。 1 つの親ノードに複数の子ノードがある場合もありますが、 これらの子ノードをまとめて、、兄弟 " と呼びます 1 。兄弟 のノード間に順番がある場合、順番がさきのものを兄、順 番があとのものを弟と呼びます。親や子という関係を 2 段 1 英語では sibling で男女の区別はないのですが、日 : 吾では適当な言吾 リンク ( 辺、枝 ) リンク 106 がないので、とりあえす、兄弟 " とします。 図 7 告の用語 ( 2 ) 祖先 親 / ヾス 子兄弟 (sibling) 子 子孫 葉 階たどると、それそれ祖父や孫のノードとなります。ある ノード ( 仮にノード A とします ) を中心としてみたとき、 根からノード A まで到達するための経路をパス (path) と 呼びます。ノード A へのパス上に位置するすべてのノー ドをノード A の、、祖先 " 、ノード A の子やその子たちを あるノードのパスの長さ、つまり、根のノードから対象 ードを、、内部ノード " などと呼びます。 のがあります。子をもたないノードを、、葉 " 、子をもつノ ありません。一方、ノードには子をもつもの、 普通のノードには親がありますが、根のノードには親が すべてまとめてツ了孫 " と呼びます。 もたないも UNIX MAGAZIN E 2003.2 は、二分木において、一番下のレベルを除くどのレベルに 二分木が、、一杯 " であるということがあります。これ 扱われます。 区別するため、図 8 の 2 つの冓造は異なるものとして 二分木では、左の子 ( 左部分木 ) と右の子 ( 右部分木 ) を ます。ただし、実際には二分木を利用する場合がほとんど いているような冓造は、、多分木 (n-ary tree)" と呼はれ るノードがもっ子の数がれ以下で、子のあいだに順番カ咐 ことを、、二分木 (binary tree)" といいます。ー殳に、あ というように ) 順番が付いています。このようなヨ冓造の つ以下に決まっていて、それぞれの子のあいだに佐と右 さきほどの図 3 ク材冓造では、ノードのもっ子の数が 2 ( y 剽に描きます。 構造を図示するときは、同じレベルのノードを同し高さ とも大きいものをその木の、高さ " と呼びます。通常、木 らに、木に含まれるすべてのノードのレベルのうち、もっ はならないかを、、レベル " または、、深さ " と呼びます。さ とするノードに到達するまで、何本のリンクを通らなけれ