zoukankan      html  css  js  c++  java
  • 数据结构(6)二叉树

    二叉查找树具有非常高的灵活性。对其优化能够生成平衡二叉树。红黑树等高效的查找和插入数据结构。后文会一一介绍。

    一 定义

    二叉查找树(Binary Search Tree),也称有序二叉树(ordered binary tree),排序二叉树(sorted binary tree),是指一棵空树或者具有下列性质的二叉树:

    1. 若随意节点的左子树不空,则左子树上全部结点的值均小于它的根结点的值。

    2. 若随意节点的右子树不空。则右子树上全部结点的值均大于它的根结点的值;

    3. 随意节点的左、右子树也分别为二叉查找树。

    4. 没有键值相等的节点(no duplicate nodes)。

    例如以下图,这个是普通的二叉树:

    在此基础上。加上节点之间的大小关系,就是二叉查找树:

    二 实现

    在实现中,我们须要定义一个内部类Node。它包括两个分别指向左右节点的Node,一个用于排序的Key,以及该节点包括的值Value,另一个记录该节点及全部子节点个数的值Number。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    public class BinarySearchTreeSymbolTable<TKey, TValue> : SymbolTables<TKey, TValue> where TKey : IComparable<TKey>, IEquatable<TValue>
    {
        private Node root;
        private class Node
        {
            public Node Left { get; set; }
            public Node Right { get; set; }
            public int Number { get; set; }
            public TKey Key { get; set; }
            public TValue Value { get; set; }
     
            public Node(TKey key, TValue value, int number)
            {
                this.Key = key;
                this.Value = value;
                this.Number = number;
            }
        }
    ...
    }

    查找

    查找操作和二分查找类似,将key和节点的key比較,假设小于,那么就在Left Node节点查找,假设大于,则在Right Node节点查找,假设相等,直接返回Value。

    该方法实现有迭代和递归两种。

    递归的方式实现例如以下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    public override TValue Get(TKey key)
    {
        TValue result = default(TValue);
        Node node = root;
        while (node != null)
        {
     
            if (key.CompareTo(node.Key) > 0)
            {
                node = node.Right;
            }
            else if (key.CompareTo(node.Key) < 0)
            {
                node = node.Left;
            }
            else
            {
                result = node.Value;
                break;
            }
        }
        return result;
    }

    迭代的例如以下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    public TValue Get(TKey key)
    {
        return GetValue(root, key);
    }
     
    private TValue GetValue(Node root, TKey key)
    {
        if (root == null) return default(TValue);
        int cmp = key.CompareTo(root.Key);
        if (cmp > 0) return GetValue(root.Right, key);
        else if (cmp < 0) return GetValue(root.Left, key);
        else return root.Value;
    }

    插入

    插入和查找类似,首先查找有没有和key同样的。假设有。更新;假设没有找到,那么创建新的节点。并更新每一个节点的Number值,代码实现例如以下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    public override void Put(TKey key, TValue value)
    {
        root = Put(root, key, value);
    }
     
    private Node Put(Node x, TKey key, TValue value)
    {
        //假设节点为空,则创建新的节点,并返回
        //否则比較依据大小推断是左节点还是右节点。然后继续查找左子树还是右子树
        //同一时候更新节点的Number的值
        if (x == null) return new Node(key, value, 1);
        int cmp = key.CompareTo(x.Key);
        if (cmp < 0) x.Left = Put(x.Left, key, value);
        else if (cmp > 0) x.Right = Put(x.Right, key, value);
        else x.Value = value;
        x.Number = Size(x.Left) + Size(x.Right) + 1;
        return x;
    }
     
    private int Size(Node node)
    {
        if (node == null) return 0;
        else return node.Number;
    }

    插入操作图演示样例如以下:

    以下是插入动画效果:

    随机插入形成树的动画例如以下,可以看到,插入的时候树还是可以保持近似平衡状态:

    最大最小值

    例如以下图能够看出,二叉查找树的最大最小值是有规律的:

    从图中能够看出,二叉查找树中,最左和最右节点即为最小值和最大值,所以我们仅仅需迭代调用就可以。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    public override TKey GetMax()
    {
        TKey maxItem = default(TKey);
        Node s = root;
        while (s.Right != null)
        {
            s = s.Right;
        }
        maxItem = s.Key;
        return maxItem;
    }
     
    public override TKey GetMin()
    {
        TKey minItem = default(TKey);
        Node s = root;
        while (s.Left != null)
        {
            s = s.Left;
        }
        minItem = s.Key;
        return minItem;
    }

    下面是递归的版本号:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    public TKey GetMaxRecursive()
    {
        return GetMaxRecursive(root);
    }
     
    private TKey GetMaxRecursive(Node root)
    {
        if (root.Right == null) return root.Key;
        return GetMaxRecursive(root.Right);
    }
     
    public TKey GetMinRecursive()
    {
        return GetMinRecursive(root);
    }
     
    private TKey GetMinRecursive(Node root)
    {
        if (root.Left == null) return root.Key;
        return GetMinRecursive(root.Left);
    }

    Floor和Ceiling

    查找Floor(key)的值就是全部<=key的最大值,相反查找Ceiling的值就是全部>=key的最小值。下图是Floor函数的查找示意图:

    以查找Floor为例。我们首先将key和root元素比較,假设key比root的key小,则floor值一定在左子树上;假设比root的key大,则有可能在右子树上。当且仅当其右子树有一个节点的key值要小于等于该key;假设和root的key相等,则floor值就是key。依据以上分析,Floor方法的代码例如以下。Ceiling方法的代码类似。仅仅须要把符号换一下就可以:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    public TKey Floor(TKey key)
    {
        Node x = Floor(root, key);
        if (x != null) return x.Key;
        else return default(TKey);
    }
     
    private Node Floor(Node x, TKey key)
    {
        if (x == null) return null;
        int cmp = key.CompareTo(x.Key);
        if (cmp == 0) return x;
        if (cmp < 0) return Floor(x.Left, key);
        else
        {
            Node right = Floor(x.Right, key);
            if (right == null) return x;
            else return right;
        }
    }

    删除

    删除元素操作在二叉树的操作中应该是比較复杂的。首先来看下比較简单的删除最大最小值得方法。

    以删除最小值为例,我们首先找到最小值,及最左边左子树为空的节点,然后返回其右子树作为新的左子树。操作示意图例如以下:

    代码实现例如以下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    public void DelMin()
    {
        root = DelMin(root);
    }
     
    private Node DelMin(Node root)
    {
        if (root.Left == null) return root.Right;
        root.Left = DelMin(root.Left);
        root.Number = Size(root.Left) + Size(root.Right) + 1;
        return root;
    }

    删除最大值也是类似。

    如今来分析普通情况,假定我们要删除指定key的某一个节点。这个问题的难点在于:删除最大最小值的操作,删除的节点仅仅有1个子节点或者没有子节点,这样比較简单。可是假设删除随意节点,就有可能出现删除的节点有0个。1 个,2个子节点的情况,如今来逐一分析。

    当删除的节点没有子节点时。直接将该父节点指向该节点的link设置为null。

    当删除的节点仅仅有1个子节点时。将该自己点替换为要删除的节点就可以。

    当删除的节点有2个子节点时,问题就变复杂了。

    如果我们删除的节点t具有两个子节点。由于t具有右子节点,所以我们须要找到其右子节点中的最小节点,替换t节点的位置。

    这里有四个步骤:

    1. 保存带删除的节点到暂时变量t

    2. 将t的右节点的最小节点min(t.right)保存到暂时节点x

    3. 将x的右节点设置为deleteMin(t.right),该右节点是删除后,全部比x.key最大的节点。

    4. 将x的做节点设置为t的左节点。

    整个步骤例如以下图:

    相应代码例如以下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    public void Delete(TKey key)
    {
        root =Delete(root, key);
             
    }
     
    private Node Delete(Node x, TKey key)
    {
        int cmp = key.CompareTo(x.Key);
        if (cmp > 0) x.Right = Delete(x.Right, key);
        else if (cmp < 0) x.Left = Delete(x.Left, key);
        else
        {
            if (x.Left == null) return x.Right;
            else if (x.Right == null) return x.Left;
            else
            {
                Node t = x;
                x = GetMinNode(t.Right);
                x.Right = DelMin(t.Right);
                x.Left = t.Left;
            }
        }
        x.Number = Size(x.Left) + Size(x.Right) + 1;
        return x;
    }
     
    private Node GetMinNode(Node x)
    {
        if (x.Left == null) return x;
        else return GetMinNode(x.Left);
    }

    以上二叉查找树的删除节点的算法不是完美的,由于随着删除的进行,二叉树会变得不太平衡。以下是动画演示。

    三 分析

    二叉查找树的执行时间和树的形状有关,树的形状又和插入元素的顺序有关。在最好的情况下,节点全然平衡,从根节点到最底层叶子节点仅仅有lgN个节点。在最差的情况下。根节点到最底层叶子节点会有N各节点。在普通情况下,树的形状和最好的情况接近。

    在分析二叉查找树的时候。我们一般会如果插入的元素顺序是随机的。

    对BST的分析类似与高速排序中的查找:

    BST中位于顶部的元素就是高速排序中的第一个划分的元素,该元素左边的元素所有小于该元素,右边的元素均大于该元素。

    对于N个不同元素,随机插入的二叉查找树来说,其平均查找/插入的时间复杂度大约为2lnN,这个和高速排序的分析一样。详细的证明方法不再赘述,參照高速排序。

     

    四 总结

    有了前篇文章 二分查找的分析。对二叉查找树的理解应该比較easy。以下是二叉查找树的时间复杂度:

    它和二分查找一样,插入和查找的时间复杂度均为lgN,可是在最坏的情况下仍然会有N的时间复杂度。原因在于插入和删除元素的时候,树没有保持平衡。我们追求的是在最坏的情况下仍然有较好的时间复杂度,这就是后面要讲的平衡查找树的内容了。下文首先解说平衡查找树的最简单的一种:2-3查找树。

    希望本文对您了解二叉查找树有所帮助。

  • 相关阅读:
    SqlServer数据库同步方案详解(包括跨网段)
    makefile 和shell文件相互调用
    处理百万级以上的数据处理
    makefile Template(添加多个lib)
    Linux下如何删除非空目录
    makefile Template
    g++ 编译和链接
    gcc include路径
    C++ XML解析之TinyXML篇
    利用Lucene.net搭建站内搜索(2)分词技术
  • 原文地址:https://www.cnblogs.com/blfbuaa/p/7028082.html
Copyright © 2011-2022 走看看