1.重新平衡2子树,体现了归纳思想,以及简单的建模思想。
2.旋转名字不太好理解。自己觉得 “沿袭” 更恰当。自己为了进行沿袭这个动作。归纳了2个定理:
1.父节点可垂直变为其左孩子或者左孩子的左孩子。左右同理。
2.子树内部任意一支子树可代替原子树。
有这2个定理。就可以不用理解书上的旋转了,用自己的沿袭就可以完成单旋转和双旋转。毕竟大部分书,都只是告诉你如何旋转,还不如自己建模,抽象,定义定理。来实现。
3.重新计算树高度时,从变化的叶子开始要一直往父节点重新计算,所以很适合使用递归的方法,并保障从上往下时,是逐层往下,那么就可以保证插入和删除以后,会逐层检查子树高度,以及对比是否需要旋转
而旋转后需要跟新子树的父节点。所以参考书上的方法是有返回节点的,一边递归回去的时候更新给父节点,而自己为了方便里面,没哟使用返回值,而是多加参数的方法,不简便,但好理解。
归纳和抽象建模思想体现在哪里。第一步到第二步的转变侧重体现了归纳,抽象,建模等基本思想。 第二步到第三步更体现了为了解决实际问题,修改模型的能力。val是一个进行抽象思维的练习的好例子,非常值得复习
采用了2中不同的函数来完成 add .一个带返回值,一个不带返回值,而是使用参数。
package com.linson.datastrcture; //自己的插入在递归中,并没有和书上返回节点。自己感觉自己无返回值的更好理解,代替方案就是放入一个头节点的父节点。好理解,但也繁琐点。 //递归返回根节点从代码的简洁上绝对更优,只是带参数的方法更容易理解。 //avl这个例子很值得复习。1.递归时的问题模型的确定 2.平衡树时的建模思维。 3.树高的递归计算,递归影响。这个很不错。4.compareble的系统库接口使用。5.左小右大思路的利用。 //6,删除时,需要平衡的时候会存在高的那一边的左右子树又一样高。比父节点的兄弟都高2级。 public class MyAVL<T extends Comparable<T>> { public static class MyAVLNode<E extends Comparable<E>> { public E mElement; public MyAVLNode<E> mLeftNode; public MyAVLNode<E> mRightNode; public int mHeight; public MyAVLNode(E _value,MyAVLNode<E> left,MyAVLNode<E> right) { mElement=_value; mLeftNode=left; mRightNode=right; mHeight=1; } public int compareTo(E other) { return mElement.compareTo(other); } } public MyAVL() { mRootNode=null; } //问题:插入节点到树。组合:插入到节点,插入到左树,插入到右树. 基本值节点为空。可以插入。不需要再判断是否继续插入左或者右 //节点高度默认是1,添加节点,必须逐层检测父节点:左右子树中最大值+1谁否大于现值? 大于要往上再检查,一直到某上层没变化。 //所以返回值可以改为返回是否需要检查高度。但是想一下,又要增加返回值,又要判断是否需要检查,还不如每层都检查,反正AVL的话,数据再大也不会很高。1024才11层。 public void add(T element,MyAVLNode<T> subTreeNode,MyAVLNode<T> fatherNode,boolean isLeft) { if(subTreeNode==null) { MyAVLNode<T> TempNode=new MyAVLNode<T>(element, null, null);//节点和树的泛型都实现了对比接口。所以树的参数,可以直接放入到节点中 if(fatherNode!=null) { if(isLeft) { fatherNode.mLeftNode=TempNode; } else { fatherNode.mRightNode=TempNode; } } else { mRootNode=TempNode; } } else { boolean addIsBiger=subTreeNode.mElement.compareTo(element)<0;//泛型实现了对比接口 if(addIsBiger) { add(element,subTreeNode.mRightNode,subTreeNode,false); } else { add(element,subTreeNode.mLeftNode,subTreeNode,true); } RotationAndHeight(subTreeNode,fatherNode,isLeft); } } //问题:匹配一个树,组合:匹配根,匹配左树,匹配右树。基本问题:节点就是。 //空:直接删,副节点设空。 有单子树。修改数据。左右树都有,找高度更高的换数据。 被换节点一定是叶子,删除叶子,父节点设空。 //旋转和高度问题。要求 //1.传过来的根节点参数的左右子树高度是正确的。那么RotationAndHeight函数就就可以往上递归正确的计算高度和进行修正。 //2.remove函数往下递归时,保证是逐层进行的。那么才能保证RotationAndHeight会逐层往上。 public void remove(T element,MyAVLNode<T> subTreeNode,MyAVLNode<T> fatherNode,boolean isLeft) { if(subTreeNode==null) { return; } int compareRet=subTreeNode.mElement.compareTo(element); if(compareRet==0) { if(subTreeNode.mLeftNode==null && subTreeNode.mRightNode==null) { if(fatherNode!=null) if(isLeft) { fatherNode.mLeftNode=null; } else { fatherNode.mRightNode=null; } else { mRootNode=null; } } else if(subTreeNode.mLeftNode==null || subTreeNode.mRightNode==null) { if(fatherNode!=null) { if(isLeft) { fatherNode.mLeftNode=subTreeNode.mLeftNode==null?subTreeNode.mRightNode:subTreeNode.mLeftNode; } else { fatherNode.mRightNode=subTreeNode.mLeftNode==null?subTreeNode.mRightNode:subTreeNode.mLeftNode; } } else { mRootNode=subTreeNode.mLeftNode==null?subTreeNode.mRightNode:subTreeNode.mLeftNode; } } else { MyAVLNode<T> minnode= findMin(subTreeNode.mRightNode); subTreeNode.mElement=minnode.mElement; //可以确定是叶子,要效率高点。就可以新写个findMin,返回父节点。直接写代码删除。不需要这里一直递归。但对于修正平衡就没有办法往上递归了。 remove(minnode.mElement, subTreeNode.mRightNode, subTreeNode, false); RotationAndHeight(subTreeNode,fatherNode,isLeft); } } else if(compareRet>0)//根节点更大。 { remove(element, subTreeNode.mLeftNode,subTreeNode, true); RotationAndHeight(subTreeNode,fatherNode,isLeft); } else { remove(element, subTreeNode.mRightNode,subTreeNode, false); RotationAndHeight(subTreeNode,fatherNode,isLeft); } } public MyAVLNode<T> findMax(MyAVLNode<T> subTreeNode) { MyAVLNode<T> tempRet=subTreeNode; while(tempRet!=null && tempRet.mRightNode!=null) { tempRet=tempRet.mRightNode; } return tempRet; } public MyAVLNode<T> findMin(MyAVLNode<T> subTreeNode) { MyAVLNode<T> tempRet=subTreeNode; while(tempRet!=null && tempRet.mLeftNode!=null) { tempRet=tempRet.mLeftNode; } return tempRet; } private void RotationAndHeight(MyAVLNode<T> subTreeNode,MyAVLNode<T> fatherNode,boolean isLeft) { //高度差=2 旋转,重新计算高度。 //高度差<2. 根节点是否需要更新高度。 //高度差>2 .错误。 //断言对于快速开发和测试非常重要,而且减少正式版的编译代码和速度。不过如果有必要后期还是要写入到日志中. int leftHeight=subTreeNode.mLeftNode==null?0:subTreeNode.mLeftNode.mHeight; int rightHeight=subTreeNode.mRightNode==null?0:subTreeNode.mRightNode.mHeight; int maxSubTreeHeight=Math.max(leftHeight, rightHeight); assert(Math.abs(leftHeight-rightHeight)<=2) : "why .left compare to right is error"; if(Math.abs(leftHeight-rightHeight)==2) { int ll=0,lr=0,rl=0,rr=0; int treetype=0; if(leftHeight-rightHeight==2) { assert(subTreeNode.mLeftNode!=null):"no way"; ll=subTreeNode.mLeftNode.mLeftNode==null?0:subTreeNode.mLeftNode.mLeftNode.mHeight; lr=subTreeNode.mLeftNode.mRightNode==null?0:subTreeNode.mLeftNode.mRightNode.mHeight; if(ll>=lr)//这里用等号的话,删除的时候,可以用更简单的单转。 { MyAVLNode<T> newRoot= subTreeNode.mLeftNode; subTreeNode.mLeftNode=newRoot.mRightNode; newRoot.mRightNode=subTreeNode; newRoot.mRightNode.mHeight=newRoot.mRightNode.mHeight-1; if(isLeft && fatherNode!=null) { fatherNode.mLeftNode=newRoot; } else if (!isLeft && fatherNode!=null) { fatherNode.mRightNode=newRoot; } else { mRootNode=newRoot; } } else { MyAVLNode<T> newRoot= subTreeNode.mLeftNode.mRightNode; subTreeNode.mLeftNode.mRightNode=newRoot.mLeftNode; newRoot.mLeftNode= subTreeNode.mLeftNode; subTreeNode.mLeftNode=newRoot.mRightNode; newRoot.mRightNode=subTreeNode; newRoot.mHeight++; newRoot.mLeftNode.mHeight--; newRoot.mRightNode.mHeight--; if(isLeft && fatherNode!=null) { fatherNode.mLeftNode=newRoot; } else if (!isLeft && fatherNode!=null) { fatherNode.mRightNode=newRoot; } else { mRootNode=newRoot; } } } else { assert(subTreeNode.mRightNode!=null):"no way"; rl=subTreeNode.mRightNode.mLeftNode==null?0:subTreeNode.mRightNode.mLeftNode.mHeight; rr=subTreeNode.mRightNode.mLeftNode==null?0:subTreeNode.mRightNode.mLeftNode.mHeight; if(rr>=rl)//这里用等号的话,删除的时候,可以用更简单的单转。 { MyAVLNode<T> newRoot= subTreeNode.mRightNode; subTreeNode.mRightNode=newRoot.mLeftNode; newRoot.mLeftNode=subTreeNode; newRoot.mLeftNode.mHeight=newRoot.mLeftNode.mHeight-1; if(isLeft && fatherNode!=null) { fatherNode.mLeftNode=newRoot; } else if (!isLeft && fatherNode!=null) { fatherNode.mRightNode=newRoot; } else { mRootNode=newRoot; } } else { MyAVLNode<T> newRoot= subTreeNode.mRightNode.mLeftNode; subTreeNode.mRightNode.mLeftNode=newRoot.mRightNode; newRoot.mRightNode= subTreeNode.mRightNode; subTreeNode.mRightNode=newRoot.mLeftNode; newRoot.mLeftNode=subTreeNode; newRoot.mHeight++; newRoot.mRightNode.mHeight--; newRoot.mLeftNode.mHeight--; if(isLeft && fatherNode!=null) { fatherNode.mLeftNode=newRoot; } else if (!isLeft && fatherNode!=null) { fatherNode.mRightNode=newRoot; } else { mRootNode=newRoot; } } } } else if(Math.abs(leftHeight-rightHeight)==1) { assert((subTreeNode.mHeight-maxSubTreeHeight==0 || subTreeNode.mHeight-maxSubTreeHeight==1)) : "top node's height was wrong compare with left and right substree"; if(subTreeNode.mHeight-maxSubTreeHeight==0) { subTreeNode.mHeight++; } } } //add:主要是4个问题。1,正确插入位置。2,更新新插入点的父节点数据。3.更新新插入点的父节点数据的高度,4.新插入点的父节点是否平衡 // 1,一般处理。2,采用递归+方法带返回值:新节点,那么当递归返回上层时,可给返回的新插入点赋予正确的父节点。 // 3.4,保证递归方法是逐层进行,并保证新插入点的左右子树高度正确。那么递归回来,会保证新插入点及其所有父节点左右子树高度正确以及都检测旋转。 public MyAVLNode<T> add(T element,MyAVLNode<T> addToThisNode) { MyAVLNode<T> ret=null; if(addToThisNode==null)//某条件下,成了基本问题 { ret= new MyAVLNode<T>(element, null, null); } else//其他条件下,用更小规模问题来组合 { int addIsBigger=element.compareTo(addToThisNode.mElement); if(addIsBigger<0) { addToThisNode.mLeftNode= add(element, addToThisNode.mLeftNode); } else { addToThisNode.mRightNode=add(element, addToThisNode.mRightNode); } ret=addToThisNode; } //检查高度,检查是否需要旋转。 ret=reHeightAndBalance(ret); if(addToThisNode==mRootNode) { mRootNode=ret; } return ret; } //remove 和add基本思路一样。稍微复杂一点. //有目标点有3种情况,1,无左右子树,那么删除叶子,2,左右一个为空。那么跳过要删除点,和左右子树想相连。 //3.左右都不为空,本质和1是一样。删除右子树的最小值节点,也就是一个叶子。并把叶子的数据给目标点。 //返回当前子树最高顶点。 public MyAVLNode<T> remove(T element,MyAVLNode<T> removeThisNode) { MyAVLNode<T> ret=null; if(removeThisNode==null) { //空树或者没找到要删除的点,什么都不做,并返回null,因为本来就是null,返回给这个节点的父节点NULL,等于什么都没做。 ret=null; } else { int removeIsBigger=element.compareTo(removeThisNode.mElement); if(removeIsBigger==0)////某条件下,成了基本问题 { if(removeThisNode.mLeftNode==null && removeThisNode.mRightNode==null) { removeThisNode=null;//其实这句没用,栈内的一个变量赋值为空而已,也没达到手动释放的堆内存,而且是java了。没必要劳心内存问题。 ret=null; } else if(removeThisNode.mLeftNode==null || removeThisNode.mRightNode==null)//不可以写成 != || != .因为会包含 != && !=.而现在这样写,额外包含的,在上面已经被排除了. { ret =removeThisNode.mLeftNode==null?removeThisNode.mRightNode:removeThisNode.mLeftNode; } else { MyAVLNode<T> minNode= findMin(removeThisNode.mRightNode); removeThisNode.mElement= minNode.mElement; removeThisNode.mRightNode=remove(minNode.mElement, removeThisNode.mRightNode); ret=removeThisNode; //return remove(minNode.mElement, removeThisNode.mRightNode);//原写代码 严重错误。 } } else if(removeIsBigger<0) { removeThisNode.mLeftNode= remove(element, removeThisNode.mLeftNode); ret=removeThisNode; } else { removeThisNode.mRightNode= remove(element, removeThisNode.mRightNode); ret=removeThisNode; } } ret=reHeightAndBalance(ret); if(removeThisNode==mRootNode) { mRootNode=ret; } return ret; } //rotation: add .可从新加入点算起,add方法已经是逐步升入,那么会原路返回 //remove:1. 双枝或空枝,逐步过来,最低点有null情况。返回nlll就好。 2.单枝情况,还好, 检查返回值的高度和平衡。再重新返回. private MyAVLNode<T> reHeightAndBalance(MyAVLNode<T> subTreeNode) { MyAVLNode<T> ret=subTreeNode; //null:返回 。 要平衡:平衡。算高度。 if(subTreeNode==null) { ret=null; } else { int leftHeight=subTreeNode.mLeftNode==null?0:subTreeNode.mLeftNode.mHeight; int rightHeight=subTreeNode.mRightNode==null?0:subTreeNode.mRightNode.mHeight; int maxSubTreeHeight=Math.max(leftHeight, rightHeight); assert(Math.abs(leftHeight-rightHeight)<=2) : "why .left compare to right is error"; if(Math.abs(leftHeight-rightHeight)==2) { int ll=0,lr=0,rl=0,rr=0; int treetype=0; if(leftHeight-rightHeight==2) { assert(subTreeNode.mLeftNode!=null):"no way"; ll=subTreeNode.mLeftNode.mLeftNode==null?0:subTreeNode.mLeftNode.mLeftNode.mHeight; lr=subTreeNode.mLeftNode.mRightNode==null?0:subTreeNode.mLeftNode.mRightNode.mHeight; if(ll>=lr)//这里用等号的话,删除的时候,可以用更简单的单转。 { MyAVLNode<T> newRoot= subTreeNode.mLeftNode; subTreeNode.mLeftNode=newRoot.mRightNode; newRoot.mRightNode=subTreeNode; newRoot.mRightNode.mHeight=newRoot.mRightNode.mHeight-1; ret=newRoot; } else { MyAVLNode<T> newRoot= subTreeNode.mLeftNode.mRightNode; subTreeNode.mLeftNode.mRightNode=newRoot.mLeftNode; newRoot.mLeftNode= subTreeNode.mLeftNode; subTreeNode.mLeftNode=newRoot.mRightNode; newRoot.mRightNode=subTreeNode; newRoot.mHeight++; newRoot.mLeftNode.mHeight--; newRoot.mRightNode.mHeight--; ret=newRoot; } } else { assert(subTreeNode.mRightNode!=null):"no way"; rl=subTreeNode.mRightNode.mLeftNode==null?0:subTreeNode.mRightNode.mLeftNode.mHeight; rr=subTreeNode.mRightNode.mLeftNode==null?0:subTreeNode.mRightNode.mLeftNode.mHeight; if(rr>=rl)//这里用等号的话,删除的时候,可以用更简单的单转。 { MyAVLNode<T> newRoot= subTreeNode.mRightNode; subTreeNode.mRightNode=newRoot.mLeftNode; newRoot.mLeftNode=subTreeNode; newRoot.mLeftNode.mHeight=newRoot.mLeftNode.mHeight-1; ret=newRoot; } else { MyAVLNode<T> newRoot= subTreeNode.mRightNode.mLeftNode; subTreeNode.mRightNode.mLeftNode=newRoot.mRightNode; newRoot.mRightNode= subTreeNode.mRightNode; subTreeNode.mRightNode=newRoot.mLeftNode; newRoot.mLeftNode=subTreeNode; newRoot.mHeight++; newRoot.mRightNode.mHeight--; newRoot.mLeftNode.mHeight--; ret=newRoot; } } } else if(Math.abs(leftHeight-rightHeight)==1) { assert((subTreeNode.mHeight-maxSubTreeHeight==0 || subTreeNode.mHeight-maxSubTreeHeight==1)) : "top node's height was wrong compare with left and right substree"; if(subTreeNode.mHeight-maxSubTreeHeight==0) { subTreeNode.mHeight++; } ret=subTreeNode; } } return ret; } //type:0:first print parent node .1 left children ,parent, right children. 2:left children ,right children parent. public void printTree(int type) { printTree(mRootNode, type,""); } //打印树:组合:打印左树,打印节点,打印右树。基本情况:叶子。 private void printTree(MyAVLNode<T> node,int type,String space) { if(node==null) { System.out.println(""); return; } String leafInfo=space+node.mElement.toString()+"["+node.mHeight+"]"; String newSpace=space+" "; if(node.mLeftNode==null && node.mRightNode==null) { System.out.println(leafInfo); } else { if(type==0) { System.out.println(leafInfo); if(node.mLeftNode!=null) { printTree(node.mLeftNode, type,newSpace); } if(node.mRightNode!=null) { printTree(node.mRightNode, type,newSpace); } } if(type==1) { if(node.mLeftNode!=null) { printTree(node.mLeftNode, type,newSpace); } System.out.println(leafInfo); if(node.mRightNode!=null) { printTree(node.mRightNode, type,newSpace); } } if(type==2) { if(node.mLeftNode!=null) { printTree(node.mLeftNode, type,newSpace); } if(node.mRightNode!=null) { printTree(node.mRightNode, type,newSpace); } System.out.println(leafInfo); } if(type==3) { if(node.mRightNode!=null) { printTree(node.mRightNode, type,newSpace); } System.out.println(leafInfo); if(node.mLeftNode!=null) { printTree(node.mLeftNode, type,newSpace); } } } } public MyAVLNode<T> mRootNode=null; }
测试代码
public static class AVL { public static void test() { MyAVL<Integer> mytreeAvl=new MyAVL<Integer>(); // mytreeAvl.add(20, mytreeAvl.mRootNode,null,true); // mytreeAvl.add(7, mytreeAvl.mRootNode,null,true); // //mytreeAvl.add(56, mytreeAvl.mRootNode,null,true); // //mytreeAvl.add(5, mytreeAvl.mRootNode,null,true); // //mytreeAvl.add(9, mytreeAvl.mRootNode,null,true); // //mytreeAvl.add(3, mytreeAvl.mRootNode,null,true); //// mytreeAvl.add(12, mytreeAvl.mRootNode,null,true); //// mytreeAvl.add(64, mytreeAvl.mRootNode,null,true); //// mytreeAvl.add(33, mytreeAvl.mRootNode,null,true); //// mytreeAvl.add(15, mytreeAvl.mRootNode,null,true); mytreeAvl.add(20, mytreeAvl.mRootNode); mytreeAvl.add(7, mytreeAvl.mRootNode); mytreeAvl.add(56, mytreeAvl.mRootNode); mytreeAvl.add(5, mytreeAvl.mRootNode); mytreeAvl.add(9, mytreeAvl.mRootNode); mytreeAvl.add(3, mytreeAvl.mRootNode); mytreeAvl.add(12, mytreeAvl.mRootNode); // mytreeAvl.add(64, mytreeAvl.mRootNode,null,true); // mytreeAvl.add(33, mytreeAvl.mRootNode,null,true); // mytreeAvl.add(15, mytreeAvl.mRootNode,null,true); //mytreeAvl.remove(20, mytreeAvl.mRootNode, null, true); //mytreeAvl.remove(5, mytreeAvl.mRootNode, null, true); mytreeAvl.printTree(3); } }