JAVA 數據結構與算法(五)—— 樹結構之二叉樹

一、樹結構

1、樹結構概述

(1)簡介

  • 樹是一種重要的非線性數據結構,它是數據元素(在樹中稱爲結點)按分支關係組織起來的結構,很象自然界中的樹那樣。
  • 一棵樹(tree)是由n(n>0)個元素組成的有限集合,其中:
    • 每個元素稱爲結點(node);
    • 有一個特定的結點,稱爲根結點或根(root);
    • 除根結點外,其餘結點被分成m(m>=0)個互不相交的有限集合,而每個子集又都是一棵樹(稱爲原樹的子樹)。
      在這裏插入圖片描述

(2)相關概念

  • 結點的度:結點擁有的子樹的數目。如上圖:結點 A 的度爲3。
  • 樹的度:樹種各結點度的最大值。如上圖:樹的度爲3。
  • 葉子結點:沒有子結點的結點。如上圖:F、I、J、K、L、M爲葉子結點。
  • 孩子結點:一個結點的子樹的根結點。如上圖:B、C、D 爲 A 的孩子結點。
  • 雙親結點:B 爲 A 的子結點,那麼 A 爲 B 的雙親結點。
  • 兄弟結點:一個雙親結點的孩子結點互爲兄弟結點。如上圖:B、C、D 爲兄弟結點。
  • 結點的層次:根結點爲第一層,子結點爲第二層,依次向下遞推…。如上圖:E、F、G、H的層次均爲 3。
  • 樹的深度:樹種結點的最大深度。如上圖:該樹的深度爲 4。
  • 路徑:從根結點找到該結點的路線,如K結點的路徑爲A-C-G-K。
  • 森林:指若干棵互不相交的樹的集合。

(3)樹的遍歷

  • 樹的遍歷是樹的一種重要的運算。所謂遍歷是指對樹中所有結點的系統的訪問,即依次對樹中每個結點訪問一次且僅訪問一次。樹的3種最重要的遍歷方式分別稱爲前序遍歷、中序遍歷和後序遍歷。以這3種方式遍歷一棵樹時,若按訪問結點的先後次序將結點排列起來,就可分別得到樹中所有結點的前序列表,中序列表和後序列表。相應的結點次序分別稱爲結點的前序、中序和後序。
  • 樹的這3種遍歷方式可遞歸地定義如下:
    • 如果T是一棵空樹,那麼對T進行前序遍歷、中序遍歷和後序遍歷都是空操作,得到的列表爲空表。
    • 如果T是一棵單結點樹,那麼對T進行前序遍歷、中序遍歷和後序遍歷都只訪問這個結點。這個結點本身就是要得到的相應列表。
    • 否則,它以n爲樹根,樹根的子樹從左到右依次爲T1,T2,…,Tk,那麼有:
      對T進行前序遍歷是先訪問樹根n,然後依次前序遍歷T1,T2,…,Tk。
      對T進行中序遍歷是先中序遍歷T1,然後訪問樹根n,接着依次對T2,T2,…,Tk進行中序遍歷。
      對T進行後序遍歷是先依次對T1,T2,…,Tk進行後序遍歷,最後訪問樹根n。

(4)樹結構的性質

  • 非空樹的結點總數等於樹種所有結點的度之和加 1。
  • 度爲 K 的非空樹的第 i 層最多有 ki-1 個結點(i >= 1)。
  • 深度爲 h 的 k 叉樹最多有(kh - 1)/(k - 1)個結點。
  • 具有 n 個結點的 k 叉樹的最小深度爲 logk(n(k-1)+1))。

2、樹結構的分類

在這裏插入圖片描述

3、存儲方式分析

(1)數組存儲方式

  • 優點:通過下標方式訪問元素,速度快,對於有序數組,還可使用二分查找提高檢索速度。
  • 缺點:如果要檢索具體某個值,或者插入值(按一定順序)會整體移動, 效率較低。

(2)鏈式存儲方式

  • 優點:在一定程度上對數組存儲方式有優化(比如插入一個數值結點,只需要將插入結點,鏈接到鏈表中即可,刪除效率也很好)。
  • 缺點:在進行檢索時,效率仍然較低,比如(檢索某個值,需要從頭結點開始遍歷)。

