AVL二叉树的实现(C++)

        花了许多时间弄懂了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;
	}

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
AVL树是一种自平衡的二叉搜索树,可以高效地执行增删改查操作。下面以C语言为例来介绍AVL树的增删改查操作。 首先,我们需要定义AVL树节点的数据结构,包括节点的值、左子树指针、右子树指针和节点的高度。 ```c typedef struct AVLNode { int val; struct AVLNode* left; struct AVLNode* right; int height; } AVLNode; ``` 接下来,我们可以实现AVL树的插入、删除、更新和查询操作。 1. 插入操作: 插入节点的基本流程是先按照二叉搜索树的规则找到插入位置,然后更新节点的高度,并进行平衡操作,使树保持平衡。 ```c AVLNode* insert(AVLNode* root, int val) { if (root == NULL) { root = createNode(val); } else if (val < root->val) { root->left = insert(root->left, val); } else if (val > root->val) { root->right = insert(root->right, val); } root->height = max(height(root->left), height(root->right)) + 1; int balance = getBalance(root); if (balance > 1 && val < root->left->val) { return rightRotate(root); } if (balance < -1 && val > root->right->val) { return leftRotate(root); } if (balance > 1 && val > root->left->val) { root->left = leftRotate(root->left); return rightRotate(root); } if (balance < -1 && val < root->right->val) { root->right = rightRotate(root->right); return leftRotate(root); } return root; } ``` 2. 删除操作: 删除节点的基本流程是先按照二叉搜索树的规则找到待删除节点,然后更新节点的高度,并进行平衡操作,使树保持平衡。 ```c AVLNode* delete(AVLNode* root, int val) { if (root == NULL) { return root; } else if (val < root->val) { root->left = delete(root->left, val); } else if (val > root->val) { root->right = delete(root->right, val); } else { if (root->left == NULL || root->right == NULL) { AVLNode* temp = root->left ? root->left : root->right; if (temp == NULL) { temp = root; root = NULL; } else { *root = *temp; } free(temp); } else { AVLNode* temp = minValueNode(root->right); root->val = temp->val; root->right = delete(root->right, temp->val); } } if (root == NULL) { return root; } root->height = max(height(root->left), height(root->right)) + 1; int balance = getBalance(root); if (balance > 1 && getBalance(root->left) >= 0) { return rightRotate(root); } if (balance < -1 && getBalance(root->right) <= 0) { return leftRotate(root); } if (balance > 1 && getBalance(root->left) < 0) { root->left = leftRotate(root->left); return rightRotate(root); } if (balance < -1 && getBalance(root->right) > 0) { root->right = rightRotate(root->right); return leftRotate(root); } return root; } ``` 3. 更新操作: 更新操作即是先删除旧节点,然后插入新节点。 ```c AVLNode* update(AVLNode* root, int oldVal, int newVal) { root = delete(root, oldVal); root = insert(root, newVal); return root; } ``` 4. 查询操作: 查询操作即是在树中按照二叉搜索树的规则查找目标值。 ```c AVLNode* search(AVLNode* root, int val) { if (root == NULL || root->val == val) { return root; } else if (val < root->val) { return search(root->left, val); } else { return search(root->right, val); } } ``` 以上就是使用C语言实现AVL树的插入、删除、更新和查询操作的示例代码,希望对你有帮助。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值