高級數據結構---二叉樹

樹是一種一對多的數據結構,之前的數組,棧這些都是一對一的數據結構。

樹是n個結點的有限集。n=0稱空樹。在任意一棵非空樹中:有且僅有一個根(root)結點;n>1時,其餘結點可分爲m個互不相交的的有限集,其中每個集合又是一棵樹,稱爲根的子樹。

 

前面三個都是樹,最後一個不是樹,因爲最後一個的數據相交了。

樹結構中的術語:

以下圖爲例

 

結點:樹上面的元素

父子關係:結點之間相連的邊

子樹:當結點大於1時,其餘的結點分爲的互不相交的集合稱爲子樹.

度:一個結點擁有的子樹數量稱爲結點的度(Aroot結點度是2B的度是2D的度是1)

葉子:度爲0的結點(H,E,F,G)

孩子:結點的子樹的根稱爲孩子結點

雙親:和孩子結點對應,一個孩子只有一個雙親(A是B的雙親)

兄弟:同一個雙親結點(B,C

森林:由個互不相交的樹構成深林

結點高度:結點到葉子結點的最長路徑(A的高度3B的高度2H的高度0

結點深度:根結點到該結點的邊個數(B結點深度1H結點深度3

結點的層數:結點的深度加1

樹的高度:根結點的高度

樹的深度:樹的高度+1

樹的表示方法:

雙親表示法:由孩子來記住雙親的位置

public class TreeNode<T> {
    private T data;
    private TreeNode<T> parent;
    
}

 

孩子表示法:由雙親來記住所有孩子的位置

public class TreeNode<T> {
    private T data;
    private TreeNode<T> leftChild;
    private TreeNode<T> rightChild;
}

 

二叉樹:

二叉樹是由一個根結點和兩棵互不相交的,分別稱爲根結點的左子樹和右子樹的二叉樹構成。二叉樹的每個最多有兩棵子樹。也就說二叉樹每個結點的度是不會超過2的。

比如上面的ABCDEFGH就是一棵二叉樹。二叉樹非常適合用於每個階段都存在兩種結果的情形的建模。比如開關,01,真假,上下,對錯,正反,選不選(前面動態規劃分析的那個圖)

特殊的二叉樹:

斜樹:所有結點都只有左子樹的叫做左斜樹,所有結點只有右子樹的叫做右斜樹

     

 滿二叉樹:所有結點都存在左右子樹,並且所有的葉子都在同一層

 

完全二叉樹:每一層的葉子結點都是相鄰的沒有間隔,滿二叉樹也是一種特殊的完全二叉樹

             

 

後面的就不是完全二叉樹,因爲EG不相鄰了。完全二叉樹可以基於數組存儲。前面也有說到數組可以基於下標查詢,而且它在內存中是連續,CPU有會預讀相鄰的區域的。

完全二叉樹怎麼用數組存儲呢?

雙親索引爲i,那麼它的左孩子就是2i,右孩子就是2i+1

比如上面這個完全二叉樹轉換成數組表示:

 

 

FC的左孩子:C3F6,剛好是2n

非完全二叉樹也用數組存儲的話,會導致很多內存浪費,分配了卻沒有使用。

 

二叉樹的遍歷

遍歷如下這個二叉樹:

 

先來用代碼來構建這棵二叉樹:

 

    /**
     * 構造二叉樹
     * @param dataList
     * @param <T>
     * @return
     */
    public static <T> TreeNode<T> createBinaryTree(LinkedList<T> dataList) {
        TreeNode<T> node = null;
        if (dataList == null || dataList.isEmpty()) {
            return null;
        }
        T data = dataList.removeFirst();
        if (data != null) {
            node = new TreeNode(data);
            node.setLeftChild((createBinaryTree(dataList)));
            node.setRightChild((createBinaryTree(dataList)));
        }
        return node;
    }

 

調用方式:

        LinkedList<String> inputList = new LinkedList<>(
                Arrays.asList(new String[]{"A","B","D","H",null,null,null,"E",null,null,"C","F",null,null,"G"}));
        TreeNode<String> treeNode = createBinaryTree(inputList);

調用裏面傳的部分null值,是因爲有些結點沒有孩子結點,比如H,根據寫的構造樹方法(createBinaryTree)的邏輯傳入合適的null值。

 

遍歷有關鍵點:不管哪種遍歷,按照其順序遇根輸出

前序遍歷:

按照根左右的順序,遇根輸出,再去找左

上面這棵樹的結果應該是ABDHECFG

因爲遍歷的時候 對於每個節點的操作都是一樣的,所以我們首先想到的就是使用遞歸

    /**
     * 前序遍歷 根 左子樹 右子樹
     * @param node
     */
    public static<T> void preOrderTraversal(TreeNode<T> node) {
        if(node == null){
            return;
        }
        //遇根先輸出,再去找左右
        System.out.print(node.getData());
        preOrderTraversal(node.getLeftChild());
        preOrderTraversal(node.getRightChild());
    }

 

也可以利用棧的特性來實現遞歸回溯的功能

    /**
     * 利用棧前序遍歷二叉樹
     * @param root
     */
    public static <T> void preOrderTraversalByStack(TreeNode<T> root) {
        Stack<TreeNode<T>> stack = new Stack<>();
        TreeNode<T> node = root;
        while(node != null || !stack.isEmpty()) {
            //節點不爲空,遍歷節點,併入棧用於回溯
            while(node != null) {
                System.out.print(node.getData());
                stack.push(node);
                node = node.getLeftChild();
            }
            //沒有左節點,彈出該棧頂節點(回溯),訪問右節點
            if(!stack.isEmpty()) {
                node = stack.pop();
                node = node.getRightChild();
            }
        }
    }

 

中序遍歷:左

HDBEAFCG

    /**
     * 二叉樹中序遍歷 左子樹 根 右子樹
     * @param node   二叉樹節點
     */
    public static<T> void inOrderTraversal(TreeNode<T> node){
        if(node == null){
            return;
        }
        //先找左再輸出根,再去找右
        inOrderTraversal(node.getLeftChild());
        System.out.print(node.getData());
        inOrderTraversal(node.getRightChild());
    }

 

後序遍歷:左

HDEBFGCA

    /**
     * 二叉樹後序遍歷  左子樹 右子樹 根
     * @param node   二叉樹節點
     */
    public static<T> void postOrderTraversal(TreeNode<T> node){
        if(node == null){
            return;
        }
        //先找左右,最後輸出根
        postOrderTraversal(node.getLeftChild());
        postOrderTraversal(node.getRightChild());
        System.out.print(node.getData());
    }

 

層次遍歷:按層來遍歷

ABCDEFGH

層次遍歷的時候,利用隊列的特性,將所有的孩子依次入隊進行操作。

 

    public static <T> void levelOrder(TreeNode<T> root) {
        if (root == null) {
            return;
        }
        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(root);  //入隊
        while (!queue.isEmpty()) {
            TreeNode<T> node = queue.poll(); //取出
            if (node != null) {
                System.out.print(node.getData());
                queue.offer(node.getLeftChild());   //左孩子入隊
                queue.offer(node.getRightChild());  //右孩子入隊
            }
        }
    }

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章