• 【AVL树】


    基础知识

    AVL树的定义

    一棵AVL树或者是空树,或者是具有谢下列性质的二叉搜索树:他的左子树和右子树都是AVL树,且左子树和右子树的高度只差的绝对值不超过1.
    在这里插入图片描述
    如上图,左边的二叉搜索树每个结点的左右子树高度差的绝对值小于1,所以是AVL树,右边的树不是每个结点的左右子树高度差的绝对值小于1所以不是AVL树。

    结点的平衡因子

    每个结点附加一个数字,给出该结点右子树的高度减去左子树的高度所得的高度差。这个数字既是为结点的平衡因子balance。根据AVL树的定义,任何一个结点的平衡因子只能取-1,0,1。
    如果一个结点的平衡因子的绝对值大于1,则这颗二叉搜索树就失去了平衡不再是AVL树。如果一棵二叉搜索树是高度平衡的,他就称为AVL树。如果他又n个结点,其高度可保持在O(log2n),平均搜索长度可保持在O(log2n)。

    结构体

    AVL树结构体相比于二叉搜索树多了一个平衡因子

    typedef int KeyType;
    typedef struct AVLNode {
    	struct AVLNode* leftchild;
    	struct AVLNode* parent;
    	struct AVLNode* rightchild;
    	int balance;
    	KeyType key;
    }AVLNode;
    typedef struct {
    	AVLNode* root;
    	int cursize;
    }AVLTree;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    部分操作思路

    AVL树的插入

    在向一棵本来是高度平衡的的AVL树中插入一个数据导致出现了不平衡,我们就需要做平衡话处理,使得树中各结点重新平衡化。

    平衡化旋转

    • 如果在一棵平衡的二叉搜索树中插入一个新结点,造成了不平衡。此时必须调整树的结构,使之平衡化。
    • 平衡化旋转有两种类型:单旋转(左单旋和右单旋)双旋转(左平衡和右平衡)。
    • 每插入一个新结点时,AVL树中相关结点的平衡状态回发生改变。因此,在插入一个新结点后,需要从插入位置沿通向根的路径回溯,检查各个结点的平衡因子。如果发现某节点高度不平衡,停止回溯。
    • 从不平衡的结点起,沿刚才回溯的路径取直接下两层的结点。
    • 如果这三个结点处于一条直线上,则采用单旋转进行平衡化,单旋转可按其方向分为左单旋和右单旋,其中一个是另一个的镜像,其方向与不平衡的形状相关。
    • 如果这三个结点处于一条折线上,则采用双旋转进行平衡化,双旋转分为先左后右和先右后左两类。
      在这里插入图片描述
    左单旋转

    左单旋转的操作如下图,我们从新加入的结点向上遍历,找到平衡因子不小于等于1的结点后有如下三步操作:

    • 建立新的根节点指向平衡因子为2的结点的右孩子,
    • 将平衡银子结点为2的结点的右孩子指向新根节点的左孩子,
    • 新根节点的右孩子指向平衡因子为2的结点。

    在这里插入图片描述
    代码:

    AVLNode* RotateLeft(AVLTree* ptree, AVLNode* ptr) {
    	assert(ptree != nullptr && ptr != nullptr);
    	AVLNode* newroot = ptr->rightchild;//1
    	newroot->parent = ptr->parent;//1的双亲
    	ptr->rightchild = newroot->leftchild;//2
    	if (newroot->leftchild != nullptr) {//2的双亲
    		newroot->leftchild->parent = ptr;
    	}
    	newroot->leftchild = ptr;//3
    	AVLNode* pa = ptr->parent;
    	if (pa == nullptr) {//此处ptr为n根节点
    		ptree->root = newroot;
    	}
    	else {//ptr不是根节点
    		if (pa->leftchild == ptr) {
    			pa->leftchild = newroot;
    		}
    		else {
    			pa->rightchild = newroot;
    		}
    	}
    	ptr->parent = newroot;//3的双亲
    	return newroot;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    右单旋转

    右单旋转和左单旋转是相同的思想,如下图:
    在这里插入图片描述
    代码:

    void RotateRight(AVLTree* ptree, AVLNode* ptr) {
    	assert(ptree != nullptr && ptr != nullptr);
    	AVLNode* newroot = ptr->leftchild;//1
    	newroot->parent = ptr->parent;//
    	ptr->leftchild = newroot->rightchild;//2
    	if (newroot->rightchild != nullptr) {
    		newroot->rightchild->parent = ptr;
    	}
    	newroot->rightchild = ptr;//3
    	AVLNode* pa = ptr->parent;
    	if (pa == nullptr) {
    		ptree->root = newroot;
    	}
    	else {
    		if (pa->leftchild == ptr) {
    			pa->leftchild = newroot;
    		}
    		else {
    			pa->rightchild = newroot;
    		}
    	}
    	ptr->parent = newroot;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    先左后右双旋转

    下图如果新增加
    先左后右的操作如下图:
    在这里插入图片描述
    具体操作:在这里插入图片描述

    • 在子树F或G中插入新结点,该子树高度增加1.结点A的平衡因子变为-2,发生了不平衡。
    • 从结点A起沿插入路径选取三个结点A,B,E,他们位于一条形如’<'的折线上,因此需要进行先左后右的双旋转。
    • 我们的先左后右旋转呢实际上就是进行了两次单旋转。首先以B结点为ptr进行左单旋,然后以A结点为ptr进行右单旋。
      代码:
    void LeftBalance(AVLTree* ptree, AVLNode* ptr) {//左平衡
    	assert(ptree != nullptr && ptr != nullptr);
    	AVLNode* leftsub = ptr->leftchild, * ringhtsub = nullptr;
    	switch (leftsub->balance) {
    	case 0:
    		printf("tree left balance \n"); 
    		break;
    	case -1://新插入结点在D上 直线 右单旋
    		ptr->balance = 0;
    		leftsub->balance = 0;//平衡因子自行修改
    		RotateRight(ptree, ptr);
    		break;
    	case 1://折线 左右旋转
    		ringhtsub = leftsub->rightchild;
    		switch (ringhtsub->balance) {
    		case 0:
    			ptr->balance = 0;
    			leftsub->balance = 0;
    			break;
    		case -1:
    			ptr->balance = 1;
    			leftsub->balance = 0;
    			break;
    		case 1:
    			ptr->balance = 0;
    			leftsub->balance = -1;
    			break;
    		}
    		ringhtsub->balance = 0;
    		RotateLeft(ptree,leftsub);//左右单旋即先左单旋后右单旋
    		RotateRight(ptree, ptr);
    		break;
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    先右后左双旋转

    同先左后右双旋转刚好相反,思路一致,此处不多做叙述
    代码:

    AVLNode* RightBalance(AVLTree* ptree, AVLNode* ptr) {
    	assert(ptree != nullptr && ptr != nullptr);
    	AVLNode* newroot=nullptr;
    	AVLNode* rightsub = ptr->rightchild, *leftsub  = nullptr;
    	switch (rightsub->balance) {
    	case 0:
    
    		break;
    	case 1:
    		ptr->balance = 0;
    		rightsub->balance = 0;
    		newroot=RotateLeft(ptree, ptr);
    		break;
    	case -1:
    		leftsub = rightsub->leftchild;
    		switch (leftsub->balance) {
    		case 0:
    			ptr->balance = 0;
    			rightsub->balance = 0;
    			 break;
    		case 1:
    			ptr->balance = -1;
    			rightsub->balance = 0;
    			break;
    		case -1:
    			ptr->balance = 0;
    			rightsub->balance = 1;
    			break;
    		}
    		leftsub->balance = 0;
    		newroot=RotateRight(ptree, rightsub);
    		newroot=RotateLeft(ptree, ptr);
    		break;
    	}
    	return newroot;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    插入函数

    向AVL树中插入数据时,刚开始同我们二叉搜索树的插入一致。我们通过新插入的结点开始向根回溯的同时判断,我们肯定要首先判断插入的位置,判断其插入的位置是左孩子还是右孩子,
    左孩子:原来新插入结点的双亲结点的平衡因子是0则变为-1.原来平衡因子是1则变为0,且高度不会改变,如果是-1,我们就会发现发生了问题,平衡因子本质上已经变成了-2,所以我们进行左平衡,做了平衡操作(旋转)之后他就会变成了原来的高度,所以高度不会改变。
    右孩子同左孩子类似判断即可。
    代码:

    void Adjust_Insert_Item(AVLTree* ptree,AVLNode* ptr) {
    	assert(ptree != nullptr && nullptr != ptr);
    	bool taller = true;//判断高度会不会发生改变
    	AVLNode* pa = ptr->parent;
    	while (pa != nullptr && taller) {//插入位置
    		if (pa->leftchild == ptr) {//插入到左子树
    			switch (pa->balance) {
    			case 0:pa->balance = -1; break;
    			case 1:pa->balance = 0; 
    				taller = false;
    				break;
    			case -1:
    				LeftBalance(ptree, pa);
    				taller = false;
    				break;
    			}
    		}
    		else {//插入右子树
    			switch (pa->balance) {
    			case 0:pa->balance = 1; break;
    			case -1:pa->balance = 0; 
    				taller = false;
    				break;
    			case 1:
    				RightBalance(ptree, pa);
    				taller = false;
    				break;
    			}
    		}
    		ptr = pa;//回溯
    		pa = ptr->parent;
    	}
    }
    bool Insert_Item(AVLTree* ptree, KeyType kx) {
    	assert(ptree != nullptr);
    	if (ptree->root == nullptr) {//判断不存在根节点
    		ptree->root = Buynode();
    		ptree->root->key = kx;
    		ptree->cursize = 1;
    		return true;
    	}
    	AVLNode* ptr = ptree->root, * pa = nullptr;
    	while (ptr != nullptr && ptr->key != kx) {//遍历,遍历结束找出该结点本应该存放的双亲结点
    		pa = ptr;
    		ptr = kx > ptr->key ? ptr->rightchild : ptr->leftchild;
    	}
    	if (ptr != nullptr && ptr->key == kx) return false;//树中原本就存在这个数据,直接返回错误
    	//树中不存在该结点进行插入
    	ptr = Buynode();//购买新节点
    	ptr->key = kx;//新节点关键码赋值
    	ptr->parent = pa;//双亲指针赋值
    	//判断双亲结点的关键码与要插入的值的大小比较
    	if (ptr->key < pa->key) {
    		pa->leftchild = ptr;
    	}
    	else {
    		pa->rightchild = ptr;
    	}
    	Adjust_Insert_Item(ptree, ptr);
    	ptree->cursize += 1;//节点个数加一
    	return true;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63

    AVl树的删除

    • 如果被删除结点x最多只有一个子女,那么问题比较简单。如果被删除结点x有两个子女,首先搜索x在中序次序下的直接前驱y(同样可以找直接后继)。再把结点y的内容送给结点x,现在问题转移到和删除结点y。
    • 把结点y当作被删除结点x。
    • 将结点x从树中删除。因为结点x最多有一个子女,我们可以简单的把x的双亲结点中原来指向x的指针改指到这个子女结点;如果结点x没有子女,x双亲结点的相应指针置为NULL。然后将原来以结点x为根的子树的高度减1,++
    • 必须沿x通向根的路径反向追踪高度的变化对路径上各个结点的影响
    • 用一个布尔变量shorter来指明树的高度是否被缩短。在这个结点上要做的操作取决于shorter的值和结点的balance,有时还要以来子女的balance。
    • 布尔变量shorter的值初始化为true,然后对于从x的双亲到根的路径上的各个结点p,在shorter保持为true时执行下面的操作。如果shorter变为false,算法终止。

    删除分析:
    case 1:当前结点p的balance为0.如果他的左子树或右子树被缩短,则balance(下图中的3类似于p)在这里插入图片描述

    case 2:结点p的balance不为0,且较高的子树被缩短,则p的balance改为0,同时shorter置为true。(下图中的7和3结点即为p)
    在这里插入图片描述

    case 3:结点p的balance不为0,且较矮的子树又被虽短,且在结点p发生不平衡。需要进行平衡话旋转来恢复平衡。令p的较高的子树的根为q,根据q的balance,有如下三种情况(下图中q的平衡因子大小来判断,下图均是右哦平衡为例)
    case 3a:q的balance为1,我们发现他是直线,先将旋转后的平衡因子进行赋值,然后进行左单旋。
    在这里插入图片描述
    case 3b:q的平衡因子是0,同上,先进行旋转后的赋值,然后进行左单旋。
    在这里插入图片描述
    case 3c:q的平衡因子为-1,所以是折线,要进行双旋转,我们因此我们根据r的balance来判断,进行操作。在这里插入图片描述

    左平衡代码
    bool Adj_LeftBalance(AVLTree* ptree, AVLNode* &pa) {
    	assert(ptree != nullptr && pa != nullptr);
    	AVLNode* leftsub = pa->leftchild, * rightsub = nullptr;
    	bool ret = false;
    	switch (leftsub->balance) {
    	case 0:
    		pa->balance = -1;
    		leftsub->balance=1;
    		RotateRight(ptree, pa);
    		ret = false;
    		break;
    	case -1:
    		pa->balance = 0;
    		leftsub->balance = 0;
    		RotateRight(ptree,pa);
    		ret = true;
    		break;
    	case 1:
    		rightsub = leftsub->rightchild;
    		switch (rightsub->balance) {
    		case 0:
    			pa->balance = 0;
    			leftsub->balance = 0;
    			break;
    		case 1:
    			break;
    			pa->balance = 0;
    			leftsub->balance = -1;
    		case -1:
    			pa->balance = 1;
    			leftsub->balance = 0;
    			break;
    		}
    		rightsub->balance = 0;
    		RotateLeft(ptree, leftsub);
    	  pa=RotateRight(ptree, pa);
    		ret = true;
    		break;
    	}
    	return ret;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    右平衡代码
    bool Adj_RightBalance(AVLTree* ptree,AVLNode* &pa){
    	assert(ptree != nullptr && pa != nullptr);
    	AVLNode* rightsub = pa->rightchild, * leftsub = nullptr;
    	bool ret = false;
    	switch (rightsub->balance){//q的平衡因子判断后面是如何旋转
    	case 0:
    		pa->balance = 1;
    		rightsub->balance = -1;
    		pa=RotateLeft(ptree,pa);
    		ret = false;
    		break;
    	case 1:
    		pa->balance = 0;
    		rightsub->balance = 0;
    		pa=RotateLeft(ptree, pa);
    		ret = true;
    		break;
    	case -1:
    		leftsub = rightsub->leftchild;
    		switch (leftsub->balance) {
    		case 0:
    			pa->balance = 0;
    			rightsub->balance = 0;
    			break;
    		case 1:
    			pa->balance = -1;
    			rightsub->balance = 0;
    			break;
    		case -1:
    			pa->balance = 0;
    			rightsub->balance = 1;
    			break;
    		}
    		rightsub->balance = 0;
    		RotateRight(ptree, rightsub);
    		pa=RotateLeft(ptree, pa);
    		ret = true;
    		break;
    	}
    	return ret;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    删除函数
    void Adjust_Del_Tree(AVLTree* ptree, AVLNode* ptr, bool leftTag)
    {
    	assert(ptree != nullptr && ptr != nullptr);
    	bool shorter = true;
    	AVLNode* pa = ptr->parent;
    	while (pa != nullptr && shorter)
    	{
    		if (leftTag)
    		{ // pa->leftchild ==> ptr;
    			switch (pa->balance)
    			{
    			case 0: pa->balance = 1;
    				shorter = false;//本来左右高度一样,删除左孩子,高度没发生变化
    				break;
    			case -1: pa->balance = 0;
    				shorter = true;//原本左高右低,删除左孩子,高度变化
    				break;
    			case 1://左低右高,删除左孩子,需要进行右平衡。
    				shorter = Adj_RightBalance(ptree, pa);
    				break;
    			}
    
    		}
    		else
    		{	// pa->rightchild ==> ptr;
    			switch (pa->balance)
    			{
    			case 0: pa->balance = -1;
    				shorter = false;
    				break;
    			case 1: pa->balance = 0;
    				shorter = true;
    				break;
    			case -1:
    				shorter = Adj_LeftBalance(ptree, pa);
    				break;
    			}
    		}
    		ptr = pa;
    		pa = ptr->parent;
    		if (pa != nullptr)
    		{
    			leftTag = pa->leftchild == ptr ? true : false;
    		}
    	}
    }
    bool Remove_Item(AVLTree* ptree, const KeyType kx)
    {
    	assert(ptree != nullptr);
    	if (ptree->root == nullptr) return false;
    	AVLNode* ptr = FindValue(ptree, kx);
    	bool leftTag = true;
    	if (ptr == nullptr) return false;
    	if (ptr->leftchild != nullptr && ptr->rightchild != nullptr)
    	{
    		AVLNode* nextnode = Next(ptr); // Prev(ptr);
    		ptr->key = nextnode->key;
    		ptr = nextnode;
    	}
    	// del leaf; del oneBrch
    	AVLNode* child = ptr->leftchild != nullptr ? ptr->leftchild : ptr->rightchild;
    	AVLNode* pa = ptr->parent;
    	if (pa != nullptr)//修改指向关系之前判断出待删除结点是双亲指针的左孩子还是右孩子
    	{
    		leftTag = pa->leftchild == ptr ? true : false;	
    	}
    	if (child != nullptr) child->parent = pa;
    	if (pa != nullptr)
    	{
    		if (pa->leftchild == ptr)
    		{
    			pa->leftchild = child;
    		}
    		else
    		{
    			pa->rightchild = child;
    		}
    	}
    	else
    	{
    		ptree->root = child;
    	}
    	if (ptr != nullptr && ptr->parent != nullptr)
    	{
    		Adjust_Del_Tree(ptree, ptr, leftTag); // pa = ptr->parent;
    	}
    	Freenode(ptr);
    	ptree->cursize -= 1;
    	return true;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
  • 相关阅读:
    Node.js 入门教程 26 npm 依赖与开发依赖 & 27 Node.js 包运行器 npx
    十六、Webpack常见的插件和模式
    FPGA杂记
    其实很多人都在学电子
    数字孪生园区智慧运营平台的应用场景
    GBASE 8s 通过light scan优化查询性能
    CyberSploit:1
    Mysql的分布式事务原理理解
    Effective C++ 规则39:明智而谨慎的使用private继承
    代码随想录day46 | 动态规划P8 | ● 139. ● 多重背包● 背包问题总结
  • 原文地址:https://blog.csdn.net/m0_56246173/article/details/128149483