平衡二叉排序树精讲--JAVA实现

版权声明:071623 https://blog.csdn.net/weixin_43584220/article/details/88780850

这个类只是二叉排序树的BSTNode类,最终实现还是要自己去打测试,明天放测试代码
废话不多说先按照注释的待解决开始看,再便看带有序号的实现方法(注释)便看代码


package cn.BST01.Ravanla.copy;
/**
 * 
 * @author Ravanla
 *   待解决:
 *   1.什么叫旋转
 *   

在这里插入图片描述

 * 4				例子是右旋转
 * 答:这里可以看出以6为轴,6的根8在6右上边,8需要转到6的右下方,所以顺时针转了大概120°吧
 * (根据等边三角形)
 * ,若7为空,8也便没有左子树了这里左旋转也一样的道理
 *   2.为什么要旋转
 * 答:左右高度差的绝对值大于1
 *   3.为什么要求高度差
 * 答:左右高度差的绝对值大于1就不叫平衡二叉树了
 *   为什么需要平衡二叉树?
 * 答:在某些情况下我数组是{1,2,3,4,5,6,7,8}有序排列的,那么这颗二叉树就变成了右斜树,那岂不是
 * 变成了链表
 * ,时间复杂度也变成了线性的O(n),我们的二叉树期望是O(log2n),相比之下这颗右斜树不就是退化
 * 了嘛
 *   4.什么时候要进行双旋转

在这里插入图片描述

 * 					例子是先左旋后右旋转
 * 答:如图,还没加上7时他是平衡二叉树,加上后应当让8右旋转,但是右转后还是不平衡,所以我们
 * 先给5左转,
 * 使左子结点的leftHeight的高度大于右,而不是左子结点的rightHeight的高度大于右的话让8右旋转才右
 * 意义(平衡了二叉树)

 *B.再创建二叉排序树的node类
 * 1.这个类里有左右子结点,淡然还有它自己的数据值(value)
public class BSTNode {
   BSTNode left;
   BSTNode right;
   int value;

 * 2.必备的还有value构造方法
  //2.必备的还有value构造方法
  public BSTNode(int value) {
  	this.value = value;
  }

3.添加结点方法

 * 
 * 	1.首先node不为空,node为形参
 * 	    我们规定了左子结点的值是小于等于node的值
 * 	    右子结点的值大于node的值
 *  2.如果node的值小于等于该结点的值
 *     则进行如下判断
 *  	1.如果左为空,则传入的node为左子结点
 * 	    2.否则递归调用左子结点的add方法
 * 	  否则
 * 		1.如果右为空,则传入的node为右子结点
 * 	    2.否则递归调用右子结点的add方法
 * 	这里我们还要进行二叉树的平衡,我们就先写完其他的方法再写平衡吧,先不看3这个步骤
 * 	3.如果左边高度-右边高度大于1(调用左右两边高度的方法[leftHeight和rightHeight]
 * 		则进行如下判断
 * 		如果左子结点不为空且左子结点的左边高度小于左子结点的右边高度
 * 		 1.进行左旋转
 * 	  1.进行右旋转
 * 	  如果左边高度-右边高度小于1
 * 		1.进行右旋转		
	//3.添加结点方法
	public void add(BSTNode node) {
		if(node == null) {
			return;
		}
		if(this.value > node.value) {
			if(this.left == null) {
				this.left = node;
			}
			else {
				this.left.add(node);
			}
		}
		else {
			if(this.right == null) {
				this.right = node;
			}
			else {
				this.right.add(node);
			}
		}
		
		if(leftHeight()-rightHeight() >= 2) {
			//双旋转
			if(left != null && left.leftHeight() < left.rightHeight()) {
				leftRotate();
			}
			//单旋转
			rightRotate();
		}
		if(leftHeight()-rightHeight() <= -2){
			leftRotate();
		}
	}

4.需要一个中序遍历方法

 * 
 *  1.首先node不为空,node为形参
 *  2.递归调用自己的左子结点
 *  3.打印当前结点的值
 *  4.递归调用自己的右子结点
	//4.需要一个中序遍历方法
	public void middleShow(BSTNode node) {
		if(node == null) {
			return;
		}
		
		middleShow(node.left);
		System.out.println(node.value);
		middleShow(node.right);
	}

5.需要一个搜索结点方法 返回结点的方法

 * 
 *  1.首先如果这个value是当前结点的value,那就把这个结点return出去就好了
 *  2.否则如果这个value小于等于(前面3.2中写的是小于,这里就要小于)当前结点的value,那就在left
 * 不为空的情况下递归调用right的搜索函数,否则return空代表没有找到
 *  3.否则在right不为空的情况下递归调用right的搜索函数,否则return空代表没有找到
	//5.需要一个搜索结点方法   返回结点的方法
	public BSTNode search(int value) {
		if(this.value == value) {
			return this;
		}
		else if(this.value < value) {
			//小心逻辑判断错误了
			if(right == null) {
				return null;
			}
				return right.search(value);
		}
		else {
			if(left == null) {
				return null;
			}
				return left.search(value);
		}
	}

6.需要一个搜索父结点方法 返回结点的方法

 *  1.首先如果在这个结点的左子结点的值等于该value(形参)且左子结点不为空的情况下(或者右子结
 * 点)return这个结点
 *  2.否则执行以下递归调用情况
 *  3.如果该value大于value(形参)且在左子结点不为空的情况下,return左子结点的搜索父结点方法
 *    否则如果该value小于等于于value(形参)且在右子结点不为空的情况下,return右子结点的搜索父
 * 结点方法
 *    这两句一定要写明,因为不排除没有这个结点的情况
 *  4.return空代表找不到value相对应的结点
	//6.需要一个搜索父结点方法  返回结点的方法
	BSTNode searchparent(int value) {
		if((this.left.value == value && this.left != null) 
		|| (this.right.value == value && this.right != null)) {
			return this;
		}
		else {
			if(this.value > value && this.left != null) {
				return this.left.searchparent(value);
			}
			else if(this.value < value && this.right != null){
				return this.right.searchparent(value);
			}
			
		}
		return null;
	}

7.需要一个删除最小值方法 返回值类型为int

 * 先看到8.b
 *
 *  1.我们需要这样的思路
 *    1.目标结点的右结点的左结点一定比目标结点大
 *    2.所以我们从目标结点的右子结点开始找它的最后一个左子结点的
 *    3.最后我们找到的这个左子结点分两种情况
 *     1.这个左子结点只有一颗树,这颗树只能是右子树(或右子结点),若是左子树则它(这个左子结点)
 * 还不是最后一个左子结点,应继续找,最后->(把它的值赋值给目标结点的值,并用删除方法的第三种
 ** 况删除了它(这个左子结点))
 *     2.这个左子结点没有子结点了->(把它的值赋值给目标结点的值,并用删除方法的第一种情况删除了
 * (这个左子结点))
   //7.需要一个删除最小值方法  返回值类型为int
   private int deleteMin(BSTNode node) {
   		BSTNode target = node;
   		while(target.left != null) {
   			target = target.left;
   		}
   		int min = target.value;
   		delete(target.value);
   		return min;
   }

8.需要一个删除方法

 * 
 *  1.首先创建目标结点(target相对应的value[形参],用搜索结点方法),创建target的父亲结点
 * (parent,用搜索父结点方法)
 *  a.如果是第一种情况(删除没有左右结点的结点)
 *   1.如果parent的左子结点的值刚好等于目标结点的值,那就把parent的左子结点设置为空就可以了
 *   2.否则parent的右子结点设置为空
 *  b.否则如果是第二种情况(删除有左右结点的结点)
 *   1.这里需要看懂删除最小值方法
 *   2.调用删除最小值方法返回的值赋值给目标结点
 *  c.否则是第三种清空(删除只有一个子结点的结点)
 *   1.如果目标结点的值为父左子结点的值
 *    1.进行判断,如果目标结点的左子结点不为空,则把目标结点的左结点赋值给父结点的左子结点(父
 * 结点的左结点时目标结点的左结点)
 *    2.否则把目标结点的右结点赋值给父结点的左子结点(父结点的左结点时目标结点的右结点)
 *   2.否则
 *    1.进行判断,如果目标结点的左子结点不为空,则把目标结点的左结点赋值给父结点的右子结点(父
 * 结点的右结点时目标结点的左结点)
 *    2.否则把目标结点的右结点赋值给父结点的右子结点(父结点的右结点时目标结点的右结点)
	//8.需要一个删除方法
	public BSTNode delete(int value) {
		BSTNode target = search(value);
		BSTNode parent = searchparent(value);
		//叶子节点(没有子树)
		if(target.left == null && target.right == null) {
			if(parent.left.value == target.value) {
				parent.left = null;
			}
			else {
				parent.right = null;
			}
		}
		//非叶子节点(有两个子树)
		else if(target.left != null && target.right != null) {
			int min = deleteMin(target.right);
			target.value = min;
		}
		//非叶子节点(有一个子树)
		else {
			if(target.value == parent.left.value) {
				if(target.left != null) {
					parent.left = target.left;
				}
				else {
					parent.left = target.right;
				}
			}
			else {
				if(target.left != null) {
					parent.right = target.left;
				}
				else {
					parent.right = target.right;
				}
			}
		}
		return null;
	}
}

9.需要三个高度方法(height)

 *  
 *   a.第一个方法是height  返回值类型为int
 *    1.直接return Math函数的比较方法(比较左右两个结点的高度,如果左子结点不为空则递归调用左
 * 子结点的height方法,注意后面要+1,因为调用一次左的就给左的高度+1,最后比较左右两结点哪个
 * 高度更加高)
 *   b.第二个方法是leftHeight
 *    1.如果左子结点为空直接返回0(高度),否则返回调用左子结点的height方法的结果
 *   c.第三个方法是rightHeight
 *    1.如果右子结点为空直接返回0(高度),否则返回调用右子结点的height方法的结果
 *    //	9.在这里我们需要三个高度方法(height)
//	a.第一个方法是height
	public int height() {
		return Math.max(left==null?0:left.height(),
		right==null?0:right.height())+1;
	}
//	b.第二个方法是leftHeight
	public int leftHeight() {
		if(left == null) {
			return 0;
		}
		return left.height();
	}
//	c.第三个方法是rightHeight
	public int rightHeight() {
		if(right == null) {
			return 0;
		}
		else {
			return right.height();
		}
	}

10.需要旋转方法

 
 *   a。分为右旋转rightRotate
 *    1.看下面的图
 *    2.首先新创建一个结点(newRight)其value为根结点的值(这里一定要你自己想想怎么表达根结点
 * 的值,想完再看代码你会感谢我的)
 *    3.把根结点的右结点赋值给新结点的右结点(也就相当于复制89这两个结点)
 *    4.把根结点的左结点的右结点(可以是空)赋值给新结点的左结点(构建了8,7,9这颗树)
 *    5.把根结点的左子结点的值赋值给根结点(覆盖)
 *    6.把根结点的左结点链接左结点的左结点(意思就是根结点和左结点断了联系)
 *    7.最后把新创建的结点(newRight)与根结点的右结点联系上
 *   b。左旋转leftRotate
 *    1.emmmmm不想写了,会自己写右旋转,左旋转也是同样的额道理,我不能扼杀了你们的想象
//	10.需要旋转方法
	//左旋转
	private void leftRotate() {
		BSTNode newLeft = new BSTNode(this.value);
		newLeft.left = left;
		newLeft.right = right.left;
		this.value = right.value;
		this.right = right.right;
		this.left = newLeft;
	}
	//右旋转
	private void rightRotate() {
		BSTNode newRight = new BSTNode(this.value);
		newRight.right = right;
		newRight.left = left.right;
		this.value = left.value;
		this.left = left.left;
		this.right = newRight;
	}

11.我们需要改进add方法看3.3,然后就完成啦

if(leftHeight()-rightHeight() >= 2) {
	//双旋转
	if(left != null && left.leftHeight() < left.rightHeight()) {
			leftRotate();
	}
	rightRotate();
}
if(leftHeight()-rightHeight() <= -2){
	//单旋转
	leftRotate();
}

猜你喜欢

转载自blog.csdn.net/weixin_43584220/article/details/88780850