Java实现平衡二叉树(AVLTree)的构建

最近在学习数据结构上关于平衡二叉树的知识,看了严老师的思路,感觉用java写出递归的构建方式有点困难,因为其中的递归需要把引用传进去,所以感觉是要实现起来比较麻烦,所以就首先想到使用非递归的方式来实现构建平衡二叉树。

使用非递归的方式,思路也很简单,就是为每一个结点都要定义一个平衡因子的属性,当成功向树中插入一个数据时,我就要进行回溯,看看有没有平衡因子的绝对值等于2的结点,如果有,那就需要进行旋转。当时的思路仅限于这些,接触java没有多久,目测如果实现起来,有点困难。

所以,上网查询了一个博客写的代码,尽管不知道原作者是谁,不过还是贴上我参考的博客地址:http://blog.csdn.net/zxman660/article/details/7940190

其中,我认为还有一个难题就是,我定义的结点使用了泛型,即结点的value也是使用的泛型<E>,所以在比较大小的时候,我就无从下手,可能是我接触java不太久的原因,当我参考上述博客的思路后,居然是这样实现的。

void compare(E element, Node node){
 	Comparable<? super E> e = (Comparable<? super E>)element;
 	int cmp = e.compareTo(node.element);
 	if(cmp > 0) //大于
 	else if(cmp == 0) //等于
 	else //小于
}

通过此次学习,虽然接触了平衡二叉树的构建,但还是感觉学到了不少的知识,并且对java的使用又有了更多的认识。

有了上述的感悟,感觉还是写一个博客来收藏一下,等自己有时间,再补充一下结点删除的代码。

可能本人代码在注释上提供的思路有限,如果有看不懂的地方,可以参考上面的博客地址的内容。

package util;

/**
 * 平衡二叉树
 * 定义:首先它是一种特殊的二叉排序树,其次它的左子树和右子树都是平衡二叉树,
 * 且左子树和右子树的深度之差不超过1
 * 平衡因子:可以定义为左子树的深度减去右子树的深度
 *
 * 平衡二叉树是对二叉排序树的优化,防止二叉排序树在最坏情况下平均查找时间为n,
 * 二叉排序树在此时形如一个单链表
 * 平衡二叉树查找元素的次数不超过树的深度,时间复杂度为logN
 */ 

public class AVLTree<E> {

	/**根节点*/
	private Node<E> root = null;

	/**树中元素的个数*/
	private int size = 0;

	private static final int LEFT_HIGH = 1;
	private static final int RIGHT_HIGH = -1;
	private static final int EQUAL_HIGH = 0;

	public AVLTree(){}

	public boolean insertElement(E element){

		Node<E> t = root;
		if(t == null){
			/**将值复制给根节点,其父节点为空*/
			root = new Node(element, null);
			size = 1;
			return true;
		}

		int cmp = 0;
		Node<E> parent; /**用来保存t的父节点*/

		/**查找结点应存放的位置*/
		Comparable<? super E> e = (Comparable<? super E>)element;
		do{
			parent = t;
			cmp = e.compareTo(t.element);
			if(cmp < 0){
				t = t.left;
			}else if(cmp > 0){
				t = t.right;
			}else{
				return false;
			}
		}while(t != null);

		/**将结点存放在相应的位置*/
		Node<E> child = new Node(element, parent);
		if(cmp < 0){
			parent.left = child;
		}else{
			parent.right = child;
		}

		/**开始回溯,为根节点修改balabce,以便进行相应的调整*/
		while(parent != null){
			cmp = e.compareTo(parent.element);
			if(cmp < 0){
				parent.balance ++;
			}else{
				parent.balance --;
			}

			if(parent.balance == 0){/**从这以上的结点都不需要修改了,也不需要旋转了*/
				break;
			}

			if(Math.abs(parent.balance) == 2){
				fixAfterInsertion(parent);
				break;
			}
			parent = parent.parent;
		}
		size ++;
		return true;
	}

	private void fixAfterInsertion(Node<E> p) {
		if(p.balance == 2){
			leftBanance(p);
		}
		if(p.balance == -2){
			rightBalance(p);
		}
	}