(3)樹存儲方式

  • 能提高數據存儲,讀取的效率,比如利用 二叉排序樹(Binary Sort Tree),既可以保證數據的檢索速度,同時也可以保證數據的插入,刪除,修改的速度。

二、二叉樹

1、二叉樹概述

(1)二叉樹介紹

  • 樹有很多種,每個結點最多只能有兩個子結點的一種形式稱爲二叉樹。
  • 二叉樹的子結點分爲左結點和右結點。
    在這裏插入圖片描述

(2)滿二叉樹

  • 如果該二叉樹的所有葉子結點都在最後一層,並且結點總數=2^n-1,n爲層數,則我們稱爲滿二叉樹。
    在這裏插入圖片描述

(3)完全二叉樹

  • 如果該二叉樹的所有葉子結點都在最後一層或者倒數第二層,而且最後一層的葉子結點在左邊連續,倒數第二層的葉子結點在右邊連續,我們稱爲完全二叉樹。
    在這裏插入圖片描述

2、二叉樹的遍歷

(1)二叉樹的遍歷介紹
二叉樹的遍歷有三種:前序遍歷、中序遍歷、後序遍歷。

  • 前序遍歷
    前序遍歷是先輸父結點,再遍歷左子樹和右子樹。
  • 中序遍歷
    中序遍歷先遍歷左子樹,再輸出父結點,再遍歷右子樹。
  • 後序遍歷
    後序遍歷先遍歷左子樹,再遍歷右子樹,最後輸出父結點。

分析:看輸出父結點的順序,就確定是前序,中序還是後序。

(2)二叉樹的遍歷分析
以下面的二叉樹爲例,進行遍歷分析:
在這裏插入圖片描述

  • 前序遍歷
    • 先輸出當前結點(初始的時候是root結點)
    • 如果左子結點不爲空,則遞歸繼續前序遍歷
    • 如果右子結點不爲空,則遞歸繼續前序遍歷
    • 上述結果應爲:A-B-D-G-E-C-F-H
  • 中序遍歷
    • 如果當前結點的左子結點不爲空,則遞歸中序遍歷,
    • 輸出當前結點
    • 如果當前結點的右子結點不爲空,則遞歸中序遍歷
    • 上述結果應爲:G-D-B-E-A-C-H-F
  • 後序遍歷
    • 如果當前結點的左子結點不爲空,則遞歸後序遍歷,
    • 如果當前結點的右子結點不爲空,則遞歸後序遍歷
    • 輸出當前結點
    • 上述結果應爲:G-D-E-B-H-F-C-A

(3)二叉樹的遍歷代碼示例
創建一個二叉樹結點類

/*結點*/
public class TreeNode {
    /*結點編號*/
    private int nodeNum;
    /*結點名稱*/
    private String nodeName;
    /*左右結點,默認爲空*/
    private TreeNode leftNode;
    private TreeNode rightNode;

    /*構造器*/
    public TreeNode(int nodeNum, String nodeName) {
        this.nodeNum = nodeNum;
        this.nodeName = nodeName;
    }

    /*getter、setter方法省略*/

    /*toString方法*/
    @Override
    public String toString() {
        return "TreeNode{" +
                "nodeNum=" + nodeNum +
                ", nodeName='" + nodeName + '\'' +
                '}';
    }

    /*前序遍歷*/
    public void firstShow(){
        /*輸出父結點*/
        System.out.println(this);

        /*遞歸向左子樹前序遍歷*/
        if(this.leftNode != null){
            this.leftNode.firstShow();
        }

        /*遞歸向右子樹前序遍歷*/
        if(this.rightNode != null){
            this.rightNode.firstShow();
        }
    }

    /*中序遍歷*/
    public void midShow(){
        /*遞歸向左子樹前序遍歷*/
        if(this.leftNode != null){
            this.leftNode.midShow();
        }

        /*輸出父結點*/
        System.out.println(this);

        /*遞歸向右子樹前序遍歷*/
        if(this.rightNode != null){
            this.rightNode.midShow();
        }
    }

