數據結構與算法(八)-二叉樹(斜二叉樹、滿二叉樹、完全二叉樹、線索二叉樹)


前言:前面了解了樹的概念和基本的存儲結構類型及樹的分類,而在樹中應用最廣泛的種類是二叉樹

一、簡介

  在樹型結構中,如果 每個父節點只有兩個子節點,那么這樣的樹被稱為二叉樹(Binary tree)。其中,一個父結點的兩個字節點分別叫做“ 左子節點”和“ 右子節點”。不過也不是所有父節點都有兩個子節點,只有左子節點或者只有右子節點的情況也存在。另外,也會存在葉子結點,也就是一個子節點都沒有的節點,唯一的限制就是每一個節點的子節點不能超過兩個。
  之前談過的單向鏈表,是一種通過“指向下一個元素的指針”來連接各個數據的數據結構,而二叉樹則為每一個元素都有兩個指向下一個元素的指針。
  所以二叉樹可以使用鏈表的形式來存儲,當然也可以使用數組來表示,可以定義某一個父結點的元素索引是i,並且其左子節點的元素索引為(i×2+1)、右子節點的元素索引為(i×2+2)。例如:將根節點存在了0位置,那么它的左子節點位置為1,右子節點的位置為2,而1位置元素的左子節點為3,右子節點為4,以此類推,可以完整的把二叉樹表示出來;
  二叉樹是n(n>=0)個節點的有限集合,該集合可以為空(空二叉樹),或由一個根節點和 兩顆互不相交的、分別稱為 根節點的左子樹和右子樹的二叉樹組成;
  這樣看來,二叉樹可以使用 遞歸來創建。
  特點:
  • 每個節點最多有兩個子節點;
  • 二叉樹中最大的度為2;
  • 無論有幾個分支,都需要區分是左子樹還是右子樹;

二、分類及實現

2.1 分類

   斜二叉樹:只有左子節點或只有右子節點的二叉樹稱為斜二叉樹;
  
  斜二叉樹特點:
  • 度為1;
  • 只有左子節點或右子節點;
   滿二叉樹:所有的分支要么左右子節點都有,要么沒有子節點,且所有葉子結點都在同一層上;
  
  滿二叉樹特點:
  • 葉子結點只能;
  • 非葉子節點的結點的度為2;
   完全二叉樹:對一棵具有n個結點的二叉樹按層序編號,如果編號為i(1<=i<=n)的結點與同樣深度的滿二叉樹中編號為i的結點位置完全相同;
  
  完全二叉樹特點:
  • 葉子結點只能出現在最下兩層;
  • 最下層的葉子結點一定集中在左邊並且連續;
  • 若結點度為1,則該節點只有左子節點;
   注:滿二叉樹一定是完全二叉樹,而完全二叉樹不一定是滿二叉樹;
 
   線索二叉樹:在空節點中加入了線索(即在某種遍歷順序下指向的下一個節點)指引的二叉樹稱為線索二叉樹;
  線索二叉樹特點:
  • 節省空間,按一定的遍歷規則,將空結點的leftNode指向前驅,rightNode指向后繼結點;
  這里先介紹這幾種特殊的二叉樹,對於平衡二叉樹、二叉排序樹、紅黑樹、哈夫曼樹想要單獨開一篇隨筆。

2.2 普通二叉樹

