二叉樹

@(數據結構)

[TOC]

樹的定義

(遞歸)一棵樹是一些節點的集合。這個集合可以是空集;若不是空集,則樹由稱作的節點 r 以及 0 個或多個非空的(子)樹 $T_1,T_2,···,T_k$ 組成,這些子樹中每一棵的根都被來自根 r 的一條有向所連結。

樹的實現

//樹節點的聲明
class TreeNode
{
    Object element;
    TreeNode firstChild;
    TreeNode netSibling;
}

將每個節點的所有兒子都放到樹節點的鏈表中。

樹的遍歷

  • 先序遍歷
  • 后序遍歷
  • 中序遍歷

二叉樹

二叉樹(binary tree)是一棵樹,其中每個節點都不能有多于兩個的兒子。

二叉樹平均深度為 $O(\sqrt{N})$,最大深度為 $N$。
二叉查找樹的平均深度為 $O(log N)$。

//二叉樹節點類
class BinaryNode
{
    //Friendly data;accessible by other package toutines
    Object element;//The data in the node
    BinaryNode left;//Left child
    BinaryNode right;//right child
}

查找樹ADT——二叉查找樹

使二叉樹成為查找樹的性質是,對于樹中的每個節點 X ,它的左子樹中所有項的值小于 X 中的項,而它的右子樹中所有項的值大于 X 中的項。

//BinaryNode類
private static class BinaryNode<AnyType>
{
    //Constructors
    BinaryNode(AnyType theElement)
    {this(theElement, null, null);}

    BinaryNode(AnyType theElement, BinaryNode<AnyType> lt, BinaryNode<AnyType> rt)
    {element = theElement; left = lt; right = rt;}

    AnyType element;//The data in the node
    BinaryNode<AnyType> left;//Left child
    BinaryNode<AnyType> right;//Right child
}

二叉查找樹架構

//二叉查找樹架構
public class BinarySearchTree<AnyType extends comparable<? super AnyType>>
{
    private static class BinaryNode<AnyType>
    {
        //Constructors
        BinaryNode(AnyType theElement)
        {this(theElement, null, null);}

        BinaryNode(AnyType theElement, BinaryNode<AnyType> lt, BinaryNode<AnyType> rt)
        {element = theElement; left = lt; right = rt;}

        AnyType element;//The data in the node
        BinaryNode<AnyType> left;//Left child
        BinaryNode<AnyType> right;//Right child
    }
        
    private BinaryNode<AnyType> root;

    public BinarySearchTree()
    { root = null; }
    
    public void makeEmpty()
    { root = null; }
    public boolean isEmpty()
    { return root == null; }

    public boolean contains( AnyType x )
    { return contains( x, root ); }
    public AnyType findMin()
    {
        if (isEmpty()) throw new UnderflowException();
        return findMin(root).element;
    }
    public AnyType finMax()
    {
        if (isEmpty()) throw new UnderflowException();
        return finMax(roow).element;
    }
    public void insert(AnyType x)
    { root = insert(x,root); }
    public void remove(AnyType x)
    { root = remove(x,root); } 
    public void printTree()
    {
        if (isEmpty())
            System.out.println("Empty tree");
        else
            printTree(roo?t);
    }

    private boolean contains(AnyType x, BinaryNode<AnyType> t)
    {
        if (t == null) 
            return false;
        int compareResult = x.compareTo(t.element);

        if(compareResult < 0)
            return contains(x, t.left);
        else if(compareResult > 0)
            return contains(x, t.right);
        else
            return true; //Match
    }
    private BinaryNode<AnyType> findMin(BinaryNode<AnyType> t)
    {
        if(t == null)
            return null;
        else if(t.left == null)
            return t;
        return findMin(t.left);
    }
    private BinaryNode<AnyType> finMax(BinaryNode<AnyType> t)
    {
        if(t != null)
            while(t.right != null)
                t = t.right;

        return t;
    }
 
