二叉树(四)| 平衡二叉树AVL(java实现)

平衡二叉树AVL

在二叉查找树中进行查找,结点的插入和删除等操作的时间复杂度都是 Ο(h),其中 h 为查找树的高度。可见,二叉查找树高度直接影响到操作实现的性能,而在某些特殊的情况下二叉查找树会退化为一个单链表,如插入的结点序列本身就有序的情况下,此时各操作的效率会下降到 Ο(n),其中 n 为树的规模。因此,在结点规模固定的前提下,二叉查找树的高度越低越好,从树的形态来看,也就是使树尽可能平衡。当二叉查找树的高度为 Ο(log n)时,此时各算法的时间复杂度均为 Ο(h)=Ο(log n);另一方面由于具有 n 个结点的树的高度为 Ω(log n),如此当树的高度为 Ο(log n)时各操作实现的效率达到最佳。

为说明二叉树的平衡性,我们定义二叉树中结点平衡因子的概念。在二叉树中,任何一个结点 v 的平衡因子都定义为其左、右子树的高度差。注意,空树的高度定义为-1。 在二叉查找树 T 中,若所有结点的平衡因子的绝对值均不超过 1,则称 T 为一棵 AVL
树。

由于 AVL 上任何结点的左右子树的高度之差都不超过 1,则可以证明高度为 h 的 AVL树至少包含 Fib(h+3)-1(其中 Fib(i)为 Fibonacci 数列的第 i 项,i ≥ 0)个结点,如此有 n 个结点的 AVL 树的高度和 log n 是同数量级的。可见,在 AVL 树中查找、插入、删除等操作的效率就渐进复杂度而言可以达到最佳。

AVL 树也是一棵二叉查找树,因此与一般二叉查找树一样,AVL 树也是动态的,也应该支持插入、删除等操作。然而原本一棵 AVL 树在经过插入或删除之类的操作后,某些结点的高度可能会发生变化,以至于不再满足 AVL 树的条件。在这种情况下,我们需要重新调整树的结构使之重新平衡。

旋转操作

在讨论调整树结构使之重新平衡的内容之前,先介绍调整树结构而不改变二叉查找树特性的手段,即旋转操作。

1、右旋(顺时针方向旋转)

如图 1所示。假设图 1(a)中结点 v 是结点 p 的左孩子,X 和 Y 分别是 v 的左、右子树,Z 为 p 的右子树。所谓围绕结点 p 的右旋操作,就是重新调整这些结点位置,将 p作为 v 的右孩子,将 X 作为 v 的左子树,将 Y 和 Z 分别作为 p 的左、右子树。围绕 p 右旋
的结果如图 1(b)所示。

图1 AVL树的右旋操作

2、左旋(逆时针方向旋转)

对称的,如图 2 所示。假设图 2(a)中结点 v 是结点 p 的右孩子,Y 和 Z 分别是 v的左、右子树,X 为 p 的左子树。所谓围绕结点 p 的左旋操作,就是将 p 作为 v 的左孩子,将 Z 作为 v 的右子树,将 X 和 Y 分别作为 p 的左、右子树。围绕 p 左旋的结果如图 2(b)所示。
 

图2 AVL树的左旋操作

在以上旋转过程中,每种旋转都只涉及常数次基本操作,因此左旋和右旋均可以在 Ο(1)时间内完成。而且,无论是右旋还是左旋,都不改变二叉树的中序遍历结果。因此,当 AVL 树失去平衡后,我们可以采用旋转的方法,在不破坏二叉查找树特性的基础上,重新使树得到平衡。

失去平衡后的重新平衡

一般的,若在插入新的结点 x 之后 AVL 树 T 失去平衡,则失去平衡的结点只可能是 x的祖先,且层次数小于等于 x 的祖父的结点;也就是说失去平衡的结点是从 x 的祖父到根的路径上的结点,但这些结点并不都是失去平衡的结点,有些结点仍然可能是平衡的。

为了修正失衡的现象,可以从结点 x 出发逆行向上,依次检查 x 祖先的平衡因子,找到第一个失衡的祖先结点 g。在从 x 到 g 的路径上,假设 p 为 g 的孩子结点,v 为 p 的孩子结点。根据前面的讨论,有结点 p、v 必不为空,p 至少是 x 的父亲,v 至少是 x 本身。结点 g、p、v 之间的父子关系共有 4 种不同的组合,以下根据这 4 种不同的组合,通过对结点 g、p 的旋转,使这一局部恢复平衡。

1. p 是 g 的左孩子,且 v 是 p 的左孩子;

在这种情况下,必定是由于在 v 的后代中插入了新结点 x 而使得 g 不再平衡。针对这种情况,可以简单的通过结点 g 的单向右旋,即可使得以 g 为根的子树得到平衡。这一过程如图 3所示。

图3 单向右旋

当这一局部经过单向右旋调整后,不但失衡的结点 g 重新平衡,而且经过这一调整后,这一局部子树的高度也恢复到插入 x 以前的高度。因此 x 所有其他祖先的平衡因子也都会得到恢复,也就是说,在这种情况下,只需要一次旋转操作,即可使整棵 AVL 树恢复平衡。在插入结点 x 之前树是平衡的,此时树的高度为 Ο(log n),则从 x 出发查找 g 需要花费的时间为 Ο(log n),而进行平衡所需的一次旋转操作只需 Ο(1)时间,因此整个平衡过程只需Ο(log n)时间。

2. p 是 g 的右孩子,且 v 是 p 的右孩子;

与第一种情况对称,此时,失衡是由于在 g 的右孩子的右子树中插入结点 x 造成的。在这种情况下需要通过结点 g 的单向左旋来完成局部的平衡。这一过程如图 4 所示。通过图 4不难看出,和单向右旋一样,在旋转之后不但失衡的结点 g 重新平衡,而且子树的高度也恢复到插入 x 以前的高度,因此局部的平衡能使得整棵树得到平衡。这一操作需要的时间与单向右旋相同,也为 Ο(log n)。

图4 单项左旋

3. p 是 g 的左孩子,且 v 是 p 的右孩子;

如果是在 p 的左孩子的右子树中插入一个结点,则对应为第三种情况。在这种情况中,要使得局部的失衡重新平衡,那么需要先后进行先左后右的双向旋转:第一次是结点 p 的左旋,第二次是结点 g 的右旋。这一过程如图 5 所示。

图5 先左后右双向旋转

失衡的局部经过双向旋转后,失衡的结点 g 重新平衡,并且子树的高度也恢复到插入结点 x 之前的高度,结点 x 的祖先结点的平衡因子都会恢复。在这种情况下,只需要两次旋转操作就可以使得整棵 AVL 树恢复平衡。同样,为了确定 g 的位置需要 Ο(log n)时间,旋转操作需要 Ο(1)时间,因此,整个双旋操作只需 Ο(log n)时间。

4. p 是 g 的右孩子,且 v 是 p 的左孩子;

第四种情况与第三种情况对称,其平衡调整过程也与第三种情况对称,可以通过先右后左的双向旋转来完成,其中第一次是结点 p 的右旋,第二次是结点 g 的左旋。调整过程如图6 所示。在旋转之后不但失衡的结点 g 重新平衡,而且子树的高度也恢复到插入 x 以前的
高度,即局部的平衡能使得整棵树得到平衡。这一操作需要的时间与先左后右双向旋转相同,也为 Ο(log n)。

通过以上四种情况的分析,可以得到如下结论:在 AVL 树中插入一个结点后,至多只需要进行两次旋转就可以使之恢复平衡。进一步,由于在 AVL 树中按一般二叉查找树的方法插入结点需要 Ο(log n)时间,旋转需要 Ο(1)时间,则在 AVL 树中结点插入操作可以在 Ο(log n)时间内完成。

图6 先右后左双向旋转

代码实现

节点定义:

	public class AVLNode{
		public int data;   //存储数据
		public int depth;  //存储深度
		public int balance; //记录是否平衡
		public AVLNode parentNode;  //存储父节点
		public AVLNode leftNode;  //存储左子节点
		public AVLNode rightNode; //存储右子节点
		
		public AVLNode(int data) {
			super();
			this.data = data;
		}
		
	}

AVL树的构建:

public class AVLTree {
	
	private AVLNode root;
	
	
	
	public AVLTree(int data) {
		super();
		this.root = new AVLNode(data);
	}

	public class AVLNode{
		public int data;   //存储数据
		public int depth;  //存储深度
		public int balance; //记录是否平衡
		public AVLNode parentNode;  //存储父节点
		public AVLNode leftNode;  //存储左子节点
		public AVLNode rightNode; //存储右子节点
		
		public AVLNode(int data) {
			super();
			this.data = data;
		}
		
	}
	
