C语言之实现二叉树结构-诺禾

二叉查找树是一种结合了二分查找策略的链接结构。二叉树的每个节点都包含一个项和两个指向其他节点(称为子节点)的指针。图17.12演示了二叉查找树中的节点是如何链接的。二叉树中的每个节点都包含两个子节点——左节点和右节点,其顺序按照如下规定确定:左节点的项在父节点的项前面,右节点的项在父节点的项后面。这种关系存在于每个有子节点的节点中。进一步而言,所有可以追溯其祖先回到一个父节点的左节点的项,都在该父节点项的前面;所有以一个父节点的右节点为祖先的项,都在该父节点项的后面。图17.12中的树以这种方式存储单词。有趣的是,与植物学的树相反,该树的顶部被称为根(root)。树具有分层组织,所以以这种方式存储的数据也以等级或层次组织。一般而言,每级都有上一级和下一级。如果二叉树是满的,那么每一级的节点数都是上一级节点数的两倍。

C语言之实现二叉树结构
A binary search tree storing words

二叉查找树中的每个节点是其后代节点的根,该节点与其后代节点构成称了一个子树(subtree)。如上图所示,包含单词fate、carpet和llama的节点构成了整个二叉树的左子树,而单词voyage是style-plenum-voyage子树的右子树。 假设要在二叉树中查找一个项(即目标项)。如果目标项在根节点项的前面,则只需查找左子树;如果目标项在根节点项的后面,则只需查找右子树。因此,每次比较就排除半个树。假设查找左子树,这意味着目标项与左子节点项比较。如果目标项在左子节点项的前面,则只需查找其后代节点的左半部分,以此类推。与二分查找类似,每次比较都能排除一半的可能匹配项。 我们用这种方法来查找puppy是否在图17.12的二叉树中。比较puppy和melon(根节点项),如果puppy在该树中,一定在右子树中。因此,在右子树中比较puppy和style,发现puppy在style前面,所以必须链接到其左节点。然后发现该节点是plenum,在puppy前面。现在要向下链接到该节点的右子节点,但是没有右子节点了。所以经过3次比较后发现puppy不在该树中。 二叉查找树在链式结构中结合了二分查找的效率。但是,这样编程的代价是构建一个二叉树比创建一个链表更复杂。下面我们在下一个ADT项目中创建一个二叉树。

1 二叉树ADT
和前面一样,先从概括地定义二叉树开始。该定义假设树不包含相同的项。许多操作与链表相同,区别在于数据层次的安排。下面建立一个非正式的树定义:

C语言之实现二叉树结构
2 二叉查找树接口

原则上,可以用多种方法实现二叉查找树,甚至可以通过操控数组下标用数组来实现。但是,实现二叉查找树最直接的方法是通过指针动态分配链式节点。因此我们这样定义:

typedef SOMETHING Item;

typedef struct trnode
{
Item item;
struct trnode * left;
struct trnode * right;
} Trn;

typedef struct tree
{
Trnode * root;
int size;
} Tree;
每个节点包含一个项、一个指向左子节点的指针和一个指向右子节点的指针。可以把Tree定义为指向Trnode的指针类型,因为只需要知道根节点的位置就可访问整个树。然而,使用有成员大小的结构能很方便地记录树的大小。 我们要开发一个维护Nerfville宠物俱乐部的花名册,每一项都包含宠物名和宠物的种类。程序tree.h就是该花名册的接口。我们把树的大小限制为10,较小的树便于在树已满时测试程序的行为是否正确。当然,你也可以把MAXITEMS设置为更大的值。

The tree.h Interface Header File