	/**
	 * 左平衡操作,即结点t的不平衡是因为左子树过深
	 *
	 * 1、如果新的结点插入到p的左孩子的左子树中,则直接进行右旋操作即可
	 * 				t								lc
	 * 			   /  \		右旋操作				   /  	 * 			  lc   rc		------------->		 lcl   t
	 * 			 /  \								 /	  /  	 * 			lcl  lcr						   lcll  lcr rc
	 * 			/
	 *         lcll
	 *
	 * 2、如果新的结点插入到p的左孩子的右子树中,则需要进行分情况讨论
	 *
	 * 	情况a:当p的左孩子的右子树根节点的balance = RIGHT_HIGH
	 *
	 * 			1						1						 	4
	 * 		   /  \					   /  \						   /  	 *        2    6      左旋     	4    6                 右旋		2    1
	 *       /  \  		------->     /  \        -------->       /    /  	 *      3    4                  2    5                      3    5    6
	 *            \                /
	 *             5			  3
	 *
	 *
	 * 	情况b:当p的左孩子的右子树根节点的balance = LEFT_HIGH
	 *
	 * 			1						1						 	4
	 * 		   /  \					   /  \						   /  	 *        2    6      左旋     	4    6          右旋		2    1
	 *       /  \  		------->     /          -------->        / \    	 *      3    4                  2                           3   5    6
	 *          /                  / 	 *         5   			  	  3   5
	 *
	 * 	情况c:当p的左孩子的右子树根节点的balance = EQUAL_HIGH
	 *
	 * 			1						1						 	4
	 * 		   /  \					   /  \						   /  	 *        2    7      左旋     	 4    7          右旋		2     1
	 *       /  \  		------->     / \         -------->       / \   / 	 *      3    4                  2   6                       3   5  6  7
	 *          / \                / 	 *         5   6			  3	  5
	 * */

	private void leftBanance(Node<E> t) {

		Node<E> lc = t.left;
		switch(lc.balance){
		case LEFT_HIGH:		/**新结点插入到t的左孩子的左子树上,需要单右旋处理*/
			lc.balance = EQUAL_HIGH;
			t.balance = EQUAL_HIGH;
			break;

		case RIGHT_HIGH:	/**新结点插入到t的左孩子的右子树上,需要双旋处理*/
			Node<E> rd = lc.right;
			switch(rd.balance){
			case LEFT_HIGH:
				lc.balance = EQUAL_HIGH;
				t.balance = RIGHT_HIGH;
				break;

			case RIGHT_HIGH:
				lc.balance = LEFT_HIGH;
				t.balance = EQUAL_HIGH;
				break;
			case EQUAL_HIGH:
				t.balance = EQUAL_HIGH;
				lc.balance = EQUAL_HIGH;
				break;
			}
			rd.balance = EQUAL_HIGH;
			/**对t的左子树进行左旋处理*/
			left_Rotate(t.left);
			/**对t进行右旋处理*/
			right_Rotate(t);
			break;
		}
	}

	/**
	 * 右平衡操作,即结点t的不平衡是因为右子树过深
	 *
	 * 1、如果新的结点插入到p的右孩子的右子树中,则直接进行左旋操作即可
	 *
	 * 			p											r
	 * 		  /   \										   /  	 * 		 l	   r			   左旋操作 				  p   rr
	 * 			 /   \			----------->             / \    	 * 			rl    rr								l   rl   rrr
	 * 				   	 * 					rrr
	 *
	 *
	 * 2、如果新的结点插入到p的右孩子的左子树中,则需要进行分情况讨论
	 *
	 * 	情况a:当p的右孩子的左子树根节点的balance = LEFT_HIGH
	 *
	 *			1						1							4
	 *		   /  \					   /  \						   /   	 *		  2    3		  右旋	  2	   4		左旋		  1     3
	 *			  /  \	   ------->		  /  \		------->	 /  \     	 * 			 4	  5					 6    3					2	6  	   5
	 * 			/							   	 * 		   6								5
	 *
	 * 情况b:当p的右孩子的左子树根节点的balance = RIGHT_HIGH
	 *
	 *			1						1							4
	 *		   /  \					   /  \						   /   	 *		  2    3		  右旋	  2	   4			左旋		  1     3
	 *			  /  \	   ------->		     \		------->	 /     /  	 * 			 4	  5					      3					2	  6	   5
	 * 			  \							 /  	 * 		   	   6						6    5
	 *
	 *
	 * 情况C:当p的右孩子的左子树根节点的balance = EQUAL_HIGH
	 *			1						1							4
	 *		   /  \					   /  \						   /   	 *		  2    3		  右旋	  2	   4 		左旋		  1     3
	 *			  /  \	   ------->		  /  \		------->	 / \    / 	 * 			 4	  5					 6    3					2	6  7   5
	 * 			/ \							 /  	 * 		   6   7						7    5
	 * */
	private void rightBalance(Node<E> p) {
		Node<E> rc = p.right;
		switch(rc.balance){
		case RIGHT_HIGH:		/**新结点插入到t的右孩子的右子树上,需要单左旋处理*/
			rc.balance = EQUAL_HIGH;
			p.balance = EQUAL_HIGH;
			break;

		case LEFT_HIGH:		/**新结点插入到t的右孩子的左子树上,需要双旋处理*/
			Node<E> ld = rc.left;
			switch(ld.balance){
			case LEFT_HIGH:
				p.balance = EQUAL_HIGH;
				rc.balance = RIGHT_HIGH;
				break;
			case RIGHT_HIGH:
				p.balance = LEFT_HIGH;
				rc.balance = EQUAL_HIGH;
				break;
			case EQUAL_HIGH:
				p.balance = EQUAL_HIGH;
				rc.balance = EQUAL_HIGH;
				break;
			}
			ld.balance = EQUAL_HIGH;
			/**对p的右子树进行右旋处理*/
			right_Rotate(p.right);
			/**对p进行左旋处理*/
			left_Rotate(p);
			break;
		}

	}

