10

AVLツリーの実装を作成しましたが、AVLツリーは非常に複雑な構造であるため、テストする必要があります。だから問題は-どうすればそれをテストできますか?

この瞬間まで、私は次のテストを行っています。

  1. 基本的な健全性チェック-すべてのノードの高さが最大に等しいことをチェックします。子ノードの高さ+1、バランスは[-1、1]、左の子のキー<このノードのキー<右の子のキー、循環参照はありません(ノードの左の子がノード自体であるように)。

  2. AVLツリー(および全体としての二分探索木)での順序どおりの走査が、基になるセットからの値を順番に返すことを確認します。

  3. AVLツリーの高さが厳密に1.44*log2(N + 2)-1(Nは要素の数)未満であることを確認します-AVLツリーの作成者によって証明されました。

  4. ビジュアルチェック-うまく機能しません。ツリーを描画しようとします(最初の行にルートノード、次の行に彼の直接の子、3行目にルートノードの直接の子の子など)が、それはでのみ機能します小さな木、大きな木にとっては完全な混乱になります。

  5. ロシアのウィキペディアは、2回の挿入で1回のリバランスが必要であり、5回の除去でも1回のリバランスが必要であることが実験的に証明されていると述べていますが、本当にそうですか?英語版ウィキペディアはそれについて何も述べていません。私のAVLの場合、2回の挿入または4回の削除に1回のリバランスが必要でしたが、これはまったく同じではありません。

これらのテストで十分かもしれませんが、実装するのが難しくないテストが他にある場合は、それを実行してみませんか?

4

5 に答える 5

40

これらすべての回答の精神で、基本的なケースでは不十分であることを示すために、いくつかの具体的な例を提供したいと思いました。

挿入 - 左/右のリバランス

挿入操作については、次の AVL バランス バイナリ ツリーを検討してください。

  20+       20+           __20+__
 /         /  \          /       \
4         4    26       4         26
         / \           / \       /  \
        3   9         3+  9    21    30
                     /   / \
                    2   7   11

これらのツリーのいずれかに 8 または 15 (たとえば) を挿入すると、本質的に同じ左/右の再バランスがトリガーされますが、最終結果は各ツリーと挿入値で大きく異なります。つまり、挿入された値の最終的な着陸場所と、ノード (4) とノード (20) の最終的なバランス係数は、ノード (4) の下の右側の子の相対値 (存在する場合) に完全に依存します。これらのケースのいずれか 1 つだけをテストしても、他のケースの正しさは必ずしも証明されません。注: node(4) は、最初にこれらのケースでバランスを取る必要があります。node(4) の初期の不均衡は、最終的に node(20) には影響しません。

ケース 1a: インサート 15

  20+      20++         20++      15
 /        /            /         /  \
4     => 4-     =>   15+     => 4    20
          \         /
           15      4

ケース 2a: インサート 15

    20+          20++           20++         9
   /  \         /  \           /  \         / \
  4    26 =>   4-   26 =>     9+   26 =>   4+  20
 / \          / \            / \          /   /  \
3   9        3   9-         4+  15       3  15    26
                   \       /
                    15    3

ケース 3a: インサート 15

      __20+__                _20++_                  __20++_                ___9___
     /       \              /      \                /       \              /       \
    4         26    =>     4-       26    =>       9+        26    =>     4+      __20__
   / \       /  \         / \      /  \           / \       /  \         / \     /      \
  3+  9    21    30      3+  9-  21    30        4+  11-  21    30      3+  7  11-       26
 /   / \                /   / \                 / \   \                /         \      /  \
2   7   11             2   7   11-             3+  7   15             2           15  21    30
                                 \            /
                                  15         2

ケース 1b: インサート 8

  20+      20++        20++      8
 /        /           /         / \
4     => 4-     =>   8+     => 4   20
          \         /
           8       4

ケース 2b: インサート 8

    20+          20++           20++         9
   /  \         /  \           /  \         / \
  4    26 =>   4-   26 =>     9++  26 =>   4   20-
 / \          / \            /            / \    \
3   9        3   9+         4            3   8    26
                /          / \
               8          3   8

ケース 3b: インサート 8

      __20+__                _20++_                  __20++_                ___9___
     /       \              /      \                /       \              /       \
    4         26           4-       26             9+        26           4        _20-
   / \       /  \         / \      /  \           / \       /  \         / \      /    \
  3+  9    21    30 =>   3+  9+  21    30 =>     4   11   21    30 =>   3+  7-  11      26
 /   / \                /   / \                 / \                    /     \         /  \
2   7   11             2   7-  11              3+  7-                 2       8      21    30
                            \                 /     \
                             8               2       8