    /*後序遍歷*/
    public void lastShow(){
        /*遞歸向左子樹前序遍歷*/
        if(this.leftNode != null){
            this.leftNode.lastShow();
        }

        /*遞歸向右子樹前序遍歷*/
        if(this.rightNode != null){
            this.rightNode.lastShow();
        }

        /*輸出父結點*/
        System.out.println(this);
    }
}

創建一個二叉樹

/*二叉樹*/
public class BinaryTree {
    private TreeNode rootNode;

    public void setRootNode(TreeNode rootNode) {
        this.rootNode = rootNode;
    }

    /*前序遍歷*/
    public void first(){
        if(this.rootNode != null){
            this.rootNode.firstShow();
        }else{
            System.out.println("二叉樹爲空");
        }
    }

    /*中序遍歷*/
    public void mid(){
        if(this.rootNode != null){
            this.rootNode.midShow();
        }else{
            System.out.println("二叉樹爲空");
        }
    }

    /*後序遍歷*/
    public void last(){
        if(this.rootNode != null){
            this.rootNode.lastShow();
        }else{
            System.out.println("二叉樹爲空");
        }
    }
}

測試代碼

public class BinaryTreeTest {
    public static void main(String[] args) {
        /*創建二叉樹*/
        BinaryTree binaryTree = new BinaryTree();

        /*創建樹結點*/
        TreeNode nodeA = new TreeNode(1, "A");
        TreeNode nodeB = new TreeNode(2, "B");
        TreeNode nodeC = new TreeNode(3, "C");
        TreeNode nodeD = new TreeNode(4, "D");
        TreeNode nodeE = new TreeNode(5, "E");
        TreeNode nodeF = new TreeNode(6, "F");
        TreeNode nodeG = new TreeNode(7, "G");
        TreeNode nodeH = new TreeNode(8, "H");

        /*將結點掛載到root結點上*/
        nodeA.setLeftNode(nodeB);
        nodeA.setRightNode(nodeC);
        nodeB.setLeftNode(nodeD);
        nodeB.setRightNode(nodeE);
        nodeC.setRightNode(nodeF);
        nodeD.setLeftNode(nodeG);
        nodeF.setLeftNode(nodeH);

        /*將root結點掛載到樹上*/
        binaryTree.setRootNode(nodeA);

        System.out.println("前序遍歷結果爲:");
        binaryTree.first();
        System.out.println("\n中序遍歷結果爲:");
        binaryTree.mid();
        System.out.println("\n後序遍歷結果爲:");
        binaryTree.last();
    }
}

輸出結果

前序遍歷結果爲:
TreeNode{nodeNum=1, nodeName='A'}
TreeNode{nodeNum=2, nodeName='B'}
TreeNode{nodeNum=4, nodeName='D'}
TreeNode{nodeNum=7, nodeName='G'}
TreeNode{nodeNum=5, nodeName='E'}
TreeNode{nodeNum=3, nodeName='C'}
TreeNode{nodeNum=6, nodeName='F'}
TreeNode{nodeNum=8, nodeName='H'}

中序遍歷結果爲:
TreeNode{nodeNum=7, nodeName='G'}
TreeNode{nodeNum=4, nodeName='D'}
TreeNode{nodeNum=2, nodeName='B'}
TreeNode{nodeNum=5, nodeName='E'}
TreeNode{nodeNum=1, nodeName='A'}
TreeNode{nodeNum=3, nodeName='C'}
TreeNode{nodeNum=8, nodeName='H'}
TreeNode{nodeNum=6, nodeName='F'}

後序遍歷結果爲:
TreeNode{nodeNum=7, nodeName='G'}
TreeNode{nodeNum=4, nodeName='D'}
TreeNode{nodeNum=5, nodeName='E'}
TreeNode{nodeNum=2, nodeName='B'}
TreeNode{nodeNum=8, nodeName='H'}
TreeNode{nodeNum=6, nodeName='F'}
TreeNode{nodeNum=3, nodeName='C'}
TreeNode{nodeNum=1, nodeName='A'}

3、二叉樹的查找

(1)介紹
二叉樹的查找分爲前序查找、中序查找、後序查找。

