• AVL树


    AVL树这样一棵搜索二叉树,它的左右子树的深度之差不超过1。因此,他是带有条件的搜索二叉树。这个条件保证了AVL树的深度是O(log n).最简单的想法是左右两棵子树保持相同的高度。但是这种条件过于苛刻,难以使用。AVL只要求深度之差不超过1。AVL解决了二叉搜索树带来的不平衡问题。但是要求变成了我们必须在每次操作后进行调整,以使得AVL树保持平衡。另一种较新的方法是放弃平衡条件,允许树有任意的深度,但是在每次操作后要进行调整,以使得后面的操作效率更高。有一种这样的树称之为伸展树。

    在AVL树的每一个节点中保留其高度信息是必须的。在一棵高度为h的AVL树中,最少节点数S(h) = S(h-1)+S(h-2)+1。对于h为0时,S(h)=1;h为2时,S(h)=2。这个函数与斐波那契数列密切相关。

    如果一个插入操作破坏了AVL树的平衡条件,那么这插入后形成的左右子树的高度之差最大是2。因此只会出现4种情形。

    1. 对节点的左儿子的左子树进行一次插入;
    2. 对节点的右儿子的右子树进行一次插入;
    3. 对节点的左儿子的右子树进行一次插入;
    4. 对节点的右儿子的左子树进行一次插入;

    上述4种情形之中,1和2是镜像对称的,3和4是镜像对称的。所以看起来只有两种情况,但是对于编程实现而言还是4种情形。对于1,2这样的插入操作,可以通过单旋转来完成;对于3,4这样的插入操作,需要通过稍微复杂的双旋转操作来完成。

    单旋转:插入之前的高度是和插入之后的高度是保持一致的。插入操作不仅仅是修改局部的变化,树的其余部分也必须知道这个变化。插入可能会导致多个节点的平衡被破坏,但是我们只需要修复距离这个插入节点最近的被破坏平衡的节点。

    双旋转:对于1,2两种情形,如果采用单旋转来做,那么会发现,单旋转并没有降低树的深度。它还是不平衡的。双旋转解决了这个问题,它等价于做了两次单旋转。

    插入以后树可能还是平衡的,这时候不需要调整树的结构,但是我们仍然需要更新树的深度。

    代码实现如下

    AVL树的ADT:(avl.h文件)

    #ifndef AVLTREE
    #define AVLTREE
    
    #include<stdio.h>
    #include<stdlib.h>
    #define MAX(a,b) ((a) > (b) ? (a) : (b))
    
    typedef int ELementType;
    typedef struct AVLNode *Position;
    typedef Position AVLTree; /* AVL树类型 */
    
    struct AVLNode {
    	ELementType Data; /* 结点数据 */
    	AVLTree Left;     /* 指向左子树 */
    	AVLTree Right;    /* 指向右子树 */
    	int Height;       /* 树高 */
    };
    
    AVLTree Insert(ELementType x, AVLTree T);
    int Height(Position P);
    AVLTree LLRotate(Position K);
    AVLTree LRRotate(Position K);
    AVLTree RRRotate(Position K);
    AVLTree RLRotate(Position K);
    void InorderTraversal(AVLTree T);
    
    #endif // AVLTREE
    
    

    main.cpp文件:(测试流程)在二叉搜索树中已经实现了绝大多数的查找树操作,例如前序遍历,中序遍历,后序遍历,求最大,最小值等。在AVL树中就不一一实现了,只就插入做了实现,我对删除采用的是懒惰删除法。在此不在说明。只测试一下AVL树的深度是不是O(log n)以及中序遍历输出是不是有序的。这些足以证明它就是我们要求的AVL树。

    #include"avl.h"
    
    int main()
    {
    	AVLTree avltree = NULL;
    	printf("AVL树元素插入顺序如下:");
    	for (int i = 5; i > 0 ; i--)
    	{
    		printf("%d ",i);
    		avltree = Insert(i, avltree);
    	}
    	for (int i = 6; i < 10; i++)
    	{
    		printf("%d ", i);
    		avltree = Insert(i, avltree);
    	}
    	printf("
    中序遍历输出:");
    	InorderTraversal(avltree);
    	printf("
    AVL树的高度:%d
    ",Height(avltree));
    	system("pause");
    	return 0;
    }

    alv.cpp文件

    #include "avl.h"
    
    AVLTree Insert(ELementType x, AVLTree T)
    {
    	if (NULL == T)	//T为空,创建树
    	{
    		T = (AVLTree)malloc(sizeof(struct AVLNode));
    		if (NULL == T)
    		{
    			printf("创建树失败!");
    		}
    		else
    		{
    			T->Data = x;
    			T->Height = 0;		//树根深度为0
    			T->Left = NULL;
    			T->Right = NULL;
    		}
    	}
    	else
    	{
    		if (x < T->Data)		//x小于根节点数据
    		{
    			T->Left = Insert(x, T->Left);	//递归插入左子树
    			if (Height(T->Left) - Height(T->Right) == 2)	//AVL树不平衡了,调整一下
    			{
    				if (x < T->Left->Data)		
    				{
    					T = LLRotate(T);		//插入到左子树的左子树
    				}
    				else
    				{
    					T = LRRotate(T);		//插入到左子树的右子树
    				}
    			}
    		}
    		else if (x > T->Data)
    		{
    			T->Right = Insert(x, T->Right);		//递归插入右子树
    			if (Height(T->Right) - Height(T->Left) == 2)		//AVL树不平衡了,调整一下
    			{
    				if (x > T->Right->Data)
    				{
    					T = RRRotate(T);		//插入右子树的右子树
    				}
    				else
    				{
    					T = RLRotate(T);		//插入右子树的左子树
    				}
    			}
    		}
    		else
    		{
    			//x在这棵AVL树中,我们什么都不做,当然,我们也可以重新设计AVL树的ADT。
    			//使得ADT可以保存数据出现的次数,如果有相同数据插入,我们就使得次数加1。
    			//这样的做法为我们在AVL树中做一个删除也提供了一种方式,即:懒惰删除。
    			//我们并不将这个节点从树中删除,而只是去更改数据出现的次数减1。
    			//这样我们就不需要做多余的操作去调整可能出现的不平衡状态。
    			//这种做法在有重复关键字时很好用。并且这种做法只会导致树的高度略微上升。
    			//这样的做法使得删除变得非常快,并且如果被删除的元素重新插入,那么省去
    			//了重新申请空间的开销。
    		}
    		T->Height = MAX(Height(T->Left), Height(T->Right)) + 1;		//更新树的高度
    	}
    	return T;
    }
    
    int Height(Position P)		//AVL树的节点保存了高度这一信息,直接返回即可
    {
    	if (NULL == P)
    	{
    		return 0;		//节点为空,定义高度为0
    	}
    	else
    	{
    		return P->Height;
    	}
    }
    
    AVLTree LLRotate(Position K)
    {
    	Position M;
    	//调整AVL树
    	M = K->Left;
    	K->Left = K->Left->Right;
    	M->Right = K;
    
    	K->Height = MAX(Height(K->Left), Height(K->Right)) + 1;		//更新右子树的高度
    	M->Height = MAX(Height(M->Left), Height(M->Right)) + 1;		//更新新的根节点高度
    	//M->Height = MAX(Height(M->Left), K->Height) + 1;     Height(M-Right) == K->Height
    	
    	return M;
    }
    
    AVLTree LRRotate(Position K)
    {
    	//这里设计的很巧妙,通过先旋转K的左子树。然后在整体旋转K。
    	K->Left = RRRotate(K->Left);
    	return LLRotate(K);
    }
    
    AVLTree RRRotate(Position K)
    {
    	Position M;
    	//调整AVL树
    	M = K->Right;
    	K->Right = M->Left;
    	M->Left = K;
    
    	K->Height = MAX(Height(K->Left), Height(K->Right)) + 1;	//更新左子树的高度
    	M->Height = MAX(Height(M->Right), Height(M->Left)) + 1;	//更新新的根节点高度
    	//M->Height = MAX(Height(M->Left), K->Height) + 1;     Height(M-Right) == K->Height
    
    	return M;
    }
    
    AVLTree RLRotate(Position K)
    {
    	K->Right = LLRotate(K->Right);
    	return RRRotate(K);
    }
    
    void InorderTraversal(AVLTree T)
    {
    	if (T)
    	{
    		InorderTraversal(T->Left);
    		printf("%d ", T->Data);
    		InorderTraversal(T->Right);
    	}
    }
  • 相关阅读:
    linux inode索引节点使用率100% 解决
    Linux常用命令
    mongodb常用命令
    抓包工具简介:fiddler、charles
    博客园自定义更换背景
    ant+jmeter应用
    BeanShell断言
    jmeter 常用函数(一):__Random
    git常见错误解决方法
    react环境搭建
  • 原文地址:https://www.cnblogs.com/zy666/p/10504286.html
Copyright © 2020-2023  润新知