1. 程式人生 > >Java 樹結構的基礎部分(二)

Java 樹結構的基礎部分(二)

1 順序儲存二叉樹 1.1 順序儲存二叉樹的概念  基本說明 從資料儲存來看,陣列儲存方式和樹的儲存方式可以相互轉換,即陣列可以轉換成樹,樹也可以轉換成陣列, 看下面的示意圖。  要求: 1) 右圖的二叉樹的結點,要求以陣列的方式來存放 arr : [1, 2, 3, 4, 5, 6, 6] 2) 要求在遍歷陣列 arr 時,仍然可以以前序遍歷,中序遍歷和後序遍歷的方式完成結點的遍歷  順序儲存二叉樹的特點: 1) 順序二叉樹通常只考慮完全二叉樹 2) 第 n 個元素的左子節點為 2 * n + 1 3) 第 n 個元素的右子節點為 2 * n + 2 4) 第 n 個元素的父節點為 (n-1) / 2 5) n : 表示二叉樹中的第幾個元素(按 0 開始編號如圖所示)   1.2 順序儲存二叉樹遍歷 需求: 給你一個數組 {1,2,3,4,5,6,7},要求以二叉樹前序遍歷的方式進行遍歷。 前序遍歷的結果應當為 1,2,4,5,3,6,7   程式碼實現
package com.lin.tree_0308;

public class ArrBinaryTreeDemo {

    public static void main(String[] args) {
        int[] arr = {1,2,3,4,5,6,7};
        ArrBinaryTree arrBinaryTree = new ArrBinaryTree(arr);
        System.out.println("前序遍歷:");
        arrBinaryTree.preOrder();
        System.out.println();
        System.out.println("中序遍歷:");
        arrBinaryTree.infixOrder();
        System.out.println();
        System.out.println("後序遍歷:");
        arrBinaryTree.postOrder();
        
    }
}

class ArrBinaryTree{
    
    private int[] arr;

    public ArrBinaryTree(int[] arr) {
        super();
        this.arr = arr;
    }
    
    // 過載
    public void preOrder() {
        this.preOrder(0);
    }
    
    public void infixOrder() {
        this.infixOrder(0);
    }
    
    public void postOrder() {
        this.postOrder(0);
    }
    
    /**
     * 
     * @Description: 
     * @author LinZM  
     * @date 2021-3-8 19:14:45 
     * @version V1.8
     * @param index 陣列下標
     */
    // 前序遍歷
    public void preOrder(int index) {
        if(arr == null || arr.length == 0) {
            System.out.println("陣列為空!");
        }
        // 輸出當前資料
        System.out.print(arr[index] + " ");
        // 向左遞迴
        if( ( index * 2 + 1 ) < arr.length ) {
            preOrder( index * 2 + 1 );
        }
        // 向右遞迴
        if( ( index * 2 +2 ) < arr.length ) {
            preOrder( index * 2 + 2 );
        }
    }
    
    // 中序遍歷
    public void infixOrder(int index) {
        if(arr == null || arr.length == 0) {
            System.out.println("陣列為空!");
        }
        // 向左遞迴
        if( ( index * 2 + 1 ) < arr.length) {
            infixOrder( index * 2 + 1);
        }
        // 輸出當前資料
        System.out.print(arr[index] + " ");
        // 向右遞迴
        if( ( index * 2 + 2 ) < arr.length) {
            infixOrder(index*2 + 2);
        }
    }
    
    // 後序遍歷
    public void postOrder(int index) {
        if(arr == null || arr.length == 0) {
            System.out.println("陣列為空!");
        }
        // 向左遞迴
        if( ( index * 2 + 1) < arr.length ) {
            postOrder(index * 2 + 1);
        }
        // 向右遞迴
        if( ( index * 2 + 2 ) < arr.length) {
            postOrder(index * 2 + 2);
        }
        // 輸出當前資料
        System.out.print(arr[index] + " ");
    }
}

 

 