(2)二叉樹的查找分析
同樣以這個二叉樹爲例進行分析:
在這裏插入圖片描述

  • 前序查找
    • 先判斷當前結點是否是要查找的結點,如果是,則返回當前結點
    • 如果不是,則判斷當前結點的左子結點是否爲空,如果不爲空則繼續遞歸前序查找
    • 如果左遞歸前序查找,找到結點,則返回
    • 如果沒有找到,則繼續查找,判斷當前的結點的右子結點是否爲空,如果不空,則繼續向右遞歸前序查找
  • 中序查找
    • 先判斷當前結點的左子結點是否爲空,如果不爲空則繼續遞歸中序查找
    • 如果在左子結點中沒有找到,就和當前結點比較是否爲要查找的結點
    • 如果不是,則繼續在當前結點向右遞歸進行中序查找
  • 後序查找
    • 先判斷當前結點的左子結點是否爲空,如果不爲空則繼續遞歸後序查找
    • 如果沒有找到,就判斷當前結點的右子結點是否爲空,不爲空則進行當前結點的右遞歸後序查找
    • 如果沒有找,則比較當前結點是否爲要查找的結點

(3)二叉樹的查找代碼示例
創建一個結點類,表示樹的結點

/*結點*/
public class TreeNode {
    /*結點編號*/
    private int nodeNum;
    /*結點名稱*/
    private String nodeName;
    /*左右結點,默認爲空*/
    private TreeNode leftNode;
    private TreeNode rightNode;

    /*構造器*/
    public TreeNode(int nodeNum, String nodeName) {
        this.nodeNum = nodeNum;
        this.nodeName = nodeName;
    }

    /*getter、setter方法省略*/

    /*toString方法*/
    @Override
    public String toString() {
        return "TreeNode{" +
                "nodeNum=" + nodeNum +
                ", nodeName='" + nodeName + '\'' +
                '}';
    }

    /*前序查找*/
    public TreeNode firstSearch(int num){
        TreeNode treeNode = null;

        /*比較當前結點*/
        if(this.nodeNum == num){
            return this;
        }

        /*左遞歸前序查找*/
        if (this.leftNode != null){
            treeNode = this.leftNode.firstSearch(num);
        }
        if(treeNode != null){
            return treeNode;
        }

        /*右遞歸前序查找*/
        if (this.rightNode != null){
            treeNode = this.rightNode.firstSearch(num);
        }
        if(treeNode != null){
            return treeNode;
        }
        return treeNode;
    }

    /*中序查找*/
    public TreeNode midSearch(int num){
        TreeNode treeNode = null;

        /*先左遞歸中序查找*/
        if(this.leftNode != null){
            treeNode = this.leftNode.midSearch(num);
        }
        if (treeNode != null){
            return treeNode;
        }

        /*與當前結點比較*/
        if(this.nodeNum == num){
            return this;
        }

        /*向右遞歸中序查找*/
        if (this.rightNode != null){
            treeNode = this.rightNode.midSearch(num);
        }
        return treeNode;
    }

    /*後序查找*/
    public TreeNode lastSearch(int num){
        TreeNode treeNode = null;

        /*先左遞歸後序查找*/
        if(this.leftNode != null){
            treeNode = this.leftNode.lastSearch(num);
        }
        if (treeNode != null){
            return treeNode;
        }

        /*向右遞歸後序查找*/
        if (this.rightNode != null){
            treeNode = this.rightNode.lastSearch(num);
        }
        if (treeNode != null){
            return treeNode;
        }

        /*與當前結點比較*/
        if(this.nodeNum == num){
            return this;
        }
        return treeNode;
    }
}

創建一個二叉樹用於掛載結點

在這裏插入代碼片

測試代碼

