【c++】avl树

简介: 【c++】avl树

avl树的概念

一棵AVL树或者是空树,或者是具有以下性质的二叉搜索树

它的左右子树都是AVL树

左右子树高度之差(简称平衡因子)的绝对值不超过1(-1/0/1)

如果一棵二叉搜索树是高度平衡的,它就是AVL树。如果它有n个结点,其高度可保持在

O ( l o g 2 n ) O(log_2 n)O(log2n),搜索时间复杂度O(l o g 2 n log_2 nlog2n)。

当插入,高度差不符合要求时,通过旋转变成avl树

avl树节点定义

template<class  K>
struct avltreenode
{
  avltreenode<K>* _left;
  avltreenode<K>* _right;
  avltreenode<K>* _parent;//使用三叉链,方便更新祖先的平衡因子
  K _key;
  int _bf;//平衡因子
  avltreenode(const K& key)
    :_left(nullptr)
    , _right(nullptr)
    ,_parent(nullptr)
    , _key(key)
    ,_bf(0)//新插入节点,因为没有左右子树,所以他的平衡因子是0
  {}
};

需要操作一个树,我们只需要知道该树的根节点即可

所以我们avl树的类成员变量为根节点地址

template<class  K>
class avltree
{
  typedef  avltreenode<K> node;
private:
  
  node* _root = nullptr;
};

avl树的插入

AVL树就是在二叉搜索树的基础上引入了平衡因子,因此AVL树也可以看成是二叉搜索树。那么

AVL树的插入过程可以分为两步:

  1. 按照二叉搜索树的方式插入新节点
  2. 调整节点的平衡因子

更新平衡因子规则:

1.新增在右,父亲的_bf+1;(根据平衡因子定义)

2.新增在左,父亲的_bf-1;(根据平衡因子定义)

3.如果更新后,父亲的_bf=1/-1,这样会影响父亲祖先的平衡因子,所以要向上继续更新父亲祖先的平衡因子.

4.父亲的平衡因子更新后变为0后,不用往上更新平衡因子

5.父亲的平衡因子为2/-2时,需要通过旋转来维持avl树的平衡

插入操作和平衡二叉树一样

bool insert(const K& key)
  {
    if (_root == nullptr)
    {
      _root = new node(key);
      return true;
    }
    node* cur = _root;
    node* parent = nullptr;
    while (cur)
    {
      if (cur->_key > key)
      {
        parent = cur;
        cur = cur->_left;
      }
      else if (cur->_key < key)
      {
        parent = cur;
        cur = cur->_right;
      }
      else
      {
        return false;
      }
    }
    cur = new node(key);
    if (parent->_key > key)
    {
      parent->_left = cur;
    }
    else
    {
      parent->_right = cur;
    }
    cur->_parent = parent;//这里区别二叉搜索树,avl树需要记录插入节点的父节点
//后序操作处理平衡因子和旋转问题
    

平衡因子的处理

根据更新平衡因子的规则

根据规则1,2

if (cur == parent->_left)
      {
        parent->_bf--;
      }
      else
      {
        parent->_bf++;
      }

cur为新插入节点的指针

更新完成后,如果父亲的平衡因子==0,停止更新

if (parent->_bf == 0)
      {
        break;
      }

更新完成后,如果父亲的平衡因子为1/-1,需要继续往上更新,

else if (parent->_bf == 1 || parent->_bf == -1)
      {
        cur = cur->_parent;
        parent = parent->_parent;
      }

更新完成后,如果父亲的平衡因子为2/-2的话,需要进行旋转,,但是旋转的逻辑先不处理

else if (parent->_bf == -2 || parent->_bf == 2)
      {
        //旋转逻辑
        break;
      }
  
    

一直往上更新,直到父亲为空为止,说明更新父亲祖先的平衡因子已经更新完了

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 = cur->_parent;
        parent = parent->_parent;
      }
      else if (parent->_bf == -2 || parent->_bf == 2)
      {
        
  
        break;
      }
    
    }

