數據結構——樹

樹形結構是一種非常重要的非線性的數據結構。樹結構與線性結構不同之處:線性結構中任意一個元素最多只有一個后繼元素,而樹形結構中每個元素可以有多個后繼;線性結構和樹形結構中每個元素最多只有一個前驅元素。這篇文章為本人原創,謝絕轉載。具體的內容目錄如下:
一、樹的屬性
二、二叉樹
三、二叉樹與樹、森林的轉換
四、二叉排序樹
五、平衡二叉樹

一、樹的屬性

樹又一個特定的稱為根的節點,這個節點無前驅節點。樹可以用遞歸的方式來定義,也就是說樹是由若干個子樹構成。如下圖所示,A是根節點,沒有前驅節點,B、C是A的子節點,也是一棵樹。這里要簡單說下樹的一些術語。
1.節點:表示樹中的一個數據元素。如下圖中,A、B、C、D、E、F、G、H、I都是是節點。
2.孩子節點:表示樹中節點的直接后驅節點。如下圖中,A的孩子節點為B、C。
3.雙親節點:表示樹中節點的直接前驅節點,如下圖中,D、E、F的雙親節點是B。
4.兄弟節點:表示具有相同雙親節點的節點。如下圖中,D、E、F是兄弟節點。
5.祖先節點:表示從根節點到該節點的雙親節點都是祖先節點。如下圖中,I的祖先節點為A、B、E。
6.子孫節點:表示該節點下所有孩子節點,包括子節點的子節點。如下圖中,A的子孫節點為B、C、D、E、F、G、H、I。
7.節點的度:該節點的子樹的個數,可以理解為該節點后代的代數。如下圖中,A的度為3,D的度為0。
8.葉子節點:度為0的節點,也就是沒有后驅節點。如下圖中,D、H、I、G都是葉子節點。
9.分支節點:度不為0的節點,可以理解為樹枝,有后驅節點。B、C、E都是分支節點。
10.樹的層數:樹的根節點所在的層樹為1,其他節點的層數等于他的雙親節點的層數+1。如下圖中,A樹的層數為4,B樹的層數為3。
11.樹的深度:表示整棵樹的最大層數,也叫高度。如下圖中,深度為4。
12.森林:零棵樹或有限棵樹互不相交的樹的集合叫森林。如下圖中,將A節點去掉,B樹與C樹可以構成森林。
13.有序樹與無序樹:樹中節點的子樹從左到右是有次序的稱為有序樹,否則為無序樹。


樹結構

二、二叉樹

二叉樹是一顆每個節點有不超過兩個孩子節點的樹。兩顆子樹有左右之分,稱為左子樹和右子樹,左子樹和右子樹都可以為空,如果不為空時,子樹也是一顆二叉樹。二叉樹也有一種遞歸的概念。二叉樹有五種基本形態
1.空樹:根節點為空的二叉樹
2.只有根節點:只有根節點,根節點沒有左右子樹
3.只有左子樹:只有左子樹,沒有右子樹
4.只有右子樹:只有右子樹,沒有左子樹
5.有左右子樹:該節點同時有左右子樹

二叉樹的性質
1.二叉樹第i(i>=1)層上最多有2^(i-1)個節點。
2.深度為k(k>=1)的二叉樹最陡有2^k-1個節點。
3.滿二叉樹:深度為k并且含有2k-1個節點的二叉樹。可以理解為,樹枝節點都有左右子樹。通俗地講,在不增加樹的深度的前提下,無法再多添加一個節點的二叉樹稱為滿二叉樹。

滿二叉樹

4.完全二叉樹:深度為k,有n個節點的二叉樹,當且僅當每個節點的編號與相應滿二叉樹節點順序編號從1~n相對應時,則稱為完全二叉樹。通俗地講,完全二叉樹相當于刪除滿二叉樹的最底層最右邊的連續若干個節點。
完全二叉樹