public class BinaryTreeTest {
    public static void main(String[] args) {
        /*創建二叉樹*/
        BinaryTree binaryTree = new BinaryTree();

        /*創建樹結點*/
        TreeNode nodeA = new TreeNode(1, "A");
        TreeNode nodeB = new TreeNode(2, "B");
        TreeNode nodeC = new TreeNode(3, "C");
        TreeNode nodeD = new TreeNode(4, "D");
        TreeNode nodeE = new TreeNode(5, "E");
        TreeNode nodeF = new TreeNode(6, "F");
        TreeNode nodeG = new TreeNode(7, "G");
        TreeNode nodeH = new TreeNode(8, "H");

        /*將結點掛載到root結點上*/
        nodeA.setLeftNode(nodeB);
        nodeA.setRightNode(nodeC);
        nodeB.setLeftNode(nodeD);
        nodeB.setRightNode(nodeE);
        nodeC.setRightNode(nodeF);
        nodeD.setLeftNode(nodeG);
        nodeF.setLeftNode(nodeH);

        /*將root結點掛載到樹上*/
        binaryTree.setRootNode(nodeA);

        TreeNode firstNode = binaryTree.first(1);
        if(firstNode != null){
            System.out.println("前序查找num = 1的結果爲:num = " + firstNode.getNodeNum() + "  name = " + firstNode.getNodeName());
        }else {
            System.out.println("前序查找沒有找到");
        }

        TreeNode midNode = binaryTree.mid(4);
        if(midNode != null){
            System.out.println("中序查找num = 4的結果爲:num = " + midNode.getNodeNum() + "  name = " + midNode.getNodeName());
        }else {
            System.out.println("中序查找沒有找到");
        }

        TreeNode lastNode = binaryTree.last(7);
        if(lastNode != null){
            System.out.println("後序查找num = 7的結果爲:num = " + lastNode.getNodeNum() + "  name = " + lastNode.getNodeName());
        }else {
            System.out.println("後序查找沒有找到");
        }
    }
}

輸出結果

前序查找num = 1的結果爲:num = 1  name = A
中序查找num = 4的結果爲:num = 4  name = D
後序查找num = 7的結果爲:num = 7  name = G

4、二叉樹刪除結點

(1)刪除結點規則

  • 如果刪除的結點是葉子結點,則刪除該結點。
  • 如果刪除的結點是非葉子結點,則刪除該子樹。

(2)刪除結點分析
仍舊以下面的二叉樹爲例,進行分析:
在這裏插入圖片描述

  • 首先判斷該樹是否爲空樹,如果至於一個root結點,則等價於將二叉樹置空
  • 因爲二叉樹是單向的,所以在刪除是需要先判斷當前結點的子結點是否需要刪除結點,而不是判斷當前這個結點是不是需要刪除的結點。
  • 如果當前結點的左子樹不爲空並且左子結點左子結點就是要刪除的結點,就將this.leftNode置爲空並返回
  • 如果當前結點的右子樹不爲空並且右子結點左子結點就是要刪除的結點,就將this.rightNode置爲空並返回
  • 如果上述步驟都沒有刪除結點,那麼就需要繼續向左子樹進行遞歸刪除,如果仍沒有刪除則向右子樹遞歸

(3)刪除結點代碼示例
創建一個結點類,表示樹的結點

/*結點*/
public class TreeNode {
    /*結點編號*/
    private int nodeNum;
    /*結點名稱*/
    private String nodeName;
    /*左右結點,默認爲空*/
    private TreeNode leftNode;
    private TreeNode rightNode;

    /*構造器*/
    public TreeNode(int nodeNum, String nodeName) {
        this.nodeNum = nodeNum;
        this.nodeName = nodeName;
    }

    public int getNodeNum() {
        return nodeNum;
    }

    public void setNodeNum(int nodeNum) {
        this.nodeNum = nodeNum;
    }

    public String getNodeName() {
        return nodeName;
    }

    public void setNodeName(String nodeName) {
        this.nodeName = nodeName;
    }

    public TreeNode getLeftNode() {
        return leftNode;
    }

    public void setLeftNode(TreeNode leftNode) {
        this.leftNode = leftNode;
    }

    public TreeNode getRightNode() {
        return rightNode;
    }

    public void setRightNode(TreeNode rightNode) {
        this.rightNode = rightNode;
    }

    /*toString方法*/

    @Override
    public String toString() {
        return "TreeNode{" +
                "nodeNum=" + nodeNum +
                ", nodeName='" + nodeName + '\'' +
                '}';
    }

