【C++从入门到踹门】第十六篇:AVL树

在这里插入图片描述

1.AVL树的概念

AVL树——自平衡二叉搜索树。

在这里插入图片描述

二叉搜索树的数据若是有序或是接近有序,则会退化成单支树,相当于在顺序表中搜索元素,复杂度为 O(n) 。因此,两位俄罗斯的数学家G.M.Adelson-Velskii和E.M.Landis在1962年发明了一种解决上述问题的方法:

向二叉搜索树中插入新结点后,如果能保证每个结点的左右子树高度之
差的绝对值不超过1(需要对树中的结点进行调整),即可保证为“对数高度”,从而降低树的高度,减少平均搜索长度。

AVL树所具备的性质

  • 一棵AVL树的左右子树都是AVL树(注意空树是AVL树的一种)
  • 左右子树的高度之差(平衡因子)的绝对值不超过1

在这里插入图片描述

一棵拥有n个结点的AVL树的搜索时间复杂度为 O(log2n)。

AVL树结点定义

我们实现 Key-Value 模型的AVL树,这里介绍AVL树的结点结构:

  • 三叉链:三个指针分别执行父结点,左子树结点和右子树结点
  • 键值对:作为数据存储的单元
  • 平衡因子:记录当前结点的左右子树的高度差,(右子树高度-左子树高度)
template<class K,class V>
struct AVLTreeNode
{
	AVLTreeNode<K, V>* _parent;
	AVLTreeNode<K, V>* _left;
	AVLTreeNode<K, V>* _right;
	//存储键值对
	pair<K, V> _kv;
	//平衡因子
	int bf;

	AVLTreeNode(const pair<K,V>& kv):
		_kv(kv),
		_left(nullptr),
		_right(nullptr),
		_parent(nullptr),
		bf(0)
	{}
};

同时构建先出AVLTree类的框架,我们重点关注插入,查找和删除的接口

template <class K,class V>
class AVLTree
{
	typedef AVLTreeNode<K, V> Node;
public:
	AVLTree() :_root(nullptr)
	{}

    //拷贝构造和赋值重载都要深拷贝

	~AVLTree()
	{
		//...
	}

	bool insert(const pair<K, V>& kv)
	{

	}

	Node* Find(const K& key)
	{

	}

	bool erase(const K& key)
	{

	}
private:
	Node* _root;
};

2. AVL树的插入

  1. 按照二叉搜索树的规则,将新增结点插入到树中(小的往左,大的往右,或者逆序)。
  2. 更新平衡因子,因为平衡因子描述了当前子树的平衡状态,如果平衡因子的绝对值等于2,就需要进行旋转操作,来使树继续保持平衡(下面详述)。

由于一个结点的平衡因子是否需要更新,是取决于该结点的左右子树的高度是否发生了变化,因此插入一个结点后,该结点的直系祖先结点的平衡因子可能需要更新。

一个可能的新增的情况:

在这里插入图片描述

因此需要我们“顺藤摸瓜”往上更新平衡因子,我们将新增结点的父结点称为parent,更新规则如下:

  1. 新增结点在parent结点的右边,那么parent的平衡因子++。
  2. 新增结点在parent结点的左边,那么parent的平衡因子–。

更新parent的平衡因子后,还需要进行如下判断:

  • 如果parent的平衡因子为1或者-1,还需继续往上更新平衡因子

    🚩parent的左右高度差变大,说明新增的结点导致parent为根结点的子树高度增加,parent的父结点的平衡因子也会变化。

  • 如果parent的平衡因子为0,则无需往上更新

    🚩parent的左右高度达到平衡,说明新增的结点填补了高度差,原先的高度保持不变,所以对parent的父结点的平衡因子不产生影响。

  • 如果parent的平衡因子为2或者-2,说明parent的平衡因子违反了AVL树的性质,需要对其进行旋转处理。

    🚩当parent的平衡因子为±2时,cur的平衡因子一定是±1,而不会是0(因为cur如果为零,就停止了向上更新平衡因子)。

    parent与cur两两组合下,共可能出现四种旋转情况:

    1. parent的平衡因子为 2 ,cur的平衡因子为 1 —— 左单旋(函数名RotateL)
    2. parent的平衡因子为 -2 ,cur的平衡因子为 -1 —— 右单旋(函数名RotateR)
    3. parent的平衡因子为 2 ,cur的平衡因子为 -1 —— 右左双旋(函数名RotateRL)
    4. parent的平衡因子为 -2 ,cur的平衡因子为 1 —— 左右双旋(函数名RotateLR)

    经过旋转处理之后的parent的平衡因子已经为0(下面会详述),无需往上更新,插入结点成功。

    这里insert函数返回值的类型与map的insert的返回值一致,方便我们后续修改AVL树。

