C语言二叉查找树(图文详解)(超详细)

二叉查找树

本人在第一次学习二叉树的时候,感觉很懵懵懂懂,勉强知道了二叉树的结构和查找方式,但要我自己去动手写的时候,可是难上加难,所以这里我用干货+实际例子的方式让你上手二叉树,这个例子几乎可以套用到所有链式结构问题,我们开始吧!

为什么要使用二叉查找树?

二叉查找树也称二叉树,是一种结合了二分查找策略的链接结构,众所周知,链式结构也称链表,它可以在不确定数据项个数的情况下存储数据项,那为什么又要使用树这种复杂的数据结构去存储呢?

在这里插入图片描述

首先,如果我们想要在链表里查找一个给定的数据项,无论是单向链表,双向链表,还是循环链表,都需要逐个遍历每一个节点去查找,如果链表中有1023个节点,顺序查找最多要查找1022次才能找到,这效率显然太低

在这里插入图片描述

那么,有没有什么更快更有效率的查找方式呢?那便是二分查找,顾名思义,就是每查找一次就排除一半的数据,在另一半继续查找,这样,查找n次,就可以找完2n-1个数据,那么1023个数据最多只需要查找10次即可找到

查看源图像

但是,现在又遇到一个问题,二分查找需要事先已经排序好的数据才方便查找,并且每次查找都需要定位到某部分数据的中间项,链表虽然可以在生成时就进行排序,也能在生成完毕后再进行排序,但如果需要每次定位到链表某部分中间可就麻烦了,既费时又费力,还不如顺序查找来得方便,这时候,就出现了二叉查找树这种数据结构:

在这里插入图片描述

我们这样定义二叉查找树:

二叉树中的每个节点都包含两个子节点—左节点右节点,其顺序按照如下规定确定:左节点的项在父节点的项前面,右节点的项在父节点的项后面。这种关系存在于每个有子节点的节点中。进一步而言,所有可以追溯其祖先回到一个父节点的左节点的项,都在该父节点项的前面;所有以一个父节点的右节点为祖先的项,都在该父节点项的后面。图中的树以这种方式储存单词。有趣的是,与植物学的树相反,该树的顶部被称为根( root)

二叉查找树怎么查找?

假设要在二叉树中查找一个项(即目标项)。如果目标项在根节点项的前面,则只需查找左子树;如果目标项在根节点项的后面,则只需查找右子树。因此,每次比较就排除半个树。假设查找左子树,这意味着目标项与左子节点项比较。如果目标项在左子节点项的前面,则只需查找其后代节点的左半部分,以此类推。与二分查找类似,每次比较都能排除一半的可能匹配项。(这样的思想刚好就实现了二分查找!惊不惊喜?意不意外?😮)

刚刚说了,二叉树实现起来比较麻烦,要是能把二叉树当成我们平常所用的普通数据(如数组)一样用那该多好:happy:当然可以!如果把二叉树定义成一种数据类型,写出来一遍后,以后直接拿着用(Ctrl+C->Ctrl+V)😁那么:

二叉树应有的功能是什么?

类型名二叉查找树
类型操作:初始化树
确认树是否为空
确认树是否为满
能够知道树中的项数
在树中添加一个项
在树中删除一个项
在树中查找一个项
在树中访问一个项
清空树

有了这些功能的话,其他人(其实就你自己)创建和使用二叉树就只需使用二叉树接口就好了

如何定义二叉树接口?

我们以一个具体的例子来进行接下来的讲述:

现在要开发一个宠物店的数据库,每一条数据都包含了宠物的名字和宠物的种类(宠物名不允许重复),要求能够快速查询,添加,访问,删除数据项

现在,我们来具体分析:

首先,既然要存储宠物的名字和种类,肯定需要用到字符串,把两个字符串存储在一起,可以用二维数组,但我们采用结构体的方式来进行存储,以方便下次存储其他类型数据时修改:

#define SLEN 20
typedef struct Item
{
    /**********这里可以是任何想要存储的东西**************/
    char petname[SLEN];	//宠物名
    char petkind[SLEN];	//宠物种类
}Item;	//以后如果需要修改存储的数据类型,可以直接在这里修改就好

接下来我们需要定义一个节点类型,用来存储一个节点包含哪些信息:

typedef struct trnode
{
    Item item;	//这是我们刚刚定义的数据
    struct trnode *left;	//该节点指向的左子树
    struct trnode *right;	//该节点指向的右子树
}Trnode;	//将需要存储的数据和两个指向子树的指针合起来构成节点

