C++ AVL ツリーの原理と実現

今日、私たちは二分探索ツリーの「兄貴分」を研究しています。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 ツリーの実装はここですべて完了しました。読んでサポートしていただきありがとうございます。

おすすめ

転載: blog.csdn.net/m0_69005269/article/details/130213017