C++:模擬實現AVL樹

一、AVL樹的概念
二叉搜索樹雖然可以縮短查找的效率,但如果數據有序或接近有序,二叉搜索樹將退化爲單支樹,查找元素相當於在順序表中搜索元素,效率低。因此,兩位俄羅斯的數學家G.M.Adelson-Velskii和E.M.Landis在1962年發明了一種解決上述問題的方法:當向二叉搜索樹中插入新結點後,如果能保證每個結點的左右子樹高度之差的絕對值不超過1(需要對樹中的結點進行調整),即可降低樹的高度,從而減少平均搜索長度。
搜索二叉樹 == 排序二叉樹,因爲走它的中序遍歷的結果是有序的。
一棵AVL樹或者空樹,都有下面的性質

  1. 它的左右子樹都輸AVL樹
  2. 左右子樹的高度之差(簡稱平衡因子)的絕對值不超過1(-1/0/1)

在這裏插入圖片描述
平衡因子 = 右減左
如果一顆二叉搜索樹是高度平衡的,它就是AVL樹,如果它有n個節點,其高度可以保持在O(logN)
搜索時間複雜度O(log2n)

二、AVL樹的模擬實現
1 AVL樹的節點定義

template<class K, class V>
struct AVLTreeNode
{
	AVLTreeNode<K, V>* _left;
	AVLTreeNode<K, V>* _right;
	AVLTreeNode<K, V>* _parent;

	std::pair<K, V> _kv;
	int _bf;
};

2 AVL樹的插入
2.1 按照二叉搜索樹的方式插入新的節點

