Java實現二叉樹的創建和遍歷操作(有更新)

<font color='green' size='5'>博主強烈建議跳過分割線前面的部分,直接看下文更新的那些即可。


最近在學習二叉樹的相關知識,一開始真的是毫無頭緒。本來學的是C++二叉樹,但苦于編譯器老是出故障,于是就轉用Java來實現二叉樹的操作。但是二者原理是一致的,而且實現的方式也是大同小異!
下面就讓我們來看看代碼吧。

1、首先我們需要創建一個二叉樹的節點類,便于我們對樹的操作,當然了,你也可以在二叉樹類的內部將節點類聲明為內部類,但是這樣會降低操作的靈活性。我才用的是單獨創建一個BinaryTreeNode類,代碼如下:

package MyBinaryTree;

public class BinaryTreeNode<T> {

    T data;
    BinaryTreeNode<T> leftChild;
    BinaryTreeNode<T> rightChild;

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }

    BinaryTreeNode() {
        this.data = null;
        this.leftChild = null;
        this.rightChild = null;
    }

    BinaryTreeNode(T data) {
        this.data = data;
        this.leftChild = null;
        this.rightChild = null;
    }

    public BinaryTreeNode(T data, BinaryTreeNode<T> leftChild,
            BinaryTreeNode<T> rightChild) {
        super();
        this.data = data;
        this.leftChild = leftChild;
        this.rightChild = rightChild;
    }

    public BinaryTreeNode<T> getLeftChild() {
        return leftChild;
    }

    public void setLeftChild(BinaryTreeNode<T> leftChild) {
        this.leftChild = leftChild;
    }

    public BinaryTreeNode<T> getRightChild() {
        return rightChild;
    }

    public void setRightChild(BinaryTreeNode<T> rightChild) {
        this.rightChild = rightChild;
    }

    public boolean isLeaf() {
        if (this.leftChild == null && this.rightChild == null) {
            return true;
        }
        return false;
    }

}
//我才用的是泛型定義,在C++中我們可以使用模板來實現相同的處理

2、有了節點類,下面就是二叉樹類了,注釋什么的在代碼中已經非常詳細了:

package MyBinaryTree;

import java.util.Queue;
import java.util.Stack;
import java.util.concurrent.LinkedBlockingQueue;

public class BinaryTree<T> {

    private BinaryTreeNode<T> root;

    public BinaryTree() {
        BinaryTreeNode<T> node = new BinaryTreeNode<T>();
        this.root = node;
    }

    public boolean isEmpty() {
        if (root == null) {
            return true;
        }
        return false;
    }

    public BinaryTreeNode<T> getRoot() {
        return this.root;
    }

    public void CreateTree(BinaryTreeNode<T> node, T data) {
        if (root == null) {
            root = new BinaryTreeNode<T>();
        } else {
            if (Math.random() > 0.5) {                   //采用隨機方式創建二叉樹
                if (node.leftChild == null) {
                    node.leftChild = new BinaryTreeNode<T>(data);
                } else {
                    CreateTree(node.leftChild, data);
                }
            } else {
                if (node.rightChild == null) {
                    node.rightChild = new BinaryTreeNode<T>(data);
                } else {
                    CreateTree(node.rightChild, data);
                }
            }
        }
    }

    /*
     * 訪問當前節點
     */
    public void Visit(BinaryTreeNode<T> current) {
        if(current!=null&&current.getData()!=null){
            System.out.println(current.getData());
        }else{
            System.out.println("null");
        }
    }

    /*
     * 廣度優先遍歷二叉樹
     */
    public void levelOrder(BinaryTreeNode<T> root) {
        Queue<BinaryTreeNode<T>> queue = new LinkedBlockingQueue<BinaryTreeNode<T>>();
        BinaryTreeNode<T> pointer = root;
        /*
         * 當前節點不為空時,放入隊首
         */
        if (pointer != null) {
            queue.add(pointer);
        }
        /*
         * 隊列不為空時,先訪問中間節點,訪問完成后彈出隊首節點;然后是左節點,再是右節點;
         */
        while (!queue.isEmpty()) {
            pointer = queue.peek();
            Visit(pointer);
            queue.remove();
            if (pointer.leftChild != null) {
                queue.add(pointer.leftChild);
            }
            if (pointer.rightChild != null) {
                queue.add(pointer.rightChild);
            }
        }
    }

    /*
     * 遞歸方式的前序遍歷
     */
    public void preOrder(BinaryTreeNode<T> root) {
            Visit(root);
            preOrder(root.leftChild);
            preOrder(root.rightChild);
    }
    
