關聯式容器構造【AVL樹】分析與實現

AVL 樹概念

二叉搜索樹雖可以縮短查找的效率,但如果數據有序或接近有序二叉搜索樹將退化爲單支樹,查找元素相當於在順序表中搜索元素,效率低下

當向二叉搜索樹中插入新結點後,保證每個結點的左右子樹高度之差的絕對值不超過1(需要對樹中的結點進行調整),降低樹的高度,從而減少平均搜索長度,這樣的樹即 AVL 樹

一棵AVL樹具有以下性質:

  1. 它的左右子樹都是AVL樹
  2. 左右子樹高度之差(簡稱平衡因子)的絕對值不超過1
  3. AVL樹有n個結點,其高度可保持在 O(log N) ,搜索時間複雜度 O(log N)

AVL 樹實現

AVL 樹定義

左孩子置空、右孩子置空、父親節點置空、平衡因子置 0

AVL 樹插入

  1. 插入(先按照二叉搜索樹的規律插入)
  2. 更新平衡因子
  3. 判斷平衡因子
    a:平衡因子爲 0,停止更新,插入結束
    b:平衡因子爲 1 / -1,繼續向上更新
    c:平衡因子爲 2 / -2,則pParent的平衡因子違反平衡樹的性質,需要對其進行旋轉處理

旋轉操作:
新節點插入較高左子樹的左側—左左:右單旋在這裏插入圖片描述
新節點插入較高右子樹的右側—右右:左單旋
在這裏插入圖片描述
新節點插入較高左子樹的右側—左右:先左單旋再右單旋
在這裏插入圖片描述
新節點插入較高右子樹的左側—右左:先右單旋再左單旋
在這裏插入圖片描述

AVL樹驗證

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

  1. 驗證其爲二叉搜索樹
    如果中序遍歷可得到一個有序的序列,就說明爲二叉搜索樹
  2. 驗證其爲平衡樹
    每個節點子樹高度差的絕對值不超過1(注意節點中如果沒有平衡因子)
    節點的平衡因子是否計算正確

AVL 樹代碼

#include <iostream>
#include <assert.h>
using namespace std;

template<class T>
struct AVLNode
{
	AVLNode<T>* _left;
	AVLNode<T>* _right;
	AVLNode<T>* _parent;
	T _value;
	//平衡因子
	int _bf;

	AVLNode(const T& value = T())
		: _left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _value(value)
		, _bf(0)
	{}
};

template<class T>
class AVLTree
{
public:
	typedef AVLNode<T> Node;
	typedef Node* pNode;

	void RotateR(pNode parent)//右旋
	{
		PNode pSubL = parent->_left;
		PNode pSubLR = pSubL->_right;
		parent->_left = pSubLR;
		
		if (pSubLR)
			pSubLR->_parent = parent;
		pSubL->_right = parent;
		PNode pParent = parent->_parent;
		parent->_parent = pSubL;
		pSubL->_parent = pParent;
		if (NULL == pParent)
		{
			_root = pSubL;
			pSubL->_parent = NULL;
		}
		else
		{
			if (pParent->_left == parent)
				pParent->_left = pSubL;
			else
				pParent->_right = pSubL;
		}
		// 根據調整後的結構更新部分節點的平衡因子
		parent->_bf = pSubL->_bf = 0;
	}

	void RotateL(pNode parent)//左旋
	{
		PNode pSubR = parent->_right;
		PNode pSubRL = pSubR->_left;
		parent->_right = pSubRL;

		if (pSubRL)
			pSubRL->_parent = parent;
		pSubR->_left = parent;
		PNode pParent = parent->_parent;
		parent->_parent = pSubR;
		pSubR->_parent = pParent;
		if (NULL == pParent)
		{
			_root = pSubR;
			pSubR->_parent = NULL;
		}
		else
		{
			if (pParent->_right == parent)
				pParent->_right = pSubR;
			else
				pParent->_left = pSubR;
		}
		// 根據調整後的結構更新部分節點的平衡因子
		parent->_bf = pSubL->_bf = 0;
	}

	void RotateLR(PNode pParent) //左右
	{
		PNode pSubL = pParent->_left;
		PNode pSubLR = pSubL->_right;

		// 旋轉之前,保存pSubLR的平衡因子,旋轉完成之後,需要根據該平衡因子來調整其他節點的平衡因子
			int bf = pSubLR->_bf;

		// 先進行左單旋
		RotateL(pParent->_pLeft);

		// 再進行右單旋
		RotateR(pParent);
		if (1 == bf)
			pSubL->_bf = -1;
		else if (-1 == bf)
			pParent->_bf = 1;
	}

	void RotateRL(PNode pParent)//右左
	{
		PNode pSubR = pParent->_right;
		PNode pSubRL = pSubR->_left;

		// 旋轉之前,保存pSubLR的平衡因子,旋轉完成之後,需要根據該平衡因子來調整其他節點的平衡因子
		int bf = pSubRL->_bf;

		// 先進行右單旋
		RotateR(pParent->_left);

		// 再進行左單旋
		RotateL(pParent);
		if (1 == bf)
			pSubR->_bf = -1;
		else if (-1 == bf)
			pParent->_bf = 1;
	}