于是可先整理出插入结点的代码框架:

pair<Node* , bool > insert(const pair<K, V>& kv)
{
    if (_root == nullptr)
    {
        _root = new Node(kv);
        return make_pair(_root,true);
    }
    Node* cur = _root;
    Node* parent=_root;
    while (cur)
    {
        parent = cur;
        if (kv.first > cur->_kv.first)
        {
            cur = cur->_right;
        }
        else if (kv.first < cur->_kv.first)
        {
            cur = cur->_left;
        }
        else
        {
            return make_pair(cur,false);
        }
    }
    cur = new Node(kv);
    Node* newnode = cur;
    if (kv.first > parent->_kv.first)
    {
        parent->_right = cur;
    }
    else
    {
        parent->_left = cur;
    }
    cur->_parent = parent;

    //更新平衡因子,检查是否需要旋转
    while (parent)//最远可更新到_root
    {
        //更新平衡因子
        if (cur == parent->_right)
            parent->_bf++;
        else
            parent->_bf--;
        //讨论平衡因子情况
        if (parent->_bf == 0)
        {
            break;
        }
        else if(parent->_bf==1 || parent->_bf==-1)
        {
            //parent的高度增加,会影响 parent->_parent
            //继续往上更新
            cur = parent;
            parent = parent->_parent;
        }
        else if(parent->_bf == 2 || parent->_bf == -2)
        {
            //parent所在子树已不平衡,旋转当前子树
            if (parent->_bf == 2 )
            {
                if (cur->_bf == 1)
                    RotateL(parent);//左单旋
                else//cur->_bf == -1
                    RotateRL(parent);//右左双旋
            }
            else //parent->_bf == -2
            {
                if (cur->_bf == -1)
                    RotateR(parent);//右单旋
                else//cur->_bf == 1
                    RotateLR(parent);//左右双旋
            }
            break;//旋转处理后,parent的平衡因子已经为0
        }
        else
        {
            assert(false);//说明在插入此节点之前树已经不平衡了,需检查代码。
        }
    }

    return make_pair(newnode,true);//插入成功
}

3.AVL树的旋转(出现不平衡子树)

前面谈的旋转是基于平衡因子的讨论,现在观察下造成“平衡破坏”的插入情况,可分为4种,

  • 外侧插入
    • parent(2),cur(1) —— 插入点在parent右子结点的右子树
    • parent(-2),cur(-1) —— 插入点在parent左子结点的左子树
  • 内测插入
    • parent(2),cur(-1) —— 插入点在parent右子结点的左子树
    • parent(-2),cur(1) —— 插入点在parent左子结点的右子树

在这里插入图片描述

单旋转

外侧插入的情况,仅需单旋转即可解决问题

左单旋

插入点在parent右子结点的右子树

在这里插入图片描述

为了调整状态,把cur向上提起,使parent自然向左侧下滑,由于cur的左子树需连接parent,所以先将cur的左子树挂到parent的右侧,这么做是因为,二叉搜索树的规则使我们知道 cur>parent,故cur左子树处于parent与cur之间,其也可以成为parent的右子树。

调整后cur成为该棵子树的根,由于cur平衡因子先前为1,而原来的父结点滑向了左子树,使得左高度+1,故此时cur的平衡因子为0,不用再往上更新平衡因子。

我们进一步抽象这种现象,得到普适的旋转情况:

在这里插入图片描述

不难理解,因为右边太重,所以需要左旋来维持平衡。

左单旋的代码如下

void RotateL(Node* parent)
{
    Node* Pparent = parent->_parent;
    Node* subR = parent->_right;
    Node* subRL = subR->_left;

    //parent 和 subR 的关系建立
    parent->_parent = subR;
    subR->_left = parent;

    //parent 和 subRL 的关系建立
    parent->_right = subRL;
    if (subRL != nullptr)
    {
        subRL->_parent = parent;
    }

    //subR 和 Pparent 的关系建立
    if (Pparent != nullptr)
    {
        subR->_parent = Pparent;
        if (parent == Pparent->_left)
        {
            Pparent->_left = subR;
        }
        else
        {
            Pparent->_right = subR;
        }
    }
    else
    {
        _root = subR;
        subR->_parent = nullptr;
    }

    //更新结点平衡因子
    subR->_bf = 0;
    parent->_bf = 0;
}

