花了许多时间弄懂了AVL二叉树的原理,为了防止自己以后忘记编程思路,也为了请路过的大佬优化程序,现将编写AVL二叉树的思路和过程记录下来。
这里只记录思路,数据一律使用整型变量,就不使用模板了。
由于递归函数的空间复杂度太高,在存储超过十万个数据时可能会没有足够的内存来运行,因此这里使用堆栈容器来存储遍历的路径。
#include<iostream>
using namespace std;
#include<stack>
class Node
{
public:
int value;
int bf;//平衡因子
Node* left;//左儿子
Node* right;//右儿子
Node()
{
this->value = 0;
this->bf = 0;
this->left = NULL;
this->right = NULL;
}
Node(int val)
{
this->value = val;
this->bf = 0;
this->left = NULL;
this->right = NULL;
}
};
创建了结点类后,建立树类。声明一个树根,构造函数初始化树根为空指针。析构函数销毁所有new出来的结点(虽然该程序中的堆区数据会在程序结束时自动释放,但是要养成释放自己new出来的空间的习惯)。获取元素个数方便检查程序,中序遍历可以输出排序好的数据,计算高度为了直观的表现出AVL二叉树查找的时间复杂度,四种旋转包含了AVL的所有调整情况。
class AVL
{
public:
Node* root;
AVL()
{
this->root = NULL;
}
~AVL();
//获取树中元素个数
int size();
//中序遍历
void middleErogodic();
//计算高度
int getHight(Node* node);
//左单旋
void LL(Node* &ptr);
//右单旋
void RR(Node* &ptr);
//左右双旋
void LR(Node* &ptr);
//右左双旋
void RL(Node* &ptr);
//插入
void insert(int val);
//查找
Node* search(int val);
//删除数据
void remove(int val);
};
1、析构函数
AVL::~AVL()
{
if (!this->root)//根为空,无需释放
{
return;
}
stack<Node*>s;//存放经过的结点
Node* temp = this->root;
Node* erase = temp;
while (temp || !s.empty())//temp走到头并且容器内的结点也全被取出时遍历完毕
{
while (temp)//让temp向左一直走到头
{
s.push(temp);
temp = temp->left;
}
if (!s.empty())
{//左边无路可走就开始回溯,将存进的结点一一取出,进入他们的右子树重复之前的操作
temp = s.top();
erase = temp;
s.pop();
temp = temp->right;
delete erase;//将取出的删除
}
}
}
2、获取元素个数
与析构一样的遍历方法
int AVL:: size()
{
if (!this->root)
{
return 0;
}
int num = 0;
stack<Node*>s;
Node* temp = this->root;
while (temp||!s.empty())
{
while (temp)
{
s.push(temp);
num++;
temp = temp->left;
}
if (!s.empty())
{
temp = s.top();
s.pop();
temp = temp->right;
}
}
return num;
}
3、中序遍历
也是一样的遍历方法
void AVL:: middleErogodic()
{
if (!this->root)
{
return;
}
stack<Node*>s;
Node* temp = this->root;
while (temp || !s.empty())
{
while (temp)
{
s.push(temp);
temp = temp->left;
}
if (!s.empty())
{
temp = s.top();
s.pop();
cout << temp->value << " ";
temp = temp->right;
}
}
}
4、计算高度
因为AVL树的高度不会太高,所以这里使用了递归函数。
int AVL:: getHight(Node* node)
{
int LH, RH, MaxH;//左子树高度,右子树高度,最大高度
if (node)//从树的最下端开始逐层计算当前的最大高度
{
LH = getHight(node->left);
RH = getHight(node->right);
MaxH = (LH > RH) ? LH : RH;
return (MaxH + 1);
}
else
{
return 0;
}
}
5、旋转算法
由于AVL树的任何一个节点的平衡因子(左子树的最大高度与右子树的最大高度之差的绝对值)不超过1,但是在插入与删除过程中会出现平衡被破坏的情况,这就需要做一些调整,因为这些调整看起来像是将几个结点旋转了一样,这里称之为旋转操作,旋转操作有且只有以下四种情况:
LL旋转:麻烦结点在发现者左儿子的左儿子上(这里的白块高度可以是0)
顺时针旋转,将B的右儿子交给A,然后A当B的右儿子,这时A、B的平衡因子都变成0
这里的指针指向B,以便在后续代码中将B的地址赋值给A的父结点
void AVL:: LL(Node* &ptr)
{
Node* A = ptr;
ptr = A->left;
A->left = ptr->right;
ptr->right = A;
ptr->bf=0;
A->bf=0;
}
LR旋转:麻烦结点在发现者左儿子的右儿子上(这里白块的高度可以为0,这时C自身是麻烦结点),根据麻烦结点位置的不同,调整后各结点的平衡因子也不同
这种情况需要进行两次旋转,先将C与B按逆时针旋转,把C的左儿子交给B,B做C的左儿子,C做A的左儿子,根据麻烦结点位置的不同,B的平衡因子为1或0
再将C与A按顺时针旋转,把C的右儿子交给A,A做C的右儿子,根据麻烦结点不同,A的平衡因子为0或-1
这里指针指向C
void AVL:: LR(Node* &ptr)
{
Node* A = ptr;
Node* B = A->left;
ptr = B->right;
B->right = ptr->left;
ptr->left = B;
if (ptr->bf >= 0)
{
B->bf = 0;
}
else
{
B->bf = 1;
}
A->left = ptr->right;
ptr->right = A;
if (ptr->bf == 1)
{
A->bf = -1;
}
else
{
A->bf = 0;
}
ptr->bf = 0;
}
RR旋转和RL旋转与LL、LR相反,这里只给出代码
void AVL:: RR(Node* &ptr)
{
Node* A = ptr;
ptr = A->right;
A->right = ptr->left;
ptr->left = A;
ptr->bf = 0;
A->bf = 0;
}
void AVL:: LR(Node* &ptr)
{
Node* A = ptr;
Node* B = A->left;
ptr = B->right;
B->right = ptr->left;
ptr->left = B;
if (ptr->bf >= 0)
{
B->bf = 0;
}
else
{
B->bf = 1;
}
A->left = ptr->right;
ptr->right = A;
if (ptr->bf == 1)
{
A->bf = -1;
}
else
{
A->bf = 0;
}
ptr->bf = 0;
}
6、插入数据
思路是将经过的结点记录在堆栈中,找到插入点后按原路返回,边返回边修改平衡因子,如果出现平衡因子为2或-2的结点,就进行旋转操作
void AVL:: insert(int val)
{
stack<Node*>s;
Node* temp = root;
Node* father = NULL;//记录父结点
while (temp)
{
father = temp;
s.push(temp);//将沿途的结点全部录入容器
if (val < temp->value)
{
temp = temp->left;
}
else if (val > temp->value)
{
temp = temp->right;
}
else//插入重复数据了,返回
{
return;
}
}
temp = new Node(val);//新建结点
if (father == NULL)
{
root = temp;
return;
}
if (val < father->value)//找到插入位置
{
father->left = temp;
}
else if (val > father->value)
{
father->right = temp;
}
while (!s.empty())//插入已完成,按原路返回
{
father = s.top();//栈顶即为父结点
s.pop();
if (temp == father->left)//判断子结点的位置,改变父结点的平衡因子
{
father->bf++;
}
else
{
father->bf--;
}
if (father->bf == 0)//如果遇到平衡因子为0的结点,那么它不会对它的父结点产生影响,直接退出
{
break;
}
else if (father->bf == 1 || father->bf == -1)//如果平衡因子为1或-1,向上寻找
{
temp = father;
}
else//平衡因子为2或-2时,进行旋转操作
{
if (father->bf == 2)
{
if (val < father->left->value)//如果插入的数据比子结点数据小,执行左左旋转
{
LL(father);
}
else
{
LR(father);
}
}
else
{
if (val > father->right->value)
{
RR(father);
}
else
{
RL(father);
}
}
break;
}
}
if (s.empty())//栈为空,说明没有爷爷结点了,要么平衡没被破坏过,要么旋转操作在根部进行
{
root = father;
}
else
{
Node* grandfather = s.top();//找到爷爷结点,连接父结点
if (father->value < grandfather->value)
{
grandfather->left = father;
}
else
{
grandfather->right = father;
}
}
}
7、删除
用堆栈容器记录路径,先进行查找操作,如果没找到就退出,找到了,分析目标结点左右子树是否齐全,如果齐全,在它的左子树中找一个最大的或是在右子树中找一个最小的替换它,就可以将删除双子树结点问题转换为单子树问题。
删掉后开始回溯,如果遇到平衡被破坏的结点,进行旋转,但是旋转方法除了那四种还有一种比较特殊的:
旋转前B的平衡因子为0,旋转完毕后 A为1,B为-1,注意这种旋转不会改变整体结构的高度,旋转前为2,旋转后依然为2,所以可以不用继续回溯直接退出
void AVL:: remove(int val)
{
Node* taget = root;
Node* father = NULL;
Node* temp = NULL;
stack<Node*>s;
while (taget)
{
if (val == taget->value)
{
break;
}
father = taget;
s.push(father);
if (val < taget->value)
{
taget = taget->left;
}
else if (val > taget->value)
{
taget = taget->right;
}
}
if (!taget)
{
cout << "没有" <<val << endl;
return;
}
if (taget->left!=NULL && taget->right!=NULL)//目标结点的左右子树都不为空
{
father = taget;
s.push(father);
temp = taget->left;//从左子树中找一个最大的来替换
while(temp->right)
{
father = temp;
s.push(father);
temp = temp->right;
}
taget->value = temp->value;
taget = temp;//从要删除双子树结点转化为要删除单子树结点
}
if (taget->left)//目标结点只有一个子树
{
temp = taget->left;
}
else if(taget->right)
{
temp = taget->right;
}
if (father == NULL)//没有父结点,说明删除的是根结点,这时此根结点只有一个分支
{
root = temp;
}
else
{
int flag = 0;//用这个标志来分辨无子结点
if(taget->left == NULL && taget->right == NULL)//目标结点是叶子结点
{
temp = NULL;
if (taget == father->left)
{
flag = 1;
}
else
{
flag = -1;
}
}
if (flag == 0)
{
if (father->left == taget)//父结点连接目标结点的下一个结点来达到删除目的
{
father->left = temp;
}
else
{
father->right = temp;
}
}
else if (flag == 1)//没有下一个节点的直接指向空
{
father->left = NULL;
}
else
{
father->right = NULL;
}
while (!s.empty())//开始回溯
{
father = s.top();
s.pop();
if (flag == 0)
{
if (father->left == temp)
{
father->bf--;
}
else
{
father->bf++;
}
}
else if(flag == 1)//从叶子结点回溯要进行一次特殊处理,随后便恢复正常
{
father->bf--;
flag = 0;
}
else
{
father->bf++;
flag = 0;
}
if (father->bf == 1 || father->bf == -1)//更改前的平衡因子为0,则删除不会对后续结点产生影响
{
break;
}
else if(father->bf==0)//更改前的平衡因子为1或-1,向上回溯寻找不平衡结点
{
temp = father;
}
else//此结点已被破坏,进行旋转
{
if (father->bf == 2)
{
if (father->left->bf > 0)//A为2,B为1,为LL旋转
{
LL(father);
}
else if(father->left->bf < 0)//A为2,B为-1,LR旋转
{
LR(father);
}
else//A为2,B为0,进行LL旋转,但旋转后的平衡因子与一般的LL旋转不同
{
LL(father);
father->bf = -1;
father->right->bf = 1;
if (s.empty())//容器为空,没有爷爷结点,那么父结点就是根
{
root = father;
}
else
{
if (father->value < s.top()->value)//连接父结点与爷爷结点
{
s.top()->left = father;
}
else
{
s.top()->right = father;
}
}
break;//这种旋转不会改变结构的高度,直接退出循环
}
}
else
{
if (father->right->bf < 0)
{
RR(father);
}
else if(father->right->bf > 0)
{
RL(father);
}
else
{
RR(father);
father->bf = 1;
father->left->bf = -1;
if (s.empty())
{
root = father;
}
else
{
if (father->value < s.top()->value)
{
s.top()->left = father;
}
else
{
s.top()->right = father;
}
}
break;
}
}
temp = father;
if (s.empty())
{
root = father;
}
else
{
if (father->value < s.top()->value)
{
s.top()->left = father;
}
else
{
s.top()->right = father;
}
}
}
if (s.empty())
{
root = father;
}
}
}
delete taget;
}