旋转逻辑

左旋:

右旋:

左旋处理:

先对需要旋转的结点命名,方便操作.

//由于旋转封装函数,我们会传入以哪个点旋转的指针
void spinleft(node* parent)//图中的30就是parent

命名

node* subr = parent->_right;
node* subrl = subr->_left;

此时只处理了旋转之后子节点的变化情况,父节点的未处理,导致结果如下

需要更新的父链有:

subr->_parent;
parent->_parent;
subrl->_parent;

由图可以得出:

subrl->_parent = parent;
parent->_parent = subr;

但是当h=0时,subrl是不存在的,所以

if (subrl)
subrl->_parent = parent;

这里的subr->_parent怎么更新

分两种情况:

1.parent就是根节点(更新前)

所以更新后subr就是根节点,

_root=subr; subr->_parent=nullptr;

2.parent不是根节点,所以我们要在改变parent->_parent之前需要记录一下,因为subr->_parent=parent->_parent;

node* ppnode = parent->_parent;//记录
parent->_parent = subr;//在改变之前

如果旋转前parent=ppnode->_left,

就有

ppnode->_left=subr;
subr->_parent=ppnode;

如果旋转前是parent=ppnode->_right;

就有

ppnode->_right=subr;
subr->_parent=ppnode;

最后需要更新一下subr和parent的平衡因子

subr->_bf=0;
parent->_bf=0;

左旋整体代码:

void spinleft(node* parent)
  {
    node* subr = parent->_right;
    node* subrl = subr->_left;
    
    parent->_right = subrl;
    if (subrl)
      subrl->_parent = parent;
    subr->_left = parent;
    node* ppnode = parent->_parent;
    parent->_parent = subr;
    
    if (ppnode == nullptr)
    {
      _root = subr;
      subr->_parent = nullptr;
    }
    else
    {
      if (ppnode->_left == parent)
      {
        ppnode->_left=subr;
        subr->_parent = ppnode;
      }
      else
      {
        ppnode->_right=subr;
        subr->_parent = ppnode;
      }
    }
  
  
  
    subr->_bf = 0;
    parent->_bf = 0;
  
  }

右旋:

void spinright(node* parent)
  {
  
    node* subl = parent->_left;
    node* sublr = subl->_right;
    
    parent->_left = sublr;
    if (sublr)
      sublr->_parent = parent;
    subl->_right = parent;
    node* ppnode = parent->_parent;
    parent->_parent = subl;
    if (ppnode == nullptr)
    {
      _root = subl;
      subl->_parent = nullptr;
    }
    else
    {
      if (ppnode->_left == parent)
      {
        ppnode->_left=subl;
        subl->_parent = ppnode;
      }
      else
      {
        ppnode->_right=subl;
        subl->_parent = ppnode;
      }
    }
  
  
  
    subl->_bf = 0;
    parent->_bf = 0;
  
  
  
  }

我们可以观察到,如果parent->_bf=2,cur->_bf=1
说明整体右边偏高,需要左旋

如果parent->_bf=-2,cur->_bf=-1
说明整体左边偏高,需要右旋

左右旋:

当parent->_bf=-2,cur->_bf=1

cur的右边高,而parent是左边高,此时我们需要对cur进行左旋,这样以来只变成了左边高,然后使用右旋即可

对30(cur)进行左旋,对parent(90)右旋

void spinlr(node* parent)
  { 
    spinleft(parent->_left);
    spinright(parent);
     
  }

如果只这样写的话,我们会发现平衡因子和图中是不同的

只会把

subl->_bf = 0;
  sublr->_bf = 0;
  parent->_bf = 0;

所以我们要分情况讨论:

我们应该按什么分呢?

这里我们需要按照插入结点后,sublr的平衡因子来区分

如果插入节点在sublr右边,sublr->_bf=1