2 線索化二叉樹 2.1 先看一個問題   將數列 {1, 3, 6, 8, 10, 14 } 構建成一顆二叉樹. n+1=7 問題分析: 1) 當我們對上面的二叉樹進行中序遍歷時,數列為 {8, 3, 10, 1, 6, 14 } 2) 但是 6, 8, 10, 14 這幾個節點的 左右指標,並沒有完全的利用上. 3) 如果我們希望充分的利用 各個節點的左右指標, 讓各個節點可以指向自己的前後節點,怎麼辦? 4) 解決方案-線索二叉樹    2.2 線索二叉樹基本介紹 1) n 個結點的二叉連結串列中含有 n+1 【公式 2n-(n-1)=n+1】 個空指標域。利用二叉連結串列中的空指標域,存放指向 該結點在某種遍歷次序下的前驅和後繼結點的指標(這種附加的指標稱為"線索") 2) 這種加上了線索的二叉連結串列稱為線索連結串列,相應的二叉樹稱為線索二叉樹(Threaded BinaryTree)。根據線索性質 的不同,線索二叉樹可分為前序線索二叉樹、中序線索二叉樹和後序線索二叉樹三種 3) 一個結點的前一個結點,稱為前驅結點 4) 一個結點的後一個結點,稱為後繼結點   2.3 線索二叉樹應用案例  應用案例說明:將下面的二叉樹,進行中序線索二叉樹。中序遍歷的數列為 {8, 3, 10, 1, 14, 6} 思路分析: 中序遍歷的結果:{8, 3, 10, 1, 14, 6}

 說明: 當線索化二叉樹後,Node 節點的 屬性 left 和 right ,有如下情況: 1) left 指向的是左子樹,也可能是指向的前驅節點. 比如 ① 節點 left 指向的左子樹, 而 ⑩ 節點的 left 指向的 就是前驅節點. 2) right 指向的是右子樹,也可能是指向後繼節點,比如 ① 節點 right 指向的是右子樹,而⑩ 節點的 right 指向 的是後繼節點.  程式碼實現: 
package com.lin.tree_0308;

public class ThreadeBinaryTreeDemo {

    public static void main(String[] args) {
        TNode tNode1 = new TNode(1, "Tom");
        TNode tNode2 = new TNode(3, "Jack");
        TNode tNode3 = new TNode(6, "Smith");
        TNode tNode4 = new TNode(8, "Marry");
        TNode tNode5 = new TNode(10, "Linda");
        TNode tNode6 = new TNode(14, "King");
        
        tNode1.setLeft(tNode2);
        tNode1.setRight(tNode3);
        tNode2.setLeft(tNode4);
        tNode2.setRight(tNode5);
        tNode3.setLeft(tNode6);
        
        TBinaryTree tBinaryTree = new TBinaryTree();
        tBinaryTree.setRoot(tNode1);
        tBinaryTree.threadedInfixNodes(tNode1);// 10test
        TNode left = tNode5.getLeft();
        TNode right = tNode5.getRight();
        
        System.out.println(left);
        System.out.println(right);
        
//        // 中序遍歷線索化二叉樹
//        System.out.println("中序遍歷線索化二叉樹:");
//        tBinaryTree.threadedInfixList();
    }
}

class TBinaryTree{
    private TNode root;

    // 前驅節點的指標,總是保留前一個節點
    private TNode pre; 
    
    public void setRoot(TNode root) {
        this.root = root;
    }
    
    
    public void threadedPreNodes(TNode node) {
        if(node == null) {
            System.out.println("空!!!");
            return;
        }
        // 1 線索當前節點
        // 先處理節點的前驅節點
        if (node.getLeft() == null) {
            // 讓當前節點的左指標指向前驅節點
            node.setLeft(pre);
            // 修改當前節點的左指標的型別
            node.setLeftType(1);
        }
        // 處理後繼節點
        if (pre != null && pre.getRight() == null) {
            pre.setRight(node);
            pre.setRigthType(1);
        }
        // 每處理一個節點後,讓當前節點是下一個節點前驅節點
        pre = node;
        
        threadedPreNodes(node.getLeft());
        
        threadedPreNodes(node.getRight());
    }
    