二叉樹的存儲結構
1.順序存儲結構
將二叉樹中所有節點元素存儲到一維數組中,這是最簡單的順序存儲結構。設一維數組list,list[0]空置不用,從第一個開始,每個數組元素存儲樹的每一個節點元素。按照完全二叉樹編號從上到下,從左到右的順序將每個節點元素存儲到數組中。如下圖所示,第i個節點的雙親節點為i/2,左右孩子節點分別為2i、2i+1。

順序儲存結構

如果二叉樹是一顆單邊二叉樹(只有左子樹或只有右子樹),對于每個二叉樹而言,有插入新元素的可以,所以需要保留存儲空間。如下圖中,紅色表示可以插入新元素,用虛線連接。

單邊二叉樹

下圖表示單邊二叉樹的順序存儲結構,本來只有四個元素,卻要分配8個存儲空間。如果一顆深度為k的單邊二叉樹,至少要2^k個存儲空間,則有2^k-k個空間為空,這樣造成資源浪費。這種存儲結構適合于滿二叉樹和完全二叉樹,一般的二叉樹很少采用這種存儲結構。
單邊二叉樹順序存儲結構

2.鏈表存儲結構
二叉樹的鏈表結構有常見的兩種:二叉鏈表、三叉鏈表。

  • 二叉鏈表:包含一個數據元素,左子樹指針,右子樹指針。可以很容易找到該節點的子孫節點,但是不容易找到其雙親節點。
  • 三叉鏈表:包含一個數據元素,左子樹指針,右子樹指針,還有一個雙親節點指針,方便找出其雙親節點。


    存儲結構

一般樹的存儲結構
1.雙親表示法:存儲父節點的序號,方便于查找父節點。下圖中的左邊為一般的樹形結構,右邊為存儲結構。

雙親表示法

2.孩子表示法:存儲孩子節點的指針,方便于查找孩子節點。下圖中的左邊為一般的樹形結構,右邊為存儲結構。


孩子表示法

3.雙親孩子表示法:結合上述兩種方式。可方便查找雙親和孩子節點。


雙親孩子表示法

4.二叉樹表示法:將一個普通樹轉換為二叉樹,具體規則如下:
-左指針域指向該節點的第一個子節點;
-右指針域指向該節點的第一個兄弟節點;


二叉樹表示法

二叉樹的遍歷
遍歷二叉樹就是以一定的順序訪問每個節點,并且每個節點只能被訪問一次。
假設有一顆二叉樹有7個節點,為了方便理解,為每個沒有同時存在左右子樹的節點補充一個空的節點。如下圖所示,圖中的紅色表示補充的空的節點,外圍虛線表示搜索路徑。

搜索路徑

按照從左到右的方式,可以分為三種遍歷方式
注意:遞歸算法實現簡單,但效率較低,這是因為系統需要維護一個工作棧以保證遞歸方法的正確執行,所有在實際使用中推薦非遞歸方式。
1.先序遍歷:先訪問根節點,再訪問左子樹,再訪問右子樹。如上圖中,第一次搜索經過的節點為A、B、D、G、C、E、F。具體的算法如下:

    /**
     * 二叉樹的先序遍歷(非遞歸)
     * @param root 根節點
     */
    public static void fristOrderTraversalByStack(WLTreeNode root) {
        Stack<WLTreeNode> stack = new Stack<>();
        WLTreeNode node = root;
        //將所有左孩子壓棧
        while (node != null || stack.size() > 0) {
            if(node != null) {
                printTreeNode(node);
                stack.push(node);
                node = node.getLeft();
            }else {
                node = stack.pop();
                node = node.getRight();
            }
        }
    }

    /**
     * 二叉樹的先序遍歷
     * @param root 根節點
     */
    public static void firstOrderTraversal(WLTreeNode root) {
        if(root != null) {
            printTreeNode(root);
            if(root.getLeft() != null) {
                firstOrderTraversal(root.getLeft());
            }
            if(root.getRight() != null) {
                firstOrderTraversal(root.getRight());
            }
        }
    }