	/**
	 * 左旋操作
	 * 			p											r
	 * 		  /   \										   /  	 * 		 l	   r			   左旋操作 				  p   rr
	 * 			 /   \			----------->             / \    	 * 			rl    rr								l   rl   rrr
	 * 				   	 * 					rrr
	 * */

	private void left_Rotate(Node<E> p) {
		if(p != null){
			Node<E> r = p.right;	/**获得p的右子树的根节点r*/

			p.right = r.left;		/**将r的左子树转接到p的右子树上*/
			if(r.left != null){		/**如果r的左子树不为空,将左子树的父节点设置为p*/
				r.left.parent = p;
			}

			r.parent = p.parent;	/**修改r的父节点,修改为p的父节点*/
			if(p.parent == null){	/**如果p的父节点为null,那么现在r就是根节点了*/
				root = r;
			}else if(p == p.parent.left){/**如果p为其父节点的左孩子,将其父节点的左孩子指向r*/
				p.parent.left = r;
			}else if(p == p.parent.right){/**如果p为其父节点的右孩子,将其父节点的右孩子指向r*/
				p.parent.right = r;
			}

			r.left = p;		/**将r的左孩子设置为p*/
			p.parent = r;	/**将p的父节点设置为r*/
		}
	}

	/**
	 * 右旋操作
	 *
	 * 				p									l
	 * 			   /  \			 右旋操作				   /  	 * 			  l	   r		------------->		  ll   p
	 * 			 /  \								 /	  /  	 * 			ll   lr								lll	 lr   r
	 * 			/
	 *         lll
	 * */
	private void right_Rotate(Node<E> p) {

		if(p != null){
			Node<E> l = p.left;		/**获取p的左孩子l*/

			p.left = l.right;		/**将l的右子树变为p的左子树*/
			if(l.right != null){	/**如果l的右子树不为空,将其父节点设置为p*/
				l.right.parent = p;
			}

			l.parent = p.parent;	/**将r的父节点修改为p的父节点*/
			if(p.parent == null){	/**如果p的父节点为null,即l为root*/
				root = l;
			}else if(p == p.parent.left){	/**如果p为其父节点的左孩子,将p的父节点的左孩子指向l*/
				p.parent.left = l;
			}else if(p == p.parent.right){ /**如果p为其父节点的右孩子,将p的父节点的右孩子指向l*/
				p.parent.right = l;
			}

			l.right = p;		/**将l的右子树变为p*/
			p.parent = l;		/**修改p的父节点为l*/
		}
	}

	/**中序非递归方式遍历平衡二叉树*/
	public void nrInOrderTraverse(){
		Stack<Node<E>> stack = new Stack<Node<E>>();
		Node<E> p = root;
		while(p != null || !stack.isEmpty()){
			while(p != null){
				stack.push(p);
				p = p.left;
			}
			p = stack.pop();
			System.out.println(p.element);
			p = p.right;
		}
	}

	/**平衡二叉树的结点定义*/
	class Node<E>{
		E element;
		/**结点的平衡因子*/
		int balance = 0;
		/**左孩子结点、右孩子结点、父节点*/
		Node<E> left;
		Node<E> right;
		Node<E> parent;

		public Node(){}
		public Node(E element, Node<E> parent){
			this.element = element;
			this.parent = parent;
		}

		public String toString(){
			return element + " BF=" + balance;
		}

	}

	public static void main(String[] args) {
		Integer[] num = {5,8,2,0,1, -2, -9, 100};
		AVLTree<Integer> avl = new AVLTree<Integer>();
		for(int i = 0; i < num.length; i++){
			avl.insertElement(num[i]);
		}
		avl.nrInOrderTraverse();
	}

}

Java实现平衡二叉树(AVLTree)的构建

时间: 2024-12-05 12:50:59

Java实现平衡二叉树(AVLTree)的构建的相关文章

Java远程过程调用基础:构建可自适应的动态代理对象的通用方法

