ストレッチツリーの紹介 スプレー ツリーは特殊な二分探索ツリーです。 特別なのは、バイナリ検索ツリーであることに加えて、ノードにアクセスすると、スプレーツリーがノードを回転させてルートにする機能も備えていることです。これを行う利点は、次回ノードにアクセスするときに、ノードにすばやくアクセスできることです。 特性 - 通常の二分探索木と比較すると、あらゆる状況でのあらゆる操作に対して償却複雑度が O(log2n) であり、時間パフォーマンスが優れています。
- 赤黒木や AVL 木などの一般的なバランス型二分木と比較すると、追加ノード情報が少なく、空間パフォーマンスが優れ、プログラミングの複雑さが低くなります。
- 多くの場合、検索操作では、後続のクエリは前のクエリと高い相関関係があります。このように、各クエリ操作ではノードがツリーのルート ノードに回転されるため、次のクエリ操作を迅速に完了できます。
- 区間に対してクエリ、変更、削除などの操作を行うことができ、セグメント ツリーとツリー配列のすべての機能を実現できます。
回転 スプレー ツリーは、スプレー ツリーに対する各クエリ、変更、および削除操作の後に回転操作 Splay(x, root) を実行し、ノード x をツリーのルートに回転させることにより、O(log2n) の償却複雑度を実現します。 ストレッチ ツリーには 6 種類の回転があります。ミラー イメージの重複が削除されると、ジグ (ザグ)、ジグジグ (ザグザグ)、ジグザグ (ザグジグ) の 3 種類になります。 1 下から上に回転する 1.1 ジグザグ回転 図に示すように、x ノードの親ノードは y であり、x は y の左の子ノードであり、y ノードはルートです。 x ノードを y の親ノードにするには、x ノードに対して右回転 (ジグ操作) を実行するだけで済みます。これにより、x はスプレー ツリーのルート ノードになります。 1.2 ジグジグ回転 上図に示すように、x ノードの親ノード y と y の親ノード z は直線チェーン上にあります。このとき、まずyノードとzノードにジグ回転を行い、次にxノードとyノードにジグ回転を行うと、最終的に右図のようになり、xはyとzの祖先ノードになります。 1.3 ジグザグ回転 上図に示すように、x ノードの親ノード y と y の親ノード z はジグザグ チェーン上にあります。このとき、x ノードと y ノードは最初にジグ回転し、次にザグ回転して、最終的に右図のようになり、x は y と z の祖先ノードになります。 2 上から下に回転する このアプローチでは、ノードが親ノードへの参照を格納する必要はありません。ツリーを下に向かってノード x を検索すると、検索パス上にあるノードとそのサブツリーが削除されます。 2 つの一時的なツリー (左のツリーと右のツリー) を構築します。削除されていないノードで構成されるツリーは、中央値ツリーと呼ばれます。 - 現在のノードxはツリーのルートです
- 左木Lはxより小さいノードを格納する。
- 右木Rはxより大きいノードを格納する
最初は、x はツリー T のルートであり、左のツリー L と右のツリー R は両方とも空です。 3 つの回転操作: 2.1 ジグザグ 図に示すように、x ノードの子ノード y が探しているノードです。y ノードを右回転 (ジグ操作) して x の親ノードにするだけで、y をスプレー ツリーのルート ノードにすることができます。中央のツリーのルートとして y を使用します。同時に、x ノードを右のツリー R に移動します。明らかに、右のツリーのノードは、検索するノードよりも大きいです。 2.2 ジグジグ回転 上図に示すように、x ノードの左の子ノード y と y の左の子ノード z は直線上にあり、検索対象のノードは z ノードをルートとするサブツリー内にあります。このとき、x ノードと y ノードをジグザグにし、次に z と y をジグザグにして、z を中央のツリーのルートにし、y とそのサブツリーを右側のツリー R にマウントします。 2.3 ジグザグ回転 上図に示すように、x ノードの左の子ノード y と y の右の子ノード z はジグザグ チェーン上にあり、検索対象の要素は z をルートとするサブツリー内にあります。このとき、まず x ノードと y ノードにジグ回転を実行し、x とその右サブツリーを右ツリー R にマウントします。このとき、y は中央ツリーのルート ノードになります。次に、z ノードと y ノードにザグ回転を実行し、z が中央ツリーのルート ノードになるようにします。 2.4 マージ 最後に、ノードが見つかった後、または空のノードに遭遇した後、左、中央、右のツリーをマージする必要があります。図に示すように、左のツリーは中央のツリーの左下にマウントされ (トラバーサル順序の要件を満たしています)、右のツリーは中央のツリーの右下にマウントされます (トラバーサル順序の要件を満たしています)。 スプレイツリーの実装 1. ノード - パブリッククラスSplayTree<TはComparable<T>>を拡張します{
- private SplayTreeNode<T> mRoot; // ルートノード
-
- パブリッククラスSplayTreeNode<TはComparable<T>>を拡張します{
- T key ; // キーワード (キー値)
- SplayTreeNode<T> left ; // 左の子
- SplayTreeNode<T> right ; // 右の子
-
- パブリックSplayTreeNode() {
- this.left = null ;
- this.right = null ;
- }
-
- パブリックSplayTreeNode(Tキー、SplayTreeNode<T>左、SplayTreeNode<T>右) {
- this.key =キー;
- this.left =左;
- this.right =右;
- }
- }
- }
SplayTree はスプレー ツリーであり、SplayTreeNode はスプレー ツリー ノードです。ここでは、SplayTreeNode を SplayTree の内部クラスとして定義します。スプレー ツリー SplayTree には、スプレー ツリーのルート ノード mRoot が含まれます。 SplayTreeNode にはいくつかのコンポーネントが含まれています: - key – スプレー ツリーのノードをソートするために使用されるキーワードです。
- left – 左の子です。
- 正しい – 正しい子供です。
2. 回転 - /*
- *キーに対応するノードをルートノードになるように回転し、ルートノードを返します。
- *
- * 知らせ:
- * (a): スプレーツリーに「キー値がキーであるノード」が存在します。
- *キー値が key のノードを回転してルート ノードにします。
- * (b): スプレーツリーには「キー値がキーであるノード」が存在せず、 key < tree.keyです。
- * b-1 「キー値がキーであるノード」の前身ノードが存在する場合、 「キー値がキーであるノード」の前身ノードをルートノードに回転します。
- * b-2 「キー値がキーであるノード」の先行ノードが存在する場合、そのキーはツリー内のどのキー値よりも小さいことを意味します。この場合、最小のノードがルートノードになるように回転します。
- * (c): スプレー ツリーには「キー値が key のノード」が存在せず、 key > tree. key です。
- * c-1 「キー値がキーであるノード」の後継ノードが存在する場合、 「キー値がキーであるノード」の後継ノードをルートノードになるように回転します。
- * c-2 「キー値がキーであるノード」の後継ノードが存在しない場合は、そのキーがツリー内のどのキー値よりも大きいことを意味します。この場合、最大のノードがルートノードになるように回転します。
- */
- プライベートSplayTreeNode<T> splay(SplayTreeNode<T> ツリー、Tキー) {
- ツリーがnullの場合
- ツリーを返します。
-
- SplayTreeNode<T> N = 新しい SplayTreeNode<T>();
- スプレイツリーノード<T> l = N;
- スプレイツリーノード r = N;
- スプレイツリーノード c;
-
- のために(; ; ) {
- int cmp = key .compareTo(tree.key ) ;
- (cmp < 0) の場合 {
- もし(キー.compareTo(tree.left .key ) < 0) {
- c = tree.left ; /*右に回転*/
- ツリーの左端をc.rightにします。
- c.right = 木;
- ツリー = c;
- もしtree.leftがnullならば
- 壊す;
- }
- r.left = tree; /*右にリンク*/
- r = 木;
- ツリー = tree.left ;
- }そうでない場合 (cmp > 0) {
-
- もしtree.rightがnullならば
- 壊す;
-
- if ( key .compareTo(tree.right.key ) > 0) {
- c = tree.right ; /*左に回転*/
- ツリーの右端をc.left にします。
- c.left = 木;
- ツリー = c;
- もしtree.rightがnullならば
- 壊す;
- }
-
- l.right = tree; /*左にリンク*/
- l = 木;
- ツリー = tree.right ;
- }それ以外{
- 壊す;
- }
- }
- l.right = tree.left ; /* アセンブル */
- r.left = tree.right ;
- ツリーの左端をN.rightにします。
- ツリーの右端をN.leftにします。
-
- ツリーを返します。
- }
-
- パブリックvoid splay(Tキー) {
- mRoot = splay(mRoot、キー);
- }
上記のコードの目的は、キー値が key のノードをルート ノードに回転し、ルート ノードを返すことです。処理には以下が含まれます: (a): スプレーツリーに「キー値がキーであるノード」が存在します。 キー値 key を持つノードを回転してルート ノードにします。 (b): スプレー ツリーには「キー値がキーであるノード」が存在せず、キー < ツリー -> キーです。 b-1) 「キー値キーを持つノード」の前身ノードが存在する場合、「キー値キーを持つノード」の前身ノードをルートノードにローテーションします。 b-2) キー値 key を持つノードの先行ノードが存在する場合、そのキーはツリー内のどのキー値よりも小さいことを意味します。この場合、最小のノードがルートノードになるように回転されます。 (c): ストレッチ ツリーには「キー値がキーであるノード」が存在せず、キー > ツリー -> キーです。 c-1) 「キー値キーを持つノード」の後継ノードが存在する場合、「キー値キーを持つノード」の後継ノードをルートノードになるように回転します。 c-2) キー値 key を持つノードの後継ノードが存在しない場合は、そのキーがツリー内のどのキー値よりも大きいことを意味します。この場合、最大のノードがルートノードになるように回転されます。 それぞれを説明するために、次の例が示されています。 次のスプレイ ツリーで 10 を検索するには、「右回転」->「右リンク」->「組み合わせ」という 3 つの手順が含まれます。 01、右手 コードの「右回転」部分に対応 02、右リンク コード内の「link right」の部分に対応 03. 組み合わせ コードの「アセンブル」部分に対応する ヒント: 上記のスプレイ ツリーで「70」を検索すると、「例 1」と完全に対称になり、対応する操作は「左に回転」、「左にリンク」、「アセンブル」になります。 「15 が b-1 の場合を見つける、5 が b-2 の場合を見つける」などの他の状況は比較的単純なので、自分で分析できます。 3. 挿入 - /**
- * ノードをスプレーツリーに挿入し、ルートノードを返します
- * @param tree 拡張ツリーのルートノード
- * @param z 挿入されたノード
- * @戻る
- */
- プライベートSplayTreeNode<T>挿入(SplayTreeNode<T>ツリー、SplayTreeNode<T>z){
- int のCMP;
- SplayTreeNode<T> y = null ;
- ツリーノードを再生します。
-
- // zの挿入位置を見つける
- (x != null ) の場合 {
- y = x;
- cmp = z.key.compareTo ( x.key ) ;
- (cmp < 0)の場合
- x = x.left ;
- そうでない場合 (cmp > 0)
- x = x.right ;
- それ以外{
- System.out.printf ( "同じノード(%d)を挿入することはできません!\n" 、 z.key );
- ;
- ツリーを返します。
- }
- }
-
- y == null の場合
- ツリー = z;
- それ以外{
- cmp = z.key.compareTo ( y.key ) ;
- (cmp < 0)の場合
- y 左= z;
- それ以外
- y 右= z;
- }
-
- ツリーを返します。
- }
-
- パブリックボイド挿入(Tキー){
- SplayTreeNode<T> z = 新しい SplayTreeNode<T>(キー、 null 、 null );
-
- // 新しいノードの作成に失敗した場合は戻ります。
- ((z = new SplayTreeNode<T>( key , null , null )) == null )の場合
- 戻る;
-
- // ノードを挿入
- mRoot = (mRoot,z)を挿入します。
- // ノード (キー) をルートノードに回転します
- mRoot = splay(mRoot、キー);
- }
insert(key) は外部に提供されるインターフェースです。その機能は、新しいノード (ノードのキー値は key) を作成し、そのノードをストレッチ ツリーに挿入し、ノードを回転させてルート ノードにすることです。 insert(tree, z) は、ノード z をツリーに挿入するために使用される内部インターフェースです。 insert(tree, z) が tree に z を挿入する場合、tree はバイナリ検索ツリーとしてのみ扱われ、同じノードを挿入することは許可されません。 4. 削除 - /**
- *
- * @param ツリー拡張ツリー
- * @param key削除するノード
- * @戻る
- */
- プライベートSplayTreeNode<T>削除(SplayTreeNode<T>ツリー、Tキー){
- スプレイツリーノード<T> x;
-
- ツリーがnullの場合
- 戻る ヌル;
-
- //キー値 key を持つノードを検索し、見つからない場合は直接返します。
- if (search(tree, key ) == null )
- ツリーを返します。
-
- //キーに対応するノードを回転してルート ノードにします。
- ツリー = splay(ツリー、キー);
-
- ツリーの左がnullの場合
- // 「ツリーの先行ノード」をルートノードに回転します
- x = splay( tree.left 、 key );
- // ツリーノードを削除する
- ツリーの右端にカーソルを移動します。
- }
- それ以外
- x =ツリーの右;
-
- ツリー = null ;
-
- xを返します。
- }
-
- パブリックvoid削除(Tキー){
- mRoot = 削除(mRoot、キー);
- }
remove(key) は外部インターフェース、remove(tree, key) は内部インターフェースです。 remove(tree, key) の機能は、ストレッチ ツリー内のキー値が key のノードを削除することです。 まず、スプレー ツリー内でキー key を持つノードを検索します。見つからない場合は直接戻ります。見つかった場合は、ノードを回転させてルート ノードにしてから、ノードを削除します。 スプレーツリー実装の完全なコード - パブリッククラスSplayTree<TはComparable<T>>を拡張します{
- private SplayTreeNode<T> mRoot; // ルートノード
-
- パブリッククラスSplayTreeNode<TはComparable<T>>を拡張します{
- T key ; // キーワード (キー値)
- SplayTreeNode<T> left ; // 左の子
- SplayTreeNode<T> right ; // 右の子
-
- パブリックSplayTreeNode() {
- this.left = null ;
- this.right = null ;
- }
-
- パブリックSplayTreeNode(Tキー、SplayTreeNode<T>左、SplayTreeNode<T>右) {
- this.key =キー;
- this.left =左;
- this.right =右;
- }
- }
-
- /*
- *キーに対応するノードをルートノードになるように回転し、ルートノードを返します。
- *
- * 知らせ:
- * (a): スプレーツリーに「キー値がキーであるノード」が存在します。
- *キー値が key のノードを回転してルート ノードにします。
- * (b): スプレーツリーには「キー値がキーであるノード」が存在せず、 key < tree.keyです。
- * b-1 「キー値がキーであるノード」の前身ノードが存在する場合、 「キー値がキーであるノード」の前身ノードをルートノードに回転します。
- * b-2 「キー値がキーであるノード」の先行ノードが存在する場合、そのキーはツリー内のどのキー値よりも小さいことを意味します。この場合、最小のノードがルートノードになるように回転します。
- * (c): スプレー ツリーには「キー値が key のノード」が存在せず、 key > tree. key です。
- * c-1 「キー値がキーであるノード」の後継ノードが存在する場合、 「キー値がキーであるノード」の後継ノードをルートノードになるように回転します。
- * c-2 「キー値がキーであるノード」の後継ノードが存在しない場合は、そのキーがツリー内のどのキー値よりも大きいことを意味します。この場合、最大のノードがルートノードになるように回転します。
- */
- プライベートSplayTreeNode<T> splay(SplayTreeNode<T> ツリー、Tキー) {
- ツリーがnullの場合
- ツリーを返します。
-
- SplayTreeNode<T> N = 新しい SplayTreeNode<T>();
- スプレイツリーノード<T> l = N;
- スプレイツリーノード r = N;
- スプレイツリーノード c;
-
- のために(; ; ) {
- int cmp = key .compareTo(tree.key ) ;
- (cmp < 0) の場合 {
- もし(キー.compareTo(tree.left .key ) < 0) {
- c = tree.left ; /*右に回転*/
- ツリーの左端をc.rightにします。
- c.right = 木;
- ツリー = c;
- ツリーの左端がnullの場合
- 壊す;
- }
- r.left = tree; /*右にリンク*/
- r = 木;
- ツリー = tree.left ;
- }そうでない場合 (cmp > 0) {
-
- もしtree.rightがnullならば
- 壊す;
-
- if ( key .compareTo(tree.right.key ) > 0) {
- c = tree.right ; /*左に回転*/
- ツリーの右端をc.left にします。
- c.left = 木;
- ツリー = c;
- もしtree.rightがnullならば
- 壊す;
- }
-
- l.right = tree; /*左にリンク*/
- l = 木;
- ツリー = tree.right ;
- }それ以外{
- 壊す;
- }
- }
- l.right = tree.left ; /* アセンブル */
- r.left = tree.right ;
- ツリーの左端をN.rightにします。
- ツリーの右端をN.leftにします。
-
- ツリーを返します。
- }
-
- パブリックvoid splay(Tキー) {
- mRoot = splay(mRoot、キー);
- }
-
-
-
- /**
- * ノードをスプレーツリーに挿入し、ルートノードを返します
- * @param tree 拡張ツリーのルートノード
- * @param z 挿入されたノード
- * @戻る
- */
- プライベートSplayTreeNode<T>挿入(SplayTreeNode<T>ツリー、SplayTreeNode<T>z){
- int のCMP;
- SplayTreeNode<T> y = null ;
- ツリーノードを再生します。
-
- // zの挿入位置を見つける
- (x != null ) の場合 {
- y = x;
- cmp = z.key.compareTo ( x.key ) ;
- (cmp < 0)の場合
- x = x.left ;
- そうでない場合 (cmp > 0)
- x = x.right ;
- それ以外{
- System.out.printf ( "同じノード(%d)を挿入することはできません!\n" 、 z.key );
- ;
- ツリーを返します。
- }
- }
-
- y == null の場合
- ツリー = z;
- それ以外{
- cmp = z.key.compareTo ( y.key ) ;
- (cmp < 0)の場合
- y 左= z;
- それ以外
- y 右= z;
- }
-
- ツリーを返します。
- }
-
- パブリックボイド挿入(Tキー){
- SplayTreeNode<T> z = 新しい SplayTreeNode<T>(キー、 null 、 null );
-
- // 新しいノードの作成に失敗した場合は戻ります。
- ((z = new SplayTreeNode<T>( key , null , null )) == null )の場合
- 戻る;
-
- // ノードを挿入
- mRoot =挿入(mRoot, z);
- // ノード (キー) をルートノードに回転します
- mRoot = splay(mRoot、キー);
- }
-
- /*
- * ノード(z)を削除し、削除されたノードを返す
- *
- * パラメータの説明:
- * bst ストレッチ ツリー
- * z 削除されたノード
- */
-
- /**
- *
- * @param ツリー拡張ツリー
- * @param key削除するノード
- * @戻る
- */
- プライベートSplayTreeNode<T>削除(SplayTreeNode<T>ツリー、Tキー){
- スプレイツリーノード x;
-
- ツリーがnullの場合
- 戻る ヌル;
-
- //キー値 key を持つノードを検索し、見つからない場合は直接返します。
- if (search(tree, key ) == null )
- ツリーを返します。
-
- //キーに対応するノードを回転してルート ノードにします。
- ツリー = splay(ツリー、キー);
-
- ツリーの左がnullの場合
- // 「ツリーの先行ノード」をルートノードに回転します
- x = splay( tree.left 、 key );
- // ツリーノードを削除する
- ツリーの右端にカーソルを移動します。
- }
- それ以外
- x =ツリーの右;
-
- ツリー = null ;
-
- xを返します。
- }
-
- パブリックvoid 削除(Tキー) {
- mRoot = 削除(mRoot、キー);
- }
-
- /*
- * (再帰的実装) 「ストレッチツリーx」でキー値keyを持つノードを検索します
- */
- プライベートSplayTreeNode<T>検索(SplayTreeNode<T> x、Tキー) {
- (x == null )の場合
- xを返します。
-
- int cmp =キー.compareTo ( x.key );
- (cmp < 0)の場合
- search(x.left , key )を返します。
- そうでない場合 (cmp > 0)
- search ( x.right 、 key )を返します。
- それ以外
- xを返します。
- }
-
- パブリックSplayTreeNode<T>検索(Tキー){
- search(mRoot, key )を返します。
- }
-
- /*
- * 最小ノードを見つける: ツリーをルート ノードとして拡張ツリーの最小ノードを返します。
- */
- プライベートSplayTreeNode<T>最小(SplayTreeNode<T>ツリー) {
- ツリーがnullの場合
- 戻る ヌル;
-
- while( tree.left != null )
- ツリー = tree.left ;
- ツリーを返します。
- }
-
- パブリックT最小値(){
- SplayTreeNode<T> p = 最小値(mRoot);
- (p != null )の場合
- p.keyを返します。
-
- 戻る ヌル;
- }
-
- /*
- * 最大ノードを見つける: ツリーをルート ノードとして拡張ツリーの最大ノードを返します。
- */
- プライベートSplayTreeNode<T>最大(SplayTreeNode<T>ツリー) {
- ツリーがnullの場合
- 戻る ヌル;
-
- while( tree.right != null )
- ツリー = tree.right ;
- ツリーを返します。
- }
-
- パブリックT最大値(){
- SplayTreeNode<T> p = 最大値(mRoot);
- (p != null )の場合
- p.keyを返します。
-
- 戻る ヌル;
- }
|