二叉树

@(数据结构)

[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);
    }
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,519评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,842评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,544评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,742评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,646评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,027评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,513评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,169评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,324评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,268评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,299评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,996评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,591评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,667评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,911评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,288评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,871评论 2 341

推荐阅读更多精彩内容

  • 一直以来,我都很少使用也避免使用到树和图,总觉得它们神秘而又复杂,但是树在一些运算和查找中也不可避免的要使用到,那...
    24K男阅读 6,725评论 5 14
  • 树的概述 树是一种非常常用的数据结构,树与前面介绍的线性表,栈,队列等线性结构不同,树是一种非线性结构 1.树的定...
    Jack921阅读 4,422评论 1 31
  • 数据结构与算法--从平衡二叉树(AVL)到红黑树 上节学习了二叉查找树。算法的性能取决于树的形状,而树的形状取决于...
    sunhaiyu阅读 7,631评论 4 32
  • 去年二叉树算法的事情闹的沸沸扬扬,起因是Homebrew 的作者 @Max Howell 在 twitter 上发...
    Masazumi柒阅读 1,566评论 0 8
  • 嗨!各位好!我是情商先生! 我们害怕困难将我们离散,责备曾经的自己那么的不勇敢。离开,释怀,往复,重来……却只能自...
    情商先生阅读 269评论 13 1