那么,要想存储一整棵树,我们需要存储树的哪些信息?我们不妨想一下,在存储链表时,我们存储了头(head)节点,在存储队列的时候我们存储了队首节点和队尾节点,在存储栈的时候,我们存储了栈顶节点,那么存储树的时候,我们只需要存储根(root)节点即可访问整棵树:

typedef struct tree
{
    Trnode *root;	//指向根节点的指针
    int size;		//存储树的节点数(大小)
}Tree;	//存储大小以方便后面判断树是否为空树

好了,定义好了数据类型,接下来就是想一想可以给用户提供哪些接口(功能实现)去使用?按照我们上面列出的表格,我们可以先写出以下接口文件(先不用管怎么实现):

//tree.h	//二叉树接口头文件
#ifndef _TREE_H_
#define _TREE_H_

#define <stdbool.h>	//可替换成enum bool {false,true};
/* 根据具体情况定义Item */
#define SLEN 20
typedef struct Item
{
    /**********这里可以是任何想要存储的东西**************/
    char petname[SLEN];	//宠物名
    char petkind[SLEN];	//宠物种类
}Item;	//以后如果需要修改存储的数据类型,可以直接在这里修改就好
typedef struct trnode
{
    Item item;	//这是我们刚刚定义的数据
    struct trnode *left;	//该节点指向的左子树
    struct trnode *right;	//该节点指向的右子树
}Trnode;	//将需要存储的数据和两个指向子树的指针合起来构成节点
typedef struct tree
{
    Trnode *root;	//指向根节点的指针
    int size;		//存储树的节点数(大小)
}Tree;	//存储大小以方便后面判断树是否为空树

/* 自定义接口函数原型 */

/*************************************
*函数名:InitializeTree
*功  能:初始化一棵树为空
*参  数:指向树的指针 Tree *ptree
*返  回:void
*************************************/
void InititalizeTree(Tree *ptree);

/*************************************
*函数名:TreeIsEmpty
*功  能:确定树是否为空
*参  数:指向树的指针 const Tree *ptree
*返  回:树为空返回true(1),否则返回false(0)
*************************************/
bool TreeIsEmpty(const Tree *ptree);

/*************************************
*函数名:TreeIsFull
*功  能:确定树是否为满
*参  数:指向树的指针 const Tree *ptree
*返  回:树为满返回true(1),否则返回false(0)
*************************************/
bool TreeIsFull(const Tree *ptree);

/*************************************
*函数名:TreeItemCount
*功  能:返回树的节点数(项数)
*参  数:指向树的指针 const Tree *ptree
*返  回:树的项数int
*************************************/
int TreeItemCount(const Tree *ptree);

/*************************************
*函数名:	AddItem
*功  能:在树中添加一个项
*参  数:指向待添加项的指针const Item *pi
*	   指向一个已初始化树的指针Tree *ptree  
*返  回:成功添加返回true,否则返回false
*************************************/
bool AddItem(const Item *pi,Tree *ptree);

/*************************************
*函数名:InTree
*功  能:在树中查找一个项
*参  数:指向待查找项的指针const Item *pi
*      指向树的指针 const Tree *ptree
*返  回:找到返回true,否则返回false
*************************************/
bool InTree(const Item *pi,const Tree *ptree);

/*************************************
*函数名:DeleteItem
*功  能:在树中删除一个项
*参  数:指向待删除项的指针const Item *pi
*      指向树的指针Tree *ptree
*返  回:删除成功返回true(1),否则返回false(0)
*************************************/
bool DeleteItem(const Item *pi,Tree *ptree);

/*************************************
*函数名:Traverse
*功  能:把树中的每一项都应用于另一个函数
*参  数:指向树的指针 const Tree *ptree
*      函数指针void(*pfun)(Item item)
*返  回:void
*************************************/
void Traverse(const Tree *ptree,void (*pfun)(Item item));

/*************************************
*函数名:DeletAll
*功  能:删除树中的所有内容(释放内存)
*参  数:指向树的指针Tree *ptree
*返  回:void
*************************************/
void DeletAll(Tree *ptree);

#endif

把这些函数的功能,需要的参数以及返回值全部放在头文件里,而不把函数的具体实现给用户看到,这是一种隐藏实现细节的做法,用户只用关系怎么用就好😄

二叉树接口的实现:

