1. 程式人生 > 實用技巧 >面向物件程式設計之——封裝,裝飾器(property,classmethod,staticmethod)

面向物件程式設計之——封裝,裝飾器(property,classmethod,staticmethod)

一、使用樹這種結構的原因:

陣列儲存方式的分析

➢優點:通過下標方式訪問元素,速度快。對於有序陣列,還可使用二分查詢提高檢索速度。
➢缺點:如果要檢索具體某個值,或者插入值(按一定順序)會整體移動,效率較低

鏈式儲存方式的分析

➢優點:在一定程度上對陣列儲存方式有優化(比如:插入一個數值節點,只需要將插入節點,連結到連結串列中即可,刪除效率也很好)。
➢缺點:在進行檢索時,效率仍然較低,比如(檢索某個值,需要從頭節點開始遍歷)

樹儲存方式的分析

➢能提高資料儲存,讀取的效率,比如利用二叉排序樹(Binary SortTree),既可以保證資料的檢索速度,同時也可以保證資料的插入,刪除,修改的速度。

二、二叉樹

➢樹有很多種,每個節點最多隻能有兩個子節點的一種形式稱為二叉樹。
➢二叉樹的子節點分為左節點和右節點。
➢如果該二叉樹的所有葉子節點都在最後一層,並且結點總數=2^n-1,n為層.數,則我們稱為滿二叉樹
➢如果該二叉樹的所有葉子節點都在最後一層或者倒數第二層,而且最後一層的葉子節點在左邊連續,倒數第二層的葉子節點在右邊連續,我們稱為完全二叉樹

前序遍歷:先輸出父節點,再遍歷左子樹和右子樹
中序遍歷:先遍歷左子樹,再輸出父節點,再遍歷右子樹
後序遍歷:先遍歷左子樹,再遍歷右子樹,最後輸出父節點
小結:看輸出父節點的順序,就確定是前序,中序還是後序

package com.xudong.DataStructures;

public class BinaryTreeDemo {
    public static void main(String[] args) {
        //建立一個二叉樹
        BinaryTree binaryTree = new BinaryTree();
        //建立需要的節點
        HeroNode1 root = new HeroNode1(1, "宋江");
        HeroNode1 node2 = new HeroNode1(2, "吳用");
        HeroNode1 node3 = new HeroNode1(3, "盧俊義");
        HeroNode1 node4 = new HeroNode1(4, "林沖");
        HeroNode1 node5 = new HeroNode1(5, "關勝");

        //方式一:手動建立二叉樹
        root.setLeft(node2);
        root.setRight(node3);
        node3.setRight(node4);
        node3.setLeft(node5);
        binaryTree.setRoot(root);

        //測試
        System.out.println("前序遍歷:");
        binaryTree.preOrder();
        System.out.println("中序遍歷:");
        binaryTree.infixOrder();
        System.out.println("後序遍歷:");
        binaryTree.postOrder();
        System.out.println("======================");

        //前序查詢
        System.out.println("前序查詢:");
        HeroNode1 resNode = binaryTree.preOrderSearch(5);
        if (resNode != null){
            System.out.printf("找到了資訊為no= %d name= %s 的英雄。\n",resNode.getNo(),resNode.getName());
        }else {
            System.out.println("沒有找到該英雄!");
        }
        //中序查詢
        System.out.println("中序查詢:");
        resNode = binaryTree.infixOrderSearch(5);
        if (resNode != null){
            System.out.printf("找到了資訊為no= %d name= %s 的英雄。\n",resNode.getNo(),resNode.getName());
        }else {
            System.out.println("沒有找到該英雄!");
        }
        //後序查詢
        System.out.println("後序查詢:");
        resNode = binaryTree.postOrderSearch(5);
        if (resNode != null){
            System.out.printf("找到了資訊為no= %d name= %s 的英雄。\n",resNode.getNo(),resNode.getName());
        }else {
            System.out.println("沒有找到該英雄!");
        }

        System.out.println("====================");
        //刪除節點
        System.out.println("刪除前,前序遍歷:");
        binaryTree.preOrder();
        binaryTree.delNode(5);
        System.out.println("刪除後,前序遍歷:");
        binaryTree.preOrder();
        System.out.println("--------------------");
        System.out.println("刪除前,中序遍歷:");
        binaryTree.infixOrder();
        binaryTree.delNode(4);
        System.out.println("刪除後,中序遍歷:");
        binaryTree.infixOrder();
        System.out.println("--------------------");
        System.out.println("刪除前,後序遍歷:");
        binaryTree.postOrder();
        binaryTree.delNode(3);
        System.out.println("刪除後,後序遍歷:");
        binaryTree.postOrder();

    }
}

