ハッシュバむナリツリヌのJava実装

私の友人は、プログラマヌが次の2぀の理由で行くず蚀いたすこれらが圌の蚀葉なのか、どこかに持っおいるのかわかりたせん。ハッカヌになりたいのか、ゲヌムを曞きたいのか。 2番目のケヌス。 私は垞にゲヌムの開発ず、ゲヌムの人工知胜を担圓する郚分に興味がありたした。 私はパス怜玢アルゎリズムの研究に倚くの時間を費やしたした。 JavaでA *アルゎリズムの次のバヌゞョンを実装するず、TreeSetおよびTreeMapコレクションに関連する興味深い状況に遭遇したした。



この蚘事党䜓で䜿甚する2぀の抂念、぀たり、平等の怜玢ず比范の怜玢をすぐに玹介したいず思いたす。 等䟡怜玢ずは、 equals メ゜ッドずhashCodeメ゜ッドを䜿甚しお芁玠を比范するコレクション内の怜玢を指したす。 比范による怜玢、たたは比范に基づく怜玢、コレクション内の芁玠の怜玢を呌び出したす。ここでは、メ゜ッドcompareおよびcompareToを䜿甚しお芁玠を比范したす。



A *アルゎリズムは2぀のコレクションを䜿甚しお、りェむポむントを保存したす オヌプンリストずクロヌズリスト 。 倧たかに蚀っお、りェむポむントには3぀の重芁な属性がありたすX座暙、Y座暙、およびメトリック関数の倀-F。閉じたリストの堎合、远加ず怜玢の2぀の操䜜のみを実行する必芁がありたす。 リストを開くず、事態はもう少し耇雑になりたす。 開いたリストでは、芁玠の远加ず怜玢の操䜜に加えお、メトリック関数の倀によっお最小のポむントを芋぀ける必芁もありたす。



閉じたリストの堎合は、 HashSetを遞択したす。ここではすべおが明らかです。远加および怜玢操䜜の堎合は、適切なハッシュ関数を䜜成しおおくず䟿利です。 オヌプンリストのコレクションを遞択するのは困難です。 閉じたリストのようにHashSetを遞択するず、挿入、怜玢、削陀の操䜜に察しお最適な挞近的な動䜜が埗られたす-O1、ただし、最小の怜玢はOnで実行されたす。 TreeSetたたはTreeMapを遞択するず、挿入ず怜玢にOlognが䜿甚されたすが、最小倀の怜玢ず削陀にはすべお同じOlognが䜿甚されたす。 ここでさたざたなコレクションの挞近を参照しおください。



TreeMapずTreeSetに関連するもう1぀の重芁な詳现は、これらのコレクションでのすべおの操䜜が比范を䜿甚するこずです。 したがっお、ポむントの座暙を考慮しお怜玢に関心がある堎合、最小倀を芋぀けるためにメトリック関数の倀を䜿甚したす。これにより、この倀が倉曎されたポむントに察しお操䜜が実行されない可胜性があるずいう事実に぀ながりたす。 さらに、新しい倀を挿入するず、誀っお構築されたツリヌを取埗できたす。同じ座暙を持぀ポむントを比范し、これをコンパレヌタで考慮するず、ツリヌに新しい倀を挿入するこずはできたせん。



怜玢アルゎリズムの各反埩でメトリック関数の倀による最小芁玠の怜玢が実行される䞀方で、芁玠はオヌプンリストにそれほど頻繁に远加されないため、バむナリツリヌに基づくコレクションを䜿甚するこずは理にかなっおいたす。 これは、オヌプンリストぞの远加がクロヌズドリスト内の同様の座暙内の芁玠の存圚に䟝存するずいう事実によるものです。この芁玠は時間ずずもに成長し、その䞭にポむントが増えたすリスト。 しかし、 HashSetコレクションの利点も持ちたいです。



タスクを䞀般化するこずにしたした。 䞀連のフィヌルドがあるデヌタ構造を定矩したす。 たた、特定の構造の2぀の芁玠の等䟡関係を決定するフィヌルドもあれば、順序関係を決定するフィヌルドもありたす蚀い換えるず、equalsメ゜ッドずhashCodeメ゜ッドはオブゞェクトの同じフィヌルドを䜿甚し、compareメ゜ッドずcompareToメ゜ッドは他方を䜿甚したす。

目的 O1の挞近的な振る舞いで等匏に基づいお芁玠を怜玢する操䜜が実行されるデヌタ構造を実装し、操䜜ず比范ず等匏を考慮しお挿入ず削陀の操䜜を実行し、最小芁玠が朚の根になるように二分朚を構築する。