接下来,就是如何实现这些接口的功能:

  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 full\n");
        return false; //提前返回
    }
    if (SeekItem(pi, ptree).child != NULL)
    {
        fprintf(stderr, "Attempted to add duplicate item\n");
        return false; 
    }
    new_node = MakeNode(pi); //指向创建的新节点
    if (new_node == NULL)
    {
        fprintf(stderr, "Couldn't create node\n");
        return false;  
    }
    ptree->size++;	//树的大小加一
    
    if (ptree->root == NULL)      //如果是空树
        ptree->root = new_node;  
    else                         //不是空树 
        AddNode(new_node,ptree->root);
    
    return true;
}

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) 
            root->left = new_node; 
        else
            AddNode(new_node, root->left);
    }
    else if (ToRight(&new_node->item, &root->item))
    {
        if (root->right == NULL)
            root->right = new_node;
        else
            AddNode(new_node, root->right);
    }
    else 
    {
        fprintf(stderr,"location error in AddNode()\n");
        exit(1);
    }
}

ToLeft()和ToRight ()函数依赖于Item类型的性质。宠物俱乐部的成员名按字母排序。如果两个宠物名相同,按其种类排序。如果种类也相同,这两项属于重复项,根据该二叉树的定义,这是不允许的。回忆一下,如果标准C库函数strcmp()中的第1个参数表示的字符串在第1个参数表示的字符串前面,该函数则返回负数;如果两个字符串相同,该函数则返回0;如果第1个字符串在第⒉个字符串后面,该函数则返回正数。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;
}
  1. 查找项

    3个接口函数都要在树中查找特定项: AddItem ()、InItem ()和 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; 
    
    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
            break;	//都不满足即为相等
    }
    
    return look;
}
  1. 考虑删除项

    删除项是最复杂的任务,因为必须重新连接剩余的子树形成有效的树。在准备编写这部分代码之前,必须明确需要做什么。
    图演示了最简单的情况。待删除的节点没有子节点,这样的节点被称为叶节点(leaf)。这种情况只需把父节点中的指针重置为NULL,并使用free ()函数释放已删除节点所占用的内存。

在这里插入图片描述

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

在这里插入图片描述

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

在这里插入图片描述

  1. 遍历树

    遍历树比遍历链表更复杂,因为每个节点都有两个分支。这种分支特性很适合使用分而制之的递归(详见第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);
    }
}
  1. 清空树

    清空树基本上和遍历树的过程相同,即清空树的代码也要访问每个节点,而且要用free()函数释放内存。除此之外,还要重置Tree类型结构的成员,表明该树为空。DeleteAll()函数负责处理Tree类
    型的结构,把释放内存的任务交给DeleteAllNode ()幽数。DeleteA11Node() 了 inoraer)A内双H构造相同,它储存了指针的值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);
    }
}
  1. 完整的实现
/* tree.c 接口的实现*/
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include "tree.h"

/* 定义一个结构体,用来存储一个树的父树和子树 */
typedef struct pair {
    Trnode * parent;
    Trnode * child;
} Pair;

/* 本地函数,用来方便实现接口,但不作为接口提供给用户使用! */
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);

/* 函数实现 */
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 full\n");
        return false;
    }
    if (SeekItem(pi, ptree).child != NULL)
    {
        fprintf(stderr, "Attempted to add duplicate item\n");
        return false;
    }
    new_node = MakeNode(pi);
    if (new_node == NULL)
    {
        fprintf(stderr, "Couldn't create node\n");
        return false;
    }

    ptree->size++;
    
    if (ptree->root == NULL)
        ptree->root = new_node;
    else
        AddNode(new_node,ptree->root);
    
    return true;
}

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)
        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;
}


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)
            root->left = new_node;
        else
            AddNode(new_node, root->left);
    }
    else if (ToRight(&new_node->item, &root->item))
    {
        if (root->right == NULL)
            root->right = new_node;
        else
            AddNode(new_node, root->right);
    }
    else
    {
        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; 
    
    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
            break; 
    }
    
    return look;
}

static void DeleteNode(Trnode **ptr)
{
    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
    {
        for (temp = (*ptr)->left; temp->right != NULL;
             temp = temp->right)
            continue;
        temp->right = (*ptr)->right;
        temp = *ptr;
        *ptr =(*ptr)->left;
        free(temp); 
    }
}

二叉树接口的使用:

那么,用户在使用的时候,就可以很轻松的使用我们提供的二叉树接口来创建二叉树,添加,删除,查找节点,例如下面的一个伪代码:

  • 创建并初始化一个一棵二叉树(pets)用来存储宠物

  • 有一个菜单供我选择做什么

  • 添加宠物

  • 显示出所有的宠物及其种类

  • 寻找一个宠物的信息

  • 总共有多少宠物

  • 从树中删除宠物

  • 清空树

    下面为用户创建的代码:

/* petclub.c 宠物俱乐部 */
#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 club\n",
                               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')
            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)
        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: %-19s\n", item.petname,
           item.petkind);
}

void findpet(const Tree * pt)
{
    Item temp;
    
    if (TreeIsEmpty(pt))
    {
        puts("No entries!");
        return;
    }
    
    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; 
    }
    
    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');
        if (find)  
            *find = '\0';
        else
            while (getchar() != '\n')
                continue; 
    }
    return ret_val;
}

  • 14
    点赞
  • 49
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
好的,下面是一个用 C 语言实现的二叉排序树通讯录的示例代码: ```c #include <stdio.h> #include <stdlib.h> #include <string.h> // 定义通讯录结构体,包含姓名和电话号码 typedef struct Contact { char name[20]; char phone[12]; } Contact; // 定义二叉排序树结点 typedef struct TreeNode { Contact contact; struct TreeNode *left; struct TreeNode *right; } TreeNode; // 插入通讯录结点到二叉排序树中 TreeNode *insertNode(TreeNode *root, Contact contact) { if (root == NULL) { TreeNode *newNode = (TreeNode *)malloc(sizeof(TreeNode)); newNode->contact = contact; newNode->left = NULL; newNode->right = NULL; return newNode; } // 根据姓名判断插入到左子树还是右子树 if (strcmp(contact.name, root->contact.name) < 0) { root->left = insertNode(root->left, contact); } else { root->right = insertNode(root->right, contact); } return root; } // 查找通讯录结点 TreeNode *searchNode(TreeNode *root, char *name) { if (root == NULL) { return NULL; } // 根据姓名比较查找 if (strcmp(name, root->contact.name) == 0) { return root; } else if (strcmp(name, root->contact.name) < 0) { return searchNode(root->left, name); } else { return searchNode(root->right, name); } } // 删除通讯录结点 TreeNode *deleteNode(TreeNode *root, char *name) { if (root == NULL) { return NULL; } // 根据姓名比较查找 if (strcmp(name, root->contact.name) == 0) { // 如果该结点没有子结点,直接删除 if (root->left == NULL && root->right == NULL) { free(root); return NULL; } // 如果该结点只有左子树或只有右子树,直接将子结点替换掉该结点 if (root->left == NULL || root->right == NULL) { TreeNode *child = root->left == NULL ? root->right : root->left; free(root); return child; } // 如果该结点有两个子结点,将其右子树中的最小值结点替换掉该结点 TreeNode *minNode = root->right; while (minNode->left != NULL) { minNode = minNode->left; } root->contact = minNode->contact; root->right = deleteNode(root->right, minNode->contact.name); } else if (strcmp(name, root->contact.name) < 0) { root->left = deleteNode(root->left, name); } else { root->right = deleteNode(root->right, name); } return root; } // 遍历二叉排序树,按照中序遍历的顺序输出通讯录 void printTree(TreeNode *root) { if (root == NULL) { return; } printTree(root->left); printf("%s %s\n", root->contact.name, root->contact.phone); printTree(root->right); } int main() { int choice; char name[20]; Contact contact; TreeNode *root = NULL; while (1) { printf("请选择操作:\n"); printf("1. 添加联系人\n"); printf("2. 查找联系人\n"); printf("3. 删除联系人\n"); printf("4. 显示通讯录\n"); printf("5. 退出程序\n"); printf("请输入操作编号:"); scanf("%d", &choice); switch (choice) { case 1: printf("请输入联系人姓名:"); scanf("%s", contact.name); printf("请输入联系人电话号码:"); scanf("%s", contact.phone); root = insertNode(root, contact); printf("联系人已添加到通讯录中!\n"); break; case 2: printf("请输入联系人姓名:"); scanf("%s", name); TreeNode *node = searchNode(root, name); if (node == NULL) { printf("未找到该联系人!\n"); } else { printf("%s %s\n", node->contact.name, node->contact.phone); } break; case 3: printf("请输入联系人姓名:"); scanf("%s", name); root = deleteNode(root, name); printf("联系人已从通讯录中删除!\n"); break; case 4: printf("通讯录如下:\n"); printTree(root); break; case 5: printf("程序已退出!\n"); exit(0); default: printf("输入错误,请重新输入!\n"); break; } } return 0; } ``` 这个代码实现了四个基本操作:插入联系人、查找联系人、删除联系人、显示通讯录。其中,通讯录使用了二叉排序树来存储,按照联系人的姓名进行排序。你可以根据自己的需要进行修改和扩展。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值