//定義BinaryTree二叉樹
class BinaryTree{
    private HeroNode1 root;

    public void setRoot(HeroNode1 root) {
        this.root = root;
    }

    //前序遍歷
    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 HeroNode1 preOrderSearch(int no){
        if (root != null){
            return root.preOrderSearch(no);
        }else {
            return null;
        }
    }

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

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

    //刪除節點
    public void delNode(int no){
        if (root != null){
            //立即判斷root是不是要刪除的節點
            if (root.getNo() == no){
                root = null;
            }else {
                //遞迴刪除
                root.delNode(no);
            }
        }else {
            System.out.println("空樹!不能刪除");
        }
    }
}



//建立heroNode節點
class HeroNode1{
    private int no;
    private String name;
    private HeroNode1 left;
    private HeroNode1 right;

    public HeroNode1(int no, String name) {
        this.no = no;
        this.name = name;
    }

    public int getNo() {
        return no;
    }

    public void setNo(int no) {
        this.no = no;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public HeroNode1 getLeft() {
        return left;
    }

    public void setLeft(HeroNode1 left) {
        this.left = left;
    }

    public HeroNode1 getRight() {
        return right;
    }

    public void setRight(HeroNode1 right) {
        this.right = right;
    }

    @Override
    public String toString() {
        return "HeroNode1{" +
                "no=" + no +
                ", name='" + name + '\'' +
                '}';
    }

    //前序遍歷的方法
    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 HeroNode1 preOrderSearch(int no){
        //比較當前節點是不是
        if (this.no == no){
            return this;
        }
        //向左遞迴查詢
        HeroNode1 resNode = null;
        if (this.left != null){
            resNode = this.left.preOrderSearch(no);
        }
        if (resNode != null){//說明在左子樹找到
            return resNode;
        }
        //向右遞迴查詢
        if (this.right != null){
            resNode = this.right.preOrderSearch(no);
        }
        if (resNode != null){//說明在右子樹找到
            return resNode;
        }
        return resNode;
    }

    //中序遍歷查詢
    public HeroNode1 infixOrderSearch(int no){
        //向左遞迴查詢
        HeroNode1 resNode = null;
        if (this.left != null){
            resNode = this.left.infixOrderSearch(no);
        }
        if (resNode != null){//說明在左子樹找到
            return resNode;
        }
        //如果沒找到,比較當前節點是不是
        if (this.no == no){
            return this;
        }
        //向右遞迴查詢
        if (this.right != null){
            resNode = this.right.infixOrderSearch(no);
        }
        if (resNode != null){//說明在右子樹找到
            return resNode;
        }
        return resNode;
    }

    //後序遍歷查詢
    public HeroNode1 postOrderSearch(int no){
        //向左遞迴查詢
        HeroNode1 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;
        }
        //如果沒找到,比較當前節點是不是
        if (this.no == no){
            return this;
        }
        return resNode;
    }

    //遞迴刪除節點
    //1.如果刪除的是葉子結點,則刪除該節點
    //2.如果刪除的節點是非葉子節點,則刪除該子數
    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);
        }
    }
}

三、順序儲存二叉樹

●從資料儲存來看,陣列儲存方式和樹的儲存方式可以相互轉換,即陣列可以轉換成樹,樹也可以轉換成陣列。

順序儲存二叉樹的特點:

➢順序二叉樹通常只考慮完全二叉樹
➢第n個元素的左子節點カ為【2n+ 1】
➢第n個元素的右子節點カ【2
n+2】
➢第n個元素的父子節點為【(n-1)/2】

package com.xudong;

public class arrBinaryTreeDemo {
    public static void main(String[] args) {
        int[] arr = {1,2,3,4,5,6,7};
        ArrBinaryTree arrBinaryTree = new ArrBinaryTree(arr);
        arrBinaryTree.preOrder();
    }
}

//實現順序儲存二叉樹遍歷
class ArrBinaryTree{
    private int[] arr;//儲存資料節點的陣列

    public ArrBinaryTree(int[] arr) {
        this.arr = arr;
    }

    //過載perOrder
    public void preOrder(){
        this.preOrder(0);
    }

    //順序儲存二叉樹的前序遍歷
    public void preOrder(int index){
        if (arr == null || arr.length == 0){
            System.out.println("陣列為空!");
        }
        //輸出當前元素
        System.out.println(arr[index]);
        //向左遞迴遍歷
        if ((index * 2 + 1) < arr.length){
            preOrder(2 * index + 1);
        }
        //向右遞迴遍歷
        if ((index * 2 + 2) < arr.length){
            preOrder(2 * index + 2);
        }
    }
}