    /*刪除結點*/
    public void delNode(int num){
        /*先判斷左子結點*/
        if(this.leftNode != null && this.leftNode.nodeNum == num){
            this.leftNode = null;
            return;
        }

        /*判斷右子結點*/
        if(this.rightNode != null && this.rightNode.nodeNum == num){
            this.rightNode = null;
            return;
        }

        /*向左子樹遞歸刪除*/
        if(this.leftNode != null){
            this.leftNode.delNode(num);
        }

        /*向右子樹遞歸刪除*/
        if(this.rightNode != null){
            this.rightNode.delNode(num);
        }
    }

    /*前序遍歷*/
    public void firstShow(){
        /*輸出父結點*/
        System.out.println(this);

        /*遞歸向左子樹前序遍歷*/
        if(this.leftNode != null){
            this.leftNode.firstShow();
        }

        /*遞歸向右子樹前序遍歷*/
        if(this.rightNode != null){
            this.rightNode.firstShow();
        }
    }
}

創建一個二叉樹用於掛載結點

/*二叉樹*/
public class BinaryTree {
    private TreeNode rootNode;

    public void setRootNode(TreeNode rootNode) {
        this.rootNode = rootNode;
    }

    /*刪除結點*/
    public void delete(int num){
        if(rootNode != null){
            /*判斷root結點*/
            if(rootNode.getNodeNum() == num){
                rootNode = null;
            }else{
                /*遞歸刪除*/
                rootNode.delNode(num);
            }
        }else{
            System.out.println("該樹爲空");
        }
    }

    /*前序遍歷*/
    public void first(){
        if(this.rootNode != null){
            this.rootNode.firstShow();
        }else{
            System.out.println("二叉樹爲空");
        }
    }
}

測試代碼

public class BinaryTreeTest {
    public static void main(String[] args) {
        /*創建二叉樹*/
        BinaryTree binaryTree = new BinaryTree();

        /*創建樹結點*/
        TreeNode nodeA = new TreeNode(1, "A");
        TreeNode nodeB = new TreeNode(2, "B");
        TreeNode nodeC = new TreeNode(3, "C");
        TreeNode nodeD = new TreeNode(4, "D");
        TreeNode nodeE = new TreeNode(5, "E");
        TreeNode nodeF = new TreeNode(6, "F");
        TreeNode nodeG = new TreeNode(7, "G");
        TreeNode nodeH = new TreeNode(8, "H");

        /*將結點掛載到root結點上*/
        nodeA.setLeftNode(nodeB);
        nodeA.setRightNode(nodeC);
        nodeB.setLeftNode(nodeD);
        nodeB.setRightNode(nodeE);
        nodeC.setRightNode(nodeF);
        nodeD.setLeftNode(nodeG);
        nodeF.setLeftNode(nodeH);

        /*將root結點掛載到樹上*/
        binaryTree.setRootNode(nodeA);

        System.out.println("刪除前:");
        binaryTree.first();
        binaryTree.delete(2);
        System.out.println("刪除後:");
        binaryTree.first();
    }
}

輸出結果

刪除前:
TreeNode{nodeNum=1, nodeName='A'}
TreeNode{nodeNum=2, nodeName='B'}
TreeNode{nodeNum=4, nodeName='D'}
TreeNode{nodeNum=7, nodeName='G'}
TreeNode{nodeNum=5, nodeName='E'}
TreeNode{nodeNum=3, nodeName='C'}
TreeNode{nodeNum=6, nodeName='F'}
TreeNode{nodeNum=8, nodeName='H'}
刪除後:
TreeNode{nodeNum=1, nodeName='A'}
TreeNode{nodeNum=3, nodeName='C'}
TreeNode{nodeNum=6, nodeName='F'}
TreeNode{nodeNum=8, nodeName='H'}

5、順序存儲二叉樹