    // 二叉樹中序線索化
    /**
     * 
     * @Description: 
     * @author LinZM  
     * @date 2021-3-8 22:14:51 
     * @version V1.8
     * @param node 線索化節點
     */
    public void threadedInfixNodes(TNode node) {
        if(node == null) {
            return;
        }
        
        // 1 先線索化左子樹
        threadedInfixNodes(node.getLeft());
        // 2 線索化當前節點
        //     先處理節點的前驅節點
        // 節點8->節點3,一開始8為node,後面3為node
        if(node.getLeft() == null ) {
            //    讓當前節點的左指標指向前驅節點
            node.setLeft(pre);
            //    修改當前節點的左指標的型別
            node.setLeftType(1);
        }
        // 處理後繼節點
        if(pre != null && pre.getRight() == null) {
            pre.setRight(node);
            pre.setRigthType(1);
        }
        // 每處理一個節點後,讓當前節點是下一個節點前驅節點
        // pre = 8
        pre = node;
        
        // 3 線索化右子樹
        threadedInfixNodes(node.getRight());
    }
    
    
    
   // 中序遍歷線索二叉樹
    public void threadedInfixList() {
        // 定義一個變數,儲存當前遍歷的節點,從root開始
        TNode node = root;
        if(node == null) {
            System.out.println("空樹!");
            return;
        }
        while(node != null) {
            // 迴圈找到leftType == 1 的節點,第一個找到就是8節點
            // 後面隨著遍歷而變化,因為當leftType == 1,說明該節點是按照線索化處理後的有效節點
            while(node.getLeftType() == 0) {
                node = node.getLeft();
            }
            // 找到8節點
            System.out.println(node);
            // 如果當前節點的右指標指向的是後繼節點,就一直輸出
            while(node.getRigthType() == 1) {
                node = node.getRight();
                System.out.println(node);
            }
            // 如果不是後繼節點,則替換這個遍歷的節點
            node = node.getRight();
        }
    }
    
    // 刪除節點
    public void delNode(int no) {
        if (root != null) {
            // 如果只有一個root
            if (root.getNo() == no) {
                root = null;
            } else {
                root.delNode(no);
            }
        } else {
            System.out.println("空樹!");
        }
    }
    
    // 前序遍歷
    public void preOrder() {
        if(this.root != null) {
            this.root.preOrder();
        } else {
            System.out.println("二叉樹為空!");
        }
    }
    
    // 中序遍歷
    public void infixOrder() {
        if(this.root != null) {
            this.root.infixOrder();
        } else {
            System.out.println("二叉樹為空!");
        }
    }
    
    // 後序遍歷
    public void postOrder() {
        if(this.root != null) {
            this.root.postOrder();
        } else {
            System.out.println("二叉樹為空!");
        }
    }
    
    // 前序查詢
    public TNode preOrderSearch(int no) {
        if(root != null) {
            return root.preOrderSearch(no);
        } else {
            return null;
        }
    }

    // 中序查詢
    public TNode infixOrderSearch(int no) {
        if (root != null) {
            return root.infixOrderSearch(no);
        } else {
            return null;
        }
    }

    // 後序查詢
    public TNode postOrderSearch(int no) {
        if (root != null) {
            return root.postOrderSearch(no);
        } else {
            return null;
        }
    }
}


class TNode{
    private String name;
    private int no;
    private TNode left;
    private TNode right;
    
    // leftType == 0 為左子樹, 如果為1則表示指向前驅節點
    // rightType == 0 為右子樹, 如果為1則表示指向後繼節點
    private int leftType;
    private int rigthType;
    
    
    
    public int getLeftType() {
        return leftType;
    }

    public void setLeftType(int leftType) {
        this.leftType = leftType;
    }

    public int getRigthType() {
        return rigthType;
    }

    public void setRigthType(int rigthType) {
        this.rigthType = rigthType;
    }

    public TNode(int no, String name) {
        this.no = no;
        this.name = name;
    }
    
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getNo() {
        return no;
    }
    public void setNo(int no) {
        this.no = no;
    }
    public TNode getLeft() {
        return left;
    }
    public void setLeft(TNode left) {
        this.left = left;
    }
    public TNode getRight() {
        return right;
    }
    public void setRight(TNode right) {
        this.right = right;
    }

    @Override
    public String toString() {
        return "TNode [name=" + name + ", no=" + no + "]";
    }
    
    // 前序遍歷
    public void preOrder() {
        System.out.println(this); // 輸出父節點
        if(this.left != null) {
            this.left.preOrder();
        }
        if(this.right != null) {
            this.right.preOrder();
        }
    }
    