四、線索化二叉樹

➢n個結點的二叉連結串列中含有n+1【公式2n-(n-1)=n+1】個空指標域。利用二叉連結串列中的空指標域,存放指向該結點在某種遍歷次序下的前驅和後繼節點的指標(這種附加的指標稱為"線索")
➢這種加上了線索的二叉連結串列稱為線索連結串列,相應的二叉樹稱為線索二叉樹(Threaded BinaryTree)。根據線索性質的不同,線索二叉樹可分為前序線索二叉樹、 中序線索二叉樹和後序線索二叉樹三種
➢一個結點的前一個節點,稱為前驅節點
➢一個結點的後一個節點,稱為後繼節點

線索化二叉樹應用案例

遍歷線索化二叉樹

說明:對前面的中序線索化的二叉樹,進行遍歷
分析:因為線索化後,各個結點指向有變化,因此原來的遍歷方式不能使用,這時需要使用新的方式遍歷線索化二叉樹,各個節點可以通過線型方式遍歷,因此無需使用遞迴方式,這樣也提高了遍歷的效率。遍歷的次序應當和中序遍歷保持一致

package com.xudong.DataStructures;

public class ThreadedBinaryTreeDemo {
    public static void main(String[] args) {
        HeroNode3 root = new HeroNode3(1, "Tom");
        HeroNode3 node2 = new HeroNode3(3, "jack");
        HeroNode3 node3 = new HeroNode3(6, "smith");
        HeroNode3 node4 = new HeroNode3(8, "mary");
        HeroNode3 node5 = new HeroNode3(10, "king");
        HeroNode3 node6 = new HeroNode3(14, "dim");

        //手動建立線索二叉樹
        root.setLeft(node2);
        root.setRight(node3);
        node2.setLeft(node4);
        node2.setRight(node5);
        node3.setLeft(node6);

        ThreadedBinaryTree threadedBinaryTree = new ThreadedBinaryTree();
        threadedBinaryTree.setRoot(root);
        threadedBinaryTree.threadedNodes();

        //測試10號節點
        HeroNode3 leftNode = node5.getLeft();
        HeroNode3 rightNode = node5.getRight();
        System.out.println("10號節點的前驅節點是 =" + leftNode);
        System.out.println("10號節點的後繼節點是 =" + rightNode);

        //遍歷線索化二叉樹
        System.out.println("使用線索化的方式遍歷線索化二叉樹:");
        threadedBinaryTree.threadedList();
    }
}

//
//定義ThreadedBinaryTree二叉樹
class ThreadedBinaryTree{
    private HeroNode3 root;
    //在遞迴線索化時,pre總保留前一個節點
    private HeroNode3 pre = null;

    public void setRoot(HeroNode3 root) {
        this.root = root;
    }

    //過載threadedNodes方法
    public void threadedNodes(){
        this.threadedNodes(root);
    }

    //對二叉樹進行中序線索化的方法.node就是當前需要線索化的節點
    public void threadedNodes(HeroNode3 node){
        //如果node == null,不能線索化
        if (node == null){
            return;
        }
        //1.線索話左子樹
        threadedNodes(node.getLeft());

        //2.線索話當前節點
        //處理當前節點的前驅結點
        if (node.getLeft() == null){
            //讓當前節點的左指標指向前驅節點
            node.setLeft(pre);
            //修改當前節點的左指標的型別,指向前驅節點
            node.setLeftType(1);
        }
        //處理後繼節點
        if (pre != null && pre.getRight() == null){
            //讓前驅節點的右指標指向當前節點
            pre.setRight(node);
            //修改前驅節點的右指標型別
            pre.setRightType(1);
        }
        //每處理一個節點後,讓當前節點是下一個節點的前驅節點
        pre = node;

        //3.線索話右子樹
        threadedNodes(node.getRight());
    }

    //線索化的遍歷線索二叉樹
    public void threadedList(){
        //定義一個變數,儲存當前遍歷的節點
        HeroNode3 node = root;
        while (node != null){
            //當leftType == 1 時,說明該節點時按照線索化處理後的有效節點
            while (node.getLeftType() == 0){
                node = node.getLeft();
            }
            //列印當前這個節點
            System.out.println(node);
            //如果當前節點的右指標指向的是後繼節點,就一直輸出
            while (node.getRightType() == 1){
                //獲得當前節點的後繼節點
                node = node.getRight();
                System.out.println(node);
            }
            //替換這個遍歷的節點
            node = node.getRight();
        }
    }
}