2.中序遍歷:先訪問左子樹,再訪問根節點,再訪問右子樹。如上圖中,第二次搜索經過的節點為G、D、B、A、E、C、F。具體的算法如下:

    /**
     * 二叉樹的中序遍歷(非遞歸)
     * @param root 根節點
     */
    public static void inOrderTraversalByStack(WLTreeNode root) {
        Stack<WLTreeNode> stack = new Stack<>();
        WLTreeNode node = root;
        while (node != null || stack.size() > 0) {
            if(node != null) {
                stack.push(node);
                node = node.getLeft();
            }else {
                node = stack.pop();
                printTreeNode(node);
                node = node.getRight();
            }
        }
    }
    /**
     * 二叉樹的中序遍歷
     * @param root 根節點
     */
    public static void inOrderTraversal(WLTreeNode root) {
        if(root != null) {
            if(root.getLeft() != null) {
                inOrderTraversal(root.getLeft());
            }
            printTreeNode(root);
            if(root.getRight() != null) {
                inOrderTraversal(root.getRight());
            }
        }
    }

3.后序遍歷:先訪問左子樹,在訪問右子樹,在訪問根節點。如上圖中,第三次搜索經過的節點為G、D、B、E、F、C、A。具體的算法如下:

/**
     * 二叉樹的后序遍歷(非遞歸)
     * @param root 根節點
     */
    public static void postOrderTraversalByStack(WLTreeNode root) {
        Stack<WLTreeNode> stack = new Stack<>();
        Stack<WLTreeNode> output = new Stack<>();
        WLTreeNode node = root;
        while (node != null || stack.size() > 0) {
            if(node != null) {
                stack.push(node);
                output.push(node);
                node = node.getRight();
            }else {
                node = stack.pop();
                node = node.getLeft();
            }
        }
        while (output.size() > 0) {
            printTreeNode(output.pop());
        }
    }
    /**
     * 二叉樹的后序遍歷
     * @param root 根節點
     */
    public static void postOrderTraversal(WLTreeNode root) {
        if(root != null) {
            if(root.getLeft() != null) {
                postOrderTraversal(root.getLeft());
            }
            if(root.getRight() != null) {
                postOrderTraversal(root.getRight());
            }
            printTreeNode(root);
        }
    }

