AVL-tree でノードのバランスを計算する最良の方法を探しています。私はそれが機能していたと思っていましたが、いくつかの重い挿入/更新の後、正しく動作していないことがわかります(まったく)。
これは、2つの部分からなる質問です。最初の部分は、サブツリーの高さを計算する方法です。定義を知っています」ノードの高さは、最も長い下向きの長さですそのノードからリーフへのパス。」と理解していますが、実装に失敗しています。さらに混乱させるために、この引用はウィキペディアのツリーの高さで見つけることができます。「通常、値-1はノードのないサブツリーに対応し、ゼロは1つのノードのサブツリーに対応します。」
そして、2番目の部分はAVLツリーのサブツリーのバランス係数を取得することです。概念を理解するのに問題はありません。 "L
とR
サブツリーとR
"からL
を減算します。そして、これは次のように定義されます:BALANCE = NODE[L][HEIGHT] - NODE[R][HEIGT]
ウィキペディアで読むと、AVLツリーへの挿入を説明する最初の数行でこれが示されています:」バランスファクターが-1、0、または1になった場合、ツリーはまだAVL形式であり、回転はありません必要。"
次に、これが続きます「バランス係数が2または-2になった場合、このノードをルートとするツリーはアンバランスになり、ツリーの回転が必要になります。最大で1回または2回の回転はツリーのバランスをとる必要がありました。」-把握に問題はありません。
しかし(はい、常にあります)。
混乱を招く箇所は次のとおりです。テキストには「Rのバランス係数が1の場合、そのノードの(外部)右側で挿入が発生し、左回転が必要であることを意味します」。しかし、mを理解することにより、テキストは(引用したように)バランス係数が[-1, 1]
その後、バランスを取る必要はありませんでしたか?
私はコンセプトを把握することにとても近づいているように感じます。ツリーの回転を下げ、通常のバイナリ検索ツリーを実装し、AVLツリーを把握する寸前になりましたが、その本質的なエピファニーが欠けているようです。
編集:常にコードで何かを把握する方がずっと楽だったので、コード例は学術式よりも好まれますが、どんな助けも大歓迎です。
編集:すべての回答を「承認済み」としてマークできればいいのですが、私にとってはNIckの回答が最初に「あは」になりました。
Starblueが言うように、高さは単なる再帰的です。擬似コード内:
height(node) = max(height(node.L), height(node.R)) + 1
現在、高さは2つの方法で定義できます。ルートからそのノードまでのパスにあるノードの数、またはリンクの数になります。 参照したページ によると、最も一般的な定義はリンクの数です。その場合、完全な擬似コードは次のようになります。
height(node):
if node == null:
return -1
else:
return max(height(node.L), height(node.R)) + 1
ノードの数が必要な場合、コードは次のようになります。
height(node):
if node == null:
return 0
else:
return max(height(node.L), height(node.R)) + 1
いずれにしても、私が考えるリバランスアルゴリズムは同じように動作するはずです。
ただし、計算するのではなく、ツリーの高さ情報を保存および更新する場合、ツリーははるかに効率的です(O(ln(n)))毎回。 (O(n))
「Rのバランスファクターが1の場合」と表示されている場合、上部のバランスファクターが2の場合、右ブランチのバランスファクターについて話していることになります。ダブルローテーション。 (Pythonのような)擬似コード:
if balance factor(top) = 2: // right is imbalanced
if balance factor(R) = 1: //
do a left rotation
else if balance factor(R) = -1:
do a double rotation
else: // must be -2, left is imbalanced
if balance factor(L) = 1: //
do a left rotation
else if balance factor(L) = -1:
do a double rotation
これが理にかなっていることを願っています
高さは再帰によって簡単に実装され、サブツリーの高さの最大値に1を加えた値を取ります。
「Rのバランスファクター」とは、バランスが取れていないツリーの正しいサブツリーを指します。
その場でツリーの深さを計算する必要はありません。
操作を実行するときにそれらを維持できます。
さらに、実際には深度の追跡を維持する必要はありません。左右のツリーの深さの違いを簡単に追跡できます。
http://www.eternallyconfuzzled.com/tuts/datastructures/jsw_tut_avl.aspx
バランス係数(左右のサブツリーの差)を追跡するだけで、POVをプログラミングする方が簡単です。ただし、回転後のバランス係数の選別はPITAです...
高さを見つける別の方法を次に示します。ノードにheightという追加の属性を追加します。
class Node
{
data value; //data is a custom data type
node right;
node left;
int height;
}
次に、ツリーの単純な幅優先走査を行い、各ノードの高さの値を更新し続けます。
int height (Node root)
{
Queue<Node> q = Queue<Node>();
Node lastnode;
//reset height
root.height = 0;
q.Enqueue(root);
while(q.Count > 0)
{
lastnode = q.Dequeue();
if (lastnode.left != null){
lastnode.left.height = lastnode.height + 1;
q.Enqueue(lastnode.left);
}
if (lastnode.right != null){
lastnode.right.height = lastnode.height + 1;
q.Enqueue(lastnode.right);
}
}
return lastnode.height; //this will return a 0-based height, so just a root has a height of 0
}
乾杯、
さて、次の再帰関数を使用してツリーの高さを計算できます。
_int height(struct tree *t) {
if (t == NULL)
return 0;
else
return max(height(t->left), height(t->right)) + 1;
}
_
max()
および_struct tree
_の適切な定義を使用します。引用するパスの長さに基づいた定義にこれが対応する理由を理解するには、時間をかける必要があります。この関数は、空のツリーの高さとしてゼロを使用します。
ただし、AVLツリーのようなものの場合、実際に必要になるたびに高さを計算するとは思いません。代わりに、各ツリーノードには、そのノードをルートとするサブツリーの高さを記憶する追加フィールドが追加されます。ツリーは挿入および削除によって変更されるため、このフィールドは最新の状態に保つ必要があります。
上記のようにツリー内にキャッシュするのではなく、毎回高さを計算すると、AVLツリーの形状は正しいものの、期待される対数パフォーマンスが得られないと思われます。
混乱を招く箇所は次のとおりです。「Rのバランス係数が1の場合、そのノードの(外部)右側で挿入が行われ、左回転が必要であることを意味します」しかし、テキストを理解することから、(私が引用したように)バランス係数が[-1、1]の範囲内であれば、バランスを取る必要はない、と言ったのですか?
R
は、現在のノードN
の右側の子です。
balance(N) = +2
の場合、何らかの回転が必要です。しかし、どの回転を使用するのでしょうか?まあ、それはbalance(R)
に依存します:balance(R) = +1
の場合、N
を左回転させる必要があります。ただし、balance(R) = -1
の場合、何らかの二重回転が必要になります。
混乱を招く箇所は次のとおりです。「Rのバランス係数が1の場合、そのノードの(外部)右側で挿入が行われ、左回転が必要であることを意味します」しかし、テキストを理解することから、(私が引用したように)バランス係数が[-1、1]の範囲内であれば、バランスを取る必要はない、と言ったのですか?
さて、ひらめきの時間。
回転が何をするかを考えてください。左回転について考えてみましょう。
P = parent
O = ourself (the element we're rotating)
RC = right child
LC = left child (of the right child, not of ourself)
P
\
O
\
RC
/
LC
P
\
RC
/
O
\
LC
10
\
15
\
20
/
18
10
\
20
/
15
\
18
basically, what happens is;
1. our right child moves into our position
2. we become the left child of our right child
3. our right child's left child becomes our right
さて、ここで気づかなければならない大きなこと-この左回転は、ツリーの深さを変更していません。私たちはそれを行ったことでバランスが取れなくなりました。
しかし、これがAVLの魔法です。右の子を右に最初に回転させた場合、これは...
P
\
O
\
LC
\
RC
そして今、私たちがOを左に回転させると、これは...
P
\
LC
/ \
O RC
魔法!ツリーのレベルを取り除くことができました-ツリーのバランスを整えました。
ツリーのバランスをとることは、余分な深さを取り除き、上位レベルをより完全にパックすることを意味します-これはまさに私たちがやったことです。
シングル/ダブルローテーションに関するすべては、サブツリーを次のようにする必要があるということです。
P
\
O
\
LC
\
RC
あなたが回転する前に-そして、あなたはその状態に入るために右回転をしなければならないかもしれません。ただし、すでにその状態にある場合は、左回転のみを行う必要があります。
このBFSのようなソリューションは非常に簡単です。レベルを1つずつジャンプするだけです。
def getHeight(self,root, method='links'):
c_node = root
cur_lvl_nodes = [root]
nxt_lvl_nodes = []
height = {'links': -1, 'nodes': 0}[method]
while(cur_lvl_nodes or nxt_lvl_nodes):
for c_node in cur_lvl_nodes:
for n_node in filter(lambda x: x is not None, [c_node.left, c_node.right]):
nxt_lvl_nodes.append(n_node)
cur_lvl_nodes = nxt_lvl_nodes
nxt_lvl_nodes = []
height += 1
return height
BinaryTree<T, Comparator>::Node
にsubtreeHeight
データメンバを与え、コンストラクタで0に初期化し、次のように毎回自動的に更新します:
template <typename T, typename Comparator>
inline void BinaryTree<T, Comparator>::Node::setLeft (std::shared_ptr<Node>& node) {
const std::size_t formerLeftSubtreeSize = left ? left->subtreeSize : 0;
left = node;
if (node) {
node->parent = this->shared_from_this();
subtreeSize++;
node->depthFromRoot = depthFromRoot + 1;
const std::size_t h = node->subtreeHeight;
if (right)
subtreeHeight = std::max (right->subtreeHeight, h) + 1;
else
subtreeHeight = h + 1;
}
else {
subtreeSize -= formerLeftSubtreeSize;
subtreeHeight = right ? right->subtreeHeight + 1 : 0;
}
}
template <typename T, typename Comparator>
inline void BinaryTree<T, Comparator>::Node::setRight (std::shared_ptr<Node>& node) {
const std::size_t formerRightSubtreeSize = right ? right->subtreeSize : 0;
right = node;
if (node) {
node->parent = this->shared_from_this();
subtreeSize++;
node->depthFromRoot = depthFromRoot + 1;
const std::size_t h = node->subtreeHeight;
if (left)
subtreeHeight = std::max (left->subtreeHeight, h) + 1;
else
subtreeHeight = h + 1;
}
else {
subtreeSize -= formerRightSubtreeSize;
subtreeHeight = left ? left->subtreeHeight + 1 : 0;
}
}
データメンバーsubtreeSize
およびdepthFromRoot
も更新されることに注意してください。これらの関数は、ノード(すべてテスト済み)を挿入するときに呼び出されます。
template <typename T, typename Comparator>
inline std::shared_ptr<typename BinaryTree<T, Comparator>::Node>
BinaryTree<T, Comparator>::Node::insert (BinaryTree& tree, const T& t, std::shared_ptr<Node>& node) {
if (!node) {
std::shared_ptr<Node> newNode = std::make_shared<Node>(tree, t);
node = newNode;
return newNode;
}
if (getComparator()(t, node->value)) {
std::shared_ptr<Node> newLeft = insert(tree, t, node->left);
node->setLeft(newLeft);
}
else {
std::shared_ptr<Node> newRight = insert(tree, t, node->right);
node->setRight(newRight);
}
return node;
}
ノードを削除する場合は、subtreeSize++;
をsubtreeSize--;
に置き換えて、removeLeft
とremoveRight
の異なるバージョンを使用します。 rotateLeft
およびrotateRight
のアルゴリズムも、大きな問題なく適合できます。以下がテストされ、合格しました。
template <typename T, typename Comparator>
void BinaryTree<T, Comparator>::rotateLeft (std::shared_ptr<Node>& node) { // The root of the rotation is 'node', and its right child is the pivot of the rotation. The pivot will rotate counter-clockwise and become the new parent of 'node'.
std::shared_ptr<Node> pivot = node->right;
pivot->subtreeSize = node->subtreeSize;
pivot->depthFromRoot--;
node->subtreeSize--; // Since 'pivot' will no longer be in the subtree rooted at 'node'.
const std::size_t a = pivot->left ? pivot->left->subtreeHeight + 1 : 0; // Need to establish node->heightOfSubtree before pivot->heightOfSubtree is established, since pivot->heightOfSubtree depends on it.
node->subtreeHeight = node->left ? std::max(a, node->left->subtreeHeight + 1) : std::max<std::size_t>(a,1);
if (pivot->right) {
node->subtreeSize -= pivot->right->subtreeSize; // The subtree rooted at 'node' loses the subtree rooted at pivot->right.
pivot->subtreeHeight = std::max (pivot->right->subtreeHeight, node->subtreeHeight) + 1;
}
else
pivot->subtreeHeight = node->subtreeHeight + 1;
node->depthFromRoot++;
decreaseDepthFromRoot(pivot->right); // Recursive call for the entire subtree rooted at pivot->right.
increaseDepthFromRoot(node->left); // Recursive call for the entire subtree rooted at node->left.
pivot->parent = node->parent;
if (pivot->parent) { // pivot's new parent will be its former grandparent, which is not nullptr, so the grandparent must be updated with a new left or right child (depending on whether 'node' was its left or right child).
if (pivot->parent->left == node)
pivot->parent->left = pivot;
else
pivot->parent->right = pivot;
}
node->setRightSimple(pivot->left); // Since pivot->left->value is less than pivot->value but greater than node->value. We use the NoSizeAdjustment version because the 'subtreeSize' values of 'node' and 'pivot' are correct already.
pivot->setLeftSimple(node);
if (node == root) {
root = pivot;
root->parent = nullptr;
}
}
どこ
inline void decreaseDepthFromRoot (std::shared_ptr<Node>& node) {adjustDepthFromRoot(node, -1);}
inline void increaseDepthFromRoot (std::shared_ptr<Node>& node) {adjustDepthFromRoot(node, 1);}
template <typename T, typename Comparator>
inline void BinaryTree<T, Comparator>::adjustDepthFromRoot (std::shared_ptr<Node>& node, int adjustment) {
if (!node)
return;
node->depthFromRoot += adjustment;
adjustDepthFromRoot (node->left, adjustment);
adjustDepthFromRoot (node->right, adjustment);
}
コード全体を次に示します。 http://ideone.com/d6arrv