//建立heroNode節點
class HeroNode3{
    private int no;
    private String name;
    private HeroNode3 left;
    private HeroNode3 right;

    //如果leftType == 0 表示指向的是左子樹,如果是 1 則表示指向前驅節點
    //如果rightType == 0 表示指向的是右子樹,如果是 1 則表示指向後繼節點
    private int leftType;
    private int rightType;

    public int getLeftType() {
        return leftType;
    }

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

    public int getRightType() {
        return rightType;
    }

    public void setRightType(int rightType) {
        this.rightType = rightType;
    }

    public HeroNode3(int no, String name) {
        this.no = no;
        this.name = name;
    }

    public int getNo() {
        return no;
    }

    public void setNo(int no) {
        this.no = no;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public HeroNode3 getLeft() {
        return left;
    }

    public void setLeft(HeroNode3 left) {
        this.left = left;
    }

    public HeroNode3 getRight() {
        return right;
    }

    public void setRight(HeroNode3 right) {
        this.right = right;
    }

    @Override
    public String toString() {
        return "HeroNode3{" +
                "no=" + no +
                ", name='" + name + '\'' +
                '}';
    }
}

五、赫夫曼樹

●給定n個權值作為n個葉子結點,構造一棵二叉樹,若該樹的帶權路徑長度(wpl)達到最小,稱這樣的二叉樹為最優二叉樹,也稱為哈夫曼樹(HuffmanTree),還有的書翻譯為霍夫曼樹。
●赫夫曼樹是帶權路徑長度最短的樹,權值較大的結點離根較近。

路徑和路徑長度:在一棵樹中,從一個結點往下可以達到的孩子或孫子結點之間的通路,稱為路徑。通路中分支的數目稱為路徑長度。若規定根結點的層數為1,則從根結點到第L層結點的路徑長度為L-1
結點的權及帶權路徑長度:若將樹中結點賦給一個有著某種含義的數值,則這個數值稱為該結點的權。結點的帶權路徑長度為:從根結點到該結點之間的路徑長度與該結點的權的乘積
樹的帶權路徑長度:樹的帶權路徑長度規定為所有葉子結點的帶權路徑長度之和,記為WPL(weighted path length) ,權值越大的結點離根結點越近的二叉樹才是最優二叉樹。
WPL最小的就是赫夫曼樹

構成赫夫曼樹的步驟

1)從小到大進行排序, 將每一個數據, 每個資料都是一個節點,每個節點可以看成是一顆最簡單的二叉樹
2)取出根節 點權值最小的兩顆二叉樹
3)組成一顆新的二叉樹,該新的二叉樹的根節點的權值是前面兩顆二叉樹根節點權值的和
4)再將這顆新的二叉樹,以根節點的權值大小再次排序,不斷重複1-2-3-4的步驟,直到數列中,所有的資料都被處理,就得到一顆赫夫曼樹

package com.xudong.DataStructures;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class HuffmanTreeDemo {
    public static void main(String[] args) {
        int arr[] = {13,7,8,3,29,6,1};
        Node root = createHuffmanTree(arr);

        preOrder(root);

    }

    //前序遍歷的方法
    public static void preOrder(Node root){
        if (root != null){
            root.preOrder();
        }else {
            System.out.println("這是個空樹!");
        }
    }

    //建立赫夫曼樹

    /**
     * @param arr 需要建立成赫夫曼樹的陣列
     * @return 建立好後的赫夫曼樹的root節點
     */
    public static Node createHuffmanTree(int[] arr){
        //將arr的每個元素構成一個Node放入ArrayList中
        List<Node> nodes = new ArrayList<>();
        for (int value : arr){
            nodes.add(new Node(value));
        }

        while (nodes.size() > 1){
            //排序
            Collections.sort(nodes);

            //1.取出權值最小的節點(二叉樹)
            Node leftNode = nodes.get(0);
            //2.取出權值第二小的節點(二叉樹)
            Node rightNode = nodes.get(1);
            //3.建立一個新的二叉樹
            Node parent = new Node(leftNode.value + rightNode.value);
            parent.left = leftNode;
            parent.right = rightNode;
            //4.從ArrayList刪除處理過的二叉樹
            nodes.remove(leftNode);
            nodes.remove(rightNode);
            //5.將parent加入到nodes
            nodes.add(parent);
        }
        //返回赫夫曼樹root節點
        return nodes.get(0);
    }
}

//建立節點類,讓Node物件持續進行Collections集合排序
class Node implements Comparable<Node>{
    int value;//節點權值
    Node left;//指向左子節點
    Node right;//指向右子節點

    //前序遍歷
    public void preOrder(){
        System.out.println(this);
        if (this.left != null){
            this.left.preOrder();
        }
        if (this.right != null){
            this.right.preOrder();
        }
    }

    public Node(int value) {
        this.value = value;
    }

    @Override
    public String toString() {
        return "Node{" +
                "value=" + value +
                '}';
    }

    @Override
    public int compareTo(Node o) {
        //從小到大排序(從大到小加負號)
        return (this.value - o.value);
    }

}

六、二叉排序樹

二叉排序樹BST:(Binary Sort(Search) Tree),對於二叉排序樹的任何一個非葉子節點,要求左子節點的值比當前節點的值小,右子節點的值比當前節點的值大。
特別說明:如果有相同的值,可以將該節點放在左子節點或右子節點

package com.xudong.DataStructures;

public class BinarySortTreeDemo {
    public static void main(String[] args) {
        int[] arr = {7,3,10,12,5,1,9,2};
        BinarySortTree binarySortTree = new BinarySortTree();
        //新增節點
        for (int i = 0; i < arr.length; i++) {
            binarySortTree.add(new Node2(arr[i]));
        }
        //中序遍歷二叉排序樹
        System.out.println("中序遍歷二叉排序樹:");
        binarySortTree.infixOrder();

        //刪除葉子結點
        binarySortTree.delNode(2);
        binarySortTree.delNode(5);
        binarySortTree.delNode(9);
        binarySortTree.delNode(12);
        binarySortTree.delNode(7);
        binarySortTree.delNode(3);
        binarySortTree.delNode(10);
        System.out.println("刪除節點後:");
        binarySortTree.infixOrder();
    }
}

//建立二叉排序樹
class BinarySortTree{
    private Node2 root;

    //查詢要刪除的節點
    public Node2 search(int value){
        if (root == null){
            return null;
        }else {
            return root.search(value);
        }
    }
    //查詢父節點
    public Node2 searchParent(int value){
        if (root == null){
            return null;
        }else {
            return root.searchParent(value);
        }
    }
    //刪除右樹的最小節點
    public int delRightTreeMin(Node2 node){
        Node2 target = node;
        //迴圈查詢左子節點,就找到最小值
        while (target.left != null){
            target = target.left;
        }
        //此時target就指向了最小節點
        delNode(target.value);
        return target.value;
    }

    //刪除節點
    public void delNode(int value){
        if (root == null){
            return;
        }else {
            //找到要刪除的節點
            Node2 targetNode = search(value);
            //如果沒有找到要刪除的節點
            if (targetNode == null){
                return;
            }
            //如果發現二叉樹只有一個節點
            if (root.left == null && root.right == null){
                root = null;
                return;
            }
            //找到targetNode的父節點
            Node2 parent = searchParent(value);

            //(一)如果要刪除的節點是葉子結點
            if (targetNode.left == null && targetNode.right == null){
                //若targetNode是父節點的左子節點
                if (parent.left != null && parent.left.value == value){
                    parent.left = null;
                }else if (parent.right != null && parent.right.value == value){//若targetNode是父節點的右子節點
                    parent.right = null;
                }
            }else if (targetNode.left != null && targetNode.right != null){//(三)刪除右兩顆子樹的節點
                int minVal = delRightTreeMin(targetNode.right);
                targetNode.value = minVal;
            }else {//(二)刪除只有一顆子樹的節點
                //如果要刪除的節點有左子節點
                if (targetNode.left != null){
                    if (parent != null){
                        //如果targetNode 是 Parent的左子節點
                        if (parent.left.value == value){
                            parent.left = targetNode.left;
                        }else {//如果targetNode 是 Parent的右子節點
                            parent.right = targetNode.left;
                        }
                    }else {
                        root = targetNode.left;
                    }
                }else {//如果要刪除的節點有右子節點
                    if (parent != null){
                        //如果targetNode 是 Parent的左子節點
                        if (parent.left.value == value){
                            parent.left = targetNode.right;
                        }else {//如果targetNode 是 Parent的右子節點
                            parent.right = targetNode.right;
                        }
                    }else {
                        root = targetNode.right;
                    }
                }
            }
        }
    }

    //新增節點
    public void add(Node2 node){
        if (root == null){
            root = node;
        }else {
            root.add(node);
        }
    }
    //中序查詢
    public void infixOrder(){
        if (root != null){
            root.infixOrder();
        }else {
            System.out.println("二叉排序樹為空!");
        }
    }
}


//建立Node節點
class Node2{
    int value;
    Node2 left;
    Node2 right;

    public Node2(int value) {
        this.value = value;
    }

    //-------------刪除節點-----------------
    //查詢要刪除的節點
    public Node2 search(int value){
        if (value == this.value){//找到就是該節點
            return this;
        }else if (value < this.value){//如果查詢的值小於當前節點,則向左遞迴查詢
            //若左子節點為空
            if (this.left == null){
                return null;
            }
            return this.left.search(value);
        }else {//如果查詢的值不小於當前節點,則向右子樹遞迴查詢
            if (this.right == null){
                return null;
            }
            return this.right.search(value);
        }
    }
    //查詢要刪除節點的父節點
    public Node2 searchParent(int value){//返回的是要刪除節點的父節點
        //如果當前節點就是要刪除的節點的父節點,那麼返回
        if ((this.left != null && this.left.value == value) || (this.right != null && this.right.value == value)){
            return this;
        }else {
            //如果查詢的值小於當前節點的值,並且當前節點的左子節點不為空
            if (value < this.value && this.left != null){
                return this.left.searchParent(value);//向左子樹遞迴查詢
            }else if (value >= this.value && this.right != null){
                return this.right.searchParent(value);//向右子樹遞迴查詢
            }else {
                return null;//沒有找到就返回
            }
        }

    }
    //



    //以二叉排序樹的方式新增節點
    public void add(Node2 node){
        if (node == null){
            return;
        }
        //判斷當前節點的值與當前子樹根節點的關係
        if (node.value < this.value){
            //如果當前左子樹節點為空
            if (this.left == null){
                this.left = node;
            }else {
                //遞迴的向左子樹新增
                this.left.add(node);
            }
        }else {
            //如果當前右子樹節點為空
            if (this.right == null){
                this.right = node;
            }else {
                //遞迴的向右子樹新增
                this.right.add(node);
            }
        }
    }

    @Override
    public String toString() {
        return "Node2{" +
                "value=" + value +
                '}';
    }

    //中序遍歷
    public void infixOrder(){
        //遞歸向左子樹中序遍歷
        if (this.left != null){
            this.left.infixOrder();
        }
        //輸出父節點
        System.out.println(this);
        //遞歸向右子樹中序遍歷
        if (this.right != null){
            this.right.infixOrder();
        }
    }
}

七、平衡二叉樹(AVL樹)

平衡二叉樹介紹

在二叉排序樹的基礎上實現
平衡二叉樹也叫平衡二叉搜尋樹(Self-balancing binary searchtree)又被稱為AVL樹,可以保證查詢效率較高
●具有以下特點:它是一棵空樹或它的根節點左右兩個子樹的高度差的絕對值不超過1,並且左右兩個子樹都是一棵平衡二叉樹。平衡二叉樹的常用實現方法有紅黑樹、AVL、替罪羊樹、Treap、 伸展樹等。

左旋轉

右旋轉

雙旋轉

➢當符合右旋轉條件時,它的左子樹的右子樹高度大於它左子樹的高度時,就先對當前節點的左節點進行左旋轉,再對當前節點進行右旋轉
➢當符合左旋轉條件時,它的右子樹的左子樹高度大於它右子樹的高度時,就先對當前節點的右節點進行右旋轉,再對當前節點進行左旋轉

package com.xudong.DataStructures;

public class AVLTreeDemo {
    public static void main(String[] args) {
        //int[] arr = {4,3,6,5,7,8};
        //int[] arr = {10,12,8,9,7,6};
        int[] arr = {10,11,7,6,8,9};
        AVLTree avlTree = new AVLTree();
        //新增節點
        for (int i = 0; i < arr.length; i++) {
            avlTree.add(new Node3(arr[i]));
        }

        System.out.println("中序遍歷:");
        avlTree.infixOrder();

        System.out.println("在平衡處理之後:");
        System.out.println("樹的高度:" + avlTree.getRoot().height());
        System.out.println("樹的左子樹高度:" + avlTree.getRoot().leftHeight());
        System.out.println("樹的右子樹高度:" + avlTree.getRoot().rightHeight());
        System.out.println("當前根節點:" + avlTree.getRoot());

    }
}

//建立AVL樹
class AVLTree{
    private Node3 root;

    public Node3 getRoot() {
        return root;
    }

    //查詢要刪除的節點
    public Node3 search(int value){
        if (root == null){
            return null;
        }else {
            return root.search(value);
        }
    }
    //查詢父節點
    public Node3 searchParent(int value){
        if (root == null){
            return null;
        }else {
            return root.searchParent(value);
        }
    }
    //刪除右樹的最小節點
    public int delRightTreeMin(Node3 node){
        Node3 target = node;
        //迴圈查詢左子節點,就找到最小值
        while (target.left != null){
            target = target.left;
        }
        //此時target就指向了最小節點
        delNode(target.value);
        return target.value;
    }

    //刪除節點
    public void delNode(int value){
        if (root == null){
            return;
        }else {
            //找到要刪除的節點
            Node3 targetNode = search(value);
            //如果沒有找到要刪除的節點
            if (targetNode == null){
                return;
            }
            //如果發現二叉樹只有一個節點
            if (root.left == null && root.right == null){
                root = null;
                return;
            }
            //找到targetNode的父節點
            Node3 parent = searchParent(value);

            //(一)如果要刪除的節點是葉子結點
            if (targetNode.left == null && targetNode.right == null){
                //若targetNode是父節點的左子節點
                if (parent.left != null && parent.left.value == value){
                    parent.left = null;
                }else if (parent.right != null && parent.right.value == value){//若targetNode是父節點的右子節點
                    parent.right = null;
                }
            }else if (targetNode.left != null && targetNode.right != null){//(三)刪除右兩顆子樹的節點
                int minVal = delRightTreeMin(targetNode.right);
                targetNode.value = minVal;
            }else {//(二)刪除只有一顆子樹的節點
                //如果要刪除的節點有左子節點
                if (targetNode.left != null){
                    if (parent != null){
                        //如果targetNode 是 Parent的左子節點
                        if (parent.left.value == value){
                            parent.left = targetNode.left;
                        }else {//如果targetNode 是 Parent的右子節點
                            parent.right = targetNode.left;
                        }
                    }else {
                        root = targetNode.left;
                    }
                }else {//如果要刪除的節點有右子節點
                    if (parent != null){
                        //如果targetNode 是 Parent的左子節點
                        if (parent.left.value == value){
                            parent.left = targetNode.right;
                        }else {//如果targetNode 是 Parent的右子節點
                            parent.right = targetNode.right;
                        }
                    }else {
                        root = targetNode.right;
                    }
                }
            }
        }
    }

    //新增節點
    public void add(Node3 node){
        if (root == null){
            root = node;
        }else {
            root.add(node);
        }
    }
    //中序查詢
    public void infixOrder(){
        if (root != null){
            root.infixOrder();
        }else {
            System.out.println("二叉排序樹為空!");
        }
    }
}


//建立Node節點
class Node3{
    int value;
    Node3 left;
    Node3 right;


    public Node3(int value) {
        this.value = value;
    }

    //找到以根節點為節點的樹高度
    public int height(){
        return Math.max(left == null ? 0 : left.height(),right == null ? 0 : right.height()) + 1;
    }

    //返回左子樹的高度
    public int leftHeight(){
        if (left == null){
            return 0;
        }
        return left.height();
    }
    //返回左子樹的高度
    public int rightHeight(){
        if (right == null){
            return 0;
        }
        return right.height();
    }

    //左旋轉的方法
    private void leftRotate(){
        //建立新的節點,以當前根節點的值
        Node3 newNode = new Node3(value);
        //把新節點的左子樹設定成當前節點的左子樹
        newNode.left = left;
        //把新的節點的右子樹設定成當前節點右子樹的左子樹
        newNode.right = right.left;
        //把當前結點的值替換成右子節點的值
        value = right.value;
        //把當前節點的右子樹設定成當前節點右子樹的右子樹
        right = right.right;
        //把當前節點的左子節點設定成新的節點
        left = newNode;
    }
    //右旋轉
    private void rightRotate(){
        Node3 newNode = new Node3(value);
        newNode.right = right;
        newNode.left = left.right;
        value = left.value;
        left = left.left;
        right = newNode;
    }

    //-------------刪除節點-----------------
    //查詢要刪除的節點
    public Node3 search(int value){
        if (value == this.value){//找到就是該節點
            return this;
        }else if (value < this.value){//如果查詢的值小於當前節點,則向左遞迴查詢
            //若左子節點為空
            if (this.left == null){
                return null;
            }
            return this.left.search(value);
        }else {//如果查詢的值不小於當前節點,則向右子樹遞迴查詢
            if (this.right == null){
                return null;
            }
            return this.right.search(value);
        }
    }
    //查詢要刪除節點的父節點
    public Node3 searchParent(int value){//返回的是要刪除節點的父節點
        //如果當前節點就是要刪除的節點的父節點,那麼返回
        if ((this.left != null && this.left.value == value) || (this.right != null && this.right.value == value)){
            return this;
        }else {
            //如果查詢的值小於當前節點的值,並且當前節點的左子節點不為空
            if (value < this.value && this.left != null){
                return this.left.searchParent(value);//向左子樹遞迴查詢
            }else if (value >= this.value && this.right != null){
                return this.right.searchParent(value);//向右子樹遞迴查詢
            }else {
                return null;//沒有找到就返回
            }
        }

    }
    //



    //以二叉排序樹的方式新增節點
    public void add(Node3 node){
        if (node == null){
            return;
        }
        //判斷當前節點的值與當前子樹根節點的關係
        if (node.value < this.value){
            //如果當前左子樹節點為空
            if (this.left == null){
                this.left = node;
            }else {
                //遞迴的向左子樹新增
                this.left.add(node);
            }
        }else {
            //如果當前右子樹節點為空
            if (this.right == null){
                this.right = node;
            }else {
                //遞迴的向右子樹新增
                this.right.add(node);
            }
        }
        //當新增完一個節點後,(右-左)子樹高度差大於1時,左旋轉
        if (rightHeight() - leftHeight() > 1){
            //如果它的右子樹的左子樹高度大於它的右子樹高度
            if (right != null && right.leftHeight() > right.rightHeight()){
                //先對當前節點的右節點(右子樹)進行右旋轉
                right.rightRotate();
                //再對當前節點進行左旋轉
                leftRotate();
            }else {
                leftRotate();
            }
            return;
        }
        //當新增完一個節點後,(左-右)子樹高度差大於1時,左旋轉
        if (leftHeight() - rightHeight() > 1){
            //如果它的左子樹的右子樹高度大於它的左子樹高度
            if (left != null && left.rightHeight() > left.leftHeight()){
                //先對當前節點的左節點(左子樹)進行左旋轉
                left.leftRotate();
                //再對當前節點進行右旋轉
                rightRotate();
            }else {
                rightRotate();
            }
        }
    }

    @Override
    public String toString() {
        return "Node2{" +
                "value=" + value +
                '}';
    }

    //中序遍歷
    public void infixOrder(){
        //遞歸向左子樹中序遍歷
        if (this.left != null){
            this.left.infixOrder();
        }
        //輸出父節點
        System.out.println(this);
        //遞歸向右子樹中序遍歷
        if (this.right != null){
            this.right.infixOrder();
        }
    }
}

八、多路查詢樹

●二叉樹需要載入到記憶體的,如果二叉樹的節點少,沒有什麼問題,但是如果二叉樹的節點很多(此如1億),就存在如下問題

問題1:在構建叉樹時,需要多次進行1/o操作海量資料存在資料庫或檔案中),節點海量,構建=叉樹時,速度有影響
問題2:節點海量,也會造成二叉樹的高度很大,會降低操作速度

1. 多叉樹

●在二叉樹中,每個節點有資料項,最多有兩個子節點。如果允許每個節點可以有更多的資料項和更多的子節點,就是多叉樹(multiwaytree)
●後面的2-3樹,2-3-4樹就是多叉樹,多叉樹通過重新組織節點,減少樹的高度,能對二叉樹進行優化。

2. B樹

●如圖B樹通過重新組織節點,降低了樹的高度

●檔案系統及資料庫系統的設計者利用了磁碟預讀原理,將一個節點的大小設為等於一個頁(頁得大小通常為4k),這樣每個節點只需要一次I/O就可以完全載入
●將樹的度M設定為1024,在600億個元素中最多隻需要4次I/O操作就可以讀取到想要的元素, B樹(廣泛應用於檔案儲存系統以及資料庫系統中)

3. B+樹

B+樹是B樹的變體,也是一種多路搜尋樹

4.B*樹

B*樹是B+樹的變體,在B+樹的非根和非葉子節點再增加指向兄弟的指標

5. 2-3樹

2-3樹是最簡單的B樹結構
●2-3樹的所有葉子節點都在同一層(只要是B樹都滿足這個條件)
●有兩個子節點的節點叫二節點,二節點要麼沒有子節點,要麼有兩個子節點.
●有三個子節點的節點叫三節點,三節點要麼沒有子節點,要麼有三個子節點.
●2-3樹是由二節點和三節點構成的樹。