二叉樹的其他操作

    /**
     * 按層遍歷二叉樹
     * 1.將二叉樹根節點入隊列
     * 2.將隊頭節點出隊列,并判斷此節點算法有左右孩子,如果有,則將其左右孩子入隊列
     * @param root 根節點
     */
    public static void levelTraversal(WLTreeNode root) {
        if(root != null) {
            List<WLTreeNode> list = new ArrayList<>();
            //1.根節點入隊列
            list.add(root);
            while (list.size() > 0) {
                //取出隊頭節點
                WLTreeNode node = list.get(0);
                printTreeNode(node);
                
                list.remove(0);
                //判斷是否有左右孩子
                if(node.getLeft() != null) {
                    list.add(node.getLeft());
                }
                if(node.getRight() != null) {
                    list.add(node.getRight());
                }
            }
        }
    }

    /**
     * 按層遍歷二叉樹 
     * @param root 根節點
     * @param index 第幾個節點,從0開始
     * @return 節點
     */
    public static WLTreeNode findNodeByIndex(WLTreeNode root,int index) {
        if(root != null && index >= 0) {
            List<WLTreeNode> list = new ArrayList<>();
            //根節點入隊列
            list.add(root);
            while (list.size() > 0) {
                //取出隊頭節點
                WLTreeNode node = list.get(0);
                //==0,則找到
                if(index == 0) {
                    return node;
                }
                //彈出隊頭節點
                list.remove(0);
                index --;
                
                //如果該節點有左右節點,則入隊列
                if(node.getLeft() != null) {
                    list.add(node.getLeft());
                }
                if(node.getRight() != null) {
                    list.add(node.getRight());
                }
            }
        }
        return null;
    }

    /**
     * 二叉樹的深度
     * @param root 根節點
     * @return 深度值 根節點深度為1
     */
    public static int depthOfBinaryTree(WLTreeNode root) {
        if(root != null) {
            if(root.getLeft() == null && root.getRight() == null) {
                return 1;
            }
            int leftDepth = depthOfBinaryTree(root.getLeft());
            int rightDepth = depthOfBinaryTree(root.getRight());
            return Math.max(leftDepth, rightDepth)+1;   
        }
        return 0;
    }
    
    /**
     * 二叉樹的寬度是指二叉樹各層結點個數的最大值
     * @param root 根節點
     * @return 深度值 根節點深度為1
     */
    public static int widthOfBinaryTree(WLTreeNode root) {
        if(root != null) {
            List<WLTreeNode>list = new ArrayList<>();
            list.add(root);
            int maxWidth = 1;//已有根節點
            int curWidth = 0;
            while (list.size() > 0) {
                curWidth = list.size();
                if(maxWidth < curWidth) {
                    maxWidth = curWidth;
                }
                
                for (int i = 0; i < curWidth; i++) {
                    WLTreeNode node = list.get(0);
                    list.remove(0);
                    if(node.getLeft() != null) {
                        list.add(node.getLeft());
                    }
                    if(node.getRight() != null) {
                        list.add(node.getRight());
                    }
                }
            }
            return maxWidth;
        }
        return 0;
    }
    
    
    /**
     * 二叉樹的節點個數
     * @param root 根節點
     * @return 全部節點個數
     */
    public static int numberOfBinaryTree(WLTreeNode root) {
        if(root != null) {
            int count = 0;
            List<WLTreeNode>list = new ArrayList<>();
            list.add(root);
            while (list.size() > 0) {
                WLTreeNode node = list.get(0);
                list.remove(0);
                
                if(node.getLeft() != null) {
                    list.add(node.getLeft());
                }
                if(node.getRight() != null) {
                    list.add(node.getRight());
                }
                count++;
            }
            return count;
        }
        return 0;
    }
    
    /**
     * 二叉樹某一層的全部節點個數
     * @param root 根節點
     * @return 節點個數
     */
    public static int numberOfNodeOnLevel(WLTreeNode root,int level) {
        if(root != null && level >= 0) {
            //根節點只有一個節點
            if(level == 1) {
                return 1;
            }
            return numberOfNodeOnLevel(root.getLeft(), level-1) + numberOfNodeOnLevel(root.getRight(), level-1);
        }
        return 0;
    }
    
    /**
     * 二叉樹的葉子節點個數
     * @param root 根節點
     * @return
     */
    public static int numberOfLeafNode(WLTreeNode root) {
        if(root != null) {
            if(root.getLeft() == null && root.getRight() == null) {
                return 1;
            }
            return numberOfLeafNode(root.getLeft()) + numberOfLeafNode(root.getRight());
        }
        return 0;
    }
    
    /**
     * 二叉樹中某個節點到根節點的路徑
     * 1.將根節點壓入棧,再從左子樹中查找,如果沒找到,再從右子樹中查找,如果沒找到,則彈出根節點,再遍歷棧中上一個節點
     * 2.如果找到,則棧中存放的節點就是路徑經過的節點
     * @param root 根節點
     * @param node 起點節點
     * @return 路徑數組
     */
    public static List<WLTreeNode> pathOfTreeNode(WLTreeNode root,WLTreeNode node){
        List<WLTreeNode>list = new ArrayList<>();
        boolean isFound = isFoundTreeNode(root, node, list);
        System.out.println("isFound = "+isFound);
        return list;
    }
    
    /**
     * 查找二叉樹中是否包含該節點
     * @param root 根節點 
     * @param node 待查找節點
     * @param list 路徑數組
     * @return 是否找到
     */
    public static boolean isFoundTreeNode(WLTreeNode root,WLTreeNode node,List<WLTreeNode>list) {

        if(root == null || node == null) {
            return false;
        }
        
        //判斷是否為當前節點
        if(root.getValue() == node.getValue()) {
            return true;
        }
        System.out.println("find root value = "+root.getValue());
        //將根節點壓入棧
        list.add(root);
        //先從左子樹中查找
        boolean isFound = isFoundTreeNode(root.getLeft(),node,list);
        if(!isFound) {
            //左子樹中沒找到,則從右子樹中查找
            isFound = isFoundTreeNode(root.getRight(),node,list);
        }
        //左右子樹中都沒找到,則彈出此根節點 
        if(!isFound) {
            list.remove(list.size()-1);
        }
        return isFound;
    }
    
    /**
     * 是否為滿二叉樹
     * 除了葉子節點,每個節點都有左右子節點
     * @param root 根節點
     * @return 
     */
    public static boolean isFullBinaryTree(WLTreeNode root) {
        if(root != null) {
            int depth = depthOfBinaryTree(root);
            int nodeCount = numberOfBinaryTree(root);
            if(nodeCount == Math.pow(2, depth)-1) {
                return true;
            }
        }
        return false;
    }
    
    /**
     * 是否為平衡二叉樹
     * 定義:一顆空樹或左右子樹的高度差的絕對值不超過1,并且左右子樹都是一個平衡二叉樹
     * @param root 根節點
     * @return
     */
    public static boolean isAVLBinaryTree(WLTreeNode root) {
        //一顆空樹
        if(root == null) {
            avlHeight = 0;
            return false;
        }
        //只有根節點
        if(root.getLeft() == null && root.getRight() == null) {
            avlHeight = 1;
            return true;
        }
        
        boolean isLeft = isAVLBinaryTree(root.getLeft());
        int leftHeight = avlHeight;
        boolean isRight = isAVLBinaryTree(root.getRight());
        int rightHeight = avlHeight;
        
        avlHeight = Math.max(leftHeight, rightHeight);
        if(isLeft && isRight && Math.abs(leftHeight-rightHeight) <= 1) {
            return true;
        }
        return false;
    }
    
    
    /**
     * 反轉二叉樹 非遞歸
     * @param root
     * @return
     */
    public static WLTreeNode invertBinaryTreeByQueue(WLTreeNode root) {
        if(root == null) {
            return root;
        }
        List<WLTreeNode>list = new ArrayList<>();
        list.add(root);
        while (list.size() > 0) {
            WLTreeNode node = list.get(0);
            list.remove(0);
            
            WLTreeNode tmpNode = node.getLeft();
            node.setLeft(node.getRight());
            node.setRight(tmpNode);
            
            if(node.left != null) {
                list.add(node.getLeft());
            }
            if(node.right != null) {
                list.add(node.getRight());
            }
        }
        
        return root;
    }
    /**
     * 打印樹形結構
     */
    private static void printTreeNode(WLTreeNode node) {
        System.out.println(node.getValue());
    }

