数据结构与算法分析:平衡二叉树(Avl树)

一棵Avl树是其每个节点的左子树和右子树的高度最多差1的二叉查找树

1. 定义节点

	//建立带高度的节点
	private static class AvlNode<AnyType>
	{
		AnyType element;
		AvlNode<AnyType> left;
		AvlNode<AnyType> right;
		int height;//为了Avl特点
		
		AvlNode(AnyType x)
		{
			this(x, null, null);
		}
		
		AvlNode (AnyType x, AvlNode<AnyType> left, AvlNode<AnyType> right)
		{
			element = x;
			this.left = left;
			this.right = right;
			height = 0;
			
		}
	}
  • 与普通二叉查找树的节点不同的是,平衡二叉树的节点增加了height属性,这是为了满足平衡二叉树的定义而设定的

2.定义树的根

	//根
	private AvlNode<AnyType> root;
  • 根是私有的

3.初始化平衡二叉树

	//初始化Avl树
	public AvlTree()
	{
		root = null;
		
	}
  • 使用构造函数初始化平衡二叉树

4.清空二叉树

	//makeEmpty方法
	public void makeEmpty()
	{
		root = null;
	}

5.判断二叉树是否为空

	//isEmpty方法
	public boolean isEmpty()
	{
		return root == null;
	}
  • 返回类型是boolean型

6.判断二叉树中是否包含某个数据

//第一部分:对外显示的方法
	public boolean contains(AnyType x)
	{
		return contains(x, root);
	}
  • return contains(x, root);中的root表示从根开始判断
//第二部分:私有方法
//采用递归
	private boolean contains(AnyType x, AvlNode<AnyType> t)
	{
		if(t == null)
			return false;
		
		int compareResult = x.compareTo(t.element);//将x与t节点的值比较
		
		if(compareResult < 0)
			return contains(x, t.left);
		else if(compareResult > 0)
			return contains(x, t.right);
		else
			return true;
	}
