數據結構:圖文詳解二叉樹(遍歷、類型、操作)

前言

  • 二叉樹是一種特殊的樹結構,應用廣泛
  • 下面,我將詳細介紹 二叉樹的相關知識,希望你們會喜歡。

目錄

示意圖


1. 簡介

示意圖


2. 性質

示意圖


3. 存儲結構

二叉樹的存儲結構包括:順序存儲結構 & 鏈式存儲結構

示意圖

注:上述的鏈式存儲方式,即爲樹結構中的孩子兄弟表示法。具體如下:

示意圖

大多數情況下,二叉樹的建立會採用 鏈式存儲結構


4. 二叉樹的建立

建立的核心:
數據結構 = 鏈表 、實現方式 = 遞歸 / 非遞歸 算法

4.1 數據結構

採用鏈表的方式,也稱爲:二叉鏈表

  1. 爲了確保每個結點都有左右孩子,所以空指針 = 虛結點 = #
  2. 這種處理也稱:擴展二叉樹

示意圖

  • 節點結構 & 樹的定義如下
   /**
     * 設置結點結構
     */
    public static class TreeNode<T> {
        T val; // 二叉樹的結點數據
        TreeNode<T> leftNode; // 二叉樹的左子樹(左孩子)
        TreeNode<T> rightNode; // 二叉樹的右子樹(右孩子)

        public TreeNode(T data,TreeNode<T> left,TreeNode<T> right) {
            this.val = data;
            this.leftNode = left;
            this.rightNode = right;
        }


        // 獲得 & 設置二叉樹的結點數據
        public T getData(){
            return val;
        }

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

        // 獲得 & 設置二叉樹的左子樹(左孩子)
        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;
        }
    }


/**
 * 作用:構造二叉樹
 * 注:必須逆序建立,即:先建立子節點,再逆序往上建立
 * 原因:非葉子節點會使用到下面的節點,而初始化是按順序初始化的,不逆序建立會報錯
 */ 
public Node init(){
    // 結構如下:(由下往上建立)
    //            A
    //       B         C
    //    D         E     F
    //  G   H         I
    Node I = new Node("I", null, null);
    Node H = new Node("H", null, null);
    Node G = new Node("G", null, null);
    Node F = new Node("F", null, null);
    Node E = new Node("E", null, I);
    Node D = new Node("D", G, H);
    Node C = new Node("C", E, F);
    Node B = new Node("B", D, null);
    Node A = new Node("A", B, C);
    return A;  // 返回根節點
}

4.2 遞歸 算法

  • 通過 遞歸方式 構造出整個二叉樹
  • 構造過程 = 將遍歷算法的輸出結點操作 替換成: 生成結點 & 賦值操作 即可

關於遍歷算法,下節會詳細說明


5. 二叉樹的遍歷

5.1 定義

從根節點出發,按照某種次序訪問二叉樹中的所有結點,使得每個結點被訪問1次 且 只被訪問1次

5.2 遍歷方式

二叉樹的遍歷方式包括:

  1. 前序遍歷(深度優先遍歷)
  2. 中序遍歷
  3. 後序遍歷
  4. 層序遍歷(廣度優先遍歷)

5.3 遍歷實現

遍歷的實現方式分爲:遞歸 & 非遞歸方式,下面會詳細說明

5.3.1 前序遍歷

也稱 深度優先遍歷

  • 簡介

示意圖

  • 遞歸實現
   /**
     * 內容:前序遍歷
     * 方式:遞歸
     */
     public void preOrder(Node root){
        // 1. 判斷二叉樹結點是否爲空;若是,則返回空操作
        if(root ==null)
            return;

        // 2. 訪問根節點(顯示根結點)
        printNode(root);

        // 3. 遍歷左子樹
        preOrder(root.getLeftNode());

        // 4. 遍歷右子樹
        preOrder(root.getRightNode());

    }

示意圖

  • 非遞歸實現
    主要採用 棧實現
    流程圖
/**
  * 方式:非遞歸(棧實現)
  */
    public static void preOrder_stack(Node root){

        Stack<Node> stack = new Stack<Node>();

        // 步驟1:直到當前結點爲空 & 棧空時,循環結束
        while(root != null || stack.size()>0){

            // 步驟2:判斷當前結點是否爲空
              // a. 若不爲空,執行3
              // b. 若爲空,執行5
              if(root != null){

                // 步驟3:輸出當前節點,並將其入棧
                printNode(root);
                stack.push(root);

                // 步驟4:置當前結點的左孩子爲當前節點
                // 返回步驟1
                root = root.getLeftNode();

            }else{

                // 步驟5:出棧棧頂結點
                root = stack.pop();
                // 步驟6:置當前結點的右孩子爲當前節點
                root = root.getRightNode();
                  // 返回步驟1
            }
        }
    }

示意圖

5.3.2 中序遍歷

  • 簡介

示意圖

  • 遞歸實現
/**
  * 方式:遞歸
  */
    public void InOrder(Node root){
    
        // 1. 判斷二叉樹結點是否爲空;若是,則返回空操作
        if(root ==null)
            return;

        // 2. 遍歷左子樹
        InOrder(root.getLeftNode());

        // 3. 訪問根節點(顯示根結點)
        printNode(root);

        // 4. 遍歷右子樹
        InOrder(root.getRightNode());

    }

示意圖

  • 非遞歸實現
    主要採用 棧實現