三、二叉樹與樹、森林的轉換

1.樹轉換為二叉樹
(1)加線:在樹中所有相鄰的兄弟節點之間加一條線。
(2)抹線:對樹中的每個節點,只保留與第一個孩子節點之間的連線,刪除與其他孩子節點之間的連線。
(3)調整:以每個節點為軸心,將其右側所有節點按順時針轉動45度,使之稱為一顆二叉樹。


樹轉換為二叉樹

2.二叉樹轉換為樹
樹與二叉樹之間的轉換是可逆的,將二叉樹轉換為樹也分為三步:
(1)加線:若某個節點是其雙親節點的左孩子,則把該節點的所有右子孫節點都與該節點的雙親節點用線連起來。
(2)抹線:刪除二叉樹中所有雙親節點與右孩子節點之間的連線。
(3)調整:把虛線改為實線,把節點按層次排列。


二叉樹轉換為樹

3.森林轉換為二叉樹
森林是樹的集合,樹可以和二叉樹相互轉換,森林也可以,只不過要麻煩一點點。
(1)轉換:將森林中的每棵子樹轉換為二叉樹,給每棵子樹編號為1,2,3,......n棵二叉樹。
(2)加線:在所有的二叉樹的根節點之間加一條線,也就是從第二棵二叉樹開始,將第i+1棵二叉樹作為第i棵樹的右子樹。