2.2.1 二叉樹的遍歷分類

  二叉樹的遍歷是指從根結點出發,按照 某種次序依次訪問二叉樹中所有節點,使得每個 節點被訪問依次且僅被訪問一次。
  二叉樹的遍歷次序不同於線性結構,線性結構最多也就是分為順序、循環、雙向等簡單的遍歷方式。而樹不存在唯一的后繼節點,在訪問一個節點后,下一個被訪問的節點面臨着不同的選擇,所以我們需要規范遍歷方式:
  為了學習遍歷方式,我們先創建一個二叉樹 ,如圖:
  
  ①、前序遍歷:
  定義:先訪問 根節點,然后訪問 左子樹,再訪問 右子樹
  按照定義遍歷的順序遍歷結果為: A B D H I E J C F K G
  訪問順序如下圖:
  
  ②、中序遍歷:
  定義:先訪問 左子樹,再訪問 根節點,最后訪問 右子樹
  按照定義遍歷的順序遍歷結果為: H D I B E J A F K C G
  訪問順序如下圖:
  
  ③、后序遍歷:
  定義:先訪問 左子樹,再訪問 右子樹,最后訪問 根節點
  按照定義遍歷的順序遍歷結果為: H I D J E B K F G C A
  訪問順序如下圖:
  
  ④、層次遍歷:
  定義: 逐層的從根節點開始,每層 從左至右遍歷;
  按照定義遍歷的順序遍歷結果為: A B C D E F G H I J K
  訪問順序如下圖:
  

2.2.2 遞歸

  程序直接或間接的調用自身的編程技巧稱為遞歸。他通常把一個大型復雜的問題層層轉化為一個與原問題相似的規模較小的問題來求解,遞歸策略只需少量的程序就可描述出解題方程所需要的多次重復計算,大大地減少了程序的代碼量。
  遞歸的能力在於用有線的語句來定義對象的無限集合。一般來說,遞歸需要有邊界條件、遞歸前進段和遞歸返回段。當邊界條件不滿足時,遞歸前進;當邊界條件滿足時,遞歸返回。
  例如很有名的斐波那契數列(又稱兔子數列,感興趣的可以了解一下)就可以用遞歸來解決:
public static Integer getNums(Integer index) {
    //邊界條件 
    if (index==1) { 
        return 1; 
    } else if (index==2) { 
        return 1; 
    } else {
        //遞歸前進 
        return getNums(index - 1) + getNums(index - 2); 
    } 
}                

2.2.3 二叉樹實現

  二叉樹API:
public class BinaryTree   
    BinaryTree()     創建一個空背包   
    print()          遍歷樹
size()       獲取二叉樹元素大小    isEmpty() 是否為空樹
  二叉樹的實現需要借助遍歷的方式和迭代算法,我們選擇前序遍歷的方式,並以 空格為空葉子節點將字符串轉換為二叉樹例如: ABDH空格空格 I空格空格 E空格 J空格空格 CF空格 K空格空格 G空格空格(空格代表的是“ ”)。
  結點Node內部類:
class Node {

        String str;

        Node leftNode;

        Node rightNode;

}
  創建二叉樹代碼:
public class BinaryTree {

    private char[] strs;

    private int index;

    private Node root;

    BinaryTree(String str) {
        strs = str.toCharArray();
        root = new Node();
        createBinaryTree(root);
        System.out.println("11");
    }

    private void createBinaryTree(Node node) {
        int currentIndex = index;
        if (currentIndex<strs.length) {
            char str = strs[currentIndex];
            index++;
            if (String.valueOf(str).isEmpty()||str==' ') {
                node.str = null;
            } else {
                node.leftNode = new Node();
                node.rightNode = new Node();
                node.str = String.valueOf(strs[currentIndex]);
                createBinaryTree(node.leftNode);
                createBinaryTree(node.rightNode);
            }
        }
    }
}
BinaryTree.java
  前序遍歷代碼:
    public void print() {
        int level = 1;
        iterator(root,level,"根節點");
    }

    private void iterator(Node node, int level, String str) {
        if (node.str==null||node.str.isEmpty()) {

        } else {
            System.out.println(node.str + "在" + level + "層的"+str);
            iterator(node.leftNode,level+1,"左子節點");
            iterator(node.rightNode,level+1,"右子節點");
        }

    }
前序遍歷
  判空和長度方法:
    public Boolean isEmpty() {
        return strs.length < 1;
    }
    
    public int size() {
        return strs.length;
    }
  書寫測試方法:
    public static void main(String[] args) {
        BinaryTree tree = new BinaryTree("ABDH  I  E J  CF K  G  ");
        tree.print();
    }
測試結果如圖:

2.3 線索二叉樹