右单旋

插入点在parent左子结点的左子树

需要右单旋的情况和左单旋正好呈镜像:

在这里插入图片描述

把cur向上提起,使parent自然向右侧下滑,由于cur的右子树需连接parent,所以先将cur的右子树挂到parent的左侧,这么做是因为,二叉搜索树的规则使我们知道 cur < parent,故cur右子树处于cur与parent之间,其也可以成为parent的左子树。

调整后cur成为该棵子树的根,由于cur的平衡因子先前为-1,而原来的父结点滑向了右子树,使得右高度+1,故此时cur的平衡因子为0,不用再往上更新平衡因子,而parent的左右子树高度都为h,平衡因子也为0。

我们进一步抽象这种现象,得到普适的旋转情况:

在这里插入图片描述

右单旋的代码如下:

void RotateR(Node* parent)
{
    Node* Pparent = parent->_parent;
    Node* subL = parent->_left;
    Node* subLR = subL->_right;

    // parent 与 subL的关系建立
    subL->_right = parent;
    parent->_parent = subL;

    // parent 与 subLR 的关系建立
    if (subLR != nullptr)
    {
        subLR->_parent = parent;
    }
    parent->_left = subLR;
  
    // Pparent 与 subL的关系建立
    if (Pparent != nullptr)
    {
        subL->_parent = Pparent;
        if (Pparent->_left == parent)
        {
            Pparent->_left = subL;
        }
        else
        {
            Pparent->_right = subL;
        }
    }
    else
    {
        _root = subL;
        subL->_parent = nullptr;
    }
    //更新结点平衡因子
    subL->_bf = 0;
    parent->_bf = 0;
}

双旋转

内侧插入导致的不平衡状态,单旋转无法解决这种情况:❌

在这里插入图片描述

面对内侧的插入需要两次旋转来达成平衡

右左双旋

插入点在parent右子结点的左子树(可能为空树),这里不再举例而直接给出普适的抽象图:

在这里插入图片描述

旋转规则

  1. subR向右旋,subRL向上提起,使subR成为subRL的右子树;
  2. subRL原先的右子树处于subRL和subR之间,故同时使其成为subR的左子树(注意橙色方框位置的变化)。
  3. parent向左旋,subRL向上提起,使parent成为subRL的左子树;
  4. subRL原先的左子树处于parent和subRL之间,故同时使其成为parent的右子树(注意蓝色方框的位置变化)
  5. 平衡因子的讨论

上面的两次单旋可以复用之前单旋的代码,然而双旋的难点在于讨论结点的平衡因子

🚩注意:新增的结点在subRL的左子树还是右子树不影响旋转的方向,但是会影响旋转后的平衡因子,上图的的新增结点在subRL的右子树(平衡因子为1)这次新增结点在subRL的左子树(平衡因子为-1)

在这里插入图片描述

可以看到旋转之后,subRL的平衡因子始终为0,但是parent和subR的平衡因子会因为新增因子在subRL的左右而导致有所不同。

还有一种情况:新增节点后subLR的平衡因子为0,也就是说subRL本身为新增结点(即subR左子树为空的情况),旋转后三者的平衡因子都为0。。(解释:如果subRL不是新增结点,而其平衡因子又为0,那么父结点subR的平衡因子就没有必要更新,于是parent的平衡因子就不会变成2(悖论)。而正是subRL自身为新增,父结点subR平衡因子更新,之后导致parent的平衡因子变为2)。

在这里插入图片描述

根据subRL的平衡因子的3种情况,更新平衡因子的规则如下:

  • 🚩 subRL的平衡因子为0,旋转后parent的平衡因子为0,subR的平衡因子为0
  • 🚩 subRL的平衡因子为1,旋转后parent的平衡因子为-1,subR的平衡因子为0
  • 🚩 subRL的平衡因子为-1,旋转后parent的平衡因子为0,subR的平衡因子为1
void RotateRL(Node* parent)
{
    Node* subR = parent->_right;
    Node* subRL = subR->_left;
    //两次单旋后会更改subRL的平衡因子,之后还需判断,所以先备份下来
    int bf = subRL->_bf;

    RotateR(subR);//先以subR为根右旋
    RotateL(parent);//后以parent为根左旋
    //更新平衡因子
    subRL->_bf = 0;
    if (bf == 0)
    {
        parent->_bf = 0;
        subR->_bf = 0;
    }
    else if (bf == 1)
    {
        parent->_bf = -1;
        subR->_bf = 0;
    }
    else if (bf == -1)
    {
        parent->_bf = 0;
        subR->_bf=1;
    }
    else
    {
        assert(false);
    }
}

