二叉树词典

目录

二叉树

二叉查找树

 二叉查找树操作的效率


二叉树

  二叉树和“普通”树是不同的。在二叉树中,节点最多有两个子节点,而且并不是自然地从左起为子节点计数,而是有两个“槽”,其中一个用来存放左子节点,另一个用来存放右子节点。这两个槽中可能有一个为空,也可能两个都为空。

  如果r是节点,而且T1和T2都是二叉树,那么以r为根节点,T1为左子树,T2为右子树可以组成一棵二叉树。也就是说,T1的根节点就是r的左子节点,除非T1是空树,因为这种情况下r没有左子节点。同样地,T2的根节点是r的右子节点,除非T2是空树,因为这种情况下r没有右子节点。

  

  如果某个节点存在两个子节点,那么这两个子节点就互为兄弟节点。叶子节点是指既没有左子节点也没有右子节点的节点,还可以认为叶子节点是左子树和右子树都为空树的节点。内部节点则指不是叶子节点的节点。

  路径长度、高度和深度的定义与普通的树是完全相同的。二叉树路径的长度要比节点数小1,也就是说,长度就是路径上父子关系对的数量。节点n的高度是指n到其子孙叶子节点最长路径的长度。二叉树的高度就是其根节点的高度。节点n的深度是指从根节点到n的路径的长度。

  有一种很自然的方式可以用于表示二叉树。节点可以表示为具有leftChild和rightChild这两个分别指向左子节点和右子节点的字段的记录。出现在这两个字段中的NULL指针就表示对应的左子树或右子树为空——也就是说节点没有左子节点或右子节点。

  二叉树可以表示为指向其根节点的指针。空二叉树很自然地就被表示为NULL。因此,如下类型定义就表示二叉树。

typedef struct NODE * TREE;
struct NODE{
    TREE leftChild,rightChild; 
};

  在这里,“指向节点的指针”类型名为TREE,因为这一类型最常见的用途就是表示树和子树。我们既可以将leftChild和rightChild字段解释为指向子节点的指针,也可以将其解释为指向左右子树本身的指针。

  此外,还可以为表示NODE的结构体添加标号字段,并(或)可以添加指向父节点的指针。请注意,父指针的类型是*NODE,或是TREE的等价类型。


二叉查找树

  各种计算机程序中有一种同样的活动,就是维护这样一组值,用户希望:

  (1) 向这组值中插入元素;

  (2) 从这组值中删除元素;

  (3) 查找某元素,看看它是否在这组值中。

  例子之一是英语词典,我们时不时地会往里面插入一些新单词,比如fax;删除一些不再使用的单词,比如aegilops;或者是要查找一串字母,看看其是否为单词,例如,这是拼写检查器程序的一部分。

  因为这个例子是我们非常熟悉的,所以不管其具体用途是什么,只要是可以按照上述定义对其执行插入、删除和查找操作的一组值,都叫作词典。再举个词典的例子,某教授可能要记录选修某课程学生的花名册。偶尔会有学生被加入这门课程(插入),或是退出该课程(删除),或是需要弄清某个学生是否选修了该课程(查找)。

  二叉查找树这种带标号的二叉树是实现词典的一个好方法。假设节点的标号是按照“小于”顺序(我们会将其写作<)从一组值中选出的。例子包括具有一般小于顺序的实数或整数,或是有着用<表示的词典顺序或字母表顺序的字符串。

  二叉查找树(BST)是一种带标号的的二叉树,以下属性对这种二叉树的每个节点x都成立:x的左子树中所有节点的标号都小于x的标号,而其右子树中所有节点的标号都大于x的标号。这种属性被称为二叉查找树属性。

  我们可以将二叉查找树表示为任何带标号的二叉树。例如,可以按如下形式定义NODE类型。

typedef struct NODE * TREE;
struct NODE{
    ETYPE element;
    TREE leftChild,rightChild; 
};

  二叉查找树被表示为指向二叉查找树根节点的指针。元素的类型ETYPE应该得到合理的设置。

  假设想在由二叉查找树T表示的某词典中查找某个元素x。如果将x与T的根节点处的元素加以比较,就可以利用二叉查找树属性快速找到x,或确定x没有出现。如果x在根节点处,就完成了查找。否则,如果x比根节点处的元素小,x就只可能在左子树中被找到,而如果x比根节点处的元素大,x就只可能出现在右子树中(还是因为二叉查找树属性)。也就是说,可以通过以下递归算法表示查找操作。

  依据。如果树T是空树,那么x未出现。如果树T非空,而且x在根节点,那么x就出现了。

  归纳。如果T非空而x未在根节点位置,设y是T根节点处的元素。如果 x<y ,则只在根节点的左子树中查找x;如果 x>y ,则只在y的右子树中查找x。二叉查找树属性保证x不可能出现在没有查找的那棵子树中。

  诸如插入、删除和查找这种可能对一组对象或特定类别执行的一系列操作,有时称为抽象数据类型或ADT。这一概念也会被称为类或模块。

  ADT可以有多种抽象实现。例如,二叉查找树是一种实现词典ADT的好方法。表是另一种看似可靠实则经常效率低下的实现词典ADT的方式。

  每种抽象实现依次可通过若干种不同的数据类型来具体实现。举例来说,可以使用二叉树的左子节点右子节点实现作为实现二叉查找树的数据结构。这一数据结构,加上用于插入、删除和查找的恰当函数,就成了词典ADT的一种实现。

  在程序中使用ADT的一个重要原因是,ADT底层的数据只能通过ADT的操作(比如插入)来访问。这一限制是防御性编程的一种形式,可以防止操作数据的函数以意料之外的方式对数据进行偶发变更。使用ADT的第二个重要原因在于,ADT让我们可以重新设计数据结构和实现其操作的函数,在不担心会为程序其余部分引入错误的前提下,这样可能提高操作的效率。如果只有用于ADT操作的接口函数被正确地重写,就不会出现新的错误。

  向二叉查找树T中增加一个新元素x是很简单的,以下递归算法简要描述了处理思路。

  依据。如果T是空树,用一棵由单个节点构成的树替代T,并在该节点处放上x。如果T非空而且其根节点处有元素x,那么x已经在字典中,不需要再做任何事情。

  归纳。如果T非空,而且x不在其根节点处,那么如果x小于根节点处的元素,就将x插入左子树,如果x大于根节点处的元素,就将x插入右子树。

TREE insert(ETYPE x, TREE T)
{
    if (T == NULL)
    {
        T = (TREE)malloc(sizeof(struct NODE));
        T->element = x;
        T->leftChild = NULL;
        T->rightChild = NULL;
    }
    else if (x < T->element)
        T->leftChild = insert(x, T->leftChild);
    else if (x > T->element)
        T->rightChild = insert(x, T->rightChild);
    return T;
}

  从二叉查找树中删除某个元素x要比查找或插入复杂一些。首先,要找出含有x的节点;如果没有这样的节点,就算是完事了,因为x不在这棵要处理的树里头。如果x在叶子节点处,那么直接删除该叶子节点就行了。不过,如果x是某个内部节点n,就不能直接删除该节点,因为这样做会破坏树的连通性。

  我们必须以某种方式对树进行重新排列,从而在维持二叉查找树属性的同时让x从树中消失。这会有两种情况。第一种,如果n只有一个子节点,就用该子节点代替n,这样二叉查找树的属性就得到了保持。

  第二种情况,假设n的两个子节点都存在。一种策略就是找到标号为y的节点m,它是n右子树中最小的元素,并在节点n处用y代替x,然后就可以从右子树中删除节点m。

  此时二叉查找树属性继续成立。原因在于,x比n的左子树中的所有元素都大,而y大于x(因为y在n的右子树中),所以y也比n左子树的所有元素都大。因此,就n的左子树而言,y是适合于位置n的元素。而对n的右子树来说,y也是适合作为根节点的,因为选出的y是右子树中的最小元素。

  我们给函数传入的参数,是指向某个位置的指针,而在这个位置可以找到指向节点(即树)的指针,这种风格的树操作叫作按引用调用。

 二叉查找树操作的效率

  二叉查找树提供了一种相当快速的词典实现。首先请注意,插入、删除和查找操作各会进行若干次递归调用,调用次数等于所经过的路径的长度。但该路径必须包含达到右子树最小元素的路线,以防deletemin被调用。对lookup、insert、delete和deletemin函数进行简单的分析,可知各操作都花费O(1) 的时间,而且要加上一次递归调用的时间。此外,因为该递归调用总是在当前节点的子节点处进行的,所以每次成功调用中节点的高度至少要减少1。

  因此,如果以指向某个高度为h的节点的指针调用这些函数所花的时间为T (h ),就有以下递推关系来为T (h )确定上界。

  依据。T (0)=O(1)。也就是说,在对叶子节点调用函数时,该调用要么终止,不再有进一步的调用,要么以NULL参数进行一次递归调用,接着会返回而不再继续调用。这些工作所花时间为O(1) 。

  归纳。对h≥1,T(h)≤T(h+1)+O(1)。

  也就是说,对任何内部节点调用函数所花的时间,都等于O(1) 加上对高度至多为 h -1的节点进行一次递归调用所花的时间。如果作出T (h ) 会随着h的增加而增加这一合理假设,那么该递归调用的时间不会大于T (h- 1)。 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值