AVLTree2.pasのソースコード
●avltree2.pas
unit AVLTree2; interface uses SysUtils, Classes, RTLConsts, avltree; type TBalance = (brLeft, brEqual, brRight); TDumpNodes = (dnPreorder, dnInorder, dnPostorder); TAVLTreeNode = class; TAVLTreeNode = class(TObject) private FItemTree : TAVLTree; public ID : Integer; LeftChild, RightChild : TAVLTreeNode; Balance : TBalance; property ItemTree : TAVLTree read FItemTree write FItemTree; constructor Create; destructor Destroy; override; end; TAVLTree2 = class(TObject) private FCount : integer; FRoot : TAVLTreeNode; protected procedure AddNode(var parent : TAVLTreeNode; newID : Integer; value : Pointer; var grow : Boolean); procedure AdjustLeftGrow(var parent : TAVLTreeNode); function AdjustLeftCutdown(var node: TAVLTreeNode):Boolean; procedure AdjustRightGrow(var parent : TAVLTreeNode); function AdjustRightCutdown(var node: TAVLTreeNode):Boolean; procedure DeleteFromNode(var node : TAVLTreeNode; delID : Integer; var cutdown : Boolean); function Get(Index: Integer): TAVLTree; procedure InternalGet(var parent: TAVLTreeNode; index : integer; var bolFind:Boolean; var item:pointer); procedure InternalDumpNodes(var parent: TAVLTreeNode; var s: string; search_algo: TDumpNodes; var depth: Integer); procedure ReplaceRightMost(var targetNode, replNode : TAVLTreeNode; var cutdown : Boolean); public procedure Add(newID : Integer; value : Pointer); procedure Delete(delID : Integer); function DumpNodes(search_algo:TDumpNodes):String; property Count : Integer read FCount; property ItemTree[Index:Integer]: TAVLTree read Get; destructor Destroy; override; end; implementation { TAVLNode } constructor TAVLTreeNode.Create; begin FItemTree := TAVLTree.Create; end; destructor TAVLTreeNode.Destroy; begin if (LeftChild <> nil) then LeftChild.Free; if (RightChild <> nil) then RightChild.Free; FItemTree.Free; inherited; end; { TAVLTree } //ツリーに値を追加する処理 procedure TAVLTree2.Add(newID: Integer; value: Pointer); var grow : Boolean; begin //Addメソッドは、まずRootから値を追加する先を探索する grow := False; AddNode(FRoot, newID, value, grow); end; //ノードの追加を再帰的に行う処理 procedure TAVLTree2.AddNode( var parent: TAVLTreeNode; newID: Integer; value : Pointer; var grow: Boolean ); begin //木の最深部まで降りた場合、そこにノードを追加する if (parent = nil) then begin parent := TAVLTreeNode.Create; parent.ID := newID; parent.Balance := brEqual; parent.ItemTree.Add(Integer(value), nil); grow := True; Inc(FCount); exit; end; //newIDが現在の節点のIDより小さい時の処理 //左側に下っていく if (newID < parent.ID) then begin //左側へ節点を追加する AddNode(parent.LeftChild, newID, value, grow); //木が成長した=高さが変わった場合、grow がTrueで返ってくる //Falseの場合、バランス調整は不要 if (grow = False) then exit; if (parent.Balance = brRight) then begin //元々は右側の高さが大きかった場合 //左に新しい節点が追加されたので、これでバランスした parent.Balance := brEqual; //上のノードには、深度が変化していないと通知する grow := False; end else if (parent.Balance = brEqual) then begin //元々がバランスしていたので、左側に節点が追加されたため //左側が深い状態になった parent.Balance := brLeft; end else begin //元々左側の高さが大きかったので、 //左側に節点が追加されたため、バランス調整が必要となった AdjustLeftGrow(parent); grow := False; end; end else //newIDが現在の節点のIDより大きい場合の処理 //右側に下っていく if (newID > parent.ID) then begin //右側に節点を追加する AddNode(parent.RightChild, newID, value, grow); //木が成長した=高さが変わった場合、grow がTrueで返ってくる //Falseの場合、バランス調整は不要 if (grow = False) then exit; if (parent.Balance = brLeft) then begin //元々は左側の高さが大きかった場合 //右に新しい節点が追加されたので、これでバランスした parent.Balance := brEqual; grow := False; end else if (parent.Balance = brEqual) then begin //元々がバランスしていたので、右側に節点が追加されたため //右側が深い状態になった parent.Balance := brRight; end else begin //元々右側の高さが大きかったので //右側に節点が追加されたため、バランス調整が必要になった AdjustRightGrow(parent); grow := False; end; end else begin //newIDと現在の節点のIDが同じ場合は、ノードの値を書き換える parent.ItemTree.Add(Integer(value), nil); grow := False; Inc(FCount); end; end; //削除時にツリーの左側が低くなった時の処理 function TAVLTree2.AdjustLeftCutdown(var node: TAVLTreeNode):Boolean; var OrigRightChild, OrigGrandChild : TAVLTreeNode; ChildBalance, GrandChildBalance : TBalance; begin //ノードの左側が高い状態だったので、左が低くなってバランスした //ということは、このノードでの左右の高さは1つ低くなっている if (node.Balance = brLeft) then begin node.Balance := brEqual; result := True; end else //ノードはバランスしていたので、右が高くなる //全体として、左右の高さは変化しない if (node.Balance = brEqual) then begin node.Balance := brRight; result := False; end else //ノードは右側が高い状態だったので、右が2つ高くなってしまった begin OrigRightChild := node.RightChild; ChildBalance := OrigRightChild.Balance; // 左部分木の左右の高さが等しい、または左が低い場合は、左回転 if (ChildBalance <> brLeft) then begin node.RightChild := OrigRightChild.LeftChild; OrigRightChild.LeftChild := node; //T2とT3が同じ高さだった場合、部分木の高さは変わらない if (ChildBalance = brEqual) then begin node.Balance := brRight; OrigRightChild.Balance := brLeft; result := False; end else //T2よりT3が高かった場合、部分木は低くなる begin node.Balance := brEqual; OrigRightChild.Balance := brEqual; result := True; end; node := OrigRightChild; end else // 右部分木の左部分木が高い場合は、右−左回転 // 右−左回転の場合は、必ず部分木の高さが1つ低くなる begin OrigGrandChild := OrigRightChild.LeftChild; GrandchildBalance := OrigGrandchild.Balance; OrigRightChild.LeftChild := OrigGrandChild.RightChild; OrigGrandChild.RightChild := OrigRightChild; node.RightChild := OrigGrandChild.LeftChild; OrigGrandChild.LeftChild := node; if (GrandChildBalance = brRight) then node.Balance := brLeft else node.Balance := brEqual; if (GrandChildBalance = brLeft) then OrigRightChild.Balance := brRight else OrigRightChild.Balance := brEqual; node := OrigGrandChild; OrigGrandChild.Balance := brEqual; result := True; end; end; end; //追加時にツリーの左側でバランスが崩れたときの処理 procedure TAVLTree2.AdjustLeftGrow(var parent: TAVLTreeNode); var OrgLeftChild, OrgGrandChild : TAVLTreeNode; begin OrgLeftChild := parent.LeftChild; if (OrgLeftChild.Balance = brLeft) then begin //左側の左側でバランスが崩れたので、右回転する parent.LeftChild := OrgLeftChild.RightChild; OrgLeftChild.RightChild := parent; parent.Balance := brEqual; parent := OrgLeftChild; end else begin //左側の右側でバランスが崩れたので、左−右回転する OrgGrandchild := OrgLeftchild.RightChild; OrgLeftchild.RightChild := OrgGrandChild.LeftChild; OrgGrandchild.LeftChild := OrgLeftchild; parent.LeftChild := OrgGrandChild.RightChild; OrgGrandChild.RightChild := parent; if (OrgGrandChild.Balance = brLeft) then parent.Balance := brRight else parent.Balance := brEqual; if (OrgGrandchild.Balance = brRight) then OrgLeftchild.Balance := brLeft else OrgLeftchild.Balance := brEqual; parent := OrgGrandChild; end; parent.Balance := brEqual; end; //削除時にツリーの右側が低くなった時の処理 function TAVLTree2.AdjustRightCutdown(var node: TAVLTreeNode):Boolean; var OrigLeftChild, OrigGrandChild : TAVLTreeNode; ChildBalance, GrandChildBalance : TBalance; begin //ノードの右側が高い状態だったので、右が低くなってバランスした //ということは、このノードでの左右の高さは1つ低くなっている if (node.Balance = brRight) then begin node.Balance := brEqual; result := True; end else //ノードはバランスしていたので、左が高くなる //全体として、左右の高さは変化しない if (node.Balance = brEqual) then begin node.Balance := brLeft; result := False; end else //ノードは左側が高い状態だったので、左が2つ高くなってしまった begin OrigLeftChild := node.LeftChild; ChildBalance := OrigLeftChild.Balance; // 左部分木の左右の高さが等しい、または右が低い場合は、右回転 if (ChildBalance <> brRight) then begin node.LeftChild := OrigLeftChild.RightChild; OrigLeftChild.RightChild := node; //T2とT3が同じ高さだった場合、部分木の高さは変わらない if (ChildBalance = brEqual) then begin node.Balance := brLeft; OrigLeftChild.Balance := brRight; result := False; end else //T2よりT3が高かった場合、部分木は低くなる begin node.Balance := brEqual; OrigLeftChild.Balance := brEqual; result := True; end; node := OrigLeftChild; end else // 左部分木の右部分木が高い場合は、左−右回転 // 左−右回転の場合は、必ず部分木の高さが1つ低くなる begin OrigGrandChild := OrigLeftChild.RightChild; GrandchildBalance := OrigGrandchild.Balance; OrigLeftChild.RightChild := OrigGrandChild.LeftChild; OrigGrandChild.LeftChild := OrigLeftChild; node.LeftChild := OrigGrandChild.RightChild; OrigGrandChild.RightChild := node; if (GrandChildBalance = brLeft) then node.Balance := brRight else node.Balance := brEqual; if (GrandChildBalance = brRight) then OrigLeftChild.Balance := brLeft else OrigLeftChild.Balance := brEqual; node := OrigGrandChild; OrigGrandChild.Balance := brEqual; result := True; end; end; end; //追加時にツリーの右側でバランスが崩れたときの処理 procedure TAVLTree2.AdjustRightGrow(var parent: TAVLTreeNode); var OrgRightChild, OrgGrandChild : TAVLTreeNode; begin OrgRightChild := parent.RightChild; if (OrgRightChild.Balance = brRight) then begin //右側の右側でバランスが崩れたので、左回転する parent.RightChild := OrgRightChild.LeftChild; OrgRightChild.LeftChild := parent; parent.Balance := brEqual; parent := OrgRightChild; end else begin //右側の左側でバランスが崩れたので、右−左回転する OrgGrandchild := OrgRightchild.LeftChild; OrgRightchild.LeftChild := OrgGrandChild.RightChild; OrgGrandChild.RightChild := OrgRightChild; parent.RightChild := OrgGrandChild.LeftChild; OrgGrandChild.LeftChild := parent; if (OrgGrandChild.Balance = brRight) then parent.Balance := brLeft else parent.Balance := brEqual; if (OrgGrandchild.Balance = brLeft) then OrgRightChild.Balance := brRight else OrgRightChild.Balance := brEqual; parent := OrgGrandChild; end; parent.Balance := brEqual; end; destructor TAVLTree2.Destroy; begin if (FRoot <> nil) then FRoot.Free; inherited; end; //ツリーの内部からIDを引き出して、文字列で返す function TAVLTree2.DumpNodes(search_algo:TDumpNodes): String; var depth : integer; begin if (FRoot = nil) then begin result := 'This tree has nothing'; exit; end; depth := 0; InternalDumpNodes(FRoot, result, search_algo, depth); end; //ツリーのインデックス参照による値の取得 function TAVLTree2.Get(Index: Integer): TAVLTree; var bolFind:Boolean; item:pointer; begin InternalGet(FRoot, Index, bolFind, item); if (bolFind = False) then raise EListError.Createfmt(LoadResString(@SListIndexError), [Index]); result := TAVLTree(item); end; //ツリーの内部状態をダンプする処理 procedure TAVLTree2.InternalDumpNodes(var parent: TAVLTreeNode; var s: string; search_algo: TDumpNodes; var depth: Integer); procedure make_result; begin if (s <> '') then s := s + ', '; if (parent = FRoot) then s := s + '['; s := s + 'ID=' + IntToStr(parent.ID) + ':Count=' + IntToStr(parent.ItemTree.Count) + ':Depth=' + IntToStr(depth); if (parent = FRoot) then s := s + ']'; end; begin //行きがけ順はここで処理 if (search_algo = dnPreorder) then make_result; if (parent.LeftChild <> nil) then begin inc(depth); InternalDumpNodes(parent.LeftChild, s, search_algo, depth); dec(depth); end; //通りがかけ順はここで処理 if (search_algo = dnInorder) then make_result; if (parent.RightChild <> nil) then begin inc(depth); InternalDumpNodes(parent.RightChild, s, search_algo, depth ); dec(depth); end; //帰りがけ順はここで処理 if (search_algo = dnPostorder) then make_result; end; //ツリーからのデータの取得を再帰的に行う処理 procedure TAVLTree2.InternalGet(var parent: TAVLTreeNode; index : integer; var bolFind:Boolean; var item:Pointer); begin if (parent.ID = Index) then begin item := parent.ItemTree; bolFind := True; exit; end; if (parent.LeftChild <> nil) then begin InternalGet(parent.LeftChild, index, bolFind, item); if (bolFind = True) then exit; end; if (parent.RightChild <> nil) then begin InternalGet(parent.RightChild, index, bolFind, item); if (bolFind = True) then exit; end; end; // ソート木の削除時にソートを崩さずに削除する処理 procedure TAVLTree2.ReplaceRightMost(var targetNode, replNode: TAVLTreeNode; var cutdown: Boolean); var OrigReplNode : TAVLTreeNode; begin // ここが、最右端のノード if (replNode.RightChild = nil) then begin // replNode は、削除されるノードを置き換えるノード // 置き換え元のノードを保持しておく OrigReplNode := replNode; // 左側の子で置き換え元のノードを置き換える // LeftChild が nil なら両方ないわけだが、結果的にnilになるのでよい replNode := replNode.LeftChild; // 削除されるノードを置き換え用ノードで置き換える OrigReplNode.LeftChild := targetNode.LeftChild; OrigReplNode.RightChild := targetNode.RightChild; OrigReplNode.Balance := targetNode.Balance; targetNode := OrigReplNode; cutdown := True; end else begin // 置き換え用ノードの再起探索 ReplaceRightMost(targetNode, replNode.RightChild, cutdown); if (cutdown) then cutdown := AdjustRightCutdown(replNode); end; end; // Publicメソッド Delete の処理 procedure TAVLTree2.Delete(delID : Integer); var cutdown : Boolean; begin DeleteFromNode(FRoot, delID, cutdown); end; // Delete処理の本体 procedure TAVLTree2.DeleteFromNode(var node : TAVLTreeNode; delID : Integer; var cutdown : Boolean); var targetNode : TAVLTreeNode; begin //一番下まで来てしまったので、ターゲットはなかった if (node = nil) then begin Exception.CreateFmt('Not Find %d in the tree.', [delID]); cutdown := False; exit; end; // ターゲットがノードのIDより小さいので左側へ下りる if (delID < node.ID) then begin DeleteFromNode(node.LeftChild, delID, cutdown); if (cutdown=True) then cutdown := AdjustLeftCutdown(node); end else // ターゲットがノードのIDより大きいので右側へ下りる if (delID > node.ID) then begin DeleteFromNode(node.RightChild, delID, cutdown); if (cutdown=True) then cutdown := AdjustRightCutdown(node); end else // ターゲットとノードのIDが一致した場合 begin targetNode := node; // ソート木でのノードの削除処理を実行する // ノードの右には子がないので、左の子を自分の位置と入れ替える if (node.RightChild = nil) then begin // この操作で、木の高さは1つ低くなる node := node.LeftChild; cutdown := True; end else // ノードには左の子がないので、右の子を自分の位置と入れ替える if (node.LeftChild = nil) then begin // この操作で、木の高さは1つ低くなる node := node.RightChild; cutdown := True; // ノードには子が両側にある end else begin ReplaceRightmost(node, node.LeftChild, cutdown); if (cutdown=True) then cutdown := AdjustLeftCutdown(node); end; // Free the target node. targetNode.LeftChild := nil; targetNode.RightChild := nil; FCount := FCount - targetNode.ItemTree.Count; targetNode.Free; end; end; end.
Index | |
もっとAVL木で木構造を学ぼう | |
Page1 AVL木からの節点の削除 左回転でバランスを回復させる |
|
Page2 右−左回転でバランスを回復させる AVLTreeの拡張 AVL木はいろいろなところで使われている |
|
Appendix1 AVLTree2.pasのソースコード |
|
Appendix2 avllisttree.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 記事ランキング
本日
月間