    private BinaryNode<AnyType> insert(AnyType x, BinaryNode<AnyType> t)
    {
        if(t == null)
            return new BinaryNode<>(x, null, null);

        int compareResult = x.compareTo(t.element);

        if(compareResult < 0)
            t.left = insert(x, t.left);
        else if(compareResult > 0)
            t.right = insert(x, t.right);
        else
            ;//Duplicate; do nothing
        return t;
    }
    private BinaryNode<AnyType> remove(AnyType x, BinaryNode<AnyType> t)
    {
        if(t == null)
            return t;//Item not found; do nothing

        int compareResult = x.compareTo(t.element);

        if(compareResult < 0)
            t.left = remove(x, t.left);
        else if(compareResult > 0)
            t.right = remove(x, t.right);
        else if(t.left != null && t.right != null)//Two children
        {
            t.element = findMin(t.right).element;
            t.right = remove(t.element, t.right);
        }
        else
            t = (t.left != null) ? t.left : t.right;
        return t;
    }
    private void printTree(BinaryNode<AnyType> t)
    {
        if (t != null) {
            printTree(t.left);
            System.out.println(t.element);
            printTree(t.right);
        }
    }


}

contains方法

如果樹 $T$ 中含有項 $X$ 的節點,那么這個操作需要返回true,如果這樣的節點不存在則返回false。樹的結構使這種操作很簡單。如果 $T$ 是空集,那么久返回false。否則,如果存儲在 $T$ 處的項是 $X$ ,那么可以返回true。否則,我們對數 $T$ 的左子樹或右子樹進行一次遞歸調用,則依賴于 $X$ 與存儲在 $T$ 中的項的關系。

/**
 * Internal method to find an item in a subtree
 * @param  x is item to search for.
 * @param  t the node that roots the subtree.
 * @return true if the item is found; false otherwise.
 */

//二叉查找樹的contains操作
private boolean contains(AnyType x, BinaryNode<AnyType> t)
    {
        if (t == null) 
            return false;
        int compareResult = x.compareTo(t.element);

        if(compareResult < 0)
            return contains(x, t.left);
        else if(compareResult > 0)
            return contains(x, t.right);
        else
            return true; //Match
    }
    //遞歸用while循環代替
    while(compareResult <0)
    {
        t=t.left;
        compareResult = x.compareTo(t.element);
    }

算法表達式的簡明性是以速度的降低為代價的。

findMin方法和findMax方法

這兩個方法分別返回樹中包含最小元和最大元的節點的引用。為執行findMin,從根開始并且只要有左兒子就向左進行。 終止點就是最小的元素。findMax除分支朝向右兒子其余過程相同。

//用遞歸編寫findMin,用非遞歸編寫findMax
/**
* Internal method to find the smallest item in a subtree
* @param  t the node that roots the subtree.
* @return node containing the smallest item
*/
private BinaryNode<AnyType> findMin(BinaryNode<AnyType> t)
{
    if(t == null)
        return null;
    else if(t.left == null)
        return t;
    return findMin(t.left);
}
/**
* Internal method to find the largest item in a subtree
* @param  t the node that roots the subtree.
* @return node containing the largest item.
*/
private BinaryNode<AnyType> finMax(BinaryNode<AnyType> t)
{
    if(t != null)
        while(t.right != null)
            t = t.right;
    
    return t;
    }

insert方法

/**
 * Internal method to insert in?to a subtree
 * @param  x the item to insert
 * @param  t the node that roots the subtree
 * @return the new root of the subtree
 */ 
private BinaryNode<AnyType> insert(AnyType x, BinaryNode<AnyType> t)
{
    if(t == null)
        return new BinaryNode<>(x, null, null);

    int compareResult = x.compareTo(t.element);

    if(compareResult < 0)
        t.left = insert(x, t.left);
    else if(compareResult > 0)
        t.right = insert(x, t.right);
    else
        ;//Duplicate; do nothing
    return t;
}

remove方法

    /**
     * Internal method to remove from a subtree
     * @param  x the item to remove.
     * @param  t the node that roots the subtree.
     * @return the new root of the subtree
     */
    private BinaryNode<AnyType> remove(AnyType x, BinaryNode<AnyType> t)
    {
        if(t == null)
            return t;//Item not found; do nothing

        int compareResult = x.compareTo(t.element);

        if(compareResult < 0)
            t.left = remove(x, t.left);
        else if(compareResult > 0)
            t.right = remove(x, t.right);
        else if(t.left != null && t.right != null)//Node that has two children
        {
            t.element = findMin(t.right).element;//Find the minimum item of right subtree
            t.right = remove(t.element, t.right);//Remove the node of minimum item recursively          
        }
        else
            t = (t.left != null) ? t.left : t.right;//Node that has one children; parent of the node roots subtree of th?e node
        return t;
    }
  • 如果節點是樹葉,可以直接刪除。
  • 如果節點有一個兒子,這該節點需要在其父節點調整自己的鏈以繞過該節點
  • 如果節點有兩個兒子,一般的刪除策略是用其右子樹的最小的數據代替該節點,并在右子樹中遞歸地刪除那個最小的節點