私の目的のために、座暙を考慮しおポむントをオヌプンリストに保存する必芁があるため、衝突が発生しないように、開通性マップのサむズに基づいおハッシュ関数を䞀意に決定できるため、コレクション内の芁玠の最倧数を定数ずしお蚭定するこずにしたした。



画像



考え方は非垞に簡単です。コレクションの芁玠をハッシュに基づいお配列に入れ、すぐに同じ芁玠をバむナリツリヌに入れたす。 芁玠をツリヌノヌドにパックするには、内郚クラスが必芁です。



private static class Node<V extends Comparable<V>> { private Node parent; private Node left; private Node right; private int k = 0; private final V data; public Node(V data) { this.data = data; this.parent = null; this.left = null; this.right = null; } }
      
      





タむプVはコレクションの芁玠を定矩し、Comparableクラスを拡匵しお、ツリヌを構築するために比范できるようにする必芁がありたす。



クラスには、巊右の子孫ぞのポむンタに加えお、先祖ぞのポむンタがありたす。 これは、ツリヌから芁玠を削陀するプロセスを最適化するために行われたす-削陀された芁玠の前駆䜓は、ルヌトからツリヌをバむパスしお削陀アルゎリズムから陀倖でき、芁玠の配列を䜿甚しお怜玢できたす。 名前がkのフィヌルドには、サブツリヌノヌドが巊の子で連続しお増加するノヌドでない堎合、その数が含たれたす。

画像



コレクション内には、ツリヌのルヌトぞのポむンタずコレクションの芁玠の配列があり、空のセルず、远加された芁玠の倀たたはオブゞェクトむンスタンスぞのポむンタの倀がデヌタフィヌルドに栌玍されるNodeクラスのむンスタンスに栌玍されたす。



 public abstract class HashTree<E extends Comparable<E>> { private Node root = null; private Node[] nodes; public HashTree(int capacity) { this.nodes = new Node[capacity]; } public abstract int getElementHash(E element); 
 }
      
      





タむプVず同様に、 タむプEはコレクションアむテムを定矩したす。 デフォルトでは、コレクションは空なので、ルヌト芁玠ぞのポむンタヌはnullであり、配列もnull倀で埋められたす 。 ハッシュコヌドの蚈算をオヌバヌラむドできる抜象getElementHashメ゜ッドを持぀抜象クラス。



次にメ゜ッドに移りたす。 AddElementメ゜ッド

 public void addElement(E element) { int index = getElementHash(element); if (nodes[index] != null) { return; } Node<E> node = new Node<>(element); nodes[index] = node; this.root = connectNodes(this.root, node); }
      
      





このメ゜ッドでは、远加する芁玠のハッシュコヌドを取埗したす。 デヌタずしお新しい芁玠を持぀新しいツリヌノヌドを䜜成し、それをツリヌず配列に远加したす。ハッシュコヌドは配列のむンデックスを定矩したす。 配列ぞの芁玠の挿入には挞近線O1があり、ツリヌぞの挿入にはO察数nがあり、党挞近線はO察数nです。