	/**
	 * AVL树插入节点
	 */
	public void insertNode(AVLNode root, int data) {
		
		//如果插入data比root值小,则往root左子树插
		if (data < root.data) {
			if (root.leftNode != null) {   //左子树不为空,递归继续往里插
				insertNode(root.leftNode, data);
			}else {   //左子树为空,则新节点就是root的左子节点
				root.leftNode = new AVLNode(data);
				root.leftNode.parentNode = root;
			}
		}else {  //如果插入data比root值大,则往root右子树插
			if (root.rightNode!=null) {  //如果右子树不为空,递归继续往里插
				insertNode(root.rightNode, data);
			}else {   //右子树为空,则新节点就是root的右节点
				root.rightNode = new AVLNode(data);
				root.rightNode.parentNode = root;
			}
		}
		
		//插入之后,开始平衡性调整
		root.balance = getBalance(root);
		
		//左子树高,应该右旋
		if (root.balance >= 2) {
			//右孙高,先左旋
			if (root.leftNode.balance == -1) {
				left_rotate(root.leftNode);
			}
			right_rotate(root);
		}
		
		//右子树高,应该左旋
		if (root.balance <= -2) {
			//左孙高,先右旋
			if (root.rightNode.balance == 1) {
				right_rotate(root.rightNode);
			}
			left_rotate(root);
		}
		
		//调整之后,重新计算平衡因子和数的深度
		root.balance = getBalance(root);
		root.depth = getDept(root);
		
	}
	
	/**
	 * AVL树的右旋操作
	 * @param p
	 */
	public void right_rotate(AVLNode p) {
		//一次右旋操作
		AVLNode parentNode = p.parentNode;
		AVLNode leftSonNode = p.leftNode;
		AVLNode rightGrandSonNode = leftSonNode.rightNode;
		
		//左子节点变父节点
		leftSonNode.parentNode = parentNode;
		if (parentNode != null) {
			if (p == parentNode.leftNode) {
				parentNode.leftNode = leftSonNode;
			}else if (p == parentNode.rightNode) {
				parentNode.rightNode = leftSonNode;
			}
		}
		
		leftSonNode.rightNode = p;
		p.parentNode = leftSonNode;
		
		//右孙节点变左孙节点
		p.leftNode = rightGrandSonNode;
		if (rightGrandSonNode !=null) {
			rightGrandSonNode.parentNode = p;
		}
		p.depth = getDept(p);
		p.balance = getBalance(p);
		leftSonNode.depth = getDept(leftSonNode);
		leftSonNode.balance = getBalance(leftSonNode);
	}
	
	/**
	 * AVL树的左旋操作
	 * @param p
	 */
	public void left_rotate(AVLNode p) {
		//一次左旋操作
		AVLNode parentNode = p.parentNode;
		AVLNode rightSonNode = p.rightNode;
		AVLNode leftGrandSonNode = rightSonNode.leftNode;
		
		//右子变父
		rightSonNode.parentNode = parentNode;
		if (parentNode!=null) {
			if (p == parentNode.rightNode) {
				parentNode.rightNode = rightSonNode;
			}else if (p == parentNode.leftNode) {
				parentNode.leftNode = rightSonNode;
			}
		}
		rightSonNode.leftNode = p;
		p.parentNode = rightSonNode;
		//左孙变右孙
		p.rightNode = leftGrandSonNode;
		if (leftGrandSonNode!=null) {
			leftGrandSonNode.parentNode = p;
		}
		p.depth = getDept(p);
		p.balance = getBalance(p);
		rightSonNode.depth = getDept(rightSonNode);
		rightSonNode.balance = getBalance(rightSonNode);
	}
	
	/**
	 * 计算二叉树的平衡性
	 * @param p
	 * @return
	 */
	public int getBalance(AVLNode p) {
		int left_depth;
		int right_depth;
		if (p.leftNode!=null) {
			left_depth = p.leftNode.depth;
		}else {
			left_depth = 0;
		}
		if (p.rightNode!=null) {
			right_depth = p.rightNode.depth;
		}else {
			right_depth = 0;
		}
		return left_depth - right_depth;
	}
	
	/**
	 * 计算AVL树的深度
	 * @param p
	 * @return
	 */
	public int getDept(AVLNode p) {
		int depth = 0;
		if (p.leftNode!=null) {
			depth = p.leftNode.depth;
		}
		if (p.rightNode!=null && depth < p.rightNode.depth) {
			depth = p.rightNode.depth;
		}
		depth++;
		return depth;
	}
	//先序递归遍历
	public void preOrderTraveral(AVLNode root) {
		if (root == null) {
			return;
		}
		System.out.println(root.data);
		preOrderTraveral(root.leftNode);
		preOrderTraveral(root.rightNode);
	}

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		AVLTree avlTree = new AVLTree(5);
		avlTree.insertNode(avlTree.root, 9);
		avlTree.insertNode(avlTree.root, 1);
		avlTree.insertNode(avlTree.root, 12);
		avlTree.insertNode(avlTree.root, 4);
		avlTree.insertNode(avlTree.root, 6);
		avlTree.insertNode(avlTree.root, 20);
		avlTree.insertNode(avlTree.root, 14);
		avlTree.preOrderTraveral(avlTree.root);

	}

}

 


 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值