/* tree.h – binary search tree /
/
no duplicate items are allowed in this tree /
#ifndef TREE_H
#define TREE_H
#include <stdbool.h>

/
redefine Item as appropriate /
typedef struct item
{
char petname[20];
char petkind[20];
} Item;

#define MAXITEMS 10

typedef struct trnode
{
Item item;
struct trnode * left; /
pointer to right branch /
struct trnode * right; /
pointer to left branch /
} Trnode;

typedef struct tree
{
Trnode * root; /
pointer to root of tree /
int size; /
number of items in tree /
} Tree;

/
function prototypes /

/
operation: initialize a tree to empty /
/
preconditions: ptree points to a tree /
/
postconditions: the tree is initialized to empty /
void InitializeTree(Tree * ptree);

/
operation: determine if tree is empty /
/
preconditions: ptree points to a tree /
/
postconditions: function returns true if tree is /
/
empty and returns false otherwise /
bool TreeIsEmpty(const Tree * ptree);

/
operation: determine if tree is full /
/
preconditions: ptree points to a tree /
/
postconditions: function returns true if tree is /
/
full and returns false otherwise /
bool TreeIsFull(const Tree * ptree);

/
operation: determine number of items in tree /
/
preconditions: ptree points to a tree /
/
postconditions: function returns number of items in /
/
tree /
int TreeItemCount(const Tree * ptree);

/
operation: add an item to a tree /
/
preconditions: pi is address of item to be added /
/
ptree points to an initialized tree /
/
postconditions: if possible, function adds item to /
/
tree and returns true; otherwise, /
/
the function returns false /
bool AddItem(const Item * pi, Tree * ptree);

/
operation: find an item in a tree /
/
preconditions: pi points to an item /
/
ptree points to an initialized tree /
/
postconditions: function returns true if item is in /
/
tree and returns false otherwise /
bool InTree(const Item * pi, const Tree * ptree);

/
operation: delete an item from a tree /
/
preconditions: pi is address of item to be deleted /
/
ptree points to an initialized tree /
/
postconditions: if possible, function deletes item /
/
from tree and returns true; /
/
otherwise the function returns false*/
bool DeleteItem(const Item * pi, Tree * ptree);

/* operation: apply a function to each item in /
/
the tree /
/
preconditions: ptree points to a tree /
/
pfun points to a function that takes*/
/* an Item argument and has no return /
/
value /
/
postcondition: the function pointed to by pfun is /
/
executed once for each item in tree /
void Traverse (const Tree * ptree, void (
pfun)(Item item));

/* operation: delete everything from a tree /
/
preconditions: ptree points to an initialized tree /
/
postconditions: tree is empty */
void DeleteAll(Tree * ptree);

#endif
3 二叉树的实现
接下来,我们要实现tree.h中的每个函数。InitializeTree()、EmptyTree()、FullTree()和TreeItems()函数都很简单,与链表ADT、队列ADT类似,所以下面着重讲解其他函数。

1.添加项

在树中添加一个项,首先要检查该树是否有空间放得下一个项。由于我们定义二叉树时规定其中的项不能重复,所以接下来要检查树中是否有该项。通过这两步检查后,便可创建一个新节点,把待添加项拷贝到该节点中,并设置节点的左指针和右指针都为NULL。这表明该节点没有子节点。然后,更新Tree结构的size成员,统计新增了一项。接下来,必须找出应该把这个新节点放在树中的哪个位置。如果树为空,则应设置根节点指针指向该新节点。否则,遍历树找到合适的位置放置该节点。AddItem()函数就根据这个思路来实现,并把一些工作交给几个尚未定义的函数:SeekItem()、MakeNode()和AddNode()。