//采用循环
	private boolean contains(AnyType x, AvlNode<AnyType> t)
	{
		int compareResult = x.compareTo(t.element);
		while(t != null)
		{
			if(compareResult < 0)
				t = t.left; 
			else if(compareResult > 0)
				t = t.right;
			else
				return true;
		}
		return false;
  • contains的思想是将要判断的数据先与根节点的值比较,若小于根节点的值,则找根节点的左子树…以此类推,这也是二叉查找树的属性:对于树中的每个节点x,它的左子树中所有项的值小于X中的项,它的右子树中的所有项的值大于X中的项

7.寻找树中最小值

	//findMin方法:使用递归
	public AnyType findMin() 
	{
		if(isEmpty())
			throw new RuntimeException();
		return findMin(root).element;
	}
	private AvlNode<AnyType> findMin(AvlNode<AnyType> t)
	{
		if(t == null)
			return null;
		
		else if (t.left == null)
			return t;
		else 
			return findMin(t.left);
	}
  • 二叉树中的最小值位于最左端
  • 当二叉树为空时抛出异常:无法执行该方法
  • 私有的findMin方法返回的是一个节点,这样包含的信息更多,方面后面的使用

8.寻找树中最大值

	//findMax方法:使用循环
	public AnyType findMax()
	{
		if(isEmpty())
			throw new RuntimeException();
		return findMax(root).element;
	}
	private AvlNode<AnyType> findMax(AvlNode<AnyType> t)
	{
		if(t == null)
			return null;
		while(t.right != null)
			t = t.right;
		return t;
	}

9.向树中添加一项

	//insert方法
	public void insert(AnyType x)
	{
		root = insert(x,root);
	}
	private AvlNode<AnyType> insert(AnyType x, AvlNode<AnyType> t)
	{
		if(t == null)
			return new AvlNode<AnyType>(x, null, null);
		
		int compareResult = x.compareTo(t.element);
		
		if(compareResult < 0)
			t.left =  insert(x, t.left);//在t的左子树中继续寻找应在位置
		else if (compareResult > 0)
			t.right = insert(x, t.right);
		else
			;
		return balance(t);
				
	}
  • 大部分的私有方法返回的都是节点而不是某项数据,这样包含的信息更多,提高代码复用性
  • 首先创建一个数据项是要求值的节点,再经过判断将节点置于应在位置
  • 在return时调用一次balance方法使得该二叉树平衡

10.从树中移除某项

	//remove方法
	public void remove(AnyType x)
	{
		root = remove(x, root);
	}
	private AvlNode<AnyType> remove(AnyType x, AvlNode<AnyType> t)
	{
		if(t == null)
			return null;
		
		int compareResult = x.compareTo(t.element);
		
		if(compareResult < 0)
			t.left = remove(x, t.left);
		else if(compareResult > 0)
			t.right = remove(x, t.right);
		else if(t.left != null && t.right != null)//被移除节点有两个子树
		{
			t.element = findMin(t.right).element;
			t.right = remove(t.element, t.right);
		}
		else // 被移除节点只有一个子树
			t = (t.left != null)? t.left : t.right;
		
		return balance(t);
	}
  • 首先找到要被移除的节点
  • 若该节点有两个子树,那么选取该节点右子树中最小的值来代替 t 节点的值,然后递归地在t节点的右子树中删除这个值的节点(这个节点不可能有左儿子)
  • 若该节点只有一个子树,那么用该子树节点的值代替t 的值
  • 返回时仍然需要平衡

11.打印该树

	//printTree方法
	public void printTree()
	{
		printTree(root);
	}
	private void printTree(AvlNode<AnyType> t)
	{
		if(t != null)
			printTree(t.left);
			System.out.println(t.element);
			printTree(t.right);
			
	}
  • 采用中序打印

12.求一个节点的高度

	//height方法
	private int height(AvlNode<AnyType> t)
	{
		if(t == null)
			return -1;
		else
			return t.height;
	}
  • 空节点的高度为-1

13.平衡该二叉树

	//balance方法
	private static final int ALLOWED_IMBALANCE = 1;
	private AvlNode<AnyType> balance(AvlNode<AnyType> t)
	{
		if(t == null)
			return t;
		
		if(height(t.left) - height(t.right) > ALLOWED_IMBALANCE )//左子树高于右子树
			if (height(t.left.left) >= height(t.left.right) )//左左//等号对应的是删除现象
				t = rotateWithLeftChild(t);
			else //左右
				t = doubleWithLeftChild(t);
		else if(height(t.right) - height(t.left) > ALLOWED_IMBALANCE )//右子树高于左子树
			if(height(t.right.right) >= height(t.right.left))//右右
				t = rotateWithRightChild(t);
			else//右左
				t = doubleWithRightChild(t);
		
		t.height = Math.max(height(t.left), height(t.right)) + 1;
		return t;
				
	}
  • 一个节点a的不平衡现象有四种
    1.对a的左儿子的左子树进行一次插入(或是一次删除操作后等效于此情况)
    2.对a的左儿子的右子树进行一次插入(或是一次删除操作后等效于此情况)
    3.对a的右儿子的左子树进行一次插入(或是一次删除操作后等效于此情况)
    4.对a的右儿子的右子树进行一次插入(或是一次删除操作后等效于此情况)

  • 对情况1、4,进行一次单旋转就足够了

  • 对情况2、3,需要进行双旋转

14.两种单旋转

	//左左单旋转
	private AvlNode<AnyType> rotateWithLeftChild(AvlNode<AnyType> k2)
	{
		AvlNode<AnyType> k1 = k2.left; 
		k2.left = k1.right;
		k1.right = k2;
		
		k2.height = Math.max(height(k2.left), height(k2.right)) + 1;
		k1.height = Math.max(height(k1.left), height(k2)) + 1;
		
		return k1;
	}
//右右单旋转
	private AvlNode<AnyType> rotateWithRightChild(AvlNode<AnyType> k1)
	{
		AvlNode<AnyType> k2 = k1.right;
		k1.right = k2.left;
		k2.left = k1;
		k1.height = Math.max(height(k1.left), height(k1.right)) + 1;
		k2.height = Math.max(height(k1), height(k2.right)) + 1;
		
		return k2;
				
	}

15.两种双旋转

	//左右双旋转
	private AvlNode<AnyType> doubleWithLeftChild(AvlNode<AnyType> k3)
	{
		k3.left = rotateWithRightChild(k3.left);
		return rotateWithLeftChild(k3);
		
	}
	//右左双旋转
	private AvlNode<AnyType> doubleWithRightChild(AvlNode<AnyType> k1)
	{
		k1.right = rotateWithLeftChild(k1.right);
		return rotateWithRightChild(k1);
	}
  • 双旋转相当于对a的儿子和孙子之间单旋转后再在a和它的新儿子之间单旋转
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值