森林轉換為二叉樹

4.二叉樹轉換為森林
(1)抹線:從二叉樹的根節點開始搜索所有的右子孫節點,將其之間的連線抹去,這樣就得到包含若干棵二叉樹的森林。
(2)還原:將每棵二叉樹還原為一般的樹。


二叉樹轉換為森林

四、二叉排序樹

二叉排序樹,顧名思義就是用二叉樹實現排序功能,是一種特殊的二叉樹,需要滿足一下性質
1.若左子樹不為空,則左子樹中所有節點的數據值均小于根節點的數據值。
2.若右子樹不為空,則右子樹中所有節點的數據值均大于根接待你的數據值。
3.左子樹和右子樹也分別是二叉排序樹。
面試的時候可能會遇到這樣的問題,給出一組數字,按照順序插入,畫出二叉排序樹的結構圖。下面舉個例子,畫出10,20,16,6,4,8,30,55這組數字的二叉排序樹的結構。這里要說明一下插入的原理,插入的元素比上一個元素大,則插入在元素的右邊,否則插入在元素的左邊。


二叉排序樹添加過程

二叉排序樹的插入
二叉排序樹是動態查找,動態插入的樹表,不是一次完成的。在查找的過程中,當書中不存在待插入的值時才進行插入操作。新插入的節點一定是葉子節點。

    /**
     * 二叉排序樹的插入節點操作
     * @param root 樹的根節點
     * @param value 插入值
     * @return 插入的新節點
     */
    public static WLTreeNode binaryTreeInsertion(WLTreeNode root,int value) {
        //創建新節點
        WLTreeNode node = new WLTreeNode(null, null, value);
        if(root == null) {
            return node;
        }
        
        WLTreeNode pNode = root;
        WLTreeNode fNode = null;//找出新節點的直接父節點
        
        //遍歷二叉樹,查找新節點添加位置
        while (pNode != null) {
            fNode = pNode;
            if (value < pNode.getValue()) {
                pNode = pNode.getLeft();
            }else if(value > pNode.getValue()){
                pNode = pNode.getRight();
            }else {
                break;
            }
        }
        if(value < fNode.getValue()) {
            fNode.setLeft(node);
        }else if(value > fNode.getValue()){
            fNode.setRight(node);
        }
        return node;
    }

二叉排序樹的查找
二叉排序樹的查找就是遍歷每個節點,比較關鍵字與每個節點的值是否相等,如果相等,則查找成功;否則在根節點的左子樹或右子樹中繼續查找,這是一個遞歸的過程。
(1)如果二叉排序樹為空,則查找失敗,返回空指針。
(2)如果查找關鍵字和根節點值相等,則查找成功,返回根節點指針。
(3)如果查找關鍵字小于根節點值,則在根節點的左子樹中查找。
(4)如果查找關鍵字大于根節點值,則在根節點的右子樹中查找。

    /**
     * 二叉排序樹的查找
     * @param root 根節點
     * @param x 關鍵字
     * @return 關鍵字節點
     */
    public static WLTreeNode binaryTreeSearch(WLTreeNode root,int x) {
        if(root == null) {
            return null;
        }else if (x == root.getValue()) {
            return root;
        }else if (x < root.getValue()) {
            return binaryTreeSearch(root.getLeft(), x);
        }else {
            return binaryTreeSearch(root.getRight(), x);
        }
    }
    /**
     * 二叉排序樹的查找 非遞歸
     * @param root 根節點
     * @param x 關鍵字
     * @return 關鍵字節點
     */
    public static WLTreeNode binaryTreeSearchByStack(WLTreeNode root,int x) {
        if(root != null) {
            Stack<WLTreeNode> stack = new Stack<>();
            while (root != null) {
                if(root.getValue() == x) {
                    return root;
                }else if (root.getValue() > x) {
                    stack.push(root);
                    root = root.getLeft();
                }else {
                    stack.push(root);
                    root = root.getRight();
                }
            }
        }
        return null;
    }