(1)介紹

  • 從數據存儲來看,數組存儲方式和樹的存儲方式可以相互轉換,即數組可以轉換成樹,樹也可以轉換成數組。二叉樹以數組的形式存儲,但仍舊保存着二叉樹的結構,成爲順序存儲二叉樹。
    在這裏插入圖片描述
  • 順序存儲二叉樹的特點:
    • 順序二叉樹通常只考慮完全二叉樹
    • 第n個元素的左子結點爲2*n+1,如B結點爲數組中的第1個元素,B的左子結點爲2*1+1=3,即數組中的第三個元素D
    • 第n個元素的右子結點爲2*n+2,如B結點爲數組中的第1個元素,B的右子結點爲2*1+2=4,即數組中的第四個元素E
    • 第n個元素的父結點爲(n-1)/ 2,如B結點爲數組中的第1個元素,B的父結點爲(1-1)/2=0,即數組中的第0個元素A

(2)代碼示例
創建一個類,用於前序遍歷順序存儲二叉樹的數組

public class ArrayBinaryTree {
    /*存儲數據結點的數組*/
    private String[] arr;

    /*構造器*/
    public ArrayBinaryTree(String[] arr) {
        this.arr = arr;
    }

    /*前序遍歷*/
    public void firstShow(int index){
        /*如果數組爲空或者數組長度爲0*/
        if(arr == null || arr.length == 0){
            System.out.println("數組爲空");
        }
        /*數組不爲空則就行遍歷*/
        /*輸出當前元素*/
        System.out.print(arr[index] + " ");
        /*向左遞歸輸出*/
        if((2 * index + 1) < arr.length){
            firstShow(2 * index + 1);
        }
        /*向右遞歸*/
        if((2 * index + 2) < arr.length){
            firstShow(2 * index + 2);
        }
    }
}

測試類代碼,定義一個數組

public class ArrayBinaryTreeTest {
    public static void main(String[] args) {
        String[] arr = {"A","B","C","D","E","F","G"};

        ArrayBinaryTree arrayBinaryTree = new ArrayBinaryTree(arr);
        System.out.println("前序遍歷結果:");
        arrayBinaryTree.firstShow(0);
    }
}

輸出結果

前序遍歷結果:
A B D E C F G 

6、線索二叉樹

(1)介紹

  • n個結點的二叉鏈表中含有2n-(n-1)=n+1個空指針域,利用二叉鏈表中的空指針域,存放指向該結點在某種遍歷次序下的前驅和後繼結點的指針(這種附加的指針稱爲"線索")。
    • 如下圖,這個二叉樹的空指針域爲6+1=7個,即箭頭所代表的位置都爲空指針域。
      在這裏插入圖片描述
  • 這種加上了線索的二叉鏈表稱爲線索鏈表,相應的二叉樹稱爲線索二叉樹(Threaded BinaryTree)。根據線索性質的不同,線索二叉樹可分爲前序線索二叉樹、中序線索二叉樹和後序線索二叉樹三種。
  • 一個結點的前一個結點,稱爲前驅結點。
  • 一個結點的後一個結點,稱爲後繼結點。

(2)線索二叉樹的遍歷

  • 因爲線索化後,各個結點指向有變化,因此原來的遍歷方式不能使用,這時需要使用新的方式遍歷線索化二叉樹,各個結點可以通過線型方式遍歷,因此無需使用遞歸方式,這樣也提高了遍歷的效率。

(3)代碼示例
以中序線索二叉樹爲例進行示例
創建一個結點類,表示樹的結點

/*結點*/
public class TreeNode {
    /*結點編號*/
    private int nodeNum;
    /*結點名稱*/
    private String nodeName;
    /*左右結點,默認爲空*/
    private TreeNode leftNode;
    private TreeNode rightNode;

    /*如果leftType=0,表示指向左子樹,leftType=1表示指向前驅結點*/
    private int leftType;
    /*如果rightType=0,表示指向右子樹,rightType=1表示指向後驅結點*/
    private int rightType;
    
    /*構造器*/
    public TreeNode(int nodeNum, String nodeName) {
        this.nodeNum = nodeNum;
        this.nodeName = nodeName;
    }

   	 /*getter、settet方法省略*/

    /*toString方法*/

    @Override
    public String toString() {
        return "TreeNode{" +
                "nodeNum=" + nodeNum +
                ", nodeName='" + nodeName + '\'' +
                '}';
    }
}

創建一個二叉樹,用於掛在樹結點