另外,如果刪除的次數不多,通常使用的策略是懶惰刪除(lazy deletion):當一個元素要被刪除時,它仍留在樹中,而只是被標記為刪除。

AVL樹

AVL樹是帶有平衡條件的二叉查找樹。
這個平衡條件必須要容易保持,而且它保證樹的深度須是 $O(log N)$ 。
一個AVL樹是其每個節點的左子樹和右子樹的高度最多差 1 的二叉查找樹(空樹的高度定義為 -1)。

可以知道,在?高度為 $h$ 的AVL樹中,最少節點數 $S(h)=S(h-1)+S(h-2)+1$ 給出。
對于 $h=0, S(h)=1; h=1, S(h)=2$ 。
函數 $S(h)$ 與斐波那契數密切相關。

那么重點來了,對于AVL樹的插入操作,有可能破壞樹的平衡性。這時候,我們就需要在這一步插入完成之前恢復平衡的性質。

可以知道,從插入的節點往上,逆行到根,若發生平衡信息改變,那么改變的節點一定在這條路徑上。我們需要找出這個需要重新平衡的節點 $\alpha$ 。

對于節點 $\alpha$ ,不平衡條件可能出現在一下四種操作中:

  1. 對 $\alpha$ 的左兒子的左子樹進行一次插入(LL)。
  2. 對 $\alpha$ 的左兒子的右子樹進行一次插入(LR)。
  3. 對 $\alpha$ 的右兒子的左子樹進行一次插入(RL)。
  4. 對 $\alpha$ 的右兒子的右子樹進行一次插入(RR)。

對于1和4,是插入發生在外邊的情況,通過對樹的一次單旋轉而完成調整。對于2和3,是插入發生在內部的情況,通過對樹的一次雙旋轉而完成調整。

這里先對AvlNode類進行定義:

private static class AvlNode<AnyType>
{
    //Constructors
    AvlNode(AnyType theElement)
    {this(theElement, null, null);}

    AvlNode(AnyType theElement, AvlNode<AnyType> lt, AvlNode<AnyType> rt)
    {element = theElement; left = lt; right = rt; height = 0;}

    AnyType element;//The data in the code
    AvlNode<AnyType> left;//Left child
    AvlNode<AnyType> right;//Right child
    int height;//Height
}

然后需要一個返回節點高度的方法:

    //返回AVL樹的節點高度
    /**
     * return the height of node t, or -1, if null.
     */
    private int height(AvlNode<AnyType> t)
    {
        return t == null ? -1 : t.height;
    }

單旋轉

LL單旋轉
/**
 * Rotate binary tree node with left child.
 * For AVL trees, this is a single rotation for case 1.
 * Update heights, then return new root.
 */
private AvlNode<AnyType> RotationWithLeftChild(AvlNode<AnyType> k2) 
{  
    AVLTreeNode<AnyType> k1 = k2.left;  
  
    k2.left = k1.right;  
    k1.right = k2;  
  
    k2.height = Math.max( height(k2.left), height(k2.right)) + 1;  
    k1.height = Math.max( height(k1.left), k2.height) + 1;  
  
    return k1;  
}
RR單旋轉
/**
 * Rotate binary tree node with right child.
 * For AVL trees, this is a single rotation for case 4.
 * Update heights, then return new root.
 */
private AvlNode<AnyType> RotationWithRightChild(AvlNode<AnyType> k1) 
{  
    AVLTreeNode<AnyType> k2 = k1.right;  
  
    k1.right = k2.left;  
    k2.left = k1;  
    
    k1.height = Math.max( height(k1.left), height(k1.right)) + 1;  
    k1.height = Math.max( height(k2.right), k1.height) + 1;  
  
    return k2;  
}

雙旋轉