[toc] Java远程过程调用基础:构建可自适应的动态代理对象的通用方法 前言 关于动态代理的知识,这里不再过多说明.这里要介绍的是,如何创建一个可自适应任意接口的动态代理对象的通用方法,也就是说,引用对象可为任意接口,举个例子,假如返回动态代理对象的方法是getProxy,而同时存在两个接口UserInterface和ProductInterface,这时可以这样使用: UserInterface user = getProxy(UserInterface.class); ProductIn

spark的java和scala混合工程构建记录!

今天就跟这个赶上了,各种办法,各种百度. 一.maven手动构建 1.dos命令下,进入目录,构建一个scala模板的工程 mvn archetype:generate -DarchetypeGroupId=org.scala-tools.archetypes -DarchetypeArtifactId=scala-archetype-simple -DremoteRepositories=http://scala-tools.org/repo-releases -DgroupId=com.dy

Java编码 蛇形矩阵的构建与遍历输出

一.蛇形矩阵的构建,并按行输出 例: 输入:n, 生成n*n的蛇形矩阵 1 2 3 8 9 4 7 6 5 输出:1 2 3 8 9 4 7 6 5 java编码 public static void main(String[] args) { // TODO Auto-generated method stub Scanner in = new Scanner(System.in); int n = in.nextInt(); if(n < 0) return; if(n == 1){ Sys

Eclipse的Java工作集和多工程构建路径

一.Java工作集: Eclipse有一个小功能,就是创建Java Working Set.它的作用是解决Package Explorer窗格中创建很多工程时出现拥挤的麻烦. 在创建(New对话框)时可以加入原来存在的Java工程.创建完后,在左方Package Explorer窗格的右上方下拉菜单中选择Select Working Set然后选择Selected Working Sets打开创建的工作集.之后切换就可以直接在下拉菜单中选择即可(自动出现在下拉菜单中). 如果希望加入新的工程到工

java网络编程学习之——构建基于多线程的网络通信模型1

该例展示了多用户/服务器通信模型,包含三个文件:Server.java   ServerThread.java 和Client.java类.其中ServerThread类支持多线程,为客户提供的所有操作都封装到该类中,该类的构造方法ServerThread(Socket socket)接收一个套接字对象,用来与客户进行数据通信.Server类是一个应用程序,其main方法中通过一个无限的whlie循环,等待并监听多个用户连接请求(本例中只演示了一个客户请求),每个客户的连接都由服务器端的一个单独

Java Secret: Using an enum to build a State machine(Java秘术:用枚举构建一个状态机)

近期在读Hadoop#Yarn部分的源代码.读到状态机那一部分的时候,感到enmu的使用方法实在是太灵活了,在给并发编程网翻译一篇文章的时候,正好碰到一篇这种文章.就赶紧翻译下来,涨涨姿势. 原文链接:http://www.javacodegeeks.com/2011/07/java-secret-using-enum-to-build-state.html 作者:Peter Lawrey    译者:陈振阳 综述 Java中的enum比其它的语言中的都强大,这产生了非常多令人吃惊的使用方法.

一个java创建,删除,构建Jenkins等功能的JenkinsUtil工具类

package com.vip.webpagetest.utils; import java.io.InputStream; import java.io.UnsupportedEncodingException; import java.util.List; import org.apache.commons.httpclient.HttpClient; import org.apache.commons.httpclient.UsernamePasswordCredentials; impo

【Java TCP/IP Socket】构建和解析自定义协议消息(含代码)

在传输消息时,用Java内置的方法和工具确实很用,如:对象序列化,RMI远程调用等.但有时候,针对要传输的特定类型的数据,实现自己的方法可能更简单.容易或有效.下面给出一个实现了自定义构建和解析协议消息的Demo(书上例子). 该例子是一个简单的投票协议.这里,一个客户端向服务器发送一个请求消息,消息中包含了一个候选人的ID,范围在0~1000.程序支持两种请求:一种是查询请求,即向服务器询问候选人当前获得的投票总数,服务器发回一个响应消息,包含了原来的候选人ID和该候选人当前获得的选票总数:另

如何正确地实现Java模块与inter-module Maven构建测试依赖关系

我有一个多模块使用Maven和Java项目. 我现在想迁移到Java 9/10/11和实现模块(如JSR 376:Java平台模块系统). 随着项目已经由Maven模块,和依赖直,为项目创建模块描述符很直接. 现在每个Maven模块有自己的模块描述符(module-info.java),在src/main/java文件夹中. 没有测试类的模块描述符. 然而,我偶然发现了我一直未能解决的问题,并没有找到任何描述如何解决:我怎么能有inter-module吗测试与Maven和Java模块依赖关系?