2.3.1 介紹

  在上面普通二叉樹的代碼實現里,有沒有發現葉子節點的leftNode和rightNode為空,而且葉子結點不在少數,那么怎么避免空葉子節點的空間浪費呢?
  
  而線索二叉樹是在上面null的位置放入遍歷時的前驅結點和后繼結點,如下圖:

  

2.3.2 線索二叉樹的實現

  線索二叉樹API:
public class BinaryTree
  BinaryTree()            創建一個空背包
  InOrderTraverse()       中序遍歷二叉樹
  boolean isEmpty()       是否為空樹
  首先先要改變Node結點的屬性結構,增加ltag和rtag來標記是否為線索。
public class ThreadBinaryTread {

    private char[] strs;

    private int index;

    private Node root;

    private Node pre;

    ThreadBinaryTread(String str) {
        strs = str.toCharArray();
        root = new Node();
        createBinaryTree(root);
        //創建完二叉樹后,進行二叉樹線索化
        Node p = new Node();
        p.ltag = 0;
        p.rtag = 1;
        pre = p;
        inThreading(root);
        pre.rightNode = p;
        pre = p;
    }

    //中序遍歷線索化
    private void inThreading(Node node) {
        if (node.str!=null&&!node.str.isEmpty()) {
            inThreading(node.leftNode);
            //如果該節點沒有左子節點,則將前一個遍歷的結點放入該左子節點的位置並將標志改為線索
            if (node.leftNode.str == null || node.leftNode.str.isEmpty()) {
                node.ltag = 1;
                node.leftNode = pre;
            }

            //如果前一個遍歷結點沒有右子節點,則將本節點放到前一個節點的右子節點的位置上並將標志改為線索
            if (pre.rightNode==null||pre.rightNode.str==null) {
                pre.rtag = 1;
                pre.rightNode = node;
            }

            pre = node;

            inThreading(node.rightNode);
        }
    }

    private void createBinaryTree(Node node) {
        int currentIndex = index;
        if (currentIndex<strs.length) {
            char str = strs[currentIndex];
            index++;
            if (String.valueOf(str).isEmpty()||str==' ') {
                node.str = null;
            } else {
                node.rightNode = new Node();
                node.leftNode = new Node();
                node.str = String.valueOf(strs[currentIndex]);
                createBinaryTree(node.leftNode);
                createBinaryTree(node.rightNode);
            }
        }
    }

    public void InOrderTraverse() {
        Node node = pre.rightNode;
        while (node!=pre) {
            while (node.ltag==0) {
                node = node.leftNode;
            }
            System.out.println(node.str);
            while (node.rtag==1&&node.rightNode!=pre) {
                node = node.rightNode;
                System.out.println(node.str);
            }
            node = node.rightNode;
        }
    }

    public Boolean isEmpty() {
        return strs.length < 1;
    }

    class Node {

        String str;

        Node leftNode;

        Node rightNode;

        //是否為線索,1:前驅線索;0:左子節點;
        int ltag = 0;

        //是否為線索,1:后繼線索;0:右子節點;
        int rtag = 0;

    }

}
ThreadBinaryThread.java

  創建測試方法:

    public static void main(String[] args) {
        ThreadBinaryTread tree = new ThreadBinaryTread("ABDH  I  E J  CF K  G  ");
        tree.InOrderTraverse();
    }
運行結果如圖:
   

 

三、總結

  二叉樹的特點:
  • 每個節點下最多有兩個子節點;
  • 二叉樹的度和節點的度最大為2;
  二叉樹遍歷的方式:
  • 前序遍歷:根節點->左子節點->右子節點;
  • 中序遍歷:左子節點->根節點->右子節點;
  • 后序遍歷:左子節點->右子節點->根節點;
  • 層次遍歷:逐層從左向右遍歷;
  線索二叉樹需要線索話才能使用鏈表的方式遍歷;
 
 

本系列參考書籍:

  《寫給大家看的算法書》

  《圖靈程序設計叢書 算法 第4版》


免責聲明!

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



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