物有本末,事有始终,知所先后,则近道矣。-----题记。
公司邀约面试,除了基础的java语法和开发经验,大一点的公司都会出几道题给你做(算法题)。
一、二叉树类:
package tree; /** * 二叉树数据载体类 * @author tery * * @param <T> */ public class BinaryTreeNode<T> { private T data;//权值 private BinaryTreeNode<T> left;//左孩子 private BinaryTreeNode<T> right;//右孩子 public BinaryTreeNode(T data){ this.data=data; left=right=null; } public T getData(){ return this.data; } public void setData(T data){ this.data=data; } public BinaryTreeNode<T> getLeft() { return left; } public void setLeft(BinaryTreeNode<T> left) { this.left = left; } public BinaryTreeNode<T> getRight() { return right; } public void setRight(BinaryTreeNode<T> right) { this.right = right; } /** *插入权值 */ @SuppressWarnings("unchecked") public BinaryTreeNode<T> insert(BinaryTreeNode<T> node,Integer data){ if(node==null){ return new BinaryTreeNode<T>((T)data); } //如果当前节点的权值大于data,那么插入到左孩子节点上 if(Integer.valueOf(node.getData().toString())>data){ node.left=insert(node.getLeft(),data); } //如果当前节点的权值大于data,那么插入到左孩子节点上 if(Integer.valueOf(node.getData().toString())<data){ node.right=insert(node.getRight(),data); } //相等抛异常 if(Integer.valueOf(node.getData().toString())==data){ throw new IllegalArgumentException("the data:"+data+"is already exsist in the tree"); } return node; } }
insert方法中涉及到的递归:
0: aload_1 1: ifnonnull 13 4: new #1 // class tree/BinaryTreeNode 7: dup 8: aload_2 9: invokespecial #45 // Method "<init>":(Ljava/lang/Object;)V 12: areturn 13: aload_1 14: getfield #20 // Field data:Ljava/lang/Object; 23: aload_2 30: if_icmple 79 36: getfield #24 // Field left:Ltree/BinaryTreeNode; 39: aload_2 40: invokevirtual #53 // Method insert:(Ltree/BinaryTreeNode;Ljava/lang/Object;)Ltree/BinaryTreeNode; 43: putfield #24 // Field left:Ltree/BinaryTreeNode; 79: aload_1 80: areturn
上面是insert方法的jvm执行的指令,我摘抄了部分关键点说明一下递归是怎么做的:
insert函数栈中的局部变量表中的参数:第一个是当前树节点对象(暂且称为ref0),第二个是函数中传进来的node对象的引用(称为ref1),第三个是传进来的常量data(在常量池中#20)
0: aload_1 将第一个引用类型局部变量推送至栈顶,那个引用也就是insert方法中的node对象的引用(ref1)
1 : ifnonnull 13 如果ref1不为空,则跳到13行执行
4-12 : 如果是空,则此节点即是我们要插入的节点,调用它的构造函数<init>方法,将data值赋给它,然后areturn(将这个新生成的对象引用返回)
13 : aload_1 将ref1压入栈顶
14:getfield 将常量池中当前对象ref1的data的值压入栈顶
23:aload_2 将传入的data值压入栈顶
30:if_icmple 79,如果传入的data值小于ref1的权值,那么就跳到79执行
36:getfield #24 ,将常量池中#24,即left压入栈中,即left常量的引用
39:aload_2 将data值压入栈中。实际上36、39两步的指令都是在为下面40行做准备,40行调用insert方法,传递给它需要的参数
40:invokevirtrual #53,调用insert方法,将36、39行准备的两个参数带过去
43:putfield #24 ,给当前实例ref1的left字段赋值
79:aload_1 ,将当前实例ref1压入栈中
80:areturn ,将栈顶的ref1返回
如果你不太懂,我还给你画了个图,帮助你理解:
假设jvm只执行了在左边树插入的递归步骤:
上面的字节码指令和图解是截取了部分关键点进行讲解的,如果想进一步探讨的宝宝们,可以联系我详细讨论。
三、二叉树工具类:
1. 前根序遍历:先遍历根结点,然后遍历左子树,最后遍历右子树。(ABDHECFG)
2.中根序遍历:先遍历左子树,然后遍历根结点,最后遍历右子树。(HDBEAFCG)
3.后根序遍历:先遍历左子树,然后遍历右子树,最后遍历根节点(HDEBFGCA)
package tree; import java.util.ArrayList; import java.util.List; import java.util.Stack; public class BinaryTreeUtil { /** * 用递归的方式实现二叉树的前序遍历 * @param root * @return */ public static<T> List<T> preOrderVisit(BinaryTreeNode<T> root){ List<T> result=new ArrayList<>(); preOrderVisit(root,result); return result; } private static<T> void preOrderVisit(BinaryTreeNode<T> node, List<T> result) { //如果节点为空,返回 if(node==null){ return; } //不为空,则加入节点的值 result.add(node.getData()); //先递归左孩子 preOrderVisit(node.getLeft(),result); //再递归右孩子 preOrderVisit(node.getRight(),result); } /** * 用递归的方式实现二叉树的中序遍历 * @param root * @return */ public static<T> List<T> inOrderVisit(BinaryTreeNode<T> root){ List<T> result=new ArrayList<T>(); inOrderVisit(root,result); return result; } private static<T> void inOrderVisit(BinaryTreeNode<T> node, List<T> result) { if(node==null){ return; } inOrderVisit(node.getLeft(),result); result.add(node.getData()); inOrderVisit(node.getRight(),result); } /** * 用递归的方式实现二叉树的后遍历 * @param root * @return */ public static<T> List<T> postOrderVisit(BinaryTreeNode<T> root){ List<T> result=new ArrayList<T>(); postOrderVisit(root,result); return result; } private static<T> void postOrderVisit(BinaryTreeNode<T> node, List<T> result) { if(node==null){ return; } postOrderVisit(node.getLeft(),result); postOrderVisit(node.getRight(),result); result.add(node.getData()); } /** * 用非递归的方式实现前序遍历 * @param root * @return */ public static<T> List<T> preOrderVisitWithoutRecursion(BinaryTreeNode<T> root){ List<T> result=new ArrayList<T>(); Stack<BinaryTreeNode<T>> stack=new Stack<>(); if(root!=null){ stack.push(root); } while(!stack.isEmpty()){ BinaryTreeNode<T> node=stack.pop(); result.add(node.getData()); if(node.getRight()!=null){ stack.push(node.getRight()); } if(node.getLeft()!=null){ stack.push(node.getLeft()); } } return result; } /** * 用非递归的方式实现中序遍历 * @param root * @return */ public static<T> List<T> inOrderVisitWithoutRecursion(BinaryTreeNode<T> root){ List<T> result=new ArrayList<T>(); Stack<BinaryTreeNode<T>> stack=new Stack<>(); BinaryTreeNode<T> node=root; while(node!=null || !stack.isEmpty()){ while(node!=null){ stack.push(node); node=node.getLeft(); } BinaryTreeNode<T> currentNode=stack.pop(); result.add(currentNode.getData()); node=currentNode.getRight(); } return result; }
二叉树部分上面总共列举了15个方法,主要的思想还是用递归。关于递归,为了让宝宝们明白是怎么回事,我也是花了点心思去解释的,请各位再深入思考一下。里面涉及到了jvm函数调用层面的知识,如果不懂,建议大家去看看jvm的书,了解jvm怎么执行代码,那就可以轻轻松松地理解递归到底是怎么执行的了。