这里有一些不太合理的代码,比如把AVL树专属的平衡因子加入到了树的定义上,这是最开始遗留的问题,暂时没改,等以后敲其他树的算法的时候再改一下吧
package com.bn; public abstract class Tree{ //左孩子 private Tree left; //右孩子 private Tree right; //父亲节点 private Tree father; //数据 private int entry; //树的高度 private int height; //树的平衡因子,左子树高度-右子树高度 private int balance; public Tree(int value){ //节点数值随便现给定一个 entry=value; //此时是叶子节点,高度为1 height=1; //叶子节点必然平衡 balance=0; left=null; right=null; father=null; } public void setLeftSon(Tree son){ this.left=son; //我们可以往子树上插入空树,此时就无需设定子树的父亲 if (son!=null){ son.father=this; } } //设置右子树 public void setRightSon(Tree son){ this.right=son; //我们可以往子树上插入空树,此时就无需设定子树的父亲 if (son!=null){ son.father=this; } } //将当前树替换为t树 //不会改变父亲与当前树的关系 //这里不是深度copy,子树并没有重新构造对象,必须保证替换后t树的不再被引用 //否则会存在两颗树同时指向一个子树的BUG public void replace(Tree t){ // this.father=t.father; 除了爸爸,其他每一个都复制一下 this.setLeftSon(t.left); this.setRightSon(t.right); this.height=t.height; this.balance=t.balance; this.entry=t.entry; } //判断其是不是父亲的左孩子 public boolean isFatherLeftSon(){ return this==father.left; } //更新树的高度,只能从该位置开始,如果需要更新全部节点高度, //只能争对每一个叶子节点使用一次,建议建树的时候就开始更新 //返回第一个失衡点的引用,传送的时候需要传送一个空应用 public Tree updateHeight(Tree firstBreakBalanceTree){ //先假定一个不可能的数值 int newHeight=-1; //两颗子树都存在 if(left!=null&&right!=null){ //当前高度为,两颗子树高度的最大值加1 newHeight=Math.max(left.height, right.height)+1; //调整平衡因子 balance=left.height-right.height; }else if(left==null&&right==null){//此时为叶子节点 //叶子节点高度为0 newHeight=1; //调整平衡因子 balance=0; }else if(left!=null){//如果左子树不是空树,且右子树是空树 newHeight=left.height+1; //调整平衡因子 balance=left.height; }else if(right!=null){//如果右子树不是空树,且左子树是空树 newHeight=right.height+1; //调整平衡因子 balance=-right.height; } //这棵树破坏平衡,并且我们还没有找到破坏平衡的树 if ((balance<-1||balance>1)&&firstBreakBalanceTree==null){ //那么这个第一棵破坏平衡的树就是他了 firstBreakBalanceTree=this; } //前后两次高度不相等,说明树高度变化 if(height!=newHeight){ height=newHeight; //如果存在父节点 if(father!=null){ //开始递归 return father.updateHeight(firstBreakBalanceTree); } } //返回第一棵破坏平衡的数 return firstBreakBalanceTree; } public void setBalance(int balance) { this.balance = balance; } public void setHeight(int height) { this.height = height; } public void setEntry(int entry){ this.entry=entry; } //获得数据 public int getEntry() { return entry; } //获得高度 public int getHeight() { return height; } //获得平衡因子 public int getBalance() { return balance; } //获得父亲节点 public Tree getFather() { return father; } //获得左子树 public Tree left() { return left; } //获得右子树 public Tree right() { return right; } //递归插入树,内部调用 public abstract void insert_rec(Tree newTree); //插入一个数据到树中,开放给别人使用 public abstract void insert(int value); //递归删除一颗树 public abstract void delete_rec(); //删除一颗树,其数值等于value的数值 public abstract void delete(int value); //查找一颗树,其数值等于value的数值 public abstract Tree search(int value); //打印一棵树 public abstract void printTree(); public void Preorder(){ if(left!=null){ left.Preorder(); } System.out.print(this.entry+" "); if (right!=null){ right.Preorder(); } } //树的字符串表现形式 @Override public String toString() { //如果是空树就打印____符 if (entry==-10){ return "{entry=null}t"; }else{ return "{entry="+entry+",height="+height+",balance="+balance+"}t"; } } }二、AVL定义(AVL)
想要表述的都写在注释里面啦,第一次尝试敲AVL,如有错误的地方欢迎指出,有不懂的地方也可以私信我
package com.bn; import java.util.Queue; import java.util.concurrent.ArrayBlockingQueue; public class AVL extends Tree { final static int TYPE_RR=0; final static int TYPE_LL=1; final static int TYPE_RL=2; final static int TYPE_LR=3; final static int TYPE_R0=5; final static int TYPE_L0=6; final static int TYPE_ERROR=4; public static void main(String[] args){ int[] array={4,8,16,5,3,15,14,9,6,13,7,11,12,10,1,2,19,18,17}; AVL avl=null; for(int i:array){ if (avl==null){ avl=new AVL(i); }else{ avl.insert(i); System.out.println("======插入"+i+"后======"); avl.printTree(); System.out.println(); } } avl.Preorder(); for(int i:array){ System.out.println("n======删除"+i+"后======"); avl.delete(i); avl.printTree(); } } //生成一颗树 public AVL(int entry){ super(entry); } //返回自己平衡的类型 public int breakBalanceType(){ int balance=this.getBalance(); Tree left=this.left(); Tree right=this.right(); //左子树高度高了2 if(balance==2){ //左子树的左子树高,为LL形状 if(left.getBalance()==1){ return TYPE_LL; }else if(left.getBalance()==-1){//如果是左子树的右子树高,则为LR形状 return TYPE_LR; }else if (left.getBalance()==0){//删除子树的时候会出现这种情况,具体情形见调整部分 return TYPE_L0; } }else if(balance==-2){//右子树高了2 //右子树的右子树高,为RR形状 if(right.getBalance()==-1){ return TYPE_RR; }else if(right.getBalance()==1){//如果是右子树的左子树高,则为RL形状 return TYPE_RL; }else if (right.getBalance()==0){//删除子树的时候会出现这种情况,具体情形见调整部分 return TYPE_R0; } } //不是这几种情况的都是错误的 //如果自己是平衡树,也不该调用这个方法,所以返回ERROR return TYPE_ERROR; } //调整RR型 public void adjustLL(){ //A,B,C,D,E为中序遍历序列对应编号、x //A,B,两棵树需要大改动,故重新建立节点 AVL A=new AVL(this.getEntry()); AVL B=new AVL(this.left().getEntry()); //C,D,E三棵子树只需调整位置,无需改动 AVL C=(AVL) this.left().left(); AVL D=(AVL) this.left().right(); AVL E=(AVL) this.right(); B.setLeftSon(C); B.setRightSon(A); A.setLeftSon(D); A.setRightSon(E); //假设调整情况如上图,由于A是我们新new的根节点,高度为1而不是2 //调整后高度任然为1,此时就会认为A的高度没有发生改变 //自然不会去递归的调整B的高度,但是实际上A的高度是发生改变的 //故我们需要将A的高度设定为一个不可能的数值,调整A节点后必然回去调整B节点 A.setHeight(-1); //调整A的高度后会递归的调整B的高度 A.updateHeight(null); //用新树替换this //这里只是将新树中除father的成员拷贝到this中 this.replace(B); } //调整LL型 public void adjustRR(){ //A,C,两棵树需要大改动,故重新建立节点 AVL A=new AVL(this.getEntry()); AVL C=new AVL(this.right().getEntry()); //C,D,E三棵子树只需调整位置,无需改动 AVL D=(AVL) this.right().left(); AVL E=(AVL) this.right().right(); AVL B=(AVL) this.left(); C.setLeftSon(A); C.setRightSon(E); A.setLeftSon(B); A.setRightSon(D); //情况同上 A.setHeight(-1); //调整A的高度后会递归的调整C的高度 A.updateHeight(null); //用新树替换this //这里只是将新树中除father的成员拷贝到this中 this.replace(C); } //调整LR型 public void adjustLR(){ //A,B,D节点变动较大,就不采用原本节点 Tree A=new AVL(this.getEntry()); Tree B=new AVL(this.left().getEntry()); Tree D=new AVL(this.left().right().getEntry()); //剩余树只是改变了父亲节点,无需改动,故不重新建树 Tree C=this.left().left(); Tree E=this.left().right().left(); Tree F=this.left().right().right(); Tree G=this.right(); D.setLeftSon(B); D.setRightSon(A); B.setLeftSon(C); B.setRightSon(E); A.setLeftSon(F); A.setRightSon(G); //情况同上 A.setHeight(-1); B.setHeight(-1); //只需调整A,C节点,D节点自动就调整好了 A.updateHeight(null); B.updateHeight(null); //用新树替换this //这里只是将新树中除father的成员拷贝到this中 this.replace(D); } //调整RL型 public void adjustRL(){ //A,C,D节点变动较大,就不采用原本节点 Tree A=new AVL(this.getEntry()); Tree C=new AVL(this.right().getEntry()); Tree D=new AVL(this.right().left().getEntry()); //剩余树只是改变了父亲节点,无需改动,故不重新建树 Tree B=this.left(); Tree E=this.right().left().left(); Tree F=this.right().left().right(); Tree G=this.right().right(); D.setLeftSon(A); D.setRightSon(C); A.setLeftSon(B); A.setRightSon(E); C.setLeftSon(F); C.setRightSon(G); //情况同上 A.setHeight(-1); C.setHeight(-1); //只需调整A,C节点,D节点自动就调整好了 A.updateHeight(null); C.updateHeight(null); //用新树替换this //这里只是将新树中除father的成员拷贝到this中 this.replace(D); } //调整R0型 删除后A平衡因子变为-2,C为0 public void adjustR0() { //A,C节点变动较大,就不采用原本节点 Tree A=new AVL(this.getEntry()); Tree C=new AVL(this.right().getEntry()); //剩余树只是改变了父亲节点,无需改动,故不重新建树 Tree B=this.left(); Tree D=this.right().left(); Tree E=this.right().right(); C.setRightSon(E); C.setLeftSon(A); A.setRightSon(D); A.setLeftSon(B); //情况同上 A.setHeight(-1); //只需调整A,C节点,D节点自动就调整好了 A.updateHeight(null); //用新树替换this //这里只是将新树中除father的成员拷贝到this中 this.replace(C); } //调整L0型 删除后A平衡因子变为2,B为0 public void adjustL0() { //A,B节点变动较大,就不采用原本节点 Tree A=new AVL(this.getEntry()); Tree B=new AVL(this.left().getEntry()); //剩余树只是改变了父亲节点,无需改动,故不重新建树 Tree C=this.right(); Tree D=this.left().left(); Tree E=this.left().right(); B.setRightSon(A); B.setLeftSon(D); A.setRightSon(C); A.setLeftSon(E); //情况同上 A.setHeight(-1); //只需调整A,C节点,D节点自动就调整好了 A.updateHeight(null); //用新树替换this //这里只是将新树中除father的成员拷贝到this中 this.replace(B); } //如果当前树不平衡,会将当前树调整至平衡树 //调整后的树依旧保持原来的父子树关系 public void adjustTree(){ //判断本树不平衡类型 int type=this.breakBalanceType(); //根据非平衡类型进行调整 switch (type){ case TYPE_RR://RR型 adjustRR(); break; case TYPE_LL://LL型 adjustLL(); break; case TYPE_RL://RL型 adjustRL(); break; case TYPE_LR://LR型 adjustLR(); break; case TYPE_R0://R0型,删除后可能出现的类型 adjustR0(); break; case TYPE_L0://L0型,删除后可能出现的类型 adjustL0(); break; case TYPE_ERROR://如果树已经平衡,或者出现了逻辑错误,我的思路下是不会出现这种情况的 System.out.println(this+" 出现错误"); break; } } //check的同时会更新树的高度 //检查当前树是否平衡,不平衡则调整,之后递归的检查父节点是否平衡 public void check(){ //调整树高和平衡因子,并且检查返回第一个破坏平衡的树 AVL bbt=(AVL) this.updateHeight(null); //如果存在不平衡的树,则需要对其进行调整 if (bbt!=null){ //调整bbt为平衡树,其父亲不一定是平衡树 bbt.adjustTree(); //先找到父亲节点 AVL father=(AVL)bbt.getFather(); //父亲不是根节点 if (father!=null){ //检查父亲节点是否平衡 father.check(); } } } @Override public void insert(int value) { this.insert_rec(new AVL(value)); } @Override //newTree里面必须放的是AVL的引用,所以建议插入数据使用insert方法而不是这个 public void insert_rec(Tree newTree){ //小于应该去左子树 if(newTree.getEntry()三、输出结果this.getEntry()){//大于则去右子树 //如果正好没有右子树 if(this.right()==null){ //将新节点插入右子树 this.setRightSon(newTree); //当前树的高度和平衡因子都有可能发生改变 //调整高度和平衡因子,并且检查是否出现失衡 //如果失衡则调整 this.check(); }else{//如果已经存在右子树 //则将节点插入到右子树 this.right().insert_rec(newTree); } } } public Tree search(int value){ //小于应该去左子树 if(value this.getEntry()){//大于则去右子树 //如果存在右子树 if(this.right()!=null){ //去右子树搜索 return this.right().search(value); }else{//如果右子树为空,代表元素不在书中 System.out.println("==="+value+"不在本树中==="); return null; } }else{//正好等于,当前树就是我们要找的树 return this; } } @Override public void delete_rec() { AVL father=(AVL)this.getFather(); Tree t=this.left(); AVL left=(AVL)this.left(); AVL right=(AVL)this.right(); //此时分为四种情况 if(left==null&&right==null){//第一种情况,要删除的点正好是叶子节点 //判断当前树是否为父亲的左子树 if(this.isFatherLeftSon()){ //删除该节点,方法结束后,delTree会变为野指针,自动会被GC回收 father.setLeftSon(null); }else { father.setRightSon(null); } //父亲有可能不平衡 //此时调整父亲节点直到平衡 if(father!=null){//如果delTree不是根节点的话 father.check(); } }else if(left!=null&&right!=null){//左子树和右子树都存在 //找到左子树的最右边的节点E,用这个节点的值替换A的值,并且删除E节点,找右子树的最左节点也可 AVL E=left; while (E.right()!=null){ E=(AVL) E.right(); } //将E中的数值赋给A this.setEntry(E.getEntry()); //删除E节点 E.delete_rec(); //删除节点后,父亲节点的高度可能发生变化,需要检查父亲节点是否平衡 if(father!=null){//如果delTree不是根节点的话 father.check(); } }else if(left!=null){//只存在左子树,右子树不存在 //直接用左子树替代当前树即可 this.replace(this.left()); //删除节点后,父亲节点的高度可能发生变化,需要检查父亲节点是否平衡 if(father!=null){//如果delTree不是根节点的话 father.check(); } }else if(right!=null){//只存在右子树,与只存在左子树类似 this.replace(this.right()); //删除节点后,父亲节点的高度可能发生变化,需要检查父亲节点是否平衡 if(father!=null){//如果delTree不是根节点的话 father.check(); } } } // @Override public void delete(int value) { //先查找值在不在树中 Tree delTree=this.search(value); //如果数值不在树中,我们自然也无法删除这个值 if(delTree==null){ System.out.println("===删除失败==="); return; }else if(this.getFather()==null&&this.getHeight()==1){ //节点高度为1并且无父亲节点,也就是单个节点的树,自然不能删除 System.out.println("n该树只剩值为"+value+"这一个节点,无法删除"); return; } //将这颗树删除 delTree.delete_rec(); } //打印一棵树看看 @Override public void printTree(){ //当前访问节点指针 Tree p; //当前访问了几个节点 int visitedCount=0; //当前访问的节点在第几层 int curLevel=0; //高度为k的平衡树,至多2^k-1个节点,但是我这里又多画了一层空树,空树之多为2*n+1,n是节点数 int maxNodeCount=2*((int)Math.pow(2,getHeight())-1)+1; //保证队列足够长 Queue queue=new ArrayBlockingQueue<>(maxNodeCount); //根节点入队 queue.add(this); //当队列中还有着元素时 while (!queue.isEmpty()){ //第一个元素出队 p=queue.remove(); //当前访问的元素自增 visitedCount++; //节点所在层数 int newLevel=(int) Math.floor(Math.log(visitedCount)/Math.log(2))+1; //换层即换行 if(newLevel!=curLevel){ System.out.println(); System.out.println("Level "+newLevel+":"); curLevel=newLevel; } //打印树的信息 System.out.print(p); //记录当前指针的1左右子树 Tree left=p.left(); Tree right=p.right(); //多打印一层空树,-10判断当前树是否为空树 if (left==null&&p.getEntry()!=-10){ //创建一棵值为-10的树 left=new AVL(-10); //调整树高度 left.updateHeight(null); } if (right==null&&p.getEntry()!=-10){ //创建一棵值为-10的树 right=new AVL(-10); //调整树高度 right.updateHeight(null); } if(left!=null){ queue.add(left); } if(right!=null){ queue.add(right); } } } }
运行代码后会 依次插入4,8,16,5,3,15,14,9,6,13,7,11,12,10,1,2,19,18,17这些节点构造树,并且再依次删除。每次插入删除都会打印一下树的形状,采取层次遍历的方式打印树,打印的时候如过一个节点存在空子树,会把空子树在下一层也打印一遍,不过空子树的空子树不会打印,第四层的两颗null树分别是第三层值为5的节点的两颗空子树,差不多就是这个规律吧。等以后有时间我把具体过程写上。
Level 1:
{entry=8,height=3,balance=1}
Level 2:
{entry=4,height=2,balance=-1} {entry=16,height=1,balance=0}
Level 3:
{entry=null} {entry=5,height=1,balance=0} {entry=null} {entry=null}
Level 4:
{entry=null} {entry=null}
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)