標籤:代碼 一個 center 指令 朋友 treenode == 入行 new
物有本末,事有始終,知所先後,則近道矣。-----題記。
BotWong半路入行it做碼農,也就半年時間,竟“不知天高地厚”地來到了深圳闖天下。一口氣投了百個簡曆,一周后終於有公司邀約面試,除了基礎的java文法和開發經驗,大一點的公司都會出幾道題給你做(演算法題)。BotWong是一頭霧水,而且心裡很生氣!氣!氣!氣!以前自己學的是“人類心理學理論與實踐”專業,唯一的電腦基礎也就是用過word、excel給客戶報過價。自己硬著頭皮把java文法和javaEE架構學了一遍,像我這樣的個例已經算是出類拔萃的了,為什麼還要對我這麼苛刻,哼!BotWong從那以後就開始討厭那些喜歡考試的公司,並發誓以後再也不去那樣的公司面試!
我和BotWong是好朋友,他提出了自己的困惑:到底要不要學這些看著“沒用”的演算法,問我該怎麼看待這件事,該如何做。好在BotWang還是個頭腦清醒的人,要不然也不會問這樣的問題了。我看他挺有誠意,就把自己以前寫的一些代碼,其中就包含一些演算法,拿出來泛泛地說了一遍,建議他自己都去實現一遍。BotWang消失了三月,再見之時,已能感知其功力大漲,已非三月前的那個BotWong了。我心甚慰,特此將自己以前寫過的集合類都貼出來,方便自己和網上的寶寶們隨時翻看,隨時補充能量。
一、二叉樹類:
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怎麼執行代碼,那就可以輕輕鬆鬆地理解遞迴到底是怎麼執行的了。
java遞迴之“二叉樹”