LR雙旋轉
    /**
     * Double rotate binary tree node: first left child
     * with its right child; then node k3 with new left child.
     * For AVL trees, this is a double rotation for case 2.
     * Update heights, then return new root.
     */
    private AvlNode<AnyType> doubleWithLeftChild(AvlNode<AnyType> k3)
    {
        k3.left = RotationWithRightChild(k3.left);
        return RotationWithLeftChild(k3);
    }
RL雙旋轉
    /**
     * Double rotate binary tree node: first right child
     * with its left child; then node k1 with new right child.
     * For AVL trees, this is a double rotation for case 3.
     * Update heights, then return new root.
     */
    private AvlNode<AnyType> doubleWithRightChild(AvlNode<AnyType> k1)
    {
        k1.right = RotationWithRightChild(k1.right);
        return RotationWithLeftChild(k1);
    }

AVL樹的插入方法

插入方法就是前文中的insert方法,只是在最后一行調用平衡的方法以保持AVL樹的平衡性。

    /**
     * Internal method to insert into a subtree.
     * @param  x the item to insert.
     * @param  t the node that roots the subtree.
     * @return the new root of the subtree.
     */
    private AvlNode<AnyType> insert(AnyType x, AvlNode<AnyType> t)
    {
        if(t == null)
            return new  AvlNode<>(x, null, null);

        int compareResult = x.compareTo(t.element);

        if(compareResult < 0)
            t.left = insert(x, t.left);
        else if(compareResult > 0)
            t.right = insert(x, t.right);
        else
            ;//Duplicate; do nothing
        return balance(t);
    }

    private static final int ALLOWED_IMBALLANCE = 1;

    //Assume t is either balanced of within one of being balanced
    private AvlNode<AnyType> balance(AvlNode<AnyType> t)
    {
        if(t == null)
            return t;

        if(height(t.left) - height(t.right) > ALLOWED_IMBALLANCE)
            if(height(t.left.left) >= height(t.left.right))
                t = RotationWithLeftChild(t);
            else
                t = doubleWithLeftChild(t);
        else
        if(height(t.right) - height(t.left) > ALLOWED_IMBALLANCE)
            if(height(t.right.right) >= height(t.right.left))
                t = RotationWithRightChild(t);
            else
                t = doubleWithRightChild(t);

        t.height = Math.max(height(t.left), height(t.right)) + 1;
        return t;
    }

AVL樹的刪除方法

和AVL樹的插入一樣,只用在前文的刪除方法最后加上一行調用平衡的方法即可。

    private AvlNode<AnyType> remove(AnyType x, AvlNode<AnyType> t)
    {
        if(t == null)
            return t;//Item not found; do nothing

        int compareResult = x.compareTo(t.element);

        if(compareResult < 0)
            t.left = remove(x, t.left);
        else if(compareResult > 0)
            t.right = remove(x, t.right);
        else if(t.left != null && t.right != null)//Node that has two children
        {
            t.element = findMin(t.right).element;//Find the minimum item of right subtree
            t.right = remove(t.element, t.right);//Remove the node of minimum item recursively          
        }
        else
            t = (t.left != null) ? t.left : t.right;//Node that has one children; parent of the node roots subtree of th?e node
        return balance(t);
    }
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 一直以來,我都很少使用也避免使用到樹和圖,總覺得它們神秘而又復雜,但是樹在一些運算和查找中也不可避免的要使用到,那...
    24K男閱讀 6,782評論 5 14
  • 樹的概述 樹是一種非常常用的數據結構,樹與前面介紹的線性表,棧,隊列等線性結構不同,樹是一種非線性結構 1.樹的定...
    Jack921閱讀 4,489評論 1 31
  • 數據結構與算法--從平衡二叉樹(AVL)到紅黑樹 上節學習了二叉查找樹。算法的性能取決于樹的形狀,而樹的形狀取決于...
    sunhaiyu閱讀 7,685評論 4 32
  • 去年二叉樹算法的事情鬧的沸沸揚揚,起因是Homebrew 的作者 @Max Howell 在 twitter 上發...
    Masazumi柒閱讀 1,628評論 0 8
  • 嗨!各位好!我是情商先生! 我們害怕困難將我們離散,責備曾經的自己那么的不勇敢。離開,釋懷,往復,重來……卻只能自...
    情商先生閱讀 295評論 13 1