B木のテストプログラム
今回も、B木の様子がわかるようなテストプログラムを作成しました。既定値では、次数が2のB木が生成されるようになっていますが、右側のSpinEditで次数を指定して「K次のB木を生成」ボタンをクリックすれば、K次のB木が内部に作成されます。
「キーの追加」ボタンでは指定したキーを追加できます。また、「ランダムにn個追加」でn個のキーをB木に追加してその様子を見られるようにもしました。次数が変わると木の成長の速度も変わりますので、この辺を実際に見ながら確認するとよりイメージが湧くのではないかと思います。

●BTreeのソースコード(BTree.pas)
001: unit BTree;
002:
003: interface
004:
005: uses
006: SysUtils, Classes, StrUtils, Dialogs;
007:
008: type
009:
010: TKeyVal = record
011: Key: Integer;
012: Val: Integer;
013: end;
014:
015: TBTreeNode = class(TObject)
016: private
017: FCount: Integer;
018: FMaxKeys: integer;
019: FKeys: array of Integer;
020: FVals: array of integer;
021: FChildNodes: array of TBTreeNode;
022: FOrder: integer;
023: function GetChildNodes(Index: Integer): TBtreeNode;
024: function GetKeys(Index: Integer): Integer;
025: function GetVals(Index: Integer): Integer;
026: procedure InsertKeyVal(Index: Integer; const new_key, new_val: Integer);
027: procedure InsertChildNode(Index: Integer; const new_node: TBtreeNode);
028: procedure SetChildNodes(Index: Integer; const Value: TBtreeNode);
029: procedure SetKeys(Index: Integer; const Value: Integer);
030: procedure SetVals(Index: Integer; const Value: Integer);
031: function IsLeaf:Boolean;
032: protected
033: procedure DumpNodes(var S : String; depth : Integer);
034: function InternalAdd(new_key, new_val:Integer; var new_node:TBtreeNode;var return_key, return_val:Integer):boolean;
035: procedure SetCount(Value : Integer);
036: public
037: constructor Create(Order:Integer);
038: destructor Destroy;override;
039: property Order : Integer read FOrder;
040: property MaxKeys : Integer read FMaxKeys;
041: property Keys[Index:Integer]:Integer read GetKeys write SetKeys;
042: property Vals[Index:Integer]:Integer read GetVals write SetVals;
043: property ChildNodes[Index:Integer]:TBtreeNode read GetChildNodes write SetChildNodes;
044: property Count:Integer read FCount;
045: end;
046:
047: TBtree = class(TObject)
048: private
049: FCount : Integer;
050: FOrder : Integer;
051: FRoot : TBTreeNode;
052: protected
053: public
054: function DumpNodes:String;
055: procedure Add(new_key, new_val:Integer);
056: constructor Create(Order:Integer);
057: destructor Destroy;override;
058: property Order : Integer read FOrder;
059: property Count : Integer read FCount;
060: end;
061:
062: implementation
063:
064: { TBTreeNode }
065:
066: //節点のコンストラクタ
067: constructor TBTreeNode.Create(Order:Integer);
068: begin
069: inherited Create;
070:
071: FCount := 0;
072: FOrder := Order;
073: FMaxKeys := Order * 2;
074:
075: //実装を簡易にするため、0..2*K、つまり要素数2K+1の配列とする
076: SetLength(FKeys, FMaxKeys + 1);
077: SetLength(FVals, FMaxKeys + 1);
078:
079: //子節点へのリンクは2K+1個を使用するので、余分を1つとる
080: //こうしておくと、分割の際に、K+1個ずつ分配しやすい
081: SetLength(FChildNodes, FMaxKeys + 2);
082:
083: end;
084:
085: //節点のデストラクタ
086: //子節点があれば、それを解放する
087: destructor TBTreeNode.Destroy;
088: var
089: idx : Integer;
090: begin
091: for idx := 0 to FCount do
092: begin
093: if (FChildNodes[idx] <> nil) then FChildNodes[idx].Free;
094: end;
095:
096: inherited;
097: end;
098:
099: //節点の状態を返す処理
100: procedure TBTreeNode.DumpNodes(var S: String; depth: Integer);
101: var
102: idx : Integer;
103: begin
104: //節点が葉かどうかで処理を分ける
105: if (IsLeaf = True) then
106: //葉である場合
107: begin
108: for idx := 0 to FCount -1 do
109: begin
110: S := S + DupeString(' ', depth) + IntToStr(FKeys[idx]) + #13#10;
111: end;
112: end else
113: //葉でない場合
114: begin
115: for idx := 0 to FCount -1 do
116: begin
117: FChildNodes[idx].DumpNodes(S, depth + 1);
118: S := S + DupeString(' ', depth) + IntToStr(FKeys[idx]) + #13#10;
119: end;
120: FChildNodes[FCount].DumpNodes(S, depth + 1);
121: end;
122: end;
123:
124: function TBTreeNode.GetChildNodes(Index: Integer): TBtreeNode;
125: begin
126: result := FChildNodes[Index];
127: end;
128:
129: function TBTreeNode.GetKeys(Index: Integer): Integer;
130: begin
131: result := FKeys[Index];
132: end;
133:
134: function TBTreeNode.GetVals(Index: Integer): Integer;
135: begin
136: result := FVals[Index];
137: end;
138:
139: //節点への要素の追加
140: function TBTreeNode.InternalAdd(new_key, new_val:Integer; var new_node:TBtreeNode;var return_key, return_val:Integer):boolean;
141: var
142: idx : Integer;
143: begin
144: //新しいキーがバケット内のどの位置にあたるかをチェックしておく
145: //idxには、new_key の位置が入る
146: if (FCount = 0) then
147: idx := 0
148: else
149: for idx := 0 to FCount - 1 do
150: if (FKeys[idx] > new_key) then break;
151:
152: //葉の場合とそうでない場合で処理を分ける
153: if (IsLeaf = True) then
154: //節点が葉である場合
155: begin
156: //新しいキーより大きい値の左側へ新しいキーを挿入する
157: //余分を1つ取ってあるので必ず挿入できる
158: InsertKeyVal(idx, new_key, new_val);
159:
160: //すでにバケットがいっぱいかどうかで処理を分ける
161: if (FCount > FMaxkeys) then
162: begin
163: //バケットの分割が発生する
164: result := True;
165:
166: //分割用の新しい節点を生成
167: new_node := TBTreeNode.Create(FOrder);
168:
169: //新節点へ値を移動する
170: for idx := 0 to FOrder - 1 do
171: begin
172: //中央値はK番目にあたるので、K+1番目から上を新節点へ移動
173: //(K-1)+(K+1)=2K
174: new_node.Keys[idx] := FKeys[idx + FOrder + 1];
175: new_node.Vals[idx] := FVals[idx + FOrder + 1];
176: end;
177:
178: //親節点へ返すキーと値の組をセット
179: return_key := FKeys[FOrder];
180: return_val := FVals[FOrder];
181:
182: //分割によって、CountはKになる
183: FCount := FOrder;
184: new_node.SetCount(FOrder);
185:
186: exit;
187: end else
188: begin
189: //分割は発生していない
190: result := False;
191: exit;
192: end;
193: //節点が葉で無い場合の処理
194: end else
195: begin
196: //新しいキーより大きいキーの左側の子節点へキーを追加する
197: //idxには、キーの位置が入っているので、同じ位置のFChildNodesがそれにあたる
198:
199: //追加した結果分割が発生したかどうかで処理を分ける
200: if (FChildNodes[idx].InternalAdd(new_key, new_val, new_node, return_key, return_val) = True) then
201: //分割が発生した
202: begin
203: //分割の結果返されたキーを挿入するのも、idxの位置になるので
204: //これを再度チェックする必要はない
205:
206: //新しいキーより大きい値の左側へ新しいキーを挿入する
207: //余分を1つ取ってあるので必ず挿入できる
208: InsertKeyVal(idx, return_key, return_val);
209:
210: //新しい子節点を追加する
211: //位置としては、右側の子節点となるので、idx+1の位置へ挿入する
212: InsertChildNode(idx + 1, new_node);
213:
214: //すでにバケットがいっぱいかどうかで処理を分ける
215: if (FCount > FMaxkeys) then
216: begin
217: //バケットの分割が発生する
218: result := True;
219:
220: //分割用の新しい節点を生成
221: new_node := TBTreeNode.Create(FOrder);
222:
223: //新節点へ値を移動する
224: for idx := 0 to FOrder - 1 do
225: begin
226: //中央値はK番目にあたるので、K+1番目から上を新節点へ移動
227: //0+K+1=K+1 〜 (K-1)+(K+1)=2K を移動する
228: new_node.Keys[idx] := FKeys[idx + FOrder + 1];
229: new_node.Vals[idx] := FVals[idx + FOrder + 1];
230:
231: //子節点へのリンクも同様に移動し、移動元をnilで埋める
232: new_node.ChildNodes[idx] := FChildNodes[idx + FOrder + 1];
233: FChildNodes[idx + FOrder + 1] := nil;
234: end;
235:
236: //子節点へのリンクは、一番右側がはみ出すのでこれを移動する
237: new_node.ChildNodes[FOrder] := FChildNodes[FMaxKeys + 1];
238: FChildNodes[FMaxKeys + 1] := nil;
239:
240: //親節点へ返すキーと値の組をセット
241: return_key := FKeys[FOrder];
242: return_val := FVals[FOrder];
243:
244: //分割によって、CountはKになる
245: FCount := FOrder;
246: new_node.SetCount(FOrder);
247:
248: exit;
249:
250: end else
251: //分割は発生していない
252: begin
253: result := False;
254: exit;
255: end;
256: end else
257: //分割が発生していない
258: begin
259: result := False;
260: exit;
261: end;
262: end;
263: end;
264:
265: function TBTreeNode.IsLeaf: Boolean;
266: begin
267: result := (FChildNodes[0] = nil);
268: end;
269:
270: procedure TBTreeNode.SetChildNodes(Index: Integer; const Value: TBtreeNode);
271: begin
272: FChildNodes[index] := Value;
273: end;
274:
275: procedure TBTreeNode.SetCount(Value: Integer);
276: begin
277: FCount := Value;
278: end;
279:
280: procedure TBTreeNode.SetKeys(Index: Integer; const Value: Integer);
281: begin
282: FKeys[Index] := Value;
283: end;
284:
285: //Indexを指定した位置に、ChildNodeを挿入する
286: procedure TBTreeNode.InsertChildNode(Index: Integer;
287: const new_node: TBtreeNode);
288: var
289: idx : Integer;
290: begin
291: //追加するキーのために場所を確保する
292: for idx := FCount + 1 downto index + 1 do
293: begin
294: FChildNodes[idx] := FChildNodes[idx - 1];
295: end;
296:
297: FChildNodes[index] := new_node;
298:
299:
300: end;
301:
302: //Indexを指定した位置に、キー=Valueを挿入する
303: procedure TBTreeNode.InsertKeyVal(Index: Integer; const new_key, new_val: Integer);
304: var
305: idx : Integer;
306: begin
307:
308: //追加するキーのために場所を確保する
309: for idx := FCount downto index + 1 do
310: begin
311: FKeys[idx] := FKeys[idx - 1];
312: FVals[idx] := FVals[idx - 1];
313: end;
314:
315: FKeys[index] := new_key;
316: FVals[index] := new_val;
317:
318: Inc(FCount);
319: end;
320:
321: procedure TBTreeNode.SetVals(Index: Integer; const Value: Integer);
322: begin
323: FVals[Index] := Value;
324: end;
325:
326: { TBtree }
327:
328: procedure TBtree.Add(new_key, new_val: Integer);
329: var
330: created_node, old_root : TBTreeNode;
331: return_key, return_val : Integer;
332: begin
333: //ルートに対して新しいキーを追加した結果、ルートが分割されるかどうかで処理を分ける
334: if(FRoot.InternalAdd(new_key, new_val, created_node, return_key, return_val)=True) then
335: begin
336: old_root := FRoot;
337: FRoot := TBTreeNode.Create(FOrder);
338: FRoot.Keys[0] := return_key;
339: FRoot.Vals[0] := return_val;
340: FRoot.ChildNodes[0] := old_root;
341: FRoot.ChildNodes[1] := created_node;
342: FRoot.SetCount(1);
343: end;
344: end;
345:
346: //B木のコンストラクタ
347: constructor TBtree.Create(Order: Integer);
348: begin
349: inherited Create;
350:
351: FOrder := Order;
352: FRoot := TBtreeNode.Create(Order);
353: end;
354:
355: //B木のデストラクタ
356: destructor TBtree.Destroy;
357: begin
358: FRoot.Free;
359:
360: inherited;
361: end;
362:
363: //ツリーの内部状態を返す
364: function TBtree.DumpNodes: String;
365: begin
366: FRoot.DumpNodes(Result, 0);
367: end;
368:
369: end.今回はB木に対する要素の追加についてみてきました、AVL木と比べると直感的には分かりやすいのではないかと思います。なるべく各節点のオブジェクト自体にロジックを集中しようと思って実装してみましたが、通常はツリーオブジェクト側にこうしたロジックを実装しています。
なぜかというと、B木の利点として1つのノードが一定の大きさをもったデータ構造となるため、ファイルシステムへの出し入れ(ページングともいいますね)がやりやすいという利点があるので、データを含む節点側にはあまりロジックを実装していません。
しかし、いずれにせよTObject派生のオブジェクトとして実装しているわけですし、ページングする必要のあるキーやノードの配列については、そこだけをストリームにして書き出したり読み込んだりするということも可能なわけですから、よりオブジェクト指向的な実装をという考えでチャレンジしてみました。
さて、次回はB木からのデータの削除にチャレンジします。ご期待下さい。
3/3 |
| Index | |
| RDBMSで使われるB木を学ぼう | |
| Page1 B木とは何か B木の成長 |
|
| Page2 B木への要素の追加(葉の場合) B木への要素の追加(葉でない場合) B木の実装の工夫 |
|
| Page3 B木のテストプログラム |
|
| Appendix BTree.pasのソースコード |
|
| Delphiアルゴリズムトレーニング |
| Coding Edgeお勧め記事 |
| いまさらアルゴリズムを学ぶ意味 コーディングに役立つ! アルゴリズムの基本(1) コンピュータに「3の倍数と3の付く数字」を判断させるにはどうしたらいいか。発想力を鍛えよう |
|
| Zope 3の魅力に迫る Zope 3とは何ぞや?(1) Pythonで書かれたWebアプリケーションフレームワーク「Zope 3」。ほかのソフトウェアとは一体何が違っているのか? |
|
| 貧弱環境プログラミングのススメ 柴田 淳のコーディング天国 高性能なIT機器に囲まれた環境でコンピュータの動作原理に触れることは可能だろうか。貧弱なPC上にビットマップの直線をどうやって引く? |
|
| Haskellプログラミングの楽しみ方 のんびりHaskell(1) 関数型言語に分類されるHaskell。C言語などの手続き型言語とまったく異なるプログラミングの世界に踏み出してみよう |
|
| ちょっと変わったLisp入門 Gaucheでメタプログラミング(1) Lispの一種であるScheme。いくつかある処理系の中でも気軽にスクリプトを書けるGaucheでLispの世界を体験してみよう |
|
Coding Edge フォーラム 新着記事
- プログラムの実行はどのようにして行われるのか、Linuxカーネルのコードから探る (2017/7/20)
C言語の「Hello World!」プログラムで使われる、「printf()」「main()」関数の中身を、デバッガによる解析と逆アセンブル、ソースコード読解などのさまざまな側面から探る連載。最終回は、Linuxカーネルの中では、プログラムの起動時にはどのような処理が行われているのかを探る - エンジニアならC言語プログラムの終わりに呼び出されるexit()の中身分かってますよね? (2017/7/13)
C言語の「Hello World!」プログラムで使われる、「printf()」「main()」関数の中身を、デバッガによる解析と逆アセンブル、ソースコード読解などのさまざまな側面から探る連載。今回は、プログラムの終わりに呼び出されるexit()の中身を探る - VBAにおけるFileDialog操作の基本&ドライブの空き容量、ファイルのサイズやタイムスタンプの取得方法 (2017/7/10)
指定したドライブの空き容量、ファイルのタイムスタンプや属性を取得する方法、FileDialog/エクスプローラー操作の基本を紹介します - さらば残業! 面倒くさいエクセル業務を楽にする「Excel VBA」とは (2017/7/6)
日頃発生する“面倒くさい業務”。簡単なプログラミングで効率化できる可能性がある。本稿では、業務で使うことが多い「Microsoft Excel」で使えるVBAを紹介する。※ショートカットキー、アクセスキーの解説あり
|
|
>
Coding Edge 記事ランキング
本日
月間