bool Insert(const std::pair<K, V>& kv)
	{
		if (_root == nullptr)
		{
			_root = new Node(kv);
			_root->_bf = 0;
			return true;
		}

		Node* parent = nullptr;
		Node* cur = _root;
		while (cur != nullptr)
		{
			if (cur->_kv.first < kv.first)
			{
				cur = parent;
				cur = cur->_right;
			}
			else if (cur->_kv.first > kv.first)
			{
				cur = parent;
				cur = cur->_left;
			}
			else
			{
				return false;
			}
		}
		cur = new Node(kv);
		if (parent->_kv.first > kv.first)
		{
			parent->_left = cur;
			cur->_parent = parent;
		}
		else
		{
			parent->_right = cur;
			cur->_parent = parent;
		}
		//更新後,可能破壞了AVL樹的平衡性,所以需要更新平衡因子,並檢測是否破壞了AVL樹的平衡性。

2.2 更新平衡因子

在這裏插入圖片描述

2.3 AVL樹旋轉
在這裏插入圖片描述
如果在一顆樹原本是平衡的AVL樹中插入一個新節點,可能造成不平衡,此時必須調整樹的結構,讓它平衡。根據節點插入位置的不同,AVL樹的旋轉分爲四種:

2.3.1 新節點插入較高左子樹的左側–左左:右單旋
在這裏插入圖片描述

		上圖在插入前,AVL樹是平衡的,新節點插入到30的左子樹(這不是左孩子)中,30左子樹增加了一層,導致以60爲根的二叉樹不平衡,要讓60平衡,只能把60的左子樹高度減少一層,右子樹增加一層。
		也就是把左子樹往上提,這樣60轉下來,因爲60比30大,只能將其放在30的右子樹,而如果30有右子樹,右子樹根的值一定大於30,小於60,只能將其放在60的左子樹,旋轉完成後,更新節點的平衡因子就好了。
在旋轉時,有以下幾種情況要考慮:
	1、30節點的右孩子可能存在,也可能不存在
	2、60可能是根節點,也可能是子樹
		如果是根節點,旋轉完成後,要更新根節點
		如果是子樹,可能是某個節點的左子樹,也可能是右子樹
void RotateRight(Node* parent)//parent->_bf == -2 && cur->_bf == -1
	{
		Node* cur = parent->_left;
		Node* right = cur->_right;//把cur的right給parent的左
		if (right != nullptr)
		{
			parent->_left = right;
			right->_parent = parent;
		}
		Node* node = parent->_parent;//先記錄一下parent原來的父節點
		cur->_right = parent;//把parent給cur的右
		parent->_parent = cur;//更新parent的父節點

		//把cur與parent之前的父節點鏈接
		if (parent == _root)//如果之前parent就是根,直接把根變成cur
		{
			_root = cur;
			_root->_parent = nullptr;
		}
		else//如果之前的parent有父節點,那麼就要鏈接
		{
			if (node->_left == parent)
				node->_left = cur;
			else
				node->_right = cur;
			cur->_parent = node;
			//node->_bf = 0;
			//cur->_bf = 0;
		}
	}

2.3.2 新節點插入較高右子樹的右側–右右:左單旋
在這裏插入圖片描述

void RotateLeft(Node* parent)//parent->_bf == 2 && parent->_bf ==1
	{
		Node* cur = parent->_right;
		Node* left = cur->_left;//把cur的left給parent的右

		if (left != nullptr)
		{
			parent->_right = left;
			left->_parent = parent;
		}
		Node* node = parent->_parent;//先記錄一下parent原來的父節點
		cur->_left = parent;//把parent給cur的左
		parent->_parent = cur;//更新parent的父節點
		//把cur與parent之前的父節點鏈接
		if (parent == _root)//如果parent就是之前的根,直接把根變成cur,並且把cur的_parent置空
		{
			_root = cur;
			_root->_parent = nullptr;
		}
		else//如果之前的parent右父節點,就要鏈接
		{
			if (node->_left == parent)
				node->_left = cur;
			else
				node->_right = cur;
			cur->_parent = node;
		}
	}

2.3.3 新節點插入較高左子樹的右側–左右:先左單旋在右單旋
在這裏插入圖片描述
把雙旋變成單旋在旋轉,即:先對30進行左單旋,然後在對90進行右單旋,旋轉完成後在考慮平衡因子的更新

void RotateLR(Node* parent)//parent->_bf == -2 && cur->_bf == 1
	{
		RotateLeft(parent->_left);
		RotateRight(parent);
	}

2.3.4 新節點插入較高右子樹的左側–右左:先右單旋在左單旋
在這裏插入圖片描述

void RoataeRL(Node* parent)//parent->_bf == 2 && cur->_bf == -1
	{
		RotateRight(parent->_right);
		RotateLeft(parent);
	}

三、AVL樹的驗證
AVL樹是在二叉搜索樹的基礎加入了平衡性的限制,因此要驗證AVL樹,可以分兩步:
1、驗證其是二叉搜索樹
2、驗證其是平衡樹

  • 每個節點的子樹高度差的絕對值不超過1
  • 節點的平衡因子是否計算正確
int _Height(Node* pRoot)
	 {
		 if (nullptr == pRoot)
		 return 0;
		 // 計算pRoot左右子樹的高度
		 int leftHeight = _Height(pRoot->_pLeft);
		 int rightHeight = _Height(pRoot->_pRight);
		 // 返回左右子樹中較高的子樹高度+1
		 return (leftHeight > rightHeight) ? (leftHeight + 1) : (rightHeight + 1);
	 }

	int Height()
	{
		_Height(_root);
	}

	bool _IsBalanceTree(Node* pRoot)
	{
		 // 空樹也是AVL樹
		 if (nullptr == pRoot)
		 return true;
		 // 計算pRoot節點的平衡因子:即pRoot左右子樹的高度差
		 int leftHeight = _Height(pRoot->_pLeft);
		 int rightHeight = _Height(pRoot->_pRight);
		 int diff = rightHeight - leftHeight;
		 // 如果計算出的平衡因子與pRoot的平衡因子不相等,或者
		 // pRoot平衡因子的絕對值超過1,則一定不是AVL樹
		 if (diff != pRoot->_bf || (diff > 1 || diff < -1))
		 return false;
		 // pRoot的左和右如果都是AVL樹,則該樹一定是AVL樹
		 return _IsBalanceTree(pRoot->_pLeft) && _IsBalanceTree(pRoot->_pRight);
	}
	
	bool IsBalanceTree(Node* root)
	{
		 _IsBalanceTree(_root);
	}

驗證用例:{16, 3, 7, 11, 9, 26, 18, 14, 15}
在這裏插入圖片描述
四、AVL樹的性能
AVL樹是一顆絕對平衡的二叉搜索樹,其要求每個節點的左右子樹高度差的絕對值不超過1,這樣可以保證查詢時高效的時間複雜度,即log(N)。但是如果要對AVL樹做一些結構的修改的操作,性能非常低,比如:插入時要維護其絕對平衡,旋轉的次數比較多,更差的是在刪除是,有可能要一直旋轉到根的位置。因此需要一種查詢高效且有序的數據結構,而且數據的個數是靜態的,可以考慮AVL樹,但如果是經常涉及修改的場景,就不太適合。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章