二叉排序樹的刪除
二叉排序樹的刪除操作是在查找的基礎上進行的,也就是在二叉排序樹中查找是否存在待刪除關鍵字的節點,如果存在,則刪除。二叉排序樹的刪除節點比較麻煩,可以分為一下四種情況:
(1)待刪除節點為葉子節點,則直接刪除

刪除葉子節點

(2)待刪除節點只有左子樹,沒有右子樹,則用左子樹代替該節點


刪除左子樹

(3)待刪除節點只有右子樹,沒有左子樹,則用右子樹代替該節點


刪除右子樹

(4)待刪除節點有左右子樹,找出右子樹中最小值的節點,將最小值節點替換該節點,


刪除左右子樹

具體的實現方法如下:

    /**
     * 二叉排序樹刪除節點操作
     * 1.如果節點為葉子節點,直接刪除
     * 2.如果節點只有左子樹,無右子樹,則用左子樹代替該節點
     * 3.如果節點只有右子樹,無左子樹,則用右子樹代替該節點
     * 4.如果節點有左、右子樹,則找出其右子樹最小值節點代替該節點
     * @param root 樹的根節點
     * @param value 刪除值
     * @return 刪除后的樹
     */
    public static WLTreeNode binaryTreeDelete(WLTreeNode root,int value) {
        if(root != null) {
            WLTreeNode pNode = root;//待刪除節點
            WLTreeNode fNode = null;//fNode為pNode的直接父節點
            //找出pNode節點和pNode的直接父節點
            while(pNode != null) {
                if(value == pNode.getValue()) {
                    break;
                }else if (value < pNode.getValue()) {
                    fNode = pNode;
                    pNode = pNode.getLeft();
                }else {
                    fNode = pNode;
                    pNode = pNode.getRight();
                }
            }
            
            //沒有該節點
            if(pNode == null) {
                return root;
            }
            
            if(pNode.getLeft() == null && pNode.getRight() == null) {//待刪除節點為葉子節點
                //判斷是否為根節點
                if(fNode != null) {
                    if (pNode == fNode.getLeft()) {
                        fNode.setLeft(pNode.getLeft());
                    }else if (pNode == fNode.getRight()) {
                        fNode.setRight(pNode.getRight());
                    }
                    fNode = null;
                }else {
                    root = null;
                }
                pNode = null;
            }else if (pNode.getLeft() != null && pNode.getRight() == null) {//待刪除節點只有左子樹,沒有右子樹 
                if(fNode != null) {
                    if(pNode == fNode.getLeft()) {
                        fNode.setLeft(pNode.getLeft());
                    }else if (pNode == fNode.getRight()) {
                        fNode.setRight(pNode.getLeft());
                    }
                    fNode = null;
                }else {
                    root = root.getLeft();
                }
                pNode = null;
            }else if (pNode.getLeft() == null && pNode.getRight() != null) {//待刪除節點只有右子樹,沒有左子樹
                if(fNode != null) {
                    if(pNode == fNode.getLeft()) {
                        fNode.setLeft(pNode.getRight());
                    }else if (pNode == fNode.getRight()) {
                        fNode.setRight(pNode.getRight());
                    }
                }else {
                    root = root.getRight();
                }
            }else {//待刪除節點同時有左右子樹
                
                //1.查找待刪除節點的直接后驅節點,也就是該節點右子樹中最小值節點qNode
                //2.將qNode節點替換待刪除節點
                
                WLTreeNode sNode = pNode.getRight();//直接后驅節點
                WLTreeNode qNode = sNode;//直接后驅節點的直接父節點
                while (sNode.getLeft() != null) {
                    qNode = sNode;
                    sNode = sNode.getLeft();
                }
                
                if(sNode == qNode) {
                    fNode.setRight(qNode);
                    qNode.setLeft(pNode.getLeft());
                }else {
                    qNode.setLeft(sNode.getLeft());
                    fNode.setRight(sNode);
                    sNode.setLeft(pNode.getLeft());
                    sNode.setRight(pNode.getRight());
                }
                
                pNode = null;
                qNode = null;
                fNode = null;
                sNode = null;
            }
            
             return root;
        }
        return root;
    }