bool AddItem(const Item * pi, Tree * ptree)
{
Trnode * new_node;

if (TreeIsFull(ptree))
{
fprintf(stderr,“Tree is fulln”);
return false; /* early return /
}
if (SeekItem(pi, ptree).child != NULL)
{
fprintf(stderr, “Attempted to add duplicate itemn”);
return false; /
early return /
}
new_node = MakeNode(pi); /
points to new node /
if (new_node == NULL)
{
fprintf(stderr, “Couldn’t create noden”);
return false; /
early return /
}
/
succeeded in creating a new node /
ptree->size++;

if (ptree->root == NULL) /
case 1: tree is empty /
ptree->root = new_node; /
new node is tree root /
else /
case 2: not empty /
AddNode(new_node,ptree->root); /
add node to tree /

return true; /
successful return */
}
SeekItem()、MakeNode()和AddNode()函数不是Tree类型公共接口的一部分。它们是隐藏在tree.c文件中的静态函数,处理实现的细节(如节点、指针和结构),不属于公共接口。 MakeNode()函数相当简单,它处理动态内存分配和初始化节点。该函数的参数是指向新项的指针,其返回值是指向新节点的指针。如果malloc()无法分配所需的内存,则返回空指针。只有成功分配了内存,MakeNode()函数才会初始化新节点。下面是MakeNode()的代码:

static Trnode * MakeNode(const Item * pi)
{
Trnode * new_node;

new_node = (Trnode *) malloc(sizeof(Trnode));
if (new_node != NULL)
{
new_node->item = *pi;
new_node->left = NULL;
new_node->right = NULL;
}

return new_node;
}
AddNode()函数是二叉查找树包中第二麻烦的函数。它必须确定新节点的位置,然后添加新节点。具体来说,该函数要比较新项和根项,以确定应该把新项放在左子树还是右子树中。如果新项是一个数字,则使用<和>进行比较;如果新项是一个字符串,则使用strcmp()函数来比较。但是,该项是内含两个字符串的结构,所以,必须自定义用于比较的函数。如果新项应放在左子树中,ToLeft()函数(稍后定义)返回true;如果新项应放在右子树中,ToRight()函数(稍后定义)返回true。这两个函数分别相当于<和>。假设把新项放在左子树中。如果左子树为空,AddNode()函数只需让左子节点指针指向新项即可。如果左子树不为空怎么办?此时,AddNode()函数应该把新项和左子节点中的项做比较,以确定新项应该放在该子节点的左子树还是右子树。这个过程一直持续到函数发现一个空子树为止,并在此处添加新节点。递归是一种实现这种查找过程的方法,即把AddNode()函数应用于子节点,而不是根节点。当左子树或右子树为空时,即当root->left或root->right为NULL时,函数的递归调用序列结束。记住,root是指向当前子树顶部的指针,所以每次递归调用它都指向一个新的下一级子树。

static void AddNode (Trnode * new_node, Trnode * root)
{
if (ToLeft(&new_node->item, &root->item))
{
if (root->left == NULL) /* empty subtree /
root->left = new_node; /
so add node here /
else
AddNode(new_node, root->left);/
else process subtree*/
}
else if (ToRight(&new_node->item, &root->item))
{
if (root->right == NULL)
root->right = new_node;
else
AddNode(new_node, root->right);
}
else /* should be no duplicates */
{
fprintf(stderr,“location error in AddNode()n”);
exit(1);
}
}
ToLeft()和ToRight()函数依赖于Item类型的性质。Nerfville宠物俱乐部的成员名按字母排序。如果两个宠物名相同,按其种类排序。如果种类也相同,这两项属于重复项,根据该二叉树的定义,这是不允许的。回忆一下,如果标准C库函数strcmp()中的第1个参数表示的字符串在第2个参数表示的字符串前面,该函数则返回负数;如果两个字符串相同,该函数则返回0;如果第1个字符串在第2个字符串后面,该函数则返回正数。ToRight()函数的实现代码与该函数类似。通过这两个函数完成比较,而不是直接在AddNode()函数中直接比较,这样的代码更容易适应新的要求。当需要比较不同的数据形式时,就不必重写整个AddNode()函数,只需重写Toleft()和ToRight()即可。

static bool ToLeft(const Item * i1, const Item * i2)
{
int comp1;

if ((comp1 = strcmp(i1->petname, i2->petname)) < 0)
return true;
else if (comp1 == 0 &&
strcmp(i1->petkind, i2->petkind) < 0 )
return true;
else
return false;
}
2.查找项

3个接口函数都要在树中查找特定项:AddItem()、InTree()和DeleteItem()。这些函数的实现中使用SeekItem()函数进行查找。DeleteItem()函数有一个额外的要求:该函数要知道待删除项的父节点,以便在删除子节点后更新父节点指向子节点的指针。因此,我们设计SeekItem()函数返回的结构包含两个指针:一个指针指向包含项的节点(如果未找到指定项则为NULL);一个指针指向父节点(如果该节点为根节点,即没有父节点,则为NULL)。这个结构类型的定义如下:

typedef struct pair {
Trnode * parent;
Trnode * child;
} Pair;
SeekItem()函数可以用递归的方式实现。但是,为了给读者介绍更多编程技巧,我们这次使用while循环处理树中从上到下的查找。和AddNode()一样,SeekItem()也使用ToLeft()和ToRight()在树中导航。开始时,SeekItem()设置look.child指针指向该树的根节点,然后沿着目标项应在的路径重置look.child指向后续的子树。同时,设置look.parent指向后续的父节点。如果没有找到匹配的项,look.child则被设置为NULL。如果在根节点找到匹配的项,则设置look.parent为NULL,因为根节点没有父节点。下面是SeekItem()函数的实现代码:

static Pair SeekItem(const Item * pi, const Tree * ptree)
{
Pair look;
look.parent = NULL;
look.child = ptree->root;

if (look.child == NULL)
return look; /* early return /

while (look.child != NULL)
{
if (ToLeft(pi, &(look.child->item)))
{
look.parent = look.child;
look.child = look.child->left;
}
else if (ToRight(pi, &(look.child->item)))
{
look.parent = look.child;
look.child = look.child->right;
}
else /
must be same if not to left or right /
break; /
look.child is address of node with item /
}

return look; /
successful return */
}
注意,如果SeekItem()函数返回一个结构,那么该函数可以与结构成员运算符一起使用。例如,AddItem()函数中有如下的代码:

if (SeekItem(pi, ptree).child != NULL)
有了SeekItem()函数后,编写InTree()公共接口函数就很简单了:

bool InTree(const Item * pi, const Tree * ptree)
{
return (SeekItem(pi, ptree).child == NULL) ? false : true;
}
3.考虑删除项
删除项是最复杂的任务,因为必须重新连接剩余的子树形成有效的树。在准备编写这部分代码之前,必须明确需要做什么。图"Deleting a leaf"演示了最简单的情况。待删除的节点没有子节点,这样的节点被称为叶节点(leaf)。这种情况只需把父节点中的指针重置为NULL,并使用free()函数释放已删除节点所占用的内存。

C语言之实现二叉树结构
Deleting a leaf.

删除带有一个子节点的情况比较复杂。删除该节点会导致其子树与其他部分分离。为了修正这种情况,要把被删除节点父节点中存储该节点的地址更新为该节点子树的地址

C语言之实现二叉树结构
Deleting a one-child node.

最后一种情况是删除有两个子树的节点。其中一个子树(如左子树)可连接在被删除节点之前连接的位置。但是,另一个子树怎么处理?牢记树的基本设计:左子树的所有项都在父节点项的前面,右子树的所有项都在父节点项的后面。也就是说,右子树的所有项都在左子树所有项的后面。而且,因为该右子树曾经是被删除节点的父节点的左子树的一部分,所以该右节点中的所有项在被删除节点的父节点项的前面。想像一下如何在树中从上到下查找该右子树的头所在的位置。它应该在被删除节点的父节点的前面,所以要沿着父节点的左子树向下找。但是,该右子树的所有项又在被删除节点左子树所有项的后面。因此要查看左子树的右支是否有新节点的空位。如果没有,就要沿着左子树的右支向下找,一直找到一个空位为止。图17.15演示了这种方法。

C语言之实现二叉树结构
Deleting a two-child node

① 删除一个节点

现在可以设计所需的函数了,可以分成两个任务:第一个任务是把特定项与待删除节点关联;第二个任务是删除节点。无论哪种情况都必须修改待删除项父节点的指针。因此,要注意以下两点。该程序必须标识待删除节点的父节点。为了修改指针,代码必须把该指针的地址传递给执行删除任务的函数。第一点稍后讨论,下面先分析第二点。要修改的指针本身是Trnode *类型,即指向Trnode的指针。由于该函数的参数是该指针的地址,所以参数的类型是Trnode **,即指向指针(该指针指向Trnode)的指针。假设有合适的地址可用,可以这样编写执行删除任务的函数:

static void DeleteNode(Trnode *ptr)
/
ptr is address of parent member pointing to target node */
{
Trnode * temp;

if ( (*ptr)->left == NULL)
{
    temp = *ptr;
    *ptr = (*ptr)->right;
    free(temp);
}
else if ( (*ptr)->right == NULL)
{
    temp = *ptr;
    *ptr = (*ptr)->left;
    free(temp);
}
else    /* deleted node has two children */
{
    /* find where to reattach right subtree */
    for (temp = (*ptr)->left; temp->right != NULL;
         temp = temp->right)
        continue;
    temp->right = (*ptr)->right;
    temp = *ptr;
    *ptr =(*ptr)->left;
    free(temp);
}

}
该函数显式处理了3种情况:没有左子节点的节点、没有右子节点的节点和有两个子节点的节点。无子节点的节点可作为无左子节点的节点的特例。如果该节点没有左子节点,程序就将右子节点的地址赋给其父节点的指针。如果该节点也没有右子节点,则该指针为NULL。这就是无子节点情况的值。 注意,代码中用临时指针记录被删除节点的地址。被删除节点的父节点指针(ptr)被重置后,程序会丢失被删除节点的地址,但是free()函数需要这个信息。所以,程序把ptr的原始值存储在temp中,然后用free()函数使用temp来释放被删除节点所占用的内存。 有两个子节点的情况,首先在for循环中通过temp指针从左子树的右半部分向下查找一个空位。找到空位后,把右子树连接于此。然后,再用temp保存被删除节点的位置。接下来,把左子树连接到被删除节点的父节点上,最后释放temp指向的节点。 注意,由于ptr的类型是Trnode **,所以*ptr的类型是Trnode *,与temp的类型相同。

② 删除一个项

剩下的问题是把一个节点与特定项相关联。可以使用SeekItem()函数来完成。回忆一下,该函数返回一个结构(内含两个指针,一个指针指向父节点,一个指针指向包含特定项的节点)。然后就可以通过父节点的指针获得相应的地址传递给DeleteNode()函数。根据这个思路,DeleteNode()函数的定义如下:

static void DeleteNode(Trnode *ptr)
/
ptr is address of parent member pointing to target node */
{
Trnode * temp;

if ( (*ptr)->left == NULL)
{
    temp = *ptr;
    *ptr = (*ptr)->right;
    free(temp);
}
else if ( (*ptr)->right == NULL)
{
    temp = *ptr;
    *ptr = (*ptr)->left;
    free(temp);
}
else    /* deleted node has two children */
{
    /* find where to reattach right subtree */
    for (temp = (*ptr)->left; temp->right != NULL;
         temp = temp->right)
        continue;
    temp->right = (*ptr)->right;
    temp = *ptr;
    *ptr =(*ptr)->left;
    free(temp);
}

}
首先,SeekItem()函数的返回值被赋给look类型的结构变量。如果look.child是NULL,表明未找到指定项,DeleteItem()函数退出,并返回false。如果找到了指定的Item,该函数分3种情况来处理。第一种情况是,look.parent的值为NULL,这意味着该项在根节点中。在这情况下,不用更新父节点,但是要更新Tree结构中根节点的指针。因此,函数该函数把该指针的地址传递给DeleteNode()函数。否则(即剩下两种情况),程序判断待删除节点是其父节点的左子节点还是右子节点,然后传递合适指针的地址。注意,公共接口函数(DeleteItem())处理的是最终用户所关心的问题(项和树),而隐藏的DeleteNode()函数处理的是与指针相关的实质性任务。

4.遍历树
遍历树比遍历链表更复杂,因为每个节点都有两个分支。这种分支特性很适合使用分而治之的递归(详见第9章)来处理。对于每一个节点,执行遍历任务的函数都要做如下的工作:

处理节点中的项;
处理左子树(递归调用);
处理右子树(递归调用)。
可以把遍历分成两个函数来完成:Traverse()和InOrder()。注意,InOrder()函数处理左子树,然后处理项,最后处理右子树。这种遍历树的顺序是按字母排序进行。如果你有时间,可以试试用不同的顺序,比如,项-左子树-右子树或者左子树-右子树-项,看看会发生什么。

void Traverse (const Tree * ptree, void (* pfun)(Item item))
{

if (ptree != NULL)
    InOrder(ptree->root, pfun);

}
static void InOrder(const Trnode * root, void (* pfun)(Item item))
{
if (root != NULL)
{
InOrder(root->left, pfun);
(*pfun)(root->item);
InOrder(root->right, pfun);
}
}
5.清空树
清空树基本上和遍历树的过程相同,即清空树的代码也要访问每个节点,而且要用free()函数释放内存。除此之外,还要重置Tree类型结构的成员,表明该树为空。DeleteAll()函数负责处理Tree类型的结构,把释放内存的任务交给DeleteAllNode()函数。DeleteAllNode()与InOrder()函数的构造相同,它存储了指针的值root->right,使其在释放根节点后仍然可用。下面是这两个函数的代码:

void DeleteAll(Tree * ptree)
{
if (ptree != NULL)
DeleteAllNodes(ptree->root);
ptree->root = NULL;
ptree->size = 0;
}

static void DeleteAllNodes(Trnode * root)
{
Trnode * pright;

if (root != NULL)
{
    pright = root->right;
    DeleteAllNodes(root->left);
    free(root);
    DeleteAllNodes(pright);
}

}
6.完整的包
程序tree.c演示了整个tree.c的代码。tree.h和tree.c共同组成了树的程序包。

The tree.c Implementation File

/* tree.c – tree support functions */
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include “tree.h”

/* local data type */
typedef struct pair {
Trnode * parent;
Trnode * child;
} Pair;

/* protototypes for local functions /
static Trnode * MakeNode(const Item * pi);
static bool ToLeft(const Item * i1, const Item * i2);
static bool ToRight(const Item * i1, const Item * i2);
static void AddNode (Trnode * new_node, Trnode * root);
static void InOrder(const Trnode * root, void (
pfun)(Item item));
static Pair SeekItem(const Item * pi, const Tree * ptree);
static void DeleteNode(Trnode **ptr);
static void DeleteAllNodes(Trnode * ptr);

/* function definitions */
void InitializeTree(Tree * ptree)
{
ptree->root = NULL;
ptree->size = 0;
}

bool TreeIsEmpty(const Tree * ptree)
{
if (ptree->root == NULL)
return true;
else
return false;
}

bool TreeIsFull(const Tree * ptree)
{
if (ptree->size == MAXITEMS)
return true;
else
return false;
}

int TreeItemCount(const Tree * ptree)
{
return ptree->size;
}

bool AddItem(const Item * pi, Tree * ptree)
{
Trnode * new_node;

if  (TreeIsFull(ptree))
{
    fprintf(stderr,"Tree is fulln");
    return false;             /* early return           */
}
if (SeekItem(pi, ptree).child != NULL)
{
    fprintf(stderr, "Attempted to add duplicate itemn");
    return false;             /* early return           */
}
new_node = MakeNode(pi);      /* points to new node     */
if (new_node == NULL)
{
    fprintf(stderr, "Couldn't create noden");
    return false;             /* early return           */
}
/* succeeded in creating a new node */
ptree->size++;

if (ptree->root == NULL)      /* case 1: tree is empty  */
    ptree->root = new_node;   /* new node is tree root  */
else                          /* case 2: not empty      */
    AddNode(new_node,ptree->root); /* add node to tree  */

return true;                  /* successful return      */

}

bool InTree(const Item * pi, const Tree * ptree)
{
return (SeekItem(pi, ptree).child == NULL) ? false : true;
}

bool DeleteItem(const Item * pi, Tree * ptree)
{
Pair look;

look = SeekItem(pi, ptree);
if (look.child == NULL)
    return false;

if (look.parent == NULL)      /* delete root item       */
    DeleteNode(&ptree->root);
else if (look.parent->left == look.child)
    DeleteNode(&look.parent->left);
else
    DeleteNode(&look.parent->right);
ptree->size--;

return true;

}

void Traverse (const Tree * ptree, void (* pfun)(Item item))
{

if (ptree != NULL)
    InOrder(ptree->root, pfun);

}

void DeleteAll(Tree * ptree)
{
if (ptree != NULL)
DeleteAllNodes(ptree->root);
ptree->root = NULL;
ptree->size = 0;
}

/* local functions /
static void InOrder(const Trnode * root, void (
pfun)(Item item))
{
if (root != NULL)
{
InOrder(root->left, pfun);
(*pfun)(root->item);
InOrder(root->right, pfun);
}
}

static void DeleteAllNodes(Trnode * root)
{
Trnode * pright;

if (root != NULL)
{
    pright = root->right;
    DeleteAllNodes(root->left);
    free(root);
    DeleteAllNodes(pright);
}

}

static void AddNode (Trnode * new_node, Trnode * root)
{
if (ToLeft(&new_node->item, &root->item))
{
if (root->left == NULL) /* empty subtree /
root->left = new_node; /
so add node here /
else
AddNode(new_node, root->left);/
else process subtree*/
}
else if (ToRight(&new_node->item, &root->item))
{
if (root->right == NULL)
root->right = new_node;
else
AddNode(new_node, root->right);
}
else /* should be no duplicates */
{
fprintf(stderr,“location error in AddNode()n”);
exit(1);
}
}
static bool ToLeft(const Item * i1, const Item * i2)
{
int comp1;

if ((comp1 = strcmp(i1->petname, i2->petname)) < 0)
    return true;
else if (comp1 == 0 &&
         strcmp(i1->petkind, i2->petkind) < 0 )
    return true;
else
    return false;

}

static bool ToRight(const Item * i1, const Item * i2)
{
int comp1;

if ((comp1 = strcmp(i1->petname, i2->petname)) > 0)
    return true;
else if (comp1 == 0 &&
         strcmp(i1->petkind, i2->petkind) > 0 )
    return true;
else
    return false;

}

static Trnode * MakeNode(const Item * pi)
{
Trnode * new_node;

new_node = (Trnode *) malloc(sizeof(Trnode));
if (new_node != NULL)
{
    new_node->item = *pi;
    new_node->left = NULL;
    new_node->right = NULL;
}

return new_node;

}

static Pair SeekItem(const Item * pi, const Tree * ptree)
{
Pair look;
look.parent = NULL;
look.child = ptree->root;

if (look.child == NULL)
    return look;                        /* early return   */

while (look.child != NULL)
{
    if (ToLeft(pi, &(look.child->item)))
    {
        look.parent = look.child;
        look.child = look.child->left;
    }
    else if (ToRight(pi, &(look.child->item)))
    {
        look.parent = look.child;
        look.child = look.child->right;
    }
    else       /* must be same if not to left or right    */
        break; /* look.child is address of node with item */
}

return look;                       /* successful return   */

}

static void DeleteNode(Trnode *ptr)
/
ptr is address of parent member pointing to target node */
{
Trnode * temp;

if ( (*ptr)->left == NULL)
{
    temp = *ptr;
    *ptr = (*ptr)->right;
    free(temp);
}
else if ( (*ptr)->right == NULL)
{
    temp = *ptr;
    *ptr = (*ptr)->left;
    free(temp);
}
else    /* deleted node has two children */
{
    /* find where to reattach right subtree */
    for (temp = (*ptr)->left; temp->right != NULL;
         temp = temp->right)
        continue;
    temp->right = (*ptr)->right;
    temp = *ptr;
    *ptr =(*ptr)->left;
    free(temp);
}

}

** 7.4 使用二叉树
现在,有了接口和函数的实现,就可以使用它们了。程序petclub.c中的程序以菜单的方式提供选择:向俱乐部成员花名册添加宠物、显示成员列表、报告成员数量、核实成员及退出。main()函数很简单,主要提供程序的大纲。具体工作主要由支持函数来完成。

The petclub.c Program
#+BEGIN_SRC C :results output
/* petclub.c – use a binary search tree */
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include “tree.h”

char menu(void);
void addpet(Tree * pt);
void droppet(Tree * pt);
void showpets(const Tree * pt);
void findpet(const Tree * pt);
void printitem(Item item);
void uppercase(char * str);
char * s_gets(char * st, int n);

int main(void)
{
Tree pets;
char choice;

InitializeTree(&pets);
while ((choice = menu()) != 'q')
{
    switch (choice)
    {
        case 'a' :  addpet(&pets);
            break;
        case 'l' :  showpets(&pets);
            break;
        case 'f' :  findpet(&pets);
            break;
        case 'n' :  printf("%d pets in clubn",
                           TreeItemCount(&pets));
            break;
        case 'd' :  droppet(&pets);
            break;
        default  :  puts("Switching error");
    }
}
DeleteAll(&pets);
puts("Bye.");

return 0;

}

char menu(void)
{
int ch;

puts("Nerfville Pet Club Membership Program");
puts("Enter the letter corresponding to your choice:");
puts("a) add a pet          l) show list of pets");
puts("n) number of pets     f) find pets");
puts("d) delete a pet       q) quit");
while ((ch = getchar()) != EOF)
{
    while (getchar() != 'n')  /* discard rest of line */
        continue;
    ch = tolower(ch);
    if (strchr("alrfndq",ch) == NULL)
        puts("Please enter an a, l, f, n, d, or q:");
    else
        break;
}
if (ch == EOF)       /* make EOF cause program to quit */
    ch = 'q';

return ch;

}

void addpet(Tree * pt)
{
Item temp;

if (TreeIsFull(pt))
    puts("No room in the club!");
else
{
    puts("Please enter name of pet:");
    s_gets(temp.petname,SLEN);
    puts("Please enter pet kind:");
    s_gets(temp.petkind,SLEN);
    uppercase(temp.petname);
    uppercase(temp.petkind);
    AddItem(&temp, pt);
}

}

void showpets(const Tree * pt)
{
if (TreeIsEmpty(pt))
puts(“No entries!”);
else
Traverse(pt, printitem);
}

void printitem(Item item)
{
printf(“Pet: %-19s Kind: %-19sn”, item.petname,
item.petkind);
}

