数据结构与算法分析 —— C 语言描述:查找树 ADT —— 二叉查找树

二叉树的一个重要的应用是它们在查找中的使用。使二叉树成为二叉查找树的性质是,对于树中的每个节点 X,它的左子树中所有关键字值小于 X 的关键字值,而它的右子树中所有关键字值大于 X 的关键字值。注意,这意味着,该树所有的元素可以用某种统一的方式排序。

注意,由于树的递归定义,通常是递归地编写这些操作的例程。因为二叉查找树的平均深度是 O(log N),所以我们一般不必担心栈空间被用尽。

一些操作

MakeEmpty

操作主要用于初始化。有些程序设计人员更愿意将第一个元素初始化为单节点树,但是,我们的实现方法更紧密地遵循树的递归定义。如下所示:

SearchTree
MakeEmpty( SearchTree T )
{
    if(T != NULL) {
        MakeEmpty( T->Left );
        MakeEmpty( T->Right );
        free( T )
    }
    return NULL;
}

Find

这个操作一般需要返回指向树 T 中具有关键字 X 的节点的指针,如果这样的节点不存在则返回 NULL。书的结构使这种操作很简单。如果 T 是 NULL,那么我们就可以返回 NULL,否则,如果存储在 T 中的关键字是 X,那么我们可以返回 T。否则,我们对树 T 的左子树或右子树进行一次递归调用,这依赖于 X 与存储在 T 中关键字的关系。如下:

Position
Find( ElementType X, SearchTree T) 
{
    if( T == NULL )
        return NULL;
    if ( X < T->Element )
        return Find( X, T->Left );
    else
    if (X > T-> Element )
        return Find(X, T->Right );
    else
        return T;
}

FindMin & FindMax

这两个例程分别返回树中最小元和最大元的位置。虽然返回这些元素的准确值似乎更合理,但是这将与 Find 操作不相容。重要的是,看起来类似的操作做的工作也是类似的。为执行 FIndMin,从根开始并且只有左儿子就向左进行。终止点是最小的元素。FindMax 例程除分支朝向右儿子外其余过程相同。

这种递归是如此容易以至于许多程序设计员不厌其烦的使用它。我们用两种方法编写这两个例程,用递归编写 FindMin,而用非递归编写 FindMax。

Position
FindMin( SearchTree T ) 
{
    if( T == NULL ) 
        return NULL;
    else
    if( T->Left == NULL )
        return T;
    else 
        return FindMin( T->Left );
}
Position
FindMax( SearchTree T)
{
    if ( T != NULL )
        while( T->Right != NULL )
            T = T->Right;
    return T;
}

注意,我们需要小心的处理空树这种退化的情况。虽然小心总是重要的,但在递归程序中它尤其重要。此外,还要注意,在 FindMax 中对 T 的改变是安全的,因为我们只用拷贝来进行工作。不管怎么说,还是应该随时特别小心,因为诸如 “T→Right = T→Right→Right”这样的语句将会产生一些变化。

Insert

进行插入操作的例程在概念上是简单的。为了将 X 插入到树 “T” 中,你可以像用 Find 那样沿着树查找。如果找到 X,则什么也不用做(或做一些“更新”)。否则,将 X 插入到遍历的路径上最后一点上。

重复元的插入可以通过在节点记录中保留一个附加域以指示发生的频率来处理。这使整棵树增加了某些附加空间,但是,却比将重复信息放到树中要好(它使树的深度变得很大)。当然,如果关键字只是一个更大结构的一部分,那么这种方法行不通,此时我们可以把具有相同关键字的所有结构保留在一个辅助数据结构中,如表或是另一棵查找树中。

SearchTree
Insert( ElementType X, SearchTree T)
{
    if(T == NULL ) 
    {
        /*Create and return a one-node tree */
        T = malloc( sizeof( struct TreeNode ));
        if( T == NULL )
            FatalError( "Out of space!!!");
        else
            T->Element = X;
            T->Left = T->Right = NULL;
    }
    else
    if( X < T->Element )
        T->Left = Insert(X, T->Left);
    else
    if( X > T->Element )
        T->Right = Insert( X, T->Right );
    /* ELse X is the tree already; we'll do nothing */
    
    return  T;/* Do not forget this line!! */
}

Delete

正如许多数据结构一样,最困难的操作是删除。一旦发现要删除的节点,我们就需要考虑几种可能的情况。

  • 如果节点是一片树叶,那么可以立即删除。
  • 如果节点有一个儿子,则该节点可以在其父节点调整指针绕过该节点后删除。注意,所删除的节点现在已不再引用,而该节点只有在指向它的指针已被省去的情况才能删除。
  • 复杂的情况是处理具有两个儿子的节点。一般的删除策略是用其右子树中最小的数据(很容易找到)代替该节点的数据并递归地删除那个节点(现在它是空的)。因为右子树中最小的节点不可能有左儿子,所以第二次 Delete(删除)更容易。

如果删除的次数不多,则通常使用的策略是懒惰删除(lazy deletion):当一个元素要被删除时,它仍留在树中,只是做了个被删除的记号。这种做法特别是在有重复关键字时很流行,因为此时记录出现频率数的域可以减 1。如果树中的实际节点数和“被删除”的节点数相同,那么树的深度预计只上升一个小的常数。因此,存在一个与懒惰删除相关的非常小的时间损耗。再有,如果被删除的关键字是重新插入的,那么分配一个新的单元的开销就避免了。

平均情形分析

假设所有的树出现的机会均等,则树的所有节点的平均深度为 O(log N)。此时,除 MakeEmpty 和一些个别情形外,所有操作的平均运行时间都是O(log N)的。

一棵树的所有节点的深度的和称为内部路径长(internal path length)。

如果向一棵树输入预先排序的数据,那么一连串 Insert 操作将花费二次时间,而用链表实现 Insert 的代价会非常巨大,因为此时的树将只由那些没有左儿子的节点组成。一种解决办法就是要有一个称为平衡(balance)的附加的结构条件:任何节点的深度均不得过深。

有许多一般的算法可以实现平衡树。但是,大部分算法都要比标准的二叉查找树复杂得多,而且更新要平均花费更长的时间。不过,它们确实防止了处理起来非常麻烦的一些简单情形。例如最老的一种平衡查找树 ALV 树。

另外,较新的方法是放弃平衡条件,允许树有任意的深度,但在每次操作之后要使用一个调整规则进行调整,使得后面的操作效率更高。这种类型的数据结构一般属于自调整(self-adjusting)类结构。在二叉查找树的情况下,对于任意单个运算我们不在保证O(M \log N)。一般这足以防止令人棘手的最坏情形。

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

推荐阅读更多精彩内容