より複雑なケースは、バランス係数の計算の最適化 (つまり、ツリー全体を再計算するのではなく、影響を受けるノードに対してのみバランス係数を調整する) に取り組んでいたときの問題でした。

削除 - ダブルリバランス

次に、削除操作のためにこれらのツリーを検討してください。

  2            ___6___               ___5___
 / \          /       \             /       \
1   4        2         9           2         8
   / \      / \       / \         / \       / \
  3   5    1   4     8   B       1   3     7   A
              / \   /   / \           \   /   / \
             3   5 7   A   C           4 6   9   B
                            \                     \
                             D                     C

これらの各ツリーから node(1) を削除します。ケース 1 はケース 2 を効果的に証明しますが、ケース 3 はまったく証明しないことに注意してください。

ケース1

  2          2            4
 / \          \          / \
1   4    =>    4    =>  2   5
   / \        / \        \
  3   5      3   5        3

ケース 2

    ___6___                ___6___                 ___6___
   /       \              /       \               /       \
  2         9            2         9             4         9
 / \       / \            \       / \           / \       / \
1   4     8   B     =>     4     8   B      => 2   5     8   B
   / \   /   / \          / \   /   / \         \       /   / \
  3   5 7   A   C        3   5 7   A   C         3     7   A   C
                 \                      \                       \
                  D                      D                       D

ケース 3

    ___5___              ___5___                 ___5___                   ____8____
   /       \            /       \               /       \                 /         \
  2         8          2         8             3         8              _5_          A
 / \       / \          \       / \           / \       / \            /   \        / \
1   3     7   A     =>   3     7   A      => 2   4     7   A     =>   3     7      9   B
     \   /   / \          \   /   / \                 /   / \        / \   /            \
      4 6   9   B          4 6   9   B               6   9   B      2   4 6              C
                 \                    \                       \
                  C                    C                       C
于 2012-12-12T16:16:21.427 に答える
6

本やインターネットには AVL ローテーションの例がたくさんありますが、私が見つけたものは恣意的で、挿入と削除の 4 つのケースすべての簡単な例が含まれている場所はありませんでした。

これらは、4 種類のローテーションについて思いついた最も単純なテスト ケースです。説明を簡単にするために、ASCII 文字をキーとして使用し、テスト ケースを文字列として表現できるようにしました。たとえば、文字列「abc」は「a」を挿入し、「b」を挿入してから「c」を挿入します。

完全なテスト ケースではかなり複雑なツリーが作成されるため、2 つのテスト スイートを作成しました。1 つ目はローテーションを引き起こしますが、ローテーションされるノードのサブツリーが空のため、実際に何が起こったのかを簡単に確認できます。2 番目のスイートには、ローテーション コードを完全にテストするための空でないサブツリーがあります。

回転には 2 つの異なる命名法があるようです - 私が 2L 回転として学んだことは、一部の本では rl 回転と呼ばれ、2R 回転は lr 回転と呼ばれています。以下のテキストは 2R/2L を使用しています。

これらは、挿入の簡単なテスト ケースです。

「abc」、「c」の挿入で 1L 回転が必要になります

a                   b
 \                 / \
  b   == 1L ==>   a   c
   \
    c

「cba」、「a」の挿入で 1R 回転が必要になります

    c               b
   /               / \
  b   == 1R ==>   a   c
 /
a 

「b」のインサートの「acb」は 2L ローテーションが必要です

a                  b
 \                / \
  c   == 2L ==>  a   c
 /
b

「b」のインサートの「cab」は 2R 回転が必要です

  c                b
 /                / \
a     == 2R ==>  a   c
 \
  b

削除用

「bcad」、「a」を削除すると、1L ローテーションが必要になります

  b                   c
 x \                 / \
a   c   == 1L ==>   b   d
     \
      d

「cbda」、「d」を削除すると、1R ローテーションが必要になります

    c                  b
   / x                / \
  b   d  == 1R ==>   a   c
 /
a 

「a」の削除に関する「bdac」には 2L ローテーションが必要です

  b                  c
 x \                / \
a   d   == 2L ==>  b   d
   /
  c

「d」の削除に関する「cadb」には 2R ローテーションが必要です

  c                  b
 / x                / \
a   d   == 2R ==>  a   c
 \
  b

より複雑なテスト ケースにはサブツリーがあり、ほとんどが 1 つのノードです。この記事を短くするために、挿入と削除のテスト ケースを組み合わせます。削除例は、削除文字の挿入をスキップして挿入例になります。たとえば、「cadb」の 2R 単純な削除ケースを使用すると、「d」の挿入をスキップして挿入ケース「cab」になります。この結果の 1 つは、以下の二重ローテーションのケースでは、削除するノードを挿入した後、ツリーのバランスを維持するために余分なノードを挿入する必要があることです。これにより、挿入ケースが最小ではなくなります。

