資料結構與演算法 二叉樹基本框架與知識點

CJJMICHAEL發表於2020-10-08

二叉樹的定義

二叉樹是n個節點的集合,n >= 0。當 n = 0 時這為一顆空二叉樹。或一個根節點帶著兩顆互補相交的左子樹和右子樹,同時左右子樹仍為二叉樹。

四種特殊的二叉樹

  1. 滿二叉樹:
    高度為h,節點有 (2^h - 1 )個節點的二叉樹。對編號為 i 的節點,編號為(i/2)左右取整的兩個節點為該節點的雙親節點;如果該節點有子節點,編號2i 和 2i + 1 的節點為 該節點的孩子節點。
  2. 滿二叉樹:
    高度為h,節點個數為n;並且該樹的節點與高度為h的滿二叉樹編號為 1~n 的所有節點一一對應。
  3. 排序二叉樹:
    左子樹上的節點的排序屬性(關鍵字)比根節點的排序屬性小,右子樹上的節點的排序屬性大於根節點的排序屬性。並且左右子樹也為排序二叉樹。
  4. 平衡二叉樹:
    樹上的任意節點的左右字數的高度差不大於1的二叉樹。

二叉樹的儲存

  1. 順序儲存:
    二叉樹的節點從上至下、從左向右儲存到一塊連續的儲存空間(陣列)中,當節點為空時用數字0或者其他識別符號號替代。由儲存方式可知,當該二叉樹極為不平衡時(節點不均勻的分佈到某一邊子樹中),會出現大量的儲存空間被浪費。

  2. 鏈式儲存:
    為了彌補順序儲存的缺點,利用包含資料域、左右指標的節點資料結構儲存節點元素,這樣能很好的利用碎片化的空間。

二叉樹的遍歷

遞迴遍歷
  1. 前序遍歷(遍歷的順序的命名由訪問當前節點的順序決定)
/**
     * Traverse the tree by recursion in the pre order
     * @param node root
     */
    public void preOderRecursionTraverse(TreeNode node){ 
        if(node != null){
            System.out.println(node);//visit
            preOderRecursionTraverse(node.left);
            preOderRecursionTraverse(node.right);
        }
    }
  1. 中序遍歷
/**
     *Traverse the tree by recursion in the mid order
     * @param node root
     */
    public void midOderRecursionTraverse(TreeNode node){
        if(node != null){ 
            midOderRecursionTraverse(node.left);
            System.out.println(node);//visit
            midOderRecursionTraverse(node.right);
        }
    }
  1. 後序遍歷
/**
     *Traverse the tree by recursion in the post order
     * @param node root
     */
    public void postOderRecursionTraverse(TreeNode node){
        if(node != null){
            postOderRecursionTraverse(node.left); 
            postOderRecursionTraverse(node.right);
            System.out.println(node);//visit
        }
    }
非遞迴遍歷
  1. 深度優先(利用棧stack)

1.1. 前序遍歷

/**
     * Traverse the tree in the depth first and the pre order
     * @param node root
     */
    public void preOderDepthFirstTraverse(TreeNode node){ 
        TreeNode temp = node;
        Stack<TreeNode> stack = new Stack<>();
        while (temp != null || !stack.isEmpty()){
            if(temp != null){
                System.out.println(temp);//visit
                stack.push(temp);
                temp = temp.left;
            }else {
                temp = stack.pop();
                temp = temp.right;
            }
        }
    }

1.2. 中序遍歷

/**
     * Traverse the tree in the depth first and the post order
     * @param node root
     */
    public void midOderDepthFirstTraverse(TreeNode node){ 
        TreeNode temp = node;
        Stack<TreeNode> stack = new Stack<>();
        while (temp != null || !stack.isEmpty()){
            if(temp != null){
                stack.push(temp);
                temp = temp.left;
            }else {
                temp = stack.pop();
                System.out.println(temp);
                temp = temp.right;
            }
        }
    }
  1. 廣度優先(利用佇列queue)