左右双旋

插入点在parent左子结点的右子树,与右左双旋的情况正好呈镜像关系

这里直接给出三种平衡因子的情况

在这里插入图片描述

旋转规则

  1. subL向左单旋,subLR上抬,使subL成为subLR的左子树;
  2. subLR原先的左子树处于subLR和subL之间,故使其成为subL的右子树。
  3. parent向右单旋,subLR上抬,使parent成为subLR的右子树;
  4. subLR原先的右子树处于parent和subLR之间,故同时使其成为parent的左子树
  5. 平衡因子的讨论

根据subRL的平衡因子的3种情况,更新平衡因子的规则如下:

  • 🚩 subRL的平衡因子为0,旋转后parent的平衡因子为0,subR的平衡因子为0
  • 🚩 subRL的平衡因子为1,旋转后parent的平衡因子为0,subR的平衡因子为-1
  • 🚩 subRL的平衡因子为-1,旋转后parent的平衡因子为1,subR的平衡因子为0
void RotateLR(Node* parent)
{
    Node* subL = parent->_left;
    Node* subLR = subL->_right;
    //两次单旋后会更改subLR的平衡因子,之后还需判断,所以先备份下来
    int bf = subLR->_bf;

    RotateL(subL);
    RotateR(parent);

    subLR->_bf = 0;
    if (bf == 0)
    {
        parent->_bf = 0;
        subL->_bf = 0;
	}
    else if (bf == 1)
    {
        parent->_bf = 0;
        subL->_bf = -1;
    }
    else if (bf == -1)
    {
        parent->_bf = 1;
        subL->_bf = 0;
    }
    else
    {
        assert(false);
    }
}

4.AVL树的验证

遍历

通过中序遍历可以查看我们的这棵AVL树是否满足基本的二叉搜索树的性质:

public:
	void InOrder()
	{
		_InOrder(_root);
		cout << endl;
	}

private:
	void _InOrder(Node* root)
	{
		if (root == nullptr)
		{
			return;
		}
		_InOrder(root->_left);
		cout << root->_kv.first<<':'<<root->_kv.second<<endl;
		_InOrder(root->_right);
	}

验证平衡性

步骤:

  • 计算每棵子树的高度
  • 通过递归返回时,先判断左右子树是否为AVL树,不是则返回false;
  • 再计算当前树的左右子树的高度差,若差值的绝对值超过2,则不是AVL树返回false。若小于2,那么返回true,同时通过输出型参数返回当前子树的高度给上一层。
  • 用高度差来验证每个结点的平衡因子是否计算有误
public:
    bool  IsAVLTree()
	{
		int height = 0;
		return _IsBalance(_root,height);
	}
private:
	bool _IsBalance(Node* root,int& height)
	{

		if (root == nullptr)
		{
			height = 0;
			return true;
		}

		//判断左子树是否为AVL树,同时通过输出型参数得到左子树高度
		int leftHeight = 0;
		if (_IsBalance(root->_left, leftHeight) == false)
			return false;

		//判断右子树是否为AVL树,同时通过输出型参数得到右子树高度
		int rightHeight = 0;
		if (_IsBalance(root->_right, rightHeight) == false)
			return false;

		//检查平衡因子是否有误
		if (rightHeight - leftHeight != root->_bf)
		{
			cout << root->_kv.first << " 的平衡因子应为:" << rightHeight - leftHeight << ",实为:" << root->_bf << endl;
			return false;
		}
		//计算当前树的高度,左右子树的较高者+1
		height = max(leftHeight, rightHeight) + 1;
		return abs(rightHeight - leftHeight) < 2;
	}

5.AVL树的查找

查找的规则符合二叉搜索树中查找原则:

public:
	Node* Find(const K& key)
	{
		Node* cur = _root;
		while (cur)
		{
			if (key > cur->_kv.first)
			{
				cur = cur->_right;
			}
			else if (key < cur->_kv.first)
			{
				cur = cur->_left;
			}
			else
			{
				return cur;
			}
		}
		return nullptr;
	}

6.AVL树的删除

