今日、私たちは二分探索ツリーの「兄貴分」を研究しています。AVL ツリーです。
AVL ツリーの概念:
AVL ツリーの正式名は 2 分平衡探索ツリーで、回転によって不平衡二分探索ツリーを平衡二分探索ツリーに変換します。まず、コードを使用して AVL ツリーのノードを定義しましょう。
template <class K,class V>
struct AVLTreeNode
{
pair<K, V> _kv;
AVLTreeNode<K, V>* _parent;
AVLTreeNode<K, V>* _left;
AVLTreeNode<K, V>* _right;
int _bf;
AVLTreeNode(const pair<K, V>& kv)
: _kv(kv)
, _parent(nullptr)
, _left(nullptr)
, _right(nullptr)
, _bf(0)
{
}
};
ノードを定義した後、挿入関数を実装します. AVL ツリーの特性は 2 値平衡ツリーと同じです. ノードがノードより小さい場合は左側に挿入され, ノードより大きい場合は左側に挿入されますノードよりも右側に挿入されます。ノード内のデータと同じサイズの場合、挿入されません。
template <class K,class V>
class AVLTree
{
typedef AVLTreeNode<K, V> Node;
public:
bool insert(const pair<K, V>& kv)
{
if (_root == nullptr)
{
_root = new Node(kv);
return true;
}
Node* cur = _root;
Node* parent = nullptr;
while (cur)
{
if (kv.first > cur->_kv.first)
{
parent = cur;
cur = cur->_right;
}
else if (kv.first < cur->_kv.first)
{
parent = cur;
cur = cur->_left;
}
else
{
return false;
}
}
//插入数据
cur = new Node(kv);
// 得判断cur插入它父亲的左边还是右边
if (parent->_kv.first > cur->_kv.first)
{
parent->_left = cur;
cur->_parent = parent;
}
else
{
parent->_right = cur;
cur->_parent = parent;
}
private:
Node* _root = nullptr;
};
次に、バランス係数を調整する必要がありますが、バランス係数を調整する際には、二分木のさまざまな状況を分析する必要があります。
二分木の回転:
簡単に言うと、回転は「左一回転」、「右一回転」、 「左右一回転」、「右左一回転」の4つに分けられます。
各ケースは異なるバランス係数に対応します。
左単回転:
ノード挿入のケースは多数あるため、抽象グラフを使用して n 個のケースを表すことができます。
したがって、左一回転の場合、親のバランス係数は2、右の子のバランス係数は1となります。そのコード実装は次のとおりです。
void RotateL(Node * parent)
{
Node* subR = parent->_right;
Node* subRL = subR->_left;
parent->_right = subRL;
if (subRL)
{
subRL->_parent = parent;
}
Node* ppNode = parent->_parent;
subR->_left = parent;
parent->_parent = subR;
if (ppNode == nullptr)
{
_root = subR;
_root->_parent = nullptr;
}
else
{
if (ppNode->_right == parent)
{
ppNode->_right = subR;
subR->_parent = ppNode;
}
else
{
ppNode->_left = subR;
subR->_parent = ppNode;
}
}
//调整bf的值
subR->_bf = parent->_bf = 0;
}
右スピン:
その実装は、左の単一回転の実装と似ています。親のバランス係数は -2 で、右側の子のバランス係数は -1 です。コードは次のように実装されます。
void RotateR(Node* parent)
{
Node* subL = parent->_left;
Node* subLR = subL->_right;
parent->_left = subLR;
if (subLR)
{
subLR->_parent = parent;
}
Node* ppNode = parent->_parent;
subL->_right = parent;
parent->_parent = subL;
if (ppNode == nullptr)
{
_root = subL;
_root->_parent = nullptr;
}
else
{
if (ppNode->_left == parent)
{
ppNode->_left = subL;
subL->_parent = ppNode;
}
else
{
ppNode->_right = subL;
subL->_parent = ppNode;
}
}
//更新平衡因子
subL->_bf = parent->_bf = 0;
}
概要: ppNode を定義する必要があります。その目的は、不平衡部分を平衡部分に変換し、上のノード (平衡) に接続することです。上記のノードは空の場合と空でない場合があるため、状況に応じて議論する必要があります。最後に、ノードのバランス係数を調整します。
左右一回転:
左一回転と右一回転の実態は、左斬り、右斜め(どちらも真っすぐ)です。左右のラジオは左側に突き出たポリラインです。
左右一回転は、左一回転、右一回転の順で実現される。ただし、これら 3 つのノードのバランス係数については、ケースバイケースで議論する必要があります。ノードが b の下に挿入された場合と c の下に挿入された場合は状況が異なるため、bf を定義し、bf の値を通じてバランス係数を調整する必要があります。変数コードは次のように実装されます。
void RotateLR(Node* parent)
{
Node* subL = parent->_left;
Node* subLR = subL->_right;
int bf = subLR->_bf;
RotateL(subL);
RotateR(parent);
if (bf ==-1)
{
subL->_bf = 0;
subLR->_bf = 0;
parent->_bf = 1;
}
else if (bf == 1)
{
subL->_bf = -1;
subLR->_bf = 0;
parent->_bf = 0;
}
else if (bf == 0)
{
subL->_bf = 0;
subLR->_bf = 0;
parent->_bf = 0;
}
else
{
assert(false);
}
}
右左单旋:
左右の単一回転は、左右の単一回転と同様です。つまり、最初に右の単一回転が実行され、次に左の単一回転が実行されます。コードは次のように実装されます。
void RotateRL(Node* parent)
{
Node* subR = parent->_right;
Node* subRL = subR->_left;
int bf = subRL->_bf;
RotateR(subR);
RotateL(parent);
if (bf == -1)
{
subR->_bf = 1;
subRL->_bf = 0;
parent->_bf = 0;
}
else if (bf == 1)
{
subR->_bf = 0;
subRL->_bf = 0;
parent->_bf = -1;
}
else if (bf == 0)
{
subR->_bf = 0;
subRL->_bf = 0;
parent->_bf = 0;
}
else
{
assert(false);
}
}
AVL ツリーのコード全体:
4 つの回転がすべて判明すると、AVL ツリー全体を実現できます。
template <class K,class V>
struct AVLTreeNode
{
pair<K, V> _kv;
AVLTreeNode<K, V>* _parent;
AVLTreeNode<K, V>* _left;
AVLTreeNode<K, V>* _right;
int _bf;
AVLTreeNode(const pair<K, V>& kv)
: _kv(kv)
, _parent(nullptr)
, _left(nullptr)
, _right(nullptr)
, _bf(0)
{
}
};
template <class K,class V>
class AVLTree
{
typedef AVLTreeNode<K, V> Node;
public:
bool insert(const pair<K, V>& kv)
{
if (_root == nullptr)
{
_root = new Node(kv);
return true;
}
Node* cur = _root;
Node* parent = nullptr;
while (cur)
{
if (kv.first > cur->_kv.first)
{
parent = cur;
cur = cur->_right;
}
else if (kv.first < cur->_kv.first)
{
parent = cur;
cur = cur->_left;
}
else
{
return false;
}
}
//插入数据
cur = new Node(kv);
// 得判断cur插入它父亲的左边还是右边
if (parent->_kv.first > cur->_kv.first)
{
parent->_left = cur;
cur->_parent = parent;
}
else
{
parent->_right = cur;
cur->_parent = parent;
}
//更新平衡因子
while (parent)
{
if (cur == parent->_left)
{
parent->_bf--;
}
else
{
parent->_bf++;
}
if (parent->_bf == 0)
{
break;
}
else if (parent->_bf == 1 || parent->_bf == -1)
{
cur = parent;
parent = parent->_parent;
}
else if (parent->_bf == 2 || parent->_bf == -2)
{
if (parent->_bf == 2 && cur->_bf == 1)
{
RotateL(parent);
}
else if (parent->_bf == -2 && cur->_bf == -1)
{
RotateR(parent);
}
else if (parent->_bf == 2 && cur->_bf == -1)
{
RotateRL(parent);
}
else if (parent->_bf == -2 && cur->_bf == 1)
{
RotateLR(parent);
}
else
{
assert(false);
}
break;
}
else
{
assert(false);
}
}
return true;
}
void RotateL(Node * parent)
{
Node* subR = parent->_right;
Node* subRL = subR->_left;
parent->_right = subRL;
if (subRL)
{
subRL->_parent = parent;
}
Node* ppNode = parent->_parent;
subR->_left = parent;
parent->_parent = subR;
if (ppNode == nullptr)
{
_root = subR;
_root->_parent = nullptr;
}
else
{
if (ppNode->_right == parent)
{
ppNode->_right = subR;
subR->_parent = ppNode;
}
else
{
ppNode->_left = subR;
subR->_parent = ppNode;
}
}
//调整bf的值
subR->_bf = parent->_bf = 0;
}
void RotateR(Node* parent)
{
Node* subL = parent->_left;
Node* subLR = subL->_right;
parent->_left = subLR;
if (subLR)
{
subLR->_parent = parent;
}
Node* ppNode = parent->_parent;
subL->_right = parent;
parent->_parent = subL;
if (ppNode == nullptr)
{
_root = subL;
_root->_parent = nullptr;
}
else
{
if (ppNode->_left == parent)
{
ppNode->_left = subL;
subL->_parent = ppNode;
}
else
{
ppNode->_right = subL;
subL->_parent = ppNode;
}
}
//更新平衡因子
subL->_bf = parent->_bf = 0;
}
void RotateLR(Node* parent)
{
Node* subL = parent->_left;
Node* subLR = subL->_right;
int bf = subLR->_bf;
RotateL(subL);
RotateR(parent);
if (bf ==-1)
{
subL->_bf = 0;
subLR->_bf = 0;
parent->_bf = 1;
}
else if (bf == 1)
{
subL->_bf = -1;
subLR->_bf = 0;
parent->_bf = 0;
}
else if (bf == 0)
{
subL->_bf = 0;
subLR->_bf = 0;
parent->_bf = 0;
}
else
{
assert(false);
}
}
void RotateRL(Node* parent)
{
Node* subR = parent->_right;
Node* subRL = subR->_left;
int bf = subRL->_bf;
RotateR(subR);
RotateL(parent);
if (bf == -1)
{
subR->_bf = 1;
subRL->_bf = 0;
parent->_bf = 0;
}
else if (bf == 1)
{
subR->_bf = 0;
subRL->_bf = 0;
parent->_bf = -1;
}
else if (bf == 0)
{
subR->_bf = 0;
subRL->_bf = 0;
parent->_bf = 0;
}
else
{
assert(false);
}
}
private:
Node* _root = nullptr;
};
AVL ツリーバランスのチェック:
AVL ツリーをテストする上で最も重要なことは、左のサブツリーと右のサブツリーの差の絶対値が 1 以下であるかどうかです。まず、再帰的に木の高さを求める Height 関数を実装します。
int Height(Node* root)
{
if (root == nullptr)
{
return 0;
}
int left = Height(root->_left);
int right = Height(root->_right);
return (left > right) ? left + 1 : right + 1;
}
次に、再帰関数によって各サブツリーの左右のサブツリーの高さの差を計算し、バランスを判断します。
bool IsBalance()
{
return _IsBalance(_root);
}
bool _IsBalance(Node* root)
{
if (root == nullptr)
{
return true;
}
int lheight = Height(root->_left);
int rheight = Height(root->_right);
if (rheight - lheight != root->_bf)
{
cout << "平衡因子异常:" <<root->_kv.first<< endl;
return false;
}
return abs(lheight - rheight) < 2 && _IsBalance(root->_left) && _IsBalance(root->_right);
}
AVL ツリーの実装はここですべて完了しました。読んでサポートしていただきありがとうございます。!