流程圖

/**
  * 方式:非遞歸(棧實現)
  */
    public static void InOrder_stack(Node root){

        Stack<Node> stack = new Stack<Node>();

        // 1. 直到當前結點爲空 & 棧空時,循環結束
        while(root != null || stack.size()>0){

            // 2. 判斷當前結點是否爲空
            // a. 若不爲空,執行3、4
            // b. 若爲空,執行5、6
            if(root != null){

                // 3. 入棧當前結點
                stack.push(root);

                // 4. 置當前結點的左孩子爲當前節點
                // 返回步驟1
                root = root.getLeftNode();

            }else{

                // 5. 出棧棧頂結點
                root = stack.pop();
                // 6. 輸出當前節點
                printNode(root);
                // 7. 置當前結點的右孩子爲當前節點
                root = root.getRightNode();
                // 返回步驟1
            }
        }

5.3.3 後序遍歷

  • 簡介

示意圖

  • 遞歸實現
/**
  * 方式:遞歸
  */
    public void PostOrder(Node root){
        // 1. 判斷二叉樹結點是否爲空;若是,則返回空操作
        if(root ==null)
            return;

        // 2. 遍歷左子樹
        PostOrder(root.getLeftNode());

        // 3. 遍歷右子樹
        PostOrder(root.getRightNode());

        // 4. 訪問根節點(顯示根結點)
        printNode(root);

    }

示意圖

  • 非遞歸實現
    主要採用 棧實現

示意圖

/**
  * 方式:非遞歸(棧實現)
  */
    public void PostOrder_stack(Node root){

        Stack<Node> stack = new Stack<Node>();
        Stack<Node> output = new Stack<Node>();

        // 步驟1:直到當前結點爲空 & 棧空時,循環結束——> 步驟8
        while(root != null || stack.size()>0){

            // 步驟2:判斷當前結點是否爲空
            // a. 若不爲空,執行3、4
            // b. 若爲空,執行5、6
            if(root != null){

                // 步驟3:入棧當前結點到中間棧
                output.push(root);

                // 步驟4:入棧當前結點到普通棧
                stack.push(root);

                // 步驟4:置當前結點的右孩子爲當前節點
                // 返回步驟1
                root = root.getRightNode();

            }else{

                // 步驟5:出棧棧頂結點
                root = stack.pop();
                // 步驟6:置當前結點的右孩子爲當前節點
                root = root.getLeftNode();
                // 返回步驟1
            }
        }

        // 步驟8:輸出中間棧的結點
        while(output.size()>0){
            printNode(output.pop());

        }

    }

示意圖

5.3.4 層序遍歷

  • 簡介

示意圖

  • 實現思路
    非遞歸實現,採用 隊列

示意圖

  • 算法流程圖
    示意圖
/**
  * 方式:非遞歸(採用隊列)
  */
    public void levelTravel(Node root){
        // 創建隊列
        Queue<Node> q=new LinkedList<Node>();

        // 1. 判斷當前結點是否爲空;若是,則返回空操作
        if(root==null)
            return;
        // 2. 入隊當前結點
        q.add(root);

        // 3. 判斷當前隊列是否爲空,若爲空則跳出循環
        while(!q.isEmpty()){

            // 4. 出隊隊首元素
            root =  q.poll();

            // 5. 輸出 出隊元素
            printNode(root);

            // 6. 若出隊元素有左孩子,則入隊其左孩子
            if(root.getLeftNode()!=null) q.add(root.getLeftNode());

            // 7. 若出隊元素有右孩子,則入隊其右孩子
            if(root.getRightNode()!=null) q.add(root.getRightNode());
        }
    }

示意圖

5.4 遍歷方式總結

示意圖


6. 二叉樹的類型

  • 上述講解的是基礎的二叉樹
  • 根據不同的需求場景,二叉樹分爲許多類型,主要有:

示意圖

  • 下面,我將詳細講解各種二叉樹的類型

6.1 線索二叉樹

  • 簡介

示意圖

  • 示意圖
    示意圖

  • 特別注意

    • 問:如何區別該指針 = 指向左(右)孩子 or 前驅(後繼)
    • 答:增設標誌域:Ltag 和 Rtag

示意圖

6.2 二叉排序樹

也稱:二叉查找樹、二叉搜索樹

  • 特點
    示意圖

  • 作用 & 應用場景

示意圖

6.3 平衡二叉排序樹(AVL樹)

屬於 二叉搜索樹的一種特殊類型

  • 特點

示意圖

  • 具體介紹

示意圖

6.4 紅黑樹

屬於 二叉搜索樹的一種特殊類型

示意圖

6.5 赫夫曼樹

  • 簡介

示意圖

  • 哈夫曼樹算法
    即,如何找出哈弗曼樹。具體算法請看下圖

算法描述

示意圖

更加詳細請看文章:http://www.cnblogs.com/mcgrady/p/3329825.html

  • 哈夫曼編碼

示意圖

更加詳細請看文章:http://blog.csdn.net/lfeng_coding/article/details/47782141

6.6 其他類型(特殊形態)

包括:斜樹、滿二叉樹 & 完全二叉樹

示意圖

6.7 總結

示意圖


7. 總結


請幫頂 / 評論點贊!因爲你的鼓勵是我寫作的最大動力!

發佈了205 篇原創文章 · 獲贊 5720 · 訪問量 309萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章