/**
     * Traverse the tree in the width first
     * @param node root
     */
    public void widthFirstTraverse(TreeNode node){ 
        Queue<TreeNode> queue = new ArrayDeque<>();
        TreeNode temp;
        queue.offer(node);
        while (!queue.isEmpty()){
            temp = queue.poll();
            System.out.println(temp);//visit
            if(temp.left != null){
                queue.offer(temp.left);
            }
            if(temp.right != null){
                queue.offer(temp.right);
            }
        }
    }

排序二叉樹的基本操作

  1. 插入節點
/**
     * Add a node into the tree
     * @param value the value needs to be insert
     * @return indicator which shows if the value has been inserted.
     */
    public int insertNode(int value){
        TreeNode current = root;
        TreeNode pre = null;
        //locate the pre node which is the father node of inserted node
        while (current != null){
            pre = current;
            if(current.val < value){
                current = current.right;
            }else if(current.val > value){
                current = current.left;
            }else {
                return 0;
            }
        }
        //insert node
        if(root == null){
            root = new TreeNode(value);
        }else if(pre.val < value){
            pre.right = new TreeNode(value);
        }else {
            pre.left = new TreeNode(value);
        }
        return 1;
    }
  1. 根據陣列中元素的順序構造樹
/**
     * The constructor which builds the sorted binary tree
     * @param values, all nodes'value which will be used to construct
     *                the tree.
     */
    public SortedBinaryTree(int [] values){
        for(int i = 0;i < values.length;i ++){
            insertNode(values[i]);
        }
    }
  1. 查詢某個節點
/**
     * Find the node which have the same value with the one provided
     * @param value the value which wants to be found
     * @return the node or null
     */
    public TreeNode findNode(int value){
        TreeNode node = root;
        while (node != null){
            if(node.val < value){
                node = node.right;
            }else if(node.val > value){
                node = node.left;
            }else {
                return node;
            }
        }
        return null;
    }
  1. 刪除某個節點
 /**
     * 如果需要程式碼實現,需要為節點資料結構新增parent域用來指向父節點
     * @param value
     */
    public void removeNode(int value){
        //分類討論被刪除節點的情況
        //1.當節點為葉子節點:直接刪除該節點
        //2.當該節點只有一個子節點,用該節點的子節點替代該節點
        //3.當該節點有左右子節點:用小於該節點的最大節點(左子樹的最大節點)替代該節點
        // 或用大於該最小節點(右子樹的最小節點)替代該節點,再刪除該節點。
    }

在這裡插入圖片描述
在這裡插入圖片描述
在這裡插入圖片描述

分析排序二叉樹的查詢時間複雜度引出紅黑樹

  1. 排序二叉樹查詢的平均時間複雜度為O(log2(n))(每次查詢排除一半直到找到目標元素)。當節點都在根的一邊並且都為右節點或左節點時,時間複雜度為O(n)。這種情況被成為樹的不平衡。如果樹為平衡排序二叉樹,查詢的效能能得到比較好的保證。如果我們要保證樹的平衡,在刪除或插入節點時就要保證樹的平衡。這裡引出樹的平衡操作(調節節點位置使樹保持平衡狀態)的概念,但不做詳細討論。
  2. 同時科學家提出另一種類平衡樹的資料結構,紅黑樹。紅黑樹是大致平衡的但一般比平衡二叉樹更高效。在JDK中TreeMap就是紅黑樹的一個例項。
  3. 紅黑樹的定義和特性:
    3.1. 每個節點要麼是紅色要麼是黑色(被標記為或被認為)。
    3.2. 根節點永遠是黑色。
    3.3. 所有葉子節點都是空節點null,並且被認為是黑色的。
    3.4. 每個紅色節點的兩個節點都是黑色(每個葉子節點到根的路徑上不會有兩個連續的紅色節點)。
    3.5. 從任一節點到其子樹中每個葉子節點的路徑都包含相同數量的黑色節點。

相關文章