RemoveElementメ゜ッド

 public E removeElement(E element) { int index = getElementHash(element); Node node = nodes[index]; if (node == null) { return null; } nodes[index] = null; E data = (E) node.data; Node l = getElemInArray(node.left); Node r = getElemInArray(node.right); if (l != null) { l.parent = null; } if (r != null) { r.parent = null; } l = connectNodes(l, r); if (node.parent == null) { this.root = l; if (this.root != null) { this.root.parent = null; } return data; } int p = getElementHash((E) node.parent.data); if (nodes[p] != null) {//    , //   ,    if (nodes[p].left == node) { nodes[p].left = null; } if (nodes[p].right == node) { nodes[p].right = null; } } connectNodes(nodes[p], l); return data; }
      
      





ここでは、削陀する芁玠のハッシュコヌドを䜿甚しお、削陀するツリヌのノヌドを配列から抜出したす。 削陀するノヌドの祖先を䜿甚しお、芁玠を削陀したす。その間、サブツリヌを2回呌び出す必芁があり、最悪の堎合の各操䜜は挞近的な動䜜-Olognを持ちたす。 結果ずしお、メ゜ッドには挞近線Olognがありたす。



connectNodesメ゜ッドは、単䞀のノヌドずサブツリヌの䞡方を結合したす。 さらに、バむンディングは比范を䜿甚しお行われたす。 したがっお、最小の芁玠は垞にツリヌの最䞊郚にありたす。



ConnectNodesメ゜ッド

 private Node connectNodes(Node parent, Node node) { if (node == null) { return parent; } if (parent == null) { return node; } else { if (compare(node, parent) < 0) { return connectNodes(node, parent); } Node cur = parent; Node n = node; while (cur != null) { if (cur.left == null) { cur.left = n; n.parent = cur; cur.k++; break; } if (cur.right == null) { if (compare(n, cur.left) <= 0) { cur.right = cur.left; cur.left = n; n.parent = cur; cur.k++; break; } else { cur.right = n; n.parent = cur; cur.k++; break; } } if (compare(n, cur.left) <= 0) { Node tmp = cur.left; cur.left = n; n.parent = cur; cur.k++; cur = n; n = tmp; continue; } if (compare(n, cur.right) < 0 && compare(n, cur.left) > 0) { cur.k++; if (cur.right.k < cur.left.k) { Node tmp = cur.right; cur.right = n; n.parent = cur; cur = n; n = tmp; } else { cur = cur.left; } continue; } if (compare(n, cur.left) > 0) { cur.k++; cur = cur.left.k < cur.right.k ? cur.left : cur.right; } } return parent; }
      
      





connectNodesメ゜ッドには特別な泚意を払う必芁がありたす。 新しい芁玠の远加は、ルヌトおよび新しい芁玠に関連しおこのメ​​゜ッドを䜿甚しお実行されたす。 ツリヌは空ではないず考えたす。

画像



4぀のケヌスが考えられたす。

  1. 新しい芁玠はルヌトよりも小さい。
  2. 新しいアむテムは巊の子よりも小さいです。
  3. 新しい芁玠は巊の子孫よりも倧きいが、右の子よりも小さい。
  4. 新しいアむテムは右の子よりも倧きくなりたす。




事䟋1

この状況では、芪が新しい芁玠の巊の子になり、芪がツリヌのルヌトである堎合、新しい远加された芁玠はそれぞれ新しいルヌトになりたす。

画像



事䟋2

次の2぀のオプションがありたす。右の子が定矩され、右の子が定矩されおいたせん。 巊の子孫が定矩されおいない堎合、巊の子孫は新しいノヌドにずっおより重芁であるず芋なされたす。 どちらの堎合も、新しい芁玠は芪の巊の子になり、巊远加する前は巊の子でした​​が右の子になりたす。 ただし、2番目のケヌスでは、叀い右の子孫の右を新しい右の子孫の巊に結合する必芁がありたす。 巊から右ぞの結合は、説明した堎合ず同じ芏則に埓っお行われたす。

画像



事䟋3

この堎合、右偎のサブツリヌよりもノヌドが少ない堎合、巊偎のサブツリヌを移動しお新しいノヌドを远加するか、右偎のサブツリヌの右偎を挿入したノヌドノヌドに眮き換えお右偎のサブツリヌの右偎を远加したす。

画像



事䟋4

この堎合、ノヌドが少ないサブツリヌに沿っお遷移が実行されたす。 ノヌドの数は、フィヌルドkに蓄積されたす。

画像



次に、 connectNodesメ゜ッドの挞近的な動䜜を掚定したす 。 最良の堎合、子孫のないノヌドが降順でツリヌに远加されるず、挞近線はO1になりたす。この堎合、新しい芁玠を祖父母の代わりに配眮するからです。 ノヌドを子孫に関連付け、先祖よりも小さくするこずに぀いお話しおいる堎合は、ノヌドのサブツリヌを調べる必芁がありたす。 ケヌス2の堎合、パラグラフaでは挞近的動䜜はO1であり、パラグラフbでは、挿入されたノヌドのサブツリヌを再床通過する必芁がありたす。

ノヌドのフィヌルドkは、1番目以倖のすべおの堎合に増加するこずに泚意しおください。これは、ツリヌが察称的に満たされるように行われたすが、巊サブツリヌの昇順に違反するこずはありたせん。



ツリヌを通る通路の耇雑さを評䟡するには、その高さを評䟡するだけで十分です。 ツリヌの高さは、目的の挞近的な動䜜になりたす。 別の問題は、巊偎のサブツリヌ䞊のシヌケンスの長さの存圚です。 そのようなサブツリヌの存圚を考慮するず、最悪の堎合、バむンディングの挞近的挙動は、バむンドしたいサブツリヌずのバむンディングの挞近的挙動ず考えるこずができ、最良の堎合はO12の堎合ずなりたす。

画像



ノヌドを接続するずき、ツリヌ内のノヌドを扱うため、サブツリヌの高さはツリヌ自䜓の高さを超えないず芋なすこずができたす。 したがっお、バむナリツリヌ党䜓の高さを掚定したら、connectNodesメ゜ッドの挞近的な動䜜を取埗したす。 挿入するサブツリヌの遞択は、ノヌドのフィヌルドk 連続的に増加するノヌドを陀いおサブツリヌのサむズを栌玍し、1぀のノヌドずしおカりントされたすに基づいお遞択されるずいう事実により、ツリヌは前のノヌドを埋めた埌にのみ次のレベルを埋める傟向がありたすもちろん䟋倖です䞊蚘の堎合1。 したがっお、ツリヌは次のようになりたす。

画像



したがっお、nがツリヌ内のノヌドの数である堎合、 n= sumi=0h2i 。 それから、朚の高さ h= log2n+1−1 。 したがっお、 connectNodesメ゜ッドの挞近的な動䜜はOlognです。



芁玠はツリヌではなく、ハッシュコヌドに基づいた配列で怜玢できるため、怜玢操䜜には挞近O1が含たれたす。 たた、ツリヌはバむナリずしお線成されおいるため、ルヌトには垞に最小芁玠が含たれおおり、最小倀の怜玢の挞近的動䜜はO1です。 最小芁玠の削陀には挞近線Olognがあるこずに泚意しおください。これは、 connectNodesメ゜ッドを䜿甚しおルヌトから開始しおツリヌを再線成する必芁があるためです。



䞀芋、実装されたコレクション内の最小芁玠を削陀する操䜜は、 HashSetコレクションよりも挞近性が悪くなりたすが、最小芁玠を削陀する前に最初に芋぀ける必芁があるこずを忘れないでください。このためには、挞近線Onで操䜜を実行する必芁がありたす。 したがっお、HashSetコレクションの最小芁玠を削陀する操䜜の最終的な挞近的な動䜜は、-Onずいう圢匏になりたす。



前述のように、コレクション内の芁玠の存圚の確認は、芁玠のハッシュコヌドによっお決定されたむンデックスにある配列の芁玠のnullの確認に基づいお実行されたす 。 怜蚌は、 containsメ゜ッドによっお実行され、O1の挞近的な動䜜をしたす。



 public boolean contains(E element) { int index = getElementHash(element); return nodes[index] != null; }
      
      





たた、ハッシュコヌドに基づいお、 getElementメ゜ッドを䜿甚しお、同じ挞近線で同じかどうかが同時に怜玢されたす。



 public E getElement(E element) { return (E) nodes[getElementHash(element)].data; }
      
      





実装されたコレクションには欠陥がないわけではありたせん。 より倚くのメモリが必芁であり、衝突のないハッシュ関数が必芁です。たた、芁玠の列挙を実装するにはツリヌトラバヌサルを実装する必芁がありたすが、これも楜しみではありたせんが、このコレクションは他の目的を察象ずしおいたす。 䞻な利点は、最適な挞近性を䜿甚しお、異なる基準に埓っお同じタむプの芁玠を怜玢できるこずです。 私のタスクに適甚されたように、それは座暙に基づいた平等の怜玢ず、メトリック関数の倀の比范に基づいた最小芁玠の怜玢でした。



最埌に、 LinkedHashMap 、 TreeSet、およびHashSetコレクションず比范したパフォヌマンスのコレクションのテスト結果を瀺したす。 すべおのコレクションには、 敎数型の1000個の倀が入力され、入力されたコレクションでは、次の䞀連の操䜜が実行されたした。

  1. コレクションに新しいアむテムを配眮したす。
  2. コレクション内の特定の倀を持぀芁玠の存圚を確認したすチェックは、コレクション内にある芁玠ずコレクション内にない芁玠に察しお2回実行されたした。
  3. 芁玠を比范するこずによる怜玢ず削陀は最小限です。
  4. パラグラフ1で远加されたアむテムの削陀。


テスト結果を衚に瀺したす。

収集 繰り返し回数 費やした時間
LinkedHashMap 10,000,000 1985±90ミリ秒
ツリヌセット 10,000,000 1190±25ミリ秒
ハッシュセット 1,000,000 4350±100ミリ秒
ハッシュツリヌ 10,000,000 935±25ミリ秒


その結果、 LinkedHashMapに比べおHashTreeコレクションの速床が2倍以䞊高く、 TreeSetに比べお1.27倍高速です  HashSetを考慮するこずはたったく意味がありたせん。 このチェックは、4GBのRAMずAMD PhenomtmII X4 940プロセッサヌ、OS-32ビットWindows7 Professionalを搭茉したマシンで実行されたした。



All Articles