得到的平衡因子的更新为

subl->_bf = -1;
sublr->_bf = 0;
parent->_bf = 0;

如果插入在sublr的左边,sublr->_bf=-1;

得到的平衡因子的更新为

subl->_bf = 0;
sublr->_bf = 0;
parent->_bf = 1;

如果sublr是新插入的结点的话,得到的平衡因子的更新为

subl->_bf = 0;
  sublr->_bf = 0;
  parent->_bf = 0;

因为在左右旋转之后会改变sublr的平衡因子,所以我们必须在插入结点之后,在旋转之前保存一下我们的sublr的平衡因子

int bf = subrl->_bf;

为了方便更新平衡因子,我们将旋转需要更新的平衡因子的结点标记

node* subr = parent->_right;
node* subrl = subr->_left;

左右旋整体代码:

void spinrl(node* parent)
  {
    node* subr = parent->_right;
    node* subrl = subr->_left;
    int bf = subrl->_bf;
  
  
    spinright(subr);
    spinleft(parent);
    if (bf == 1)
    {
      parent->_bf = -1;
      subr->_bf = 0;
      subrl->_bf = 0;
    }
    else if (bf == -1)
    {
    
      parent->_bf = 0;
      subr->_bf = 1;
      subrl->_bf = 0;
    
    }
    else
    {
      subr->_bf = 0;
      subrl->_bf = 0;
      parent->_bf = 0;
    }
  }

右左旋:

根据左右旋,以及右左旋的图,可以写出代码,思路基本和左右旋一样

void spinrl(node* parent)
  {
    node* subr = parent->_right;
    node* subrl = subr->_left;
    int bf = subrl->_bf;
  
  
    spinright(subr);
    spinleft(parent);
    if (bf == 1)
    {
      parent->_bf = -1;
      subr->_bf = 0;
      subrl->_bf = 0;
    }
    else if (bf == -1)
    {
    
      parent->_bf = 0;
      subr->_bf = 1;
      subrl->_bf = 0;
    
    }
    else
    {
      subr->_bf = 0;
      subrl->_bf = 0;
      parent->_bf = 0;
    }
  }

avl树的验证

需要检测每个节点左右子树的高度差<2,所以我们需要求树高度的函数

int treeheight(node* root)
  {
    if (root == nullptr)
      return 0;
    int leftheight = treeheight(root->_left);
    int rightheight = treeheight(root->_right);
    return leftheight > rightheight ? leftheight + 1 : rightheight + 1;
  
  }

这个在二叉树那节有讲

平衡树验证:(这是力扣上的一个题,大家可以去刷一下)

平衡二叉树

bool _IsBalanceTree(node*root)
  {
    if (root == nullptr)
      return true;
    int leftheight = treeheight(root->_left);
    int rightheight = treeheight(root->_right);
      return (abs(rightheight - leftheight) < 2) && _IsBalanceTree(root->_left) && _IsBalanceTree(root->_right);
  }

整体代码:

#include<iostream>
#include<string>
using namespace std;
template<class  K>
struct avltreenode
{
  avltreenode<K>* _left;
  avltreenode<K>* _right;
  avltreenode<K>* _parent;
  K _key;
  int _bf;
  avltreenode(const K& key)
    :_left(nullptr)
    , _right(nullptr)
    ,_parent(nullptr)
    , _key(key)
    ,_bf(0)
  {}
};
template<class  K>
class avltree
{
  typedef  avltreenode<K> node;
public:
  void spinleft(node* parent)
  {
    node* subr = parent->_right;
    node* subrl = subr->_left;
    
    parent->_right = subrl;
    if (subrl)
      subrl->_parent = parent;
    subr->_left = parent;
    node* ppnode = parent->_parent;
    parent->_parent = subr;
    
    if (ppnode == nullptr)
    {
      _root = subr;
      subr->_parent = nullptr;
    }
    else
    {
      if (ppnode->_left == parent)
      {
        ppnode->_left=subr;
        subr->_parent = ppnode;
      }
      else
      {
        ppnode->_right=subr;
        subr->_parent = ppnode;
      }
    }
  
  
  
    subr->_bf = 0;
    parent->_bf = 0;
  
  }
  void spinright(node* parent)
  {
  
    node* subl = parent->_left;
    node* sublr = subl->_right;
    
    parent->_left = sublr;
    if (sublr)
      sublr->_parent = parent;
    subl->_right = parent;
    node* ppnode = parent->_parent;
    parent->_parent = subl;
    if (ppnode == nullptr)
    {
      _root = subl;
      subl->_parent = nullptr;
    }
    else
    {
      if (ppnode->_left == parent)
      {
        ppnode->_left=subl;
        subl->_parent = ppnode;
      }
      else
      {
        ppnode->_right=subl;
        subl->_parent = ppnode;
      }
    }
  
  
  
    subl->_bf = 0;
    parent->_bf = 0;
  
  
  
  }
  void spinlr(node* parent)
  { 
    node* subl = parent->_left;
    node* sublr = subl->_right;
    int bf = sublr->_bf;
    spinleft(parent->_left);
    spinright(parent);
      
    if (bf == 1)
    {
      subl->_bf = -1;
      sublr->_bf = 0;
      parent->_bf = 0;
    }
    else if (bf == -1)
    {
      subl->_bf = 0;
      sublr->_bf = 0;
      parent->_bf = 1;
    }
    else
    {
      subl->_bf = 0;
      sublr->_bf = 0;
      parent->_bf = 0;
    }
        
  }
  void spinrl(node* parent)
  {
    node* subr = parent->_right;
    node* subrl = subr->_left;
    int bf = subrl->_bf;
  
  
    spinright(subr);
    spinleft(parent);
    if (bf == 1)
    {
      parent->_bf = -1;
      subr->_bf = 0;
      subrl->_bf = 0;
    }
    else if (bf == -1)
    {
    
      parent->_bf = 0;
      subr->_bf = 1;
      subrl->_bf = 0;
    
    }
    else
    {
      subr->_bf = 0;
      subrl->_bf = 0;
      parent->_bf = 0;
    }
  }
  bool insert(const K& key)
  {
    if (_root == nullptr)
    {
      _root = new node(key);
      return true;
    }
    node* cur = _root;
    node* parent = nullptr;
    while (cur)
    {
      if (cur->_key > key)
      {
        parent = cur;
        cur = cur->_left;
      }
      else if (cur->_key < key)
      {
        parent = cur;
        cur = cur->_right;
      }
      else
      {
        return false;
      }
    }
    cur = new node(key);
    if (parent->_key > key)
    {
      parent->_left = cur;
    }
    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 = cur->_parent;
        parent = parent->_parent;
      }
      else if (parent->_bf == -2 || parent->_bf == 2)
      {
        if (parent->_bf == 2 && cur->_bf == 1)//左旋
        {
          spinleft(parent);
        }
        else if (parent->_bf == 2 && cur->_bf == -1)//先左旋后右旋
        {
          spinrl(parent);
          
        }
        else if (parent->_bf == -2 && cur->_bf == -1)//右旋
        {
           spinright(parent);
        }
        else//先右旋后左旋
        {
          spinlr(parent);
        }
        break;
      }
    
    }
    return true;
  }
   
  bool IsBalanceTree()
  {
  
    return _IsBalanceTree(_root);
  
  
  
  
  }
  void inorder()
  {
    _inorder(_root);
  }
  void _inorder(node* root)
  {
    if (root == nullptr)
      return;
    _inorder(root->_left);
    cout << root->_key << " :"<<root->_bf<<endl;
    _inorder(root->_right);
  }
  