首先搜索到要删除的结点,结点可能处于3种情况

  1. 为叶子结点,直接删除即可,随后再更新平衡因子。
  2. 度为1的结点,即左子树或者右子树为空,需将父结点连向不为空的一侧的子树后再删除,随后更新平衡因子。
  3. 结点的度为2,执行替换删除,找到删除结点的左子树的最右结点,或者右子树的最左结点来替换掉删除结点的值。注意这些替换结点只可能为叶子结点或者度为1,删除这个替换结点的问题可转变为前两种情况,当然删完后需更新平衡因子。

所以删除节点的情况只能是删除叶子结点或度为1的结点。

更新平衡因子的手法是插入结点时的逆向操作:

  • 删除的结点在parent的右边,parent的平衡因子–
  • 删除的结点在parent的左边,parent的平衡因子++

parent的平衡因子在更新后有如下情况

  • 🚩parent平衡因子==0,那么parent在删除结点前平衡因子为 ±1,说明之前parent子树的高度差被抹平了,parent子树整体高度-1,那会影响到parent的父结点及其祖先结点的平衡因子,因此需往上更新平衡因子
  • 🚩parent平衡因子==±1,那么parent在删除结点前平衡因子为 0,删除结点后,parent单侧子树少了一个结点,虽产生高度差,但是parent子树的高度没有改变,并不会影响到parent的父结点的平衡因子,故删除成功,无需往上更新平衡因子
  • 🚩parent平衡因子==±2,那么parent在删除结点前平衡因子为 ±1,说明删除结点导致了parent子树的不平衡,需要进行旋转处理,当然旋转处理后,该子树的高度会发生变化,仍需往上更新平衡因子

旋转规则:

  • parent平衡因子为 2 (右树subR更高)
    • parent的右孩子平衡因子为1,进行左单旋,parent平衡因子为0,subR平衡因子为0,树的高度降低1,说明还需往上继续更新。

在这里插入图片描述

  • parent的右孩子平衡因子为0,左单旋,parent平衡因子为-1,subR平衡因子为-1,旋转后树的高度没有变化,无需往上更新了。

在这里插入图片描述

  • parent的右孩子平衡因子为-1,进行右左双旋,右左双旋的更新平衡因子有三种情况,已在上文的双旋中讨论,按需往上更新平衡因子。

在这里插入图片描述

左侧为镜像情况,这里简述

  • parent平衡因子为-2 (左树subL更高)
    • parent左孩子平衡因子为-1,进行右单旋,还需往上继续更新。
    • parent左孩子平衡因子为1,进行左右双旋,根据旋后情况按需往上继续更新。
    • parent左孩子平衡因子为0,进行右单旋,无需往上更新。

删除结点的代码如下

