最近,想静下来刷题,好好整一整数据结构这一块,就想着手撕一个平衡二叉树。基于此本文讲解平衡二叉树的原理以及代码实现(包括插入、删除、查询以及遍历打印功能),巩固自己的基础以及方便日后复习。
一、平衡二叉树
平衡二叉树(Balanced Binary Tree)——AVL树,该树上的任一结点的左右子树的高度差不超过1(小于等于1),这里将左子树的高度减去右子树的高度定义为节点的平衡因子,之后由平衡因子来衡量该树是否为平衡二叉树。
- 节点的平衡因子=左子树高度-右子树高度
- 对于一棵平衡二叉树,任一结点的平衡因子只能是-1、0或1
- 只要有一个结点的平衡因子绝对值大于1就不满足平衡二叉树的条件
1.1 插入操作
对于平衡二叉树的插入操作,要注意一旦有新的结点插入,很有可能路径上的结点会不再满足平衡因子绝对值小于等于1的条件。这里首先要引入最小不平衡子树的概念,定义如下:从插入点往根结点回找到的第一个不平衡结点,并以该结点为根的子树。如果我们将这个最小不平衡子树调节平衡了,那么其他结点都会恢复到平衡。那么问题就是如何去调整最小不平衡子树。
接下来分四种情况讨论,目标如下:
- 恢复平衡二叉树的性质
- 保持二叉排序树的特性(左子树的所有结点小于当前父结点,右子树的所有结点大于当前父结点)
在这之前定义一下平衡二叉树的数据结构以及每个节点的数据结构,如下:
template<class T>
class AVL{
public:
typedef std::shared_ptr<AVL> ptr;
struct Node
{
T val; //结点的值
int height; //结点的高度
Node* lchild; //结点左孩子
Node* rchild; //结点右孩子
Node(T _val,int _height=1)
:val(_val)
,height(_height)
,lchild(nullptr)
,rchild(nullptr){
}
};
int GetHeight(Node* node) const{
if(!node){
return 0;
}
return node->height;
}
void insert(T val);
void erase(T val);
void Inorder();
Node* find(T val);
private:
Node* L_Rotate(Node* head);
Node* R_Rotate(Node* head);
Node* LL_Rotate(Node* head)
Node* RR_Rotate(Node* head)
Node* LR_Rotate(Node* head);
Node* RL_Rotate(Node* head);
Node* _find(Node* head,T val);
void _Inorder(Node* head);
Node* _insert(Node* head,T val);
Node* _erase(Node* head,T val);
public:
void show();
private:
Node* root=nullptr; //树的根节点
};
1.1.1 LL
LL是指在结点A的左孩子结点B的左子树插入导致不平衡,如图所示,此时我们需要进行的操作是右旋操作来维持平衡。
- A结点的左孩子B结点右旋代替A成为当前最小不平衡二叉树的根节点,A结点的左孩子指向B结点的右孩子,并把B结点的右孩子指向A结点。在维持平衡的同时保证满足二叉搜索树的性质。 代码如下:
Node* R_Rotate(Node* head){
Node* new_head=head->lchild;
head->lchild=new_head->rchild;
new_head->rchild=head;
head->height=1+std::max(GetHeight(head->lchild),GetHeight(head->rchild));
new_head->height=1+std::max(GetHeight(new_head->lchild),GetHeight(new_head->rchild));
return new_head;
}
Node* LL_Rotate(Node* head){
return R_Rotate(head);
}
1.1.2 RR
RR是指在结点A的右孩子结点B的右子树插入导致不平衡,如图所示,此时我们需要进行的操作是左旋操作来维持平衡。
- A结点的右孩子B结点左旋代替A成为当前最小不平衡二叉树的根节点,A结点的右孩子指向B结点的左孩子,并把B结点的左孩子指向A结点。在维持平衡的同时保证满足二叉搜索树的性质。 代码如下:
Node* L_Rotate(Node* head){
Node* new_head=head->rchild;
head->rchild=new_head->lchild;
new_head->lchild=head;
head->height=1+std::max(GetHeight(head->lchild),GetHeight(head->rchild));
new_head->height=1+std::max(GetHeight(new_head->lchild),GetHeight(new_head->rchild));
return new_head;
}
Node* RR_Rotate(Node* head){
return L_Rotate(head);
}
1.1.3 LR
LR是指在结点A的左孩子结点B的右子树插入导致不平衡,如图所示,此时我们需要进行的操作是先左旋再右旋操作来维持平衡。
- 这种情况和之前讨论的LL和RR不一样,不能通过单一的左旋或者右旋就能维持平衡,但是可以进行组合。如上图所示,首先针对B结点进行一次RR操作,也就是单一左旋。为什么要这样做?仔细观察经过RR操作后的树,发现现在的情况和LL一模一样也就是对于A结点的左孩子的左子树增加了结点导致不平衡,那么我们就可以看成1.1.1节所示的LL情况,对其进行单一右旋操作,来恢复平衡。
- 先将A结点的左孩子B的右子树的根结点C向左上旋转提升到B结点的位置,然后再把该C结点向右上旋转提升到A结点的位置
Node* LR_Rotate(Node* head){
Node* new_lhead=L_Rotate(head->lchild);
head->lchild=new_lhead;
return R_Rotate(head);
}
1.1.4 RL
RL是指在结点A的右孩子结点B的左子树插入导致不平衡,如图所示,此时我们需要进行的操作是先右旋再左旋操作来维持平衡。
- 这种情况和LR类似,不能通过单一的左旋或者右旋就能维持平衡,但是可以进行组合。如上图所示,首先针对B结点进行一次LL操作,也就是单一右旋。将情况和还原和RR一样也就是对于A结点的右孩子的右子树增加了结点导致不平衡,那么我们就可以看成1.1.2节所示的RR情况,对其进行单一左旋操作,来恢复平衡。
- 先将A结点的右孩子B的左子树的根结点C向右上旋转提升到B结点的位置,然后再把该C结点向左上旋转提升到A结点的位置
Node* RL_Rotate(Node* head){
Node* new_rhead=R_Rotate(head->rchild);
head->rchild=new_rhead;
return L_Rotate(head);
}
1.1.5 insert函数实现
这里的代码通过递归实现,变量bal表示平衡二叉树的平衡因子,一旦完成了插入会从下往上进行return,这样在第一次bal不满足条件时,就是最小不平衡二叉树。
void insert(T val){
root= _insert(root,val);
}
Node* _insert(Node* head,T val){
if(head==NULL){
head=new Node(val);
return head;
}
if(val>head->val){
head->rchild=_insert(head->rchild,val);
}else if(val<head->val){
head->lchild=_insert(head->lchild,val);
}else{
throw std::invalid_argument("Cannot insert elements with the same value!");
return nullptr;
}
head->height=1+std::max(GetHeight(head->lchild),GetHeight(head->rchild));
int bal = GetHeight(head->lchild) -GetHeight(head->rchild);
if(bal>1){
if(val<head->lchild->val){
return LL_Rotate(head);
}else if(val>head->lchild->val){
return LR_Rotate(head);
}
}
if(bal<-1){
if(val>head->rchild->val){
return RR_Rotate(head);
}else if(val<head->rchild->val){
return RL_Rotate(head);
}
}
return head;
}
1.2 删除操作
删除操作也会改变平衡二叉树的平衡因子,具体平衡操作类似于插入,先删除指定的结点然后自下而上找到最小不平衡二叉树进行调整。删除结点需要分几种情况进行讨论,如下图所示。
- 蓝色情况:比较简单,直接将其置空,释放,然后返回给父节点即可。
- 橙色情况:删除节点有左子树没有右子树。 先保存该结点左子树的地址,delete该结点,然后把地址等于左子树地址, 相当于将原来结点地址覆盖。
- 黄色情况:删除节点有右子树没有左子树 。与2处理相同,只是将左子树换为右子树。
- 绿色情况:既有左子树又有右子树:找到右子树中的最小值,将值赋给当前结点,然后以该最小值为目标继续往右子树寻找并删除结点。
1.2.1 erase函数实现
在分析完上面四种情况后,就可以实现平衡二叉树的删除操作了,但也要注意还需要满足平衡二叉树的性质。在这里同样用递归的方式实现,一旦bal平衡因子不满足条件就需要通过旋转来实现平衡。
void erase(T val){
root=_erase(root,val);
}
Node* _erase(Node* head,T val){
if(head==nullptr){
return nullptr;
}
if(val<head->val){
head->lchild=_erase(head->lchild,val);
}else if(val>head->val){
head->rchild=_erase(head->rchild,val);
}else{
if(!head->lchild&&!head->rchild){ //无左子树无右子树
delete(head);
head=nullptr;
}else if(head->lchild&&!head->rchild){ //有左子树无右子树
Node* lc=head->lchild;
delete(head);
head=lc;
}else if(!head->lchild&&head->rchild){ //无左子树有右子树
Node* rc=head->rchild;
delete(head);
head=rc;
}else{ //都有
Node* cur=head->rchild;
while(cur->lchild){cur=cur->lchild;}
head->val=cur->val;
head->rchild=_erase(head->rchild,cur->val);
}
}
if(head==nullptr) return head;
int bal=GetHeight(head->lchild)-GetHeight(head->rchild);
head->height = 1 + std::max(GetHeight(head->lchild), GetHeight(head->rchild));
if(bal>1){
if(GetHeight(head->lchild->lchild)>=GetHeight(head->lchild->rchild)){
return RR_Rotate(head);
}else{
return LR_Rotate(head);
}
}else if(bal<-1){
if(GetHeight(head->lchild->lchild)>=GetHeight(head->lchild->rchild)){
return RL_Rotate(head);
}else{
return RR_Rotate(head);
}
}
return head;
}
1.3 查找操作
平衡二叉树的查找的时间复杂度为O(logn)。平衡二叉搜索树查找的时间复杂度为什么是O(log n)?_平衡二叉树查询的时间复杂度-CSDN博客。N个结点的平衡二叉搜索树,高度是[log2(N)]+1。[]表示取整。由于算时间复杂度时不考虑常数以及可以通过换底公式如复杂度变为O(logn)。具体实现代码如下:
Node* find(T val){
return _find(this->root,val);
}
Node* _find(Node* head,T val){
if(head == nullptr) return nullptr;
T cur = head->val;
if(cur > val) return _find(head->lchild, val);
if(cur < val) return _find(head->rchild, val);
return head;
}
1.4 其他操作
这里的其他操作都是用来打印这个平衡二叉树的,包括了中序遍历以及层序遍历。
层序遍历:
void show(){
if (root == nullptr)
return;
std::queue<Node*> q;
q.push(root);
while (!q.empty()) {
int levelSize = q.size();
for (int i = 0; i < levelSize; ++i) {
Node* node = q.front();
q.pop();
if (node != nullptr) {
std::cout << node->val << " ";
q.push(node->lchild);
q.push(node->rchild);
} else {
std::cout << "Null ";
}
}
std::cout << std::endl;
}
}
中序遍历:
void _Inorder(Node* head){
if(head==nullptr){
return;
}
std::cout<<head->val<<" ";
_Inorder(head->lchild);
_Inorder(head->rchild);
}
1.5 完整代码
#include <iostream>
#include <memory>
#include <algorithm>
#include <queue>
template<class T>
class AVL{
public:
typedef std::shared_ptr<AVL> ptr;
struct Node
{
T val;
int height;
Node* lchild;
Node* rchild;
Node(T _val,int _height=1)
:val(_val)
,height(_height)
,lchild(nullptr)
,rchild(nullptr){
}
};
int GetHeight(Node* node) const{
if(!node){
return 0;
}
return node->height;
}
Node* L_Rotate(Node* head){
Node* new_head=head->rchild;
head->rchild=new_head->lchild;
new_head->lchild=head;
head->height=1+std::max(GetHeight(head->lchild),GetHeight(head->rchild));
new_head->height=1+std::max(GetHeight(new_head->lchild),GetHeight(new_head->rchild));
return new_head;
}
Node* R_Rotate(Node* head){
Node* new_head=head->lchild;
head->lchild=new_head->rchild;
new_head->rchild=head;
head->height=1+std::max(GetHeight(head->lchild),GetHeight(head->rchild));
new_head->height=1+std::max(GetHeight(new_head->lchild),GetHeight(new_head->rchild));
return new_head;
}
Node* LL_Rotate(Node* head){
return R_Rotate(head);
}
Node* RR_Rotate(Node* head){
return L_Rotate(head);
}
Node* LR_Rotate(Node* head){
Node* new_lhead=L_Rotate(head->lchild);
head->lchild=new_lhead;
return R_Rotate(head);
}
Node* RL_Rotate(Node* head){
Node* new_rhead=R_Rotate(head->rchild);
head->rchild=new_rhead;
return L_Rotate(head);
}
void insert(T val){
root= _insert(root,val);
}
void erase(T val){
root=_erase(root,val);
}
void Inorder(){
_Inorder(this->root);
std::cout<<std::endl;
}
Node* find(T val){
return _find(this->root,val);
}
private:
Node* _find(Node* head,T val){
if(head == nullptr) return nullptr;
T cur = head->val;
if(cur > val) return _find(head->lchild, val);
if(cur < val) return _find(head->rchild, val);
return head;
}
void _Inorder(Node* head){
if(head==nullptr){
return;
}
std::cout<<head->val<<" ";
_Inorder(head->lchild);
_Inorder(head->rchild);
}
Node* _insert(Node* head,T val){
if(head==NULL){
head=new Node(val);
return head;
}
if(val>head->val){
head->rchild=_insert(head->rchild,val);
}else if(val<head->val){
head->lchild=_insert(head->lchild,val);
}else{
throw std::invalid_argument("Cannot insert elements with the same value!");
return nullptr;
}
head->height=1+std::max(GetHeight(head->lchild),GetHeight(head->rchild));
int bal = GetHeight(head->lchild) -GetHeight(head->rchild);
if(bal>1){
if(val<head->lchild->val){
return LL_Rotate(head);
}else if(val>head->lchild->val){
return LR_Rotate(head);
}
}
if(bal<-1){
if(val>head->rchild->val){
return RR_Rotate(head);
}else if(val<head->rchild->val){
return RL_Rotate(head);
}
}
return head;
}
Node* _erase(Node* head,T val){
if(head==nullptr){
return nullptr;
}
if(val<head->val){
head->lchild=_erase(head->lchild,val);
}else if(val>head->val){
head->rchild=_erase(head->rchild,val);
}else{
if(!head->lchild&&!head->rchild){ //无左子树无右子树
delete(head);
head=nullptr;
}else if(head->lchild&&!head->rchild){ //有左子树无右子树
Node* lc=head->lchild;
delete(head);
head=lc;
}else if(!head->lchild&&head->rchild){ //无左子树有右子树
Node* rc=head->rchild;
delete(head);
head=rc;
}else{ //都有
Node* cur=head->rchild;
while(cur->lchild){cur=cur->lchild;}
head->val=cur->val;
head->rchild=_erase(head->rchild,cur->val);
}
}
if(head==nullptr) return head;
int bal=GetHeight(head->lchild)-GetHeight(head->rchild);
head->height = 1 + std::max(GetHeight(head->lchild), GetHeight(head->rchild));
if(bal>1){
if(GetHeight(head->lchild->lchild)>=GetHeight(head->lchild->rchild)){
return RR_Rotate(head);
}else{
return LR_Rotate(head);
}
}else if(bal<-1){
if(GetHeight(head->lchild->lchild)>=GetHeight(head->lchild->rchild)){
return RL_Rotate(head);
}else{
return RR_Rotate(head);
}
}
return head;
}
public:
void show(){
if (root == nullptr)
return;
std::queue<Node*> q;
q.push(root);
while (!q.empty()) {
int levelSize = q.size();
for (int i = 0; i < levelSize; ++i) {
Node* node = q.front();
q.pop();
if (node != nullptr) {
std::cout << node->val << " ";
q.push(node->lchild);
q.push(node->rchild);
} else {
std::cout << "Null ";
}
}
std::cout << std::endl;
}
}
private:
Node* root=nullptr;
};
然后写一个test看看:
#include "AVL_.hpp"
int main(){
AVL<float> t;
t.insert(1.3);
t.insert(2.4);
t.insert(3.5);
t.insert(4.6);
t.insert(5.7);
t.insert(6.8);
t.insert(7.9);
auto it=t.find(5.8);
if(it){
std::cout<<it->val<<" is find"<<std::endl;
}else{
std::cout<<"can't find"<<std::endl;
}
t.show();
t.Inorder();
t.erase(4.6);
t.erase(7.9);
t.erase(2.4);
t.show();
t.Inorder();
return 0;
}
终端打印如下:
如果有写的不对的地方,请批评指正!