    /*
     * 非遞歸方式實現的前序遍歷
     */
    public void NPreOrder(BinaryTreeNode<T> root){
        Queue<BinaryTreeNode<T>> queue=new LinkedBlockingQueue<BinaryTreeNode<T>>();
        BinaryTreeNode<T> pointer=root;
        /*
         * 當前節點不為空,就一直放入隊尾;當前節點為空時,訪問隊首元素,然后訪問做孩子節點;然后彈出,再對新的隊首元素進行判斷
         */
        while(!queue.isEmpty()||pointer!=null){
            if(pointer!=null){
                Visit(pointer);
                if(pointer.rightChild!=null){
                    queue.add(pointer.rightChild);
                }
                pointer=pointer.leftChild;
            }else{
                pointer=queue.peek();
                queue.remove();
            }
        }
    }
    
    /*
     * 采用遞歸方式實現的中序遍歷操作
     */
    public void inOrder(BinaryTreeNode<T> root){
        inOrder(root.leftChild);
        Visit(root);
        inOrder(root.rightChild);
    }
    
    
    /*
     * 非遞歸方式實現的中序遍歷
     */
    public void NInOrder(BinaryTreeNode<T> root){
        Stack<BinaryTreeNode<T>> stack=new Stack<BinaryTreeNode<T>>();
        BinaryTreeNode<T> pointer=root;
        /*
         * 當前節點不為空,就一直進棧;當前節點為空時,訪問棧頂元素,然后再訪問右孩子節點
         */
        while(!stack.isEmpty()||pointer!=null){
            if(pointer!=null){
                stack.push(pointer);
                pointer=pointer.leftChild;
            }else{
                pointer=stack.peek();
                Visit(pointer);
                pointer=pointer.rightChild;
                stack.pop();
            }
        }
    }
    
    /*
     * 遞歸方式實現的后序遍歷二叉樹
     */
    public void postOrder(BinaryTreeNode<T> root){
        postOrder(root.leftChild);
        postOrder(root.rightChild);
        Visit(root);
    }
    
    /*
     * 非遞歸方式實現的后序遍歷二叉樹
     */
    public void NPostOrder(BinaryTreeNode<T> root){
        Stack<BinaryTreeNode<T>> stack=new Stack<BinaryTreeNode<T>>();//初始化棧,用于保存帶訪問的節點
        BinaryTreeNode<T> pointer=root;                               //保存根節點
        BinaryTreeNode<T> preNode=root;                               //保存前一個被訪問的節點
        while(!stack.isEmpty()||pointer!=null){
            //若當前節點不空,就一直進棧,然后繼續向左走
            while(pointer.leftChild!=null){
                stack.push(pointer);
                pointer=pointer.leftChild;
            }
            /*
             * 當前節點為空時,分兩種情況:
             * 1、當前節點移動到棧頂處,然后訪問棧頂元素的右節點
             * 2、當前節點移動到棧頂,但是棧頂元素沒有右節點,這就需要彈出棧頂元素,再對此元素訪問;
             * 然后再對新的棧頂元素進行判斷即可
             */
            while(pointer!=null&&(pointer.rightChild==null)||(pointer.rightChild==preNode)){
                Visit(pointer);
                preNode=pointer;
                if(stack.isEmpty()){
                    return;
                }
                pointer=stack.peek();
                stack.pop();
            }
                stack.push(pointer);
                pointer=pointer.rightChild;
            
        }
    }
}

3、然后是我的測試類,下面請看代碼:

    public static void main(String[] args) {
        BinaryTree<Integer> tree = new BinaryTree<Integer>();
        for (int i = 1; i < 10; i++) {
            tree.CreateTree(tree.root, i);
        }
        System.out.println("-----------下面是廣度優先遍歷二叉樹--------------");
        tree.levelOrder(tree.root);
        System.out.println("-----------下面是非遞歸的前序遍歷方式-------------");
        tree.NPreOrder(tree.root);
        System.out.println("-----------下面是非遞歸的中序遍歷方式-------------");
        tree.NInOrder(tree.root);
        System.out.println("-----------下面是非遞歸的后序遍歷方式-------------");
        tree.NPostOrder(tree.root);
    }

4、接下來是測試的結果:

-----------下面是廣度優先遍歷二叉樹--------------
null
1
2
6
4
3
8
7
5
9
-----------下面是非遞歸的前序遍歷方式-------------
null
1
6
2
3
4
5
7
8
9
-----------下面是非遞歸的中序遍歷方式-------------
6
7
1
5
4
null
3
9
2
8
-----------下面是非遞歸的后序遍歷方式-------------
7
6
5
4
1
9
3
8
2
null