	bool insert(const T& value)
	{
		// 1. 先按照二叉搜索樹的規則將節點插入到AVL樹中
		// 如果樹爲空,直接插入
		if (nullptr == _root)
		{
			_root = new pNode(data);
			return true;
		}
		// 按照二叉搜索樹的性質查找data在樹中的插入位置
		pNode pCur = _root;
		// 記錄pCur的雙親,因爲新元素最終插入在pCur雙親左右孩子的位置
		pNode pParent = nullptr;
		while (pCur)
		{
			pParent = pCur;
			if (data < pCur->_value)
				pCur = pCur->_left;
			else if (data > pCur->_value)
				pCur = pCur->_right; // 元素已經在樹中存在
			else
				return false;
		}
		// 插入元素
		pCur = new pNode(data);
		if (data < pParent->_value)
			pParent->_left = pCur;
		else
			pParent->_right = pCur;
		// 2. 新節點插入後,AVL樹的平衡性可能會遭到破壞,此時就需要更新平衡因子,
		// 的平衡性

		/*
		pCur插入後,pParent的平衡因子一定需要調整,在插入之前,pParent
		的平衡因子分爲三種情況:-1,0, 1, 分以下兩種情況:
		1. 如果pCur插入到pParent的左側,只需給pParent的平衡因子-1即可
		2. 如果pCur插入到pParent的右側,只需給pParent的平衡因子+1即可

		此時:pParent的平衡因子可能有三種情況:0,正負1, 正負2
		1. 如果pParent的平衡因子爲0,說明插入之前pParent的平衡因子爲正負1,插入後被調整成0,此時滿
		足
		AVL樹的性質,插入成功
		2. 如果pParent的平衡因子爲正負1,說明插入前pParent的平衡因子一定爲0,插入後被更新成正負1, 此
		時以pParent爲根的樹的高度增加,需要繼續向上更新
		3. 如果pParent的平衡因子爲正負2,則pParent的平衡因子違反平衡樹的性質,需要對其進行旋轉處理
		*/
		while (pParent)
		{
			// 更新雙親的平衡因子
			if (pCur == pParent->_pLeft)
				pParent->_bf--;
			else
				pParent->_bf++;
			// 更新後檢測雙親的平衡因子
			if (0 == pParent->_bf)
				break;
			else if (1 == pParent->_bf || -1 == pParent->_bf)
			{
				// 插入前雙親的平衡因子是0,插入後雙親的平衡因爲爲1 或者 -1 ,說明以雙親爲根的二叉
				// 樹的高度增加了一層,因此需要繼續向上調整
				pCur = pParent;
				pParent = pCur->_pParent;
			}
			else
			{
				// 雙親的平衡因子爲正負2,違反了AVL樹的平衡性,需要對以pParent
				// 爲根的樹進行旋轉處理
				if (2 == pParent->_bf && pCur->_bf == 1)
				{
					RotateL(pParent);
				}
				else if (2 == pParent->_bf && pCur->_bf == -1)
				{
					RotateRL(pParent);
				}
				else if (-2 == pParent->_bf && pCur->_bf == 1)
				{
					RotateLR(pParent);
				}
				else 
				{
					RotateR(pParent);
				}
			}
		}
		return true;
	}

    //中序
	void _InOrder(pNode root)
	{
		if (root)
		{
			_InOrder(root->_left);
			cout << root->_value << " ";
			_InOrder(root->_right);
		}
	}
	
	void InOrder()
	{
		_InOrder(_root);
		cout << endl;
	}

	int _Height(PNode root)
	{
		if (root == nullptr)
			return 0;
		int left = _Height(root->_left);
		int right = _Height(root->_right);
		return left > right ? left + 1 : right + 1;
	}

    //判斷平衡
	bool _IsBalanceTree(pNode root)
	{
		// 空樹也是AVL樹
		if (nullptr == root) 
			return true;

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

private:
	pNode _root = nullptr;
};

void test()
{
	AVLTree<int>* avl = new AVLTree<int>();
	int arr[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
	for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++)
	{
		avl->insert(arr[i]);
	}
}

int main()
{
	test();
	return 0;
}

AVL 樹優缺點分析

AVL樹是一棵絕對平衡的二叉搜索樹,其要求每個節點的左右子樹高度差的絕對值都不超過1,這樣可以保證查詢時高效的時間複雜度,即 。但是如果要對AVL樹做一些結構修改的操作,性能非常低下,比如:
插入時要維護其絕對平衡,旋轉的次數比較多,更差的是在刪除時,有可能一直要讓旋轉持續到根的位置。
因此:如果需要一種查詢高效且有序的數據結構,而且數據的個數爲靜態的(即不會改變),可以考慮AVL樹,但一個結構經常修改,就不太適合

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