「a」の削除時の「cbedfag」または「a」のスキップと「g」の挿入には、c で 1L ローテーションが必要です。

      c                 e
     / \               / \
    b   e  == 1R ==>  c   f
   x   / \           / \   \
  a   d   f         b   d   g
           \
            g

「g」の削除または「g」のスキップと「a」の挿入の「ecfbdga」は、e で 1R ローテーションを必要とします。

      - e -                 c
     /     \               / \
    c       f  == 1R ==>  b   e
   / \       x           /   / \
  b   d       g         a   d   f
 /
a

「b」の削除または「b」のスキップと「f」の挿入の「ecjadhkgilbf」では、j で 2L ローテーションが必要になり、次に e が必要になります。挿入ケースは、必要に応じて「d」の挿入をスキップできます。

    - e -                       —- h —-
   /     \                     /       \
  c       j                   - e-      j
 / \     / \   == 2L ==>     /    \    / \
a   d   h   k               c      g  i   k
 x     / \   \             / \    /        \
  b   g   i   l           a   d  f          l
     /
    f

「j」の削除または「j」のスキップと「g」の挿入の「hckbeiladfjg」には、c で 2R 回転が必要であり、次に b. 挿入ケースは、オプションで「l」の挿入をスキップできます</p>

      - h -                    - e -
     /     \                  /     \
    c       k                c       - h -
   / \     / \  == 2R ==>   / \     /     \
  b   e   i   l            b   d   f       k
 /   / \   x              /         \     / \
a   d   f   j            a           g   i   l
         \
          g

質問の方法 1 と 2 を使用して、ツリーが必要に応じて再調整されていることを確認します (つまり、ツリーがまだ整然としてバランスが取れていることを確認します)。本当に確実にしたい場合は、視覚的なテスト ケースを変換して、最終的なツリーの深さとバランスの値のリストを含め、順不同のトラバーサル中に検証します。

于 2014-06-05T15:43:22.017 に答える
3

AVL ツリーの重要な特性は、そのサブツリーのそれぞれが AVL ツリーでもあるということです。つまり、基本的なシナリオをカバーすることで、AVL ツリーの機能を幅広くカバーできるはずです。

言い換えれば、それらを可能にする最小のツリー構造で行われたこれらのテストは、最も重要なものです:

  • 新しいツリーの作成。
  • 最初の値を挿入します。
  • より大きな値を挿入します。
  • より小さい値を挿入します。
  • LL ローテーションが発生する値を挿入します。
  • 他の回転も同様です。
  • 削除する場合も同じです。
  • Finding 値のすべてのバリアント。

実装がこれらのテストに合格した場合、おそらく大きなツリーでも合格するでしょう。ここでは、パフォーマンスとメモリ使用量はテストされていないことに注意してください。

于 2010-10-18T11:31:33.100 に答える
2

本当に実装を改善したい場合は、さまざまな挿入順序と削除順序のパターンを使用して、いくつかのブラック ボックス テストを行う必要があります。心に浮かぶいくつかのアイデアを次に示します。

  • 順不同
  • 昇順
  • 降順
  • 2 つのストリームをインターリーブします。1 つは昇順、もう 1 つは降順です。
    • 似たような値から始めて発散する
    • 端から始めて真ん中で合流
    • 端から始めて反対側の端に渡る
  • 上向き、下向き、中立バイアスのランダムウォーク
  • 上記のパターンの組み合わせで、挿入と削除を混同します。

正確性だけでなくパフォーマンスもテストする必要があります。これには、パフォーマンスを意味のある方法で測定できるように、大規模なデータ セットの構築が必要になる場合がある上記のパターンが適用されます。要素数が 100 の場合はすべて高速ですが、要素数が 10 5の場合、O(N 2 ) と O( N log N )の差は非常に大きくなります。

また、同じ値を 2 回追加または削除するなど、不適切な入力がないかどうかもテストする必要があります (重複を許可しない場合)。

于 2010-10-17T23:15:41.623 に答える
1

挿入と削除の場合、発生する可能性のある特定の数 (それぞれ約 5 つと思い出す) のツリー操作があります。

別の特定の要素を追加すると、これらの操作のいずれかが実行されるように、これらの操作の直前にツリーを設定する必要があります。

次に、ツリーを調べます-それをダンプします。これはかなり単純なツリーで、約 10 個の要素しかありません。

すべての挿入/削除操作が正しく機能する場合、ツリーの重要なコア動作が検証されたことになります。

(注: (そうだったと思います) 挿入操作の 1 つは、この方法ではチェックできません。一時的に存在する中間状態です)。

于 2010-10-18T09:39:18.290 に答える