bool erase(const K& key)
{
    if (_root == nullptr)
    {
        return false;
    }
    Node* cur = _root;//遍历
    Node* parent = nullptr;
    Node* delPos=nullptr;//标记删除结点
    Node* delParentPos=nullptr;
    while (cur)
    {
        if (cur->_kv.first > key)//key比当前结点小
        {
            parent = cur;
            cur = cur->_left;//往左子树走
        }
        else if(cur->_kv.first < key)//key比当前结点大
        {
            parent = cur;
            cur = cur->_right;//往右子树走
        }
        else//找到删除结点
        {
            if (cur->_left == nullptr)
            {
                if (cur == _root)
                {
                    _root = _root->_right;
                    if (_root != nullptr)
                        _root->_parent = nullptr;
                    delete cur;
                    return true;
                }
                else
                {
                    delParentPos = parent;//标记删除结点的父结点
                    delPos = cur;//标记为删除结点
                }
                break;//找到了删除点,需更新平衡因子
            }
            else if (cur->_right == nullptr)
            {
                if (cur == _root)
                {
                    _root = _root->_left;
                    if (_root != nullptr)
                        _root->_parent = nullptr;
                    delete cur;
                    return true;
                }
                else
                {
                    delParentPos = parent;
                    delPos = cur;
                }
                break;
            }
            else//左右均不为空
            {
                //替换法删除,找到右子树中key最小的结点
                Node* minParent = cur;
                Node* minRight = cur->_right;
                while (minRight->_left)
                {
                    minParent = minRight;
                    minRight = minRight->_left;
                }
                cur->_kv.first = minRight->_kv.first;
                cur->_kv.second = minRight->_kv.second;//替换
                delParentPos = minParent;
                delPos = minRight;//实际删除结点
                break;
            }
        }
    }
    //找不到删除结点
    if (cur == nullptr)
    {
        return false;
    }
    //下面开始更新平衡因子
    cur = delPos;
    parent = delParentPos;
    while (parent != nullptr)//最坏情况更新到根结点
    {
        //更新结点平衡因子
        if (cur == parent->_left)
        {
            parent->_bf++;
        }
        else if (cur == parent->_right)
        {
            parent->_bf--;
        }

        //旋转情况讨论
        if (parent->_bf == 1 || parent->_bf == -1)
        {
            break;//高度没有变化,更新完毕
        }
        else if (parent->_bf == 0)
        {
            //需往上继续更新
            cur = parent;
            parent = parent->_parent;
        }
        else if (parent->_bf == 2 || parent->_bf == -2)
        {
            //开始旋转处理
            //前两种情况旋转会为我们处理平衡因子,
            //但是subR平衡因子为0的情况需要我们手动修改
            if (parent->_bf == 2)
            {
                Node* subR = parent->_right;
                if (subR->_bf == 1)//左单旋
                {
                    RotateL(parent);
                    parent = subR;
                }
                else if (subR->_bf == -1)//右左双旋
                {
                    Node* subRL = subR->_left;
                    RotateRL(parent);
                    parent = subRL;
                }
                else if (subR->_bf == 0)//左单旋
                {
                    RotateL(parent);
                    //手动修改平衡因子
                    parent->_bf = 1;
                    subR->_bf = -1;
                    //旋后树高不变,无需再往上更新
                    break;
                }
            }
            else if (parent->_bf == -2)
            {
                Node* subL=parent->_left;
                if (subL->_bf == -1)//右单旋
                {
                    RotateR(parent);
                    parent = subL;
                }
                else if (subL->_bf == 1)//左右双旋
                {
                    Node* subLR = subL->_right;
                    RotateLR(parent);
                    parent = subLR;
                }
                else if (subL->_bf == 0)
                {
                    RotateR(parent);
                    //手动修改平衡因子
                    parent->_bf = -1;
                    subL->_bf = 1;
                    //旋后树高不变,无需再往上更新
                    break;
                }
            }
            //到了此处,说明还需要往上更新
            cur = parent;
            parent = parent->_parent;
        }
        else
        {
            assert(false);//删除前平衡因子就有错误
        }

    }
    //进行删除,现在的删除结点的度<=1
    if (delPos->_left == nullptr)//实际删除结点的左子树为空
    {
        if (delPos == delParentPos->_left)
        {
            delParentPos->_left = delPos->_right;
        }
        else if(delPos==delParentPos->_right)
        {
            delParentPos->_right = delPos->_right;
        }
        //由于删除结点的右树可能为空,需要判断
        if (delPos->_right != nullptr)
        {
            delPos->_right->_parent = delParentPos;
        }
    }
    else if (delPos->_right == nullptr)
    {
        //走到这说明删除节点的左子树不为空,右子树为空

        if (delPos == delParentPos->_left)
        {
            delParentPos->_left = delPos->_left;
        }
        else if (delPos == delParentPos->_right)
        {
            delParentPos->_right = delPos->_left;
        }
        delPos->_left->_parent = delParentPos;
    }
    delete delPos;
    delPos = nullptr;
    return true;
}

7.AVL树的键值访问及修改

对insert的函数进行复用:

V& operator[](const K& key)
{
    Node* ptr=insert(make_pair(key, V())).first;
    return ptr->_kv.second;
}

测试

测试代码如下

void TestAVLTree()
{
	int a[] = { 18,14,20,12,16,15};
	AVLTree<int, int> T;

	cout << "------insert-------" << endl;
	for (auto& e : a)
	{
		T.insert(make_pair(e, e));
		cout << e << ":" << T.IsAVLTree() << endl;
	}

	cout << "------modify-------" << endl;
	T[18]*=10;
	T[14]*=10;
	T[12]*=10;
	T.InOrder();

	cout << "-------erase-------" << endl;
	for (auto& e : a)
	{
		T.erase(e);
		cout << e << ":" << T.IsAVLTree() << endl;
	}
}

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

得到结果:

在这里插入图片描述

总结

AVL树始终保持了高度平衡,这样保证了数据搜索的高效性(log2N),但是每次插入删除的旋转操作,也会使其效率大打折扣。之后我会介绍红黑树,在略微损失数据查找的效率下,减少了大量的旋转操作,使其真正成为STL中的map和set的底层数据结构。

AVL代码实现


— end —

青山不改 绿水长流

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值