java遞歸之“二叉樹”


物有本末,事有始終,知所先后,則近道矣。-----題記。

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怎么執行代碼,那就可以輕輕松松地理解遞歸到底是怎么執行的了。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM