Java 樹結構實際應用 四(平衡二叉樹/AVL樹)

十四lin 發表於 2021-03-16
平衡二叉樹(AVL 樹)
1 看一個案例(說明二叉排序樹可能的問題)
給你一個數列{1,2,3,4,5,6},要求建立一顆二叉排序樹(BST), 並分析問題所在.
 左邊 BST 存在的問題分析:
1) 左子樹全部為空,從形式上看,更像一個單連結串列.
2) 插入速度沒有影響
3) 查詢速度明顯降低(因為需要依次比較), 不能發揮 BST
的優勢,因為每次還需要比較左子樹,其查詢速度比
單連結串列還慢
4) 解決方案-平衡二叉樹(AVL)
 
2 基本介紹
1) 平衡二叉樹也叫平衡二叉搜尋樹(Self-balancing binary search tree)又被稱為 AVL 樹, 可以保證查詢效率較高。
2) 具有以下特點:它是一 棵空樹或它的左右兩個子樹的高度差的絕對值不超過 1,並且左右兩個子樹都是一棵
平衡二叉樹。平衡二叉樹的常用實現方法有紅黑樹、AVL、替罪羊樹、Treap、伸展樹等。
3) 舉例說明, 看看下面哪些 AVL 樹, 為什麼?
Java 樹結構實際應用 四(平衡二叉樹/AVL樹)
3 應用案例-單旋轉(左旋轉)
1) 要求: 給你一個數列,建立出對應的平衡二叉樹.數列 {4,3,6,5,7,8}
2) 思路分析(示意圖)

Java 樹結構實際應用 四(平衡二叉樹/AVL樹)

 

 

3)程式碼實現

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

 

4 應用案例-單旋轉(右旋轉)
1) 要求: 給你一個數列,建立出對應的平衡二叉樹.數列 {10,12, 8, 9, 7, 6}
2) 思路分析(示意圖)
Java 樹結構實際應用 四(平衡二叉樹/AVL樹)

 

 3)程式碼實現

// 右旋轉
    private void rightRotate() {
        SNode newNode = new SNode(value);
        newNode.right = right;
        newNode.left = left.right;
        value = left.value;
        left = left.left;
        right = newNode;
        
    }

 

5 應用案例-雙旋轉
前面的兩個數列,進行單旋轉(即一次旋轉)就可以將非平衡二叉樹轉成平衡二叉樹,但是在某些情況下,單旋轉
不能完成平衡二叉樹的轉換。比如數列
int[] arr = { 10, 11, 7, 6, 8, 9 }; 執行原來的程式碼可以看到,並沒有轉成 AVL 樹.
int[] arr = {2,1,6,5,7,3}; // 執行原來的程式碼可以看到,並沒有轉成 AVL 樹
1) 問題分析
Java 樹結構實際應用 四(平衡二叉樹/AVL樹)
2) 解決思路分析
1. 當符號右旋轉的條件時
2. 如果它的左子樹的右子樹高度大於它的左子樹的高度
3. 先對當前這個結點的左節點進行左旋轉
4. 在對當前結點進行右旋轉的操作即可
3) 程式碼實現[AVL 樹的彙總程式碼(完整程式碼)]
package com.lin.avltree_0316;

import javax.security.auth.kerberos.KerberosKey;

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 SNode(arr[i]));
        }
        
        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("root = " + avlTree.getRoot());
        System.out.println("root.left = " + avlTree.getRoot().left);
        System.out.println("root.left.left = " + avlTree.getRoot().left.left);
    }
}


class AVLTree{
    private SNode root;
    // 查詢要刪除的節點
    public SNode getRoot() {
        return root;
    }
    public SNode searchDelNode(int value) {
        if(root == null) {
            return null;
        } else {
            return root.searchDelNode(value);
        }
    }
    // 查詢要刪除節點的父節點
    public SNode searchParent(int value) {
        if(root == null) {
            return null;
        } else {
            return root.searchParent(value);
        }
    }
    /**
     * @param node 傳入的節點(當作二叉排序樹的根節點)
     * @return 返回的以node為根節點的二叉排序樹的最小節點的值
     */
    public int delRightTreeMin(SNode node) {
        SNode target = node;
        //    迴圈地查詢左節點,就會找到最小值
        while(target.left != null) {
            target = target.left;
        }
        delNode(target.value);// !!!!
        return target.value;//   !!!!!
    }
    
    // 刪除節點
    public void delNode(int value) {
        if(root == null) {
            return;
        } else {
            //     找刪除節點
            SNode targetNode = searchDelNode(value);
            //     沒有找到
            if(targetNode == null) {
                return;
            }
            //    如果發現當前這棵二叉樹只有一個節點
            if(root.left == null && root.right == null) {
                root = null;
                return;
            }
            //     去找到targetNode的父節點
            SNode 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) {
                    parent.right = null;
                }
            } else if(targetNode.left != null && targetNode.right != null) { //    有左右子節點
                int delRightTreeMin = delRightTreeMin(targetNode.right);
                targetNode.value = delRightTreeMin;
            } else {//    只有一個子節點
                //     要刪除的節點只有左節點
                if(targetNode.left !=  null) {
                    if(parent != null) {
                        //     如果targetNode是parent的左子節點
                        if(parent.left.value == value) {
                            parent.left = targetNode.left;
                        } else {
                            parent.right = targetNode.left;
                        }
                    } else {
                        root = targetNode.left;
                    }
                } else {//    要刪除的節點有右子節點
                    if(parent != null) {
                        if(parent.left.value == value) {
                            parent.left = targetNode.right;
                        } else {
                            parent.right = targetNode.right;
                        }
                    } else {
                        root = targetNode.right;
                    }
                }
            }
            
            
        }
    }
    // 中序遍歷
    public void infixOrder() {
        if(root == null) {
            System.out.println("空樹!");
        } else {
            root.infixOrder();
        }
    }
    // 新增
    public void add(SNode node) {
        if(root == null) {
            root = node;
        } else {
            root.add(node);
        }
    }
}

class SNode{
    protected int value;
    protected SNode left;
    protected SNode right;
    
    public SNode(int value) {
        // TODO Auto-generated constructor stub
        this.value = value;
    }
    // 返回左子樹的高度
    public int leftHeight() {
        if(left == null) {
            return 0;
        }
        return left.height();
    }
    // 返回右子樹的高度
    public int rightHeight() {
        if(right == null) {
            return 0;
        }
        return right.height();
    }
    // 返回當前節點的高度,以該節點為根節點的樹的高度
    public int height() {
        return Math.max(left == null ? 0: left.height(), right == null ? 0 : right.height()) + 1; 
    }
    
    // 左旋轉
    private void leftRotate() {
        // 建立新的節點,以當前根節點的值
        SNode newNode = new SNode(value);
        // 把新的節點左子樹設定成當前節點的左子樹
        newNode.left = left;
        // 把新節點的右子樹設定成當前節點的右子節點的左子樹
        newNode.right = right.left;
        //    把當前節點的值換為右子節點的值
        value = right.value;
        // 把當前節點的右子樹換成右子樹的右子樹
        right = right.right;
        // 把當前節點的左子樹設定成新節點
        left = newNode;
        
    }
    
    // 右旋轉
    private void rightRotate() {
        SNode newNode = new SNode(value);
        newNode.right = right;
        newNode.left = left.right;
        value = left.value;
        left = left.left;
        right = newNode;
        
    }
    
    @Override
    public String toString() {
        // TODO Auto-generated method stub
        return "Node = [value = " + value + "]";
    }
    
    // 新增節點
    public void add(SNode 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() > rightHeight() ) ) {
                right.rightRotate();
                leftRotate();
            } else {
                leftRotate();
            }
            return;//!!!!
        }
        
        if ((leftHeight() - rightHeight()) > 1) {
            // 如果當前節點的左子樹的右子樹高度大於左子樹的高度
            if (left != null && (left.rightHeight() > left.leftHeight())) {
                // 先對當前節點的左節點進行左旋轉
                left.leftRotate();
                // 再對當前節點進行右旋轉
                rightRotate();
            } else {
                rightRotate();
            }
        }
    }
    // 中序遍歷
    public void infixOrder() {
        if(this.left != null) {
            this.left.infixOrder();
        }
        System.out.println(this);
        if(this.right != null) {
            this.right.infixOrder();
        }
    }
    // 查詢要刪除的節點
    public SNode searchDelNode(int value) {
        if(this.value == value) {
            return this;
        } else if(this.value > value) {
            // 如果左子節點為空
            if(this.left == null) {
                return null;
            }
            return this.left.searchDelNode(value);
        } else {
            if(this.right == null) {
                return null;
            }
            return this.right.searchDelNode(value);
        }
    }
    // 查詢要刪除節點的父節點, 如果沒有則返回null
    public SNode 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;
            }
        }
    }
    
}

Java 樹結構實際應用 四(平衡二叉樹/AVL樹)

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

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

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