    // 中序遍歷
    public void infixOrder() {
        if (this.left != null) {
            this.left.infixOrder();
        }
        System.out.println(this); // 輸出父節點
        if (this.right != null) {
            this.right.infixOrder();
        }
    }
        
    // 前序遍歷
    public void postOrder() {
        if (this.left != null) {
            this.left.postOrder();
        }
        if (this.right != null) {
            this.right.postOrder();
        }
        System.out.println(this); // 輸出父節點
    }
    
    // 前序查詢
    public TNode preOrderSearch(int no) {
        System.out.println("1");
        // 比較當前節點是不是
        if(this.no == no) {
            return this;
        }
        // 1 判斷當前節點的左節點是否為空,如果不為空,則遞迴前序查詢
        // 2 如果左遞迴前序查詢,找到節點,則返回
        TNode resNode = null;
        if(this.left != null) {
            resNode = this.left.preOrderSearch(no);
        }
        if(resNode != null) {// 說明左子樹找到了
            return resNode;
        }
        // 1 左遞迴如果沒有找到,則繼續判斷
        // 2 當前節點的右節點是否為空,如果不為空,則繼續向右遞迴前序查詢
        if(this.right != null) {
            resNode = this.right.preOrderSearch(no);
        }
        // 這時候不管有沒有找到都要返回resNode
        return resNode; 
    }
    
    // 中序查詢
    public TNode infixOrderSearch(int no) {
        
        TNode resNode = null;
        if(this.left != null) {
            resNode = this.left.infixOrderSearch(no);
        }
        if(resNode != null) {
            return resNode;
        }
        System.out.println("1");
        if(this.no == no) {
            return this;
        }
        
        if(this.right != null) {
            resNode = this.right.infixOrderSearch(no);
        }
        return resNode;
    }
    
    // 後序查詢
    public TNode postOrderSearch(int no) {
        
        TNode resNode = null;
        if(this.left != null) {
            resNode = this.left.postOrderSearch(no);
        }
        if(resNode != null) {
            return resNode;
        }
        
        if(this.right != null) {
            resNode = this.right.postOrderSearch(no);
        }
        if(resNode != null) {
            return resNode;
        }
        System.out.println("1");
        if(this.no == no) {
            return this;
        }

        // 如果都沒有找到
        return resNode;
    }
    
    /**
     * 
     * @Description:1 因為我們的二叉樹是單向,所以我們是判斷當前節點的子節點是否需要刪除節點,而不是直接去判斷當前節點是否需要刪除節點。<br>
     *                 2 如果當前節點的左子節點不為空,並且左子節點就是要刪除節點,就將this.left = null;並且就返回(結束遞迴刪除) <br>
     *                 3 如果當前節點的右子節點不為空,並且右子節點就是要刪除節點,就將this.right = null;並且就返回(結束遞迴刪除) <br>
     *                 4 如果第2和第3都沒有刪除節點,那麼我們就需要向左子樹進行遞迴刪除<br>
     *                 5 如果第4補也沒有刪除節點,則向右子樹進行遞迴刪除<br>
     * @author LinZM  
     * @date 2021-3-8 15:17:32 
     * @version V1.8
     */
    public void delNode(int no) {
        if(this.left != null && this.left.no == no) {
            this.left = null;
            return;
        }
        
        if(this.right != null && this.right.no == no) {
            this.right = null;
            return;
        }
        
        if(this.left != null) {
            this.left.delNode(no);
        }
        
        if(this.right != null) {
            this.right.delNode(no);
        }
    }
}
  2.4 遍歷線索化二叉樹 1) 說明:對前面的中序線索化的二叉樹, 進行遍歷 2) 分析:因為線索化後,各個結點指向有變化,因此原來的遍歷方式不能使用,這時需要使用新的方式遍歷 線索化二叉樹,各個節點可以通過線型方式遍歷,因此無需使用遞迴方式,這樣也提高了遍歷的效率。 遍歷的次 序應當和中序遍歷保持一致。 3) 程式碼:  如上面  

僅供參考,有錯誤還請指出!

有什麼想法,評論區留言,互相指教指教。

覺得不錯的可以點一下右邊的推薦喲