五、平衡二叉樹

在說平衡二叉樹之前,需要先講講二叉排序樹的查找的時間復雜度。對于含有n個節點的二叉排序樹,查找關鍵字節點是從根節點開始,所需要比較次數就是該節點所在的層數。例如下圖中,分別查找關鍵字10,6,16,55,查找的次數分別為1,2,3,4。假設每個節點的查找概率相等,所以平均查找次數為(1+22+34+4)/8 ≈ 2.6。如果是一顆單邊二叉排序樹(全部只有左子樹或只有右子樹),則平均查找次數為(1+2+3+4+6+7+8)/8 = 4.5。
總結一下,在最好的情況下,一顆包含n個節點的二叉排序樹的查找復雜度為O(log2(n));在最壞的情況下,復雜度為O(n)。一般的情況下,復雜度介于O(log2(n))到O(n)之間。因此才需要構造一顆平衡的二叉排序樹。

二叉排序樹復雜度

平衡二叉樹的定義
平衡二叉樹又叫AVL樹,具有以下性質
1.左子樹和右子樹的深度的差值的絕對值不大于1
2.左子樹和右子樹都是平衡二叉樹
以上的第二點很好理解,第一點怎么理解呢?如下圖中,左邊是平衡二叉樹,右邊是非平衡二叉樹。圖中節點右邊是該節點左右子樹深度的差值。

AVL樹

平衡二叉樹的調整
在構建二叉排序樹過程中,每插入一個新的節點,先判斷插入是否會破壞樹的平衡性。如果會,則找出最小不平衡子樹,在保持二叉排序樹的前提下,調整最小平衡子樹中個節點之間的關系。可以根據失衡的原因分為以下四種類型:
(1)LL型調整
假設在A節點左孩子B的左子樹上插入新的節點,導致A二叉排序樹失去平衡。如下圖中,插入值為2的新節點,A節點的值為10,B節點的值為6,此時A樹的平衡差值從1變為2,從而失去了平衡。
調整操作:向右順時針旋轉一次,就是將B作為根節點,A作為B的有孩子,同時將B的原來的右子樹作為A節點的左子樹。
LL型調整

(2)RR型調整
在A節點右孩子B的右子樹上插入新的節點,導致A二叉排序樹失去平衡。如下圖中,插入值為60的新節點,A節點的值為10,B節點的值為20,此時A、B樹的平衡差值從1變為2,從而失去了平衡。
調整操作:向左逆時針旋轉一次,就是將B作為根節點,A作為B的左孩子,同時B原來的左子樹調整為A子樹的右子樹。


RR型調整

(3)LR型調整
在A節點的左孩子B的右子樹C上插入新節點導致二叉排序樹A失去平衡。如下圖中,插入值為9的新節點,A節點的值為10,B節點的值為5,C節點值為7,此時A、B、C樹的平衡差值從1變為2,從而失去平衡。
調整操作:進行兩次選轉,先順時針在逆時針。就是將C作為根節點,A作為C的右孩子,B作為C的左孩子,同時調整C原來的兩棵子樹。將C原來的左子樹作為B的右子樹,C的右子樹作為A的左子樹。


LR型調整

(4)RL型調整
在A的右孩子B的左子樹C上插入新的節點導致A樹失去平衡。如下圖中,插入值為9的新節點,A節點的值為10,B節點的值為20,C節點的值為16,此時A、B樹的平衡差值從1變為2,C樹的平衡差值從0變為1,從而失去平衡。
調整操作:將C節點作為根節點,A作為C的左孩子,B作為C的右孩子,同時調整C原來的兩棵子樹,將C原來的左子樹調整為A的右子樹,C原來的右子樹調整為B的左孩子。


RL型調整
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。