/*二叉樹*/
public class ThreadedBinaryTree {
    private TreeNode rootNode;

    /*前一個結點*/
    private TreeNode prevNode = null;

    public TreeNode getRootNode() {
        return rootNode;
    }

    public void setRootNode(TreeNode rootNode) {
        this.rootNode = rootNode;
    }

    /*對二叉樹進行中序線索化*/
    public void threadedNodes(TreeNode treeNode){
        /*判斷要線索化的結點是否爲空*/
        if(treeNode == null){
            return;
        }

        /*線索化左子樹*/
        threadedNodes(treeNode.getLeftNode());

        /*線索化當前結點*/
        /*處理當前結點的前驅結點*/
        if(treeNode.getLeftNode() == null){
            /*讓當前結點的做指針指向前驅結點*/
            treeNode.setLeftNode(prevNode);
            /*修改當前結點的左指針的類型,指向前驅結點*/
            treeNode.setLeftType(1);
        }
        /*當前結點的處理後繼結點*/
        if(prevNode != null && prevNode.getRightNode() == null){
            /*讓前驅結點的右指針指向當前結點*/
            prevNode.setRightNode(treeNode);
            /*修改當前結點的右指針的類型*/
            prevNode.setRightType(1);
        }
        /*每處理一個結點後,讓當前結點是下一個結點的前驅結點*/
        prevNode = treeNode;

        /*線索化右子樹*/
        threadedNodes(treeNode.getRightNode());
    }

    /*遍歷線索二叉樹*/
    public void ThreadedsShow(){
        /*定義一個變量存儲當前遍歷的結點,從root開始*/
        TreeNode treeNode = rootNode;
        while(treeNode != null){
            /*循環找到leftTpye==1的結點*/
            while(treeNode.getLeftType() == 0){
                treeNode = treeNode.getLeftNode();
            }
            System.out.println(treeNode);

            /*如果當前結點的右指針是指向後繼結點就一直輸出*/
            while(treeNode.getRightType() == 1){
                treeNode = treeNode.getRightNode();
                System.out.println(treeNode);
            }
            /*替換遍歷的結點*/
            treeNode = treeNode.getRightNode();
        }
    }
}

測試代碼

public class ThreadedBinaryTreeTest {
    public static void main(String[] args) {
        /*創建二叉樹*/
        TreeNode nodeA = new TreeNode(1, "A");
        TreeNode nodeB = new TreeNode(2, "B");
        TreeNode nodeC = new TreeNode(3, "C");
        TreeNode nodeD = new TreeNode(4, "D");
        TreeNode nodeE = new TreeNode(5, "E");
        TreeNode nodeF = new TreeNode(6, "F");

        /*結點掛載*/
        nodeA.setLeftNode(nodeB);
        nodeA.setRightNode(nodeC);
        nodeB.setLeftNode(nodeD);
        nodeB.setRightNode(nodeE);
        nodeC.setLeftNode(nodeF);

        ThreadedBinaryTree threadedBinaryTree = new ThreadedBinaryTree();
        threadedBinaryTree.setRootNode(nodeA);
        threadedBinaryTree.threadedNodes(threadedBinaryTree.getRootNode());

        TreeNode leftNode = nodeE.getLeftNode();
        System.out.println("nodeE的前驅結點爲" + leftNode);
        TreeNode rightNode = nodeE.getRightNode();
        System.out.println("nodeE的後繼結點爲" + rightNode);

        System.out.println("\n遍歷線索二叉樹:");
        threadedBinaryTree.ThreadedsShow();
    }
}

輸出結果

nodeE的前驅結點爲TreeNode{nodeNum=2, nodeName='B'}
nodeE的後繼結點爲TreeNode{nodeNum=1, nodeName='A'}

遍歷線索二叉樹:
TreeNode{nodeNum=4, nodeName='D'}
TreeNode{nodeNum=2, nodeName='B'}
TreeNode{nodeNum=5, nodeName='E'}
TreeNode{nodeNum=1, nodeName='A'}
TreeNode{nodeNum=6, nodeName='F'}
TreeNode{nodeNum=3, nodeName='C'}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章