void findpet(const Tree * pt)
{
Item temp;

if (TreeIsEmpty(pt))
{
    puts("No entries!");
    return;     /* quit function if tree is empty */
}

puts("Please enter name of pet you wish to find:");
s_gets(temp.petname, SLEN);
puts("Please enter pet kind:");
s_gets(temp.petkind, SLEN);
uppercase(temp.petname);
uppercase(temp.petkind);
printf("%s the %s ", temp.petname, temp.petkind);
if (InTree(&temp, pt))
    printf("is a member.n");
else
    printf("is not a member.n");

}

void droppet(Tree * pt)
{
Item temp;

if (TreeIsEmpty(pt))
{
    puts("No entries!");
    return;     /* quit function if tree is empty */
}

puts("Please enter name of pet you wish to delete:");
s_gets(temp.petname, SLEN);
puts("Please enter pet kind:");
s_gets(temp.petkind, SLEN);
uppercase(temp.petname);
uppercase(temp.petkind);
printf("%s the %s ", temp.petname, temp.petkind);
if (DeleteItem(&temp, pt))
    printf("is dropped from the club.n");
else
    printf("is not a member.n");

}

void uppercase(char * str)
{
while (*str)
{
*str = toupper(*str);
str++;
}
}

char * s_gets(char * st, int n)
{
char * ret_val;
char * find;

ret_val = fgets(st, n, stdin);
if (ret_val)
{
    find = strchr(st, 'n');   // look for newline
    if (find)                  // if the address is not NULL,
        *find = '0';          // place a null character there
    else
        while (getchar() != 'n')
            continue;          // dispose of rest of line
}
return ret_val;

}
该程序把所有字母都转换为大写字母,所以SNUFFY、Snuffy和snuffy都被视为相同。下面是该程序的一个运行示例:

Nerfville Pet Club Membership Program Enter the letter corresponding to your choice: a) add a pet l) show list of pets n) number of pets f) find pets q) quit a Please enter name of pet: Quincy Please enter pet kind: pig Nerfville Pet Club Membership Program Enter the letter corresponding to your choice: a) add a pet l) show list of pets n) number of pets f) find pets q) quit a Please enter name of pet: Bennie Haha Please enter pet kind: parrot Nerfville Pet Club Membership Program Enter the letter corresponding to your choice: a) add a pet l) show list of pets n) number of pets f) find pets q) quit a Please enter name of pet: Hiram Jinx Please enter pet kind: domestic cat Nerfville Pet Club Membership Program Enter the letter corresponding to your choice: a) add a pet l) show list of pets n) number of pets f) find pets q) quit n 3 pets in club Nerfville Pet Club Membership Program Enter the letter corresponding to your choice: a) add a pet l) show list of pets n) number of pets f) find pets q) quit l Pet: BENNIE HAHA Kind: PARROT Pet: HIRAM JINX Kind: DOMESTIC CAT Pet: QUINCY Kind: PIG Nerfville Pet Club Membership Program Enter the letter corresponding to your choice: a) add a pet l) show list of pets n) number of pets f) find pets q) quit q Bye.

7.5 树的思想
二叉查找树也有一些缺陷。例如,二叉查找树只有在满员(或平衡)时效率最高。假设要存储用户随机输入的单词。该树的外观应如图17.12所示。现在,假设用户按字母顺序输入数据,那么每个新节点应该被添加到右边,该树的外观应下图所示。图"A badly unbalanced binary search tree"所示是平衡的树,图17.16所示是不平衡的树。查找这种树并不比查找链表要快。

C语言之实现二叉树结构
Figure 17.16 A badly unbalanced binary search tree.

避免串状树的方法之一是在创建树时多加注意。如果树或子树的一边或另一边太不平衡,就需要重新排列节点使之恢复平衡。与此类似,可能在进行删除操作后要重新排列树。俄国数学家Adel’son-Vel’skii和Landis发明了一种算法来解决这个问题。根据他们的算法创建的树称为AVL树。因为要重构,所以创建一个平衡的树所花费的时间更多,但是这样的树可以确保最大化搜索效率。 你可能需要一个能存储相同项的二叉查找树。例如,在分析一些文本时,统计某个单词在文本中出现的次数。一种方法是把Item定义成包含一个单词和一个数字的结构。第一次遇到一个单词时,将其添加到树中,并且该单词的数量加1。下一次遇到同样的单词时,程序找到包含该单词的节点,并递增表示该单词数量的值。把基本二叉查找树修改成具有这一特性,不费多少工夫。

考虑Nerfville宠物俱乐部的示例,有另一种情况。示例中的树根据宠物的名字和种类进行排列,所以,可以把名为Sam的猫存储在一个节点中,把名为Sam的狗存储在另一节点中,把名为Sam的山羊存储在第3个节点中。但是,不能存储两只名为Sam的猫。另一种方法是以名字来排序,但是这样做只能存储一个名为Sam的宠物。还需要把Item定义成多个结构,而不是一个结构。第一次出现Sally时,程序创建一个新的节点,并创建一个新的列表,然后把Sally及其种类添加到列表中。下一次出现Sally时,程序将定位到之前存储Sally的节点,并把新的数据添加到结构列表中。

猜你喜欢

转载自blog.csdn.net/yyone123/article/details/107822130