5、不足之處:
也許是測試的時候方式不對,因為使用遞歸方式對二叉樹進行遍歷的時候會報出NullPointerException的空指針錯誤。如果你知道原因在哪?不妨寫下你的評論。也好讓我加以改正。

6、總結:
在學習的過程中我意識到了一點,希望與君共勉!那就是埋頭敲代碼是解決不了問題的。重要的是思路。沒有思路,一味的測試也是不可能成功的。在敲代碼之前,我們一定要搞懂我們要做什么,怎么做,這樣才會事半功倍。希望能和大家共同學習,一起進步!

---------------------------時間的分割線--------------------------------------------------------------------

2016年9月12日19:01:38

看到大二剛開始學數據結構的時候,寫的這篇文章,水平真的是不忍直視啊。不過話又說回來了,編程的提高不就是這樣一點一點積聚來的嘛。下面來寫點比較容易理解的思路清晰的二叉樹遍歷相關的操作。

/**
 * @Date 2016年9月12日
 *
 * @author 郭  璞
 *
 */
package tree;

import java.util.Stack;

/**
 * @author 郭 璞 <br>
 *         二叉樹的先序,中序,以及后序,遞歸以及非遞歸的實現
 *
 */
public class FullScan {

    public static void main(String[] args) {
        Node head = createTree();
        // recurseFront(head);
        // recurseMid(head);
        recurseEnd(head);
        // front(head);
        // mid(head);
        endWith2Stack(head);
        endWithOneStack(head);
    }

    /**
     * 非遞歸實現的二叉樹后序遍歷<br>
     * 借助于一個棧進行實現
     * 
     * @param head
     */
    public static void endWithOneStack(Node head) {
        System.out.println();
        if (head == null) {
            return;
        } else {
            Stack<Node> stack = new Stack<Node>();
            stack.push(head);
            // 該節點代表已經打印過的節點,待會會及時的進行更新
            Node printedNode = null;
            while (!stack.isEmpty()) {
                // 獲取 棧頂的元素的值,而不是pop掉棧頂的值
                head = stack.peek();
                // 如果當前棧頂元素的左節點不為空,左右節點均未被打印過,說明該節點是全新的,所以壓入棧中
                if (head.getLeft() != null && printedNode != head.getLeft() && printedNode != head.getRight()) {
                    stack.push(head.getLeft());
                } else if (head.getRight() != null && printedNode != head.getRight()) {
                    // 第一層不滿足,則說明該節點的左子樹已經被打印過了。如果棧頂元素的右節點未被打印過,則將右節點壓入棧中
                    stack.push(head.getRight());
                } else {
                    // 上面兩種情況均不滿足的時候則說明左右子樹均被打印過,此時只需要彈出棧頂元素,打印該值即可
                    System.out.println("當前值為:" + stack.pop().getValue());
                    // 記得實時的更新打印過的節點的值
                    printedNode = head;
                }
            }
        }
    }

    /**
     * 非遞歸實現的二叉樹的后序遍歷<br>
     * 借助于兩個棧來實現
     * 
     * @param head
     */
    public static void endWith2Stack(Node head) {
        System.out.println();
        if (head == null) {
            return;
        } else {
            Stack<Node> stack1 = new Stack<Node>();
            Stack<Node> stack2 = new Stack<Node>();

            stack1.push(head);
            // 對每一個頭結點進行判斷,先將頭結點放入棧2中,然后依次將該節點的子元素放入棧1.順序為left-->right。便是因為后序遍歷為“左右根”
            while (!stack1.isEmpty()) {
                head = stack1.pop();
                stack2.push(head);
                if (head.getLeft() != null) {
                    stack1.push(head.getLeft());
                }

                if (head.getRight() != null) {
                    stack1.push(head.getRight());
                }
            }

            // 直接遍歷輸出棧2,即可實現后序遍歷的節點值的輸出
            while (!stack2.isEmpty()) {
                System.out.println("當前節點的值:" + stack2.pop().getValue());
            }
        }
    }

    /**
     * 非遞歸實現的二叉樹的中序遍歷
     * 
     * @param head
     */
    public static void mid(Node head) {
        System.out.println();
        if (head == null) {
            return;
        } else {
            Stack<Node> nodes = new Stack<Node>();

            // 使用或的方式是因為 第一次的時候戰中元素為空,head的非null特性可以保證程序可以執行下去
            while (!nodes.isEmpty() || head != null) {
                // 當前節點元素值不為空,則放入棧中,否則先打印出當前節點的值,然后將頭結點變為當前節點的右子節點。
                if (head != null) {
                    nodes.push(head);
                    head = head.getLeft();
                } else {
                    Node temp = nodes.pop();
                    System.out.println("當前節點的值:" + temp.getValue());
                    head = temp.getRight();
                }
            }

        }
    }

