"我们无法一直活在过去,亦如我们从未能挽救死亡"
(1)AVL树:
概念:平衡搜索二叉树。
性质:
①根节点的左右子树 是AVL树
②左右子树的高度的差 不超过1
③比根节点大的 插在根节点的右边。 小的 在左边。
(2)AVL树模拟实现:
关于AVL树概念上的东西,多讲无用。我们现在直接来实现。
① 结构框架:
注:这里实现的是key值版。
//AVL树的 节点
template<class T>
struct AVLTreeNode
{
//节点指针
AVLTreeNode<T>* _left;
AVLTreeNode<T>* _right;
AVLTreeNode<T>* _parent;
//节点数值
T _key;
//为了调节平衡 这里引入平衡因子;
int _bf;
//构造
AVLTreeNode(const T& key)
:_left(nullptr),
: _right(nullptr),
_parent(nullptr),
_key(key),
_bf(0) //任何新插入的 节点 平衡因子都默认设置为0
{}
};
template<class K>
class AVLTree
{
typedef AVLTreeNode<T> Node;
AVLTree()
:_root(nullptr)
{}
private:
Node* _root;
};
对于AVL树而言,显然难点在插入的部分。所以这留到最后讲。那么这部分进行相关功能的实现。
②查找+中序遍历:
Node* find(const K& key)
{
Node* cur = _root;
while (cur)
{
if (key > cur->_key)
{
cur = cur->_right;
}
else if(key < cur->_key)
{
cur = cur->_left;
}
else
{
//找到了
return cur;
}
}
return nullptr;
}
void _InOrder(Node* root)
{
if (root == nullptr)
return;
_InOrder(root->_left);
cout << root->_key << endl;
_InOrder(root->_right);
}
void InOrder()
{
_InOrder(_root);
}
③检查平衡+析构:
void _Destory(Node* root)
{
if (root == nullptr)
return;
_Destory(root->_left);
_Destory(root->_right);
delete root;
}
~AVLTree()
{
_Destory(_root);
_root =nullptr;
}
int TreeHight(Node* root)
{
if (root == nullptr)
return 0;
int leftdepth = TreeHight(root->_left);
int rightdepth = TreeHight(root->_right);
//返回 左右子树 大的那个
return leftdepth > rightdepth ? leftdepth + 1 : rightdepth + 1;
}
bool _isBalance(Node* root)
{
if (root == nullptr)
return true; //空树也是平衡树
int Maxleft = TreeHight(_root->_left);
int Maxright = TreeHight(_root->_right);
return abs(Maxleft - Maxright) < 2
&& _isBalance(root->_left)
&& _isBalance(root->_right);
}
bool _isBalance()
{
return _isBalance(_root);
}
关于如何理解检查这棵树是否平衡的函数,也就不多赘述。因为在前面二叉树篇讲过。
下面就进入重头戏~
④AVL的插入;
//如果是第一次插入 直接去做根 root
if (_root == nullptr)
{
_root = new Node(k);
return true;
}
//不是第一个值
Node* cur = _root;
Node* parent = nullptr;
while (cur) //这里和find的思想一样
{
if (key > cur->_key)
{
parent = cur;
cur = cur->_right;
}
else if (key < cur->_key)
{
parent = cur;
cur = cur->_left;
}
else
{
//如果走到这里 说明 已经插入了和key 一样的数
return true;
}
}
//cur 走到空的位置了
cur = new Node(key);
Node* newnode = cur;
//更新与parent的关系
if (cur->_key > parent->_key)
{
parent->_right = cur;
}
else
{
parent->_left = cur;
}
//注意我们设计的是三叉连 结构
//cur 还有反过来 指向parent
cur->_parent = parent;
任何一次对树数据的操作,都可能造成对树结构的破坏。
所以,为了 保护树的结构。我们需要根据不同情况,对树进行位置的更换
(1)平衡因子:
我们让在根节点 右插入对 _bf ++
左插入 _bf--;
由此,每棵树节点的 平衡因子会分为三种情况。
[0] [-1,1] [2,-2]
//调节平衡因子;
//while(parent)
while (cur != root)
{
//更新节点bf
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;
}
else if(parent->_bf == -2 || parent->_bf == 2)
{
//调整
}
else
{
//理论上不会走到这里来
//如果走到这里来 也是调整前 已经不是AVL数
assert(false);
}
}
(2)左右单旋:
我们在看单旋图中,就是bf=-1,1 的节点(也就是parent) 是旋转的轴。
else if(parent->_bf == -2 || parent->_bf == 2)
{
//调整
//左边新增节点
if (parent->_bf == -2)
{
if (cur->_bf == -1) //新节点在左边插入
{
//左高 右边低 需要右翻转
RotateR(parent);
}
else //新增节点插入在右边
{
}
}
else //右边新增节点
{
if (cur->_bf == 1) //新节点从右边插入
{
//此时 右高 左低 需要 右翻转
RotateL(parent);
}
else //新增节点在左边
{
}
}
//翻转完 就跳出
break;
}
这也就是单旋插入的逻辑。
右单旋实现:
也及时sub subLR parent 三方的关系;
void RotateR(Node* parent)
{
Node* sub = parent->_left;
Node* subLR = sub->_right;
//让subLR去做parent的左
parent->_left = subLR;
//subLR可能不存在
if(subLR)
subLR->_parent = parent;
sub->_right = parent;
//记录 父节点 因为 sub 可能不是唯一独立的树
Node* grandparent = parent->_parent;
parent->_parent = sub;
if (parent == _root)
{
//独立树
_root = sub;
sub->_parent = nullptr;
}
else
{
//子树
//parent的 父亲仍然保留着 节点地址
if (grandparent->_left == parent)
{
grandparent->_left = sub;
}
else
{
grandparent->_right = sub;
}
//更新回来
sub->_parent = grandparent;
}
//调节平衡因子
parent->_bf = sub->_bf = 0;
}
RotateL(Node* parent)
{
Node* sub = parent->_right;
Node* subRL = sub->_left;
//让subRL 去做parent的右边
parent->_right = subRL;
//仍然注意避坑
if(subRL)
subRL->_parent = parent;
Node* grandparent = parent->_parent;
sub->_left = parent;
parent->_parent = sub;
if (parent == _root)
{
_root = sub;
sub->_parent = nullptr;
}
else
{
if (grandparent->_left == parent)
{
grandparent->_left = sub;
}
else
{
grandparent->_right = sub;
}
sub->_parent = grandparent;
}
parent->_bf = sub->_bf = 0;
}
(3)双旋转:
左右双旋转:
void RotateLR(Node* parent)
{
Node* sub = parent->_left;
Node* subLR = sub->_right;
//保存插入处 的平衡因子~
int bf = subLR->_bf;
RotateL(sub);
RotateR(parent);
//调整平衡因子
if (bf == -1) //左边B插入
{
sub->_bf = 0;
subRL->_bf = 0;
parent->_bf = 1;
}
else if(bf ==1)
{
sub->_bf = -1;
subRL->_bf = 0;
parent->_bf = 0;
}
else if(bf ==0)
{
sub->_bf = 0;
subRL->_bf = 0;
parent->_bf = 0;
}
else
{
assert(false);
}
}
右左双旋:
这个同上也就 不再多分析。
void RotateRL(Node* parent)
{
Node* sub = parent->_right;
Node* subRL = sub->_left;
int bf = subRL->_bf;
RotateR(sub);
RotateL(parent);
if (bf == 1) //从C插入
{
subRL->_bf = 0;
sub->_bf = 0;
parent->_bf = -1;
}
else if (bf == -1)
{
parent->_bf = 0;
subRL->_bf = 0;
sub->_bf = 1;
}
else if (bf == 0)
{
parent->_bf = 0;
subRL->_bf = 0;
sub->_bf = 0;
}
else
{
assert(false);
}
}
④测试;
插入也就到此为止,我们先来试着跑几组数据看是否正确。
(3)K、V版AVL 树
namespace dy
{
template<class K, class V>
struct AVLTreeNode
{
//节点指针
AVLTreeNode<K, V>* _left;
AVLTreeNode<K, V>* _right;
AVLTreeNode<K, V>* _parent;
//节点数值
pair<K, V> _kv;
//为了调节平衡 这里引入平衡因子;
int _bf;
//构造
AVLTreeNode(const pair<K,V>& kv)
:_left(nullptr),
_right(nullptr),
_parent(nullptr),
_kv(kv),
_bf(0) //任何新插入的 节点 平衡因子都默认设置为0
{}
};
template<class K,class V>
struct AVLTreeKV
{
typedef AVLTreeNode<K, V> Node;
Node* _root;
AVLTreeKV()
:_root(nullptr)
{}
void _InOrder(Node* root)
{
if (root == nullptr)
return;
_InOrder(root->_left);
cout << root->_kv.first << ":"<<root->_kv.second<<endl;
_InOrder(root->_right);
}
//没封装迭代器 暂且用Node* 替
pair<Node*,bool> insert(const pair<K,V> kv)
{
//如果是第一次插入 直接去做根 root
if (_root == nullptr)
{
_root = new Node(kv);
return make_pair(_root,true);
}
//不是第一个值
Node* cur = _root;
Node* parent = nullptr;
while (cur) //这里和find的思想一样
{
if (kv.first > cur->_kv.first)
{
parent = cur;
cur = cur->_right;
}
else if (kv.first < cur->_kv.first)
{
parent = cur;
cur = cur->_left;
}
else
{
//如果走到这里 说明 已经插入了和key 一样的数
return make_pair(cur,true);
}
}
//cur 走到空的位置了
cur = new Node(kv);
Node* newnode = cur;
//更新与parent的关系
if (cur->_kv.first > parent->_kv.first)
{
parent->_right = cur;
}
else
{
parent->_left = cur;
}
//注意我们设计的是三叉连 结构
//cur 还有反过来 指向parent
cur->_parent = parent;
//调节平衡因子;
//while(parent)
while (cur != _root)
{
//更新节点bf
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)
{
if (cur->_bf == -1) //新节点在左边插入
{
//左高 右边低 需要右翻转
RotateR(parent);
}
else //新增节点插入在右边
{
//此时 是折线
//使用双旋
RotateLR(parent);
}
}
else //右边新增节点
{
if (cur->_bf == 1) //新节点从右边插入
{
//此时 右高 左低 需要 右翻转
RotateL(parent);
}
else //新增节点在左边
{
RotateRL(parent);
}
}
//翻转完 就跳出
break;
}
else
{
//理论上不会走到这里来
//如果走到这里来 也是调整前 已经不是AVL数
assert(false);
}
}
return make_pair(newnode,true);
}
}
总结:
①AVL树 插入节点时很看重平衡因子。
②平衡因子条件 [0] [-1,1] [-2,2]
③单旋是直线 双旋 为折线
内容到这里也就结束啦,感谢你的阅读。
祝你好运~