private:
  int treeheight(node* root)
  {
    if (root == nullptr)
      return 0;
    int leftheight = treeheight(root->_left);
    int rightheight = treeheight(root->_right);
    return leftheight > rightheight ? leftheight + 1 : rightheight + 1;
  
  }
  bool _IsBalanceTree(node*root)
  {
    if (root == nullptr)
      return true;
    int leftheight = treeheight(root->_left);
    int rightheight = treeheight(root->_right);
     
    return (abs(rightheight - leftheight) < 2) && _IsBalanceTree(root->_left) && _IsBalanceTree(root->_right);
    
  }
  node* _root = nullptr;
};
int main()
{
  avltree<int>st;
  int a[] = //{ 16,3,1 };//测试右旋
            //{ 16,32,58 };//测试左旋
    //{ 8,3,1,10,6,4};//测试右左旋
    { 4, 2, 6, 1, 3, 5, 15, 7, 16,14};
    // { 16, 3, 7, 11, 9, 26, 18, 14, 15 };
  //{ 8,3,1,10,6,4,7,14,13 };
  for (auto e : a)
  {
    st.insert(e);
    
    
  }
    st.inorder();
  int ret=st.IsBalanceTree();
  cout << endl;
  cout << ret;
}
目录
相关文章
|
1月前
|
算法 测试技术 C++
【C++】map&set的底层结构 -- AVL树(高度平衡二叉搜索树)(下)
【C++】map&set的底层结构 -- AVL树(高度平衡二叉搜索树)(下)
|
1月前
|
C++ 容器
【C++】map&set的底层结构 -- AVL树(高度平衡二叉搜索树)(上)
【C++】map&set的底层结构 -- AVL树(高度平衡二叉搜索树)(上)
|
1月前
|
算法 C语言 容器
从C语言到C++_25(树的十道OJ题)力扣:606+102+107+236+426+105+106+144+94+145(下)
从C语言到C++_25(树的十道OJ题)力扣:606+102+107+236+426+105+106+144+94+145
34 7
|
1月前
|
C语言 容器
从C语言到C++_27(AVL树)概念+插入接口实现(四种旋转)(上)
从C语言到C++_27(AVL树)概念+插入接口实现(四种旋转)
27 4
|
1月前
|
C语言 C++
从C语言到C++_27(AVL树)概念+插入接口实现(四种旋转)(下)
从C语言到C++_27(AVL树)概念+插入接口实现(四种旋转)
25 2
|
1月前
|
C语言
从C语言到C++_25(树的十道OJ题)力扣:606+102+107+236+426+105+106+144+94+145(中)
从C语言到C++_25(树的十道OJ题)力扣:606+102+107+236+426+105+106+144+94+145
28 1
|
1月前
|
算法 C语言 C++
从C语言到C++_25(树的十道OJ题)力扣:606+102+107+236+426+105+106+144+94+145(上)
从C语言到C++_25(树的十道OJ题)力扣:606+102+107+236+426+105+106+144+94+145
20 1
|
1月前
|
存储 算法 C++
c++算法学习笔记 (8) 树与图部分
c++算法学习笔记 (8) 树与图部分
|
1月前
|
存储 算法 C++
数据结构/C++:AVL树
数据结构/C++:AVL树
17 2
|
1天前
|
C++
【C++】日期类Date(详解)②
- `-=`通过复用`+=`实现,`Date operator-(int day)`则通过创建副本并调用`-=`。 - 前置`++`和后置`++`同样使用重载,类似地,前置`--`和后置`--`也复用了`+=`和`-=1`。 - 比较运算符重载如`&gt;`, `==`, `&lt;`, `&lt;=`, `!=`,通常只需实现两个,其他可通过复合逻辑得出。 - `Date`减`Date`返回天数,通过迭代较小日期直到与较大日期相等,记录步数和符号。 ``` 这是236个字符的摘要,符合240字符以内的要求,涵盖了日期类中运算符重载的主要实现。