    /**
     * 非遞歸實現的二叉樹的先序遍歷
     * 
     * @param head
     */
    public static void front(Node head) {
        System.out.println();

        // 如果頭結點為空,則沒有遍歷的必要性,直接返回即可
        if (head == null) {
            return;
        } else {
            // 初始化用于存放節點順序的棧結構
            Stack<Node> nodes = new Stack<Node>();
            // 先把head節點放入棧中,便于接下來的循環放入節點操作
            nodes.add(head);

            while (!nodes.isEmpty()) {
                // 取出棧頂元素,判斷其是否有子節點
                Node temp = nodes.pop();

                System.out.println("當前節點的值:" + temp.getValue());
                // 先放入右邊子節點的原因是先序遍歷的話輸出的時候左節點優先于右節點輸出,而棧的特性決定了要先放入右邊的節點
                if (temp.getRight() != null) {
                    nodes.push(temp.getRight());
                }
                if (temp.getLeft() != null) {
                    nodes.push(temp.getLeft());
                }
            }
        }
    }

    /**
     * 遞歸實現的先序遍歷
     * 
     * @param head
     */
    public static void recurseFront(Node head) {
        System.out.println();

        if (head == null) {
            return;
        }
        System.out.println("當前節點值:" + head.getValue());
        recurseFront(head.left);
        recurseFront(head.right);
    }

    /**
     * 遞歸實現的中序遍歷
     * 
     * @param head
     */
    public static void recurseMid(Node head) {
        System.out.println();
        if (head == null)
            return;
        recurseMid(head.getLeft());
        System.out.println("當前節點的值:" + head.getValue());
        recurseMid(head.getRight());
    }

    /**
     * 遞歸實現的后序遍歷遞歸實現
     * 
     * @param head
     */
    public static void recurseEnd(Node head) {
        System.out.println();
        if (head == null)
            return;
        recurseEnd(head.getLeft());
        recurseEnd(head.getRight());
        System.out.println("當前節點的值為:" + head.getValue());
    }

    public static Node createTree() {
        // 初始化節點
        Node head = new Node(1);
        Node headLeft = new Node(2);
        Node headRight = new Node(3);
        Node headLeftLeft = new Node(4);
        Node headLeftRigth = new Node(5);
        Node headRightLeft = new Node(6);
        // 為head節點 賦予左右值
        head.setLeft(headLeft);
        head.setRight(headRight);

        headLeft.setLeft(headLeftLeft);
        headLeft.setRight(headLeftRigth);
        headRight.setLeft(headRightLeft);

        // 返回樹根節點
        return head;
    }

}

class Node {
    public int value;
    public Node left;
    public Node right;

    public int getValue() {
        return value;
    }

    public void setValue(int value) {
        this.value = value;
    }

    public Node getLeft() {
        return left;
    }

    public void setLeft(Node left) {
        this.left = left;
    }

    public Node getRight() {
        return right;
    }

    public void setRight(Node right) {
        this.right = right;
    }

    public Node() {
    }

    public Node(int value) {
        this.value = value;
    }
}
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,646評論 6 533
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,595評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,560評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,035評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,814評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,224評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,301評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,444評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,988評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,804評論 3 355
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,998評論 1 370
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,544評論 5 360
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,237評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,665評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,927評論 1 287
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,706評論 3 393
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,993評論 2 374

推薦閱讀更多精彩內容

  • 樹的概述 樹是一種非常常用的數據結構,樹與前面介紹的線性表,棧,隊列等線性結構不同,樹是一種非線性結構 1.樹的定...
    Jack921閱讀 4,473評論 1 31
  • 基于樹實現的數據結構,具有兩個核心特征: 邏輯結構:數據元素之間具有層次關系; 數據運算:操作方法具有Log級的平...
    yhthu閱讀 4,299評論 1 5
  • 周末看天氣不錯,臨時決定回趟老家,來一次說走就走的旅程。吃完早飯,碗都沒來得及洗就出發了。 雖說離家不遠,可回去的...
    李在在閱讀 264評論 1 1
  • 每一個人進入大學的時候,都是新鮮的個體,未來的四年都是一張白紙。這張白紙的第一個點該怎么畫,該畫在那兒, 是一門學...
    懶小姐xx閱讀 817評論 2 6