My Blog

ADT

记录 C Prime Plus 关于数据结构的知识

学习笔记,仅供参考

参考C Prime Plus - 十七章


ADT 抽象数据类型

学习计算机语言首先要学会如何使用工具(创建变量、结构、函数等),然而工具是次要的,真正的挑战是设计和创建一个项目。

程序开发最重要的部分是找到程序中表示数据的好方法。目前 C 语言内置类型有基本类型、数组、指针、结构和联合。然而找出正确的数据不仅仅是选择一种数据类型,还要考虑必须进行哪些操作,即确定如何存储数据,并为数据类型定义有效的操作。

简而言之,设计一种数据类型包含设计如何储存该数据类型(数据结构)和设计一系列管理该数据的方法(算法)

类型特指两类信息:属性和操作。如 int 类型的属性是它表示一个整数值,享有整数的属性。int 类型的操作,改变 int 值的符号,能让两个 int 值加减乘除等算数运算。

数据类型的抽象与具体实现,还以整数为例,数学家通过如下定义给出整数的抽象概念:假设 M 和 N 为整数,那么 M+N=N+M;假设 S 和 Q 也为整数,若 N+M=S,且 N+Q=S,则 M=Q。C 语言也实现了整数的这种算数运算。再如数学中的整数可以是无穷大,但 C 中会受到硬件的影响只能表示一定的范围内的整数。所以要清除抽象概念与实际实现。

下面用 3 个步骤完成从抽象到具体的过程:

  1. 提供类型属性和相关操作的抽象描述。这些描述既不依赖特定实现,也不依赖特定的编程语言。这种抽象描述被称为抽象数据类型(ADT)

  2. 开发一个实现 ADT 的编程接口,即指明如何储存数据和执行所需操作的函数。如 C 中,可提供结构定义和操控该结构的函数原型。这些作用于用户定义类型的函数相当于 C 基本类型的内置运算符,需使用该新类型的程序员可使用此接口进行编程

  3. 编写代码实现接口。使用者无需了解此新类型的具体实现细节

    /* 以电影项目为例,采用简化链表作为 ADT */
    // 1. 类型属性及操作
    类型名:简单链表
    类型属性:可存储一系列项
    类型操作:初始化链表为空
        确定链表为空
        确定链表已满
        确定链表中的项数
        在链表末尾添加项
        遍历链表,处理链表中的项
        清空链表
    
    // 2. 建立接口 - 表示数据及实现操作
    /* list.h */
    #ifndef __LIST_H_
    #define __LIST_H_
    
    #include <stdbool.h>
    
    #define TSIZE       45
    
    /* 定义电影数据类型 */
    typedef struct film {
    char title[TSIZE];      // 影片名
    int rating;             // 排名
    } Item;
    
    /* 以链表储存数据类型 */
    typedef struct node {
    Item item;              // 数据项
    struct node *next;      // 后驱指针
    } Node;
    
    typedef Node *List;
    
    /* 函数原型声明 */
    void InitializeList(List *plist);
    bool ListIsEmpty(const List *plist);
    bool ListIsFull(const List *plist);
    unsigned int ListItemCount(const List *plist);
    bool AddItem(Item item, List *plist);
    void Traverse(const List *plist, void(*pfun)(Item item));
    void EmptyTheList(List *plist);
    
    #endif
    /*********************************/
    
    /* list.c */
    #include <stdio.h>
    #include <stdlib.h>
    
    #include "list.h"
    
    static void CopyToNode(Item item, Node *pnode);
    
    void InitializeList(List *plist)
    {
    *plist = NULL;
    }
    
    bool ListIsEmpty(const List *plist)
    {
    if (*plist == NULL)
        return true;
    else
        return false;
    }
    
    bool ListIsFull(const List *plist)
    {
    Node *pt;
    bool full;
    
    pt = (Node *)malloc(sizeof(Node));
    if (pt == NULL)
        full = true;
    else
        full = false;
    
    free(pt);
    return full;
    }
    
    unsigned int ListItemCount(const List *plist)
    {
    unsigned int count = 0;
    Node *pnode = *plist;   /* 设置链表的开始 */
    
    while (pnode != NULL) {
        ++count;
        pnode = pnode->next;    /* 设置下一节点 */
    }
    
    return count;
    }
    
    bool AddItem(Item item, List *plist)
    {
    Node *pnew;
    Node *scan = *plist;
    pnew = (Node *)malloc(sizeof(Node));
    if (pnew == NULL)
        return false;       /* 失败时退出函数 */
        
    CopyToNode(item, pnew);
    pnew->next = NULL;
    
    if (scan == NULL) {       /* 空链表,指向新节点 */
        *plist = pnew;
    } else {
        while (scan->next != NULL)  /* 遍历链表 */
            scan = scan->next;
        scan->next = pnew;          /* 将新节点加到链表末尾 */
    }
    
    return true;
    }
    
    void Traverse(const List *plist, void(*pfun)(Item item))
    {
    Node *pnode = *plist;
    
    while (pnode != NULL) {
        (*pfun)(pnode->item);
        pnode = pnode->next;
    }
    }
    
    void EmptyTheList(List *plist)
    {
    Node *psave;
    
    while (*plist != NULL) {
        psave = (*plist)->next;
        free(*plist);
        *plist = psave;
    }
    }
    
    static void CopyToNode(Item item, Node *pnode)
    {
    pnode->item = item;
    }
    /*******************************/
    
    /* main.c */
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include "list.h"
    
    void showmovies(Item item);
    char *s_gets(char *st, int n);
    
    int main(void)
    {
    List movies;
    Item temp;
    
    InitializeList(&movies);
    if (ListIsFull(&movies)) {
        fprintf(stderr, "No memory available! Bye\n");
        exit(1);
    }
    
    puts("Enter first movie title:");
    while (s_gets(temp.title, TSIZE) != NULL && temp.title[0] != '\0') {
        puts("Enter your rating <0-10>:");
        scanf("%d", &temp.rating);
        while (getchar() != '\n')
            continue;
        printf("get Item OK\n");
        if (AddItem(temp, &movies) == false) {
            fprintf(stderr, "Problem allocating memory\n");
            break;
        }
        printf("add Item OK\n");
        if (ListIsFull(&movies)) {
            puts("The list is now full.");
            break;
        }
        puts("Enter next movies title (empty line to stop):");
    }
    
    /* 显示 */
    if (ListIsEmpty(&movies))
        printf("No data entered.  ");
    else {
        printf("Here is the movies list:\n");
        Traverse(&movies, showmovies);
    }
    printf("You entered %d movies.\n", ListItemCount(&movies));
    
    /* 清理 */
    EmptyTheList(&movies);
    printf("Bye!\n");
    
    return 0;
    }
    
    void showmovies(Item item)
    {
    printf("Movies: %s  Rating: %d\n", item.title, item.rating);
    }
    
    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;
    }

接着再看看队列,它是具有两个特殊属性的链表。第一,新项只能从链表末尾添加;第二,只能从链表开头移出项。队列是一种“先进先出”的数据形式,像排队买票的队伍一样

/* 建立非正式的抽象定义 */
类型名:队列
类型属性:可储存一系列项
类型操作:初始化队列为空
         确定队列为空
         确定队列已满
         确定队列中的项数
         在队列末尾添加项
         在队列开头删除或恢复项
         清空队列

/* 简单队列的数据定义及接口实现 */
/* queue.h */
#ifndef __DATATYPE_H_
#define __DATATYPE_H_

#include <stdbool.h>

#define MAXQUEUE    10

typedef int Item;
typedef struct node {
    Item item;          /* 数据项 */
    struct node *next;  /* 后驱指针 */
} Node;

typedef struct queue {
    Node *front;        /* 队列首指针 */
    Node *rear;         /* 队列尾指针 */
    int items;          /* 队列项数 */
} Queue;

void InitializeQueue(Queue *pq);
bool QueueIsFull(const Queue *pq);
bool QueueIsEmpty(const Queue *pq);
int QueueItemCount(const Queue *pq);
bool EnQueue(Item item, Queue *pq);
bool DeQueue(Item *pitem, Queue *pq);
void EmptyTheQueue(Queue *pq);

#endif
/****************************************************/

/* queue.c */
#include <stdio.h>
#include <stdlib.h>

#include "datatype.h"

static void CopyToNode(Item item, Node *pn);
static void CopyToItem(Node *pn, Item *pi);

/**
 * @brief 初始化队列
 * @param pq 要初始化的队列
 * @retval null
*/
void InitializeQueue(Queue *pq)
{
    pq->front = pq->rear = NULL;
    pq->items = 0;
}

/**
 * @brief 队列是否已满
 * @param pq 所检查的队列
 * @retval true or false
*/
bool QueueIsFull(const Queue *pq)
{
    return pq->items == MAXQUEUE;
}

/**
 * @brief 队列是否为空
 * @param pq 所检查的队列
 * @retval true or false
*/
bool QueueIsEmpty(const Queue *pq)
{
    return pq->items == 0;
}

/**
 * @brief 获取队列项数
 * @param pq 目标队列
 * @retval 该队列项数
*/
int QueueItemCount(const Queue *pq)
{
    return pq->items;
}

/**
 * @brief 在队列尾端添加项
 * @param item 要添加的项
 * @param pq 队列
 * @retval true or false
*/
bool EnQueue(Item item, Queue *pq)
{
    Node *pnew;

    if (QueueIsFull(pq))    /* 队列已满,添加失败 */
        return false;

    pnew = (Node *)malloc(sizeof(Node));
    if (pnew == NULL) {
        fprintf(stderr, "Unable to allocate memory!\n");
        exit(1);
    }

    CopyToNode(item, pnew);
    pnew->next = NULL;

    if (QueueIsEmpty(pq))
        pq->front = pnew;       /* 队列为空,项位与队列首端 */
    else
        pq->rear->next = pnew;  /* 链接到队列尾端 */
    
    pq->rear = pnew;        /* 队列尾指针更新 */
    pq->items++;            /* 队列项数加 1 */
    
    return true;
}


/**
 * @brief 在队列首端删除项
 * @param item 删除的项
 * @param pq 队列
 * @retval true or false
*/
bool DeQueue(Item *pitem, Queue *pq)
{
    Node *pt;

    if (QueueIsEmpty(pq))
        return false;

    CopyToItem(pq->front, pitem);
    pt = pq->front;
    pq->front = pq->front->next;
    free(pt);
    pq->items--;

    if (pq->items == 0)
        pq->rear = NULL;

    return true;
}

void EmptyTheQueue(Queue *pq)
{
    Item dummy;

    while (!QueueIsEmpty(pq))
        DeQueue(&dummy, pq);
}

static void CopyToNode(Item item, Node *pn)
{
    pn->item = item;
}

static void CopyToItem(Node *pn, Item *pi)
{
    *pi = pn->item;
}
/****************************************************/

/* main.c */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "datatype.h"

int main(void)
{
    Queue line;
    Item temp;
    char ch;
    InitializeQueue(&line);
    
    puts("Testing the Queue interface. Type a to add a value,");
    puts("type d to delete a value, and type q to quit.");
    while ((ch = getchar()) != 'q') {
        if (ch != 'a' && ch != 'd')
            continue;

        if (ch == 'a') {
            printf("Interger to add: ");
            scanf("%d", &temp);
            if (!QueueIsFull(&line)) {
                printf("Putting %d into queue\n", temp);
                EnQueue(temp, &line);
            } else {
                puts("Queue is full!");
            }
        } else {
            if (QueueIsEmpty(&line)) {
                puts("Nothing to delete!");
            } else {
                DeQueue(&temp, &line);
                printf("Removing %d from queue\n", temp);
            }
        }

        printf("%d items in queue\n", QueueItemCount(&line));
        puts("Type a to add, d to delete, q to quit:");
    }

    EmptyTheQueue(&line);
    puts("Bye!");

    return 0;
}
/****************************************************/

链表与数组

许多编程问题,如创建简单链表或队列,都可用链表(动态分配的序列链)或数组来处理。下面是两者的优缺点:

数组:优点-C 直接支持;提供随机访问 | 缺点-在编译时确定大小;插入删除元素很费时

链表:优点-运行时确定大小;快速插入删除元素 | 缺点-不能随机访问;用户必须提供编程支持

当需向数组或链表中插入数据时,数组就需将附近的元素搬移以便腾出空间存储要插入的项;而链表只需修改插入位置前后节点的指针即可,删除亦如此。当要在数组或队列中查找某一元素是否存在时,数组可以使用顺序查找或二分查找(数组已排序),而链表只能用顺序查找。所以选择何种数据类型取决于具体问题,若需频繁增删数据而不怎么查找数据的情况下,应选择链表;若只偶尔增删数据,但要经常进行查找,应选用数组。要是需要既能支持频繁增删数据也能支持频繁查找的数据类型时,则要选择下面介绍的二叉查找树

二叉查找树

二叉查找树是一种结合了二分查找策略的链接结构。二叉树的每个节点都含有一个项和两个节点指针,分别指向左节点和右节点。并且节点有顺序规则:左节点的项在父节点项的前面,右节点的项在父节点项的后面。这种数据结构类似于自然界中的树形结构,因此以‘树’来代指这一结构。树顶被称为 ,树具有分层组织或等级,每级都有上一级和下一级。若二叉树是满的,那么每一级的节点数是上一级节点数的两倍。

二叉查找树的每个节点是其后代节点的根,该节点与其后代节点构成的树称为一个子树。二叉树查找目标项:若目标项在根节点项的前面,则只需查找左子树;若在根节点项的后面,则只需查找右子树。二叉查找树在链式结构中结合了二分查找的效率,但所带来的代价是编程时要构建一个二叉树比创建一个链表跟复杂

/* 二叉树的 ADT */
类型名:二叉查找树
类型属性:二叉树要么是空节点的集合(空树),要么是有一个根节点的节点集合
         每个节点都有两个子树,称左子树和右子树
         每个子树本身也是一个二叉树,抑或是空树
         二叉查找树是一个有序的二叉树,每个节点包含一个项
         左子树的所有项都在根节点项的前面,右子树的所有项都在根节点项的后面
类型操作:初始化树为空
         确定树是否为空
         确定树是否已满
         在树中添加一个项
         在树中删除一个项
         在树中查找一个项
         在树中访问一个项
         清空树

/* 二叉查找树的定义及接口实现 */
/* tree.h */
#ifndef __DATATYPE_H_
#define __DATATYPE_H_

#include <stdbool.h>

#define SLEN        20
#define MAXITEMS    10

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;

void InitializeTree(Tree *ptree);
bool TreeIsEmpty(const Tree *ptree);
bool TreeIsFull(const Tree *ptree);
int TreeItemCount(const Tree *ptree);
bool AddItem(const Item *pi, Tree *ptree);
bool InTree(const Item *pi, const Tree *ptree);
bool DeleteItem(const Item *pi, Tree *ptree);
void Traverse(const Tree *ptree, void(*pfun)(Item item));
void DeleteAll(Tree *ptree);

#endif
/*****************************************************/

/* tree.c */
#include <stdio.h>
#include <stdlib.h>

#include "datatype.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;
}

/**
 * @brief 向树中添加新项
 * @param pi 要添加的新项
 * @param ptree 树形结构指针
 * @retval true or false
*/
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)        /* 情况1:树为空 */
        ptree->root = new_node;     /* 新节点为树根节点 */
    else                            /* 情况2:树非空 */
        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;
}

/**
 * @brief 动态分配树形节点,并初始化项及左右指针
 * @param pi 所要初始化的项
 * @retval 成功创建后的树节点指针
*/
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 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;
    
    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;
    
    return false;
}

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 void InOrder(const Trnode *root, void(*pfun)(Item item))
{
    if (root != NULL) {
        InOrder(root->left, pfun);
        (*pfun)(root->item);
        InOrder(root->right, pfun);
    }
}

/**
 * @brief 检查目标项 pi 是否已存在于树中
 * @param pi 目标项
 * @param ptree 树形指针
 * @retval look.child 为 NULL 则目标项不在树中,否则在树中
*/
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;      /* look.child 为目标项节点 */
    }

    return look;
}
static void DeleteNode(Trnode **ptr)
{
    /* ptr 是指向目标节点的父节点指针成员的地址 */
    Trnode *temp;

    if ((*ptr)->left == NULL) {     /* 左节点为 NULL,释放根节点并将右节点作为根节点 */
        temp = *ptr;
        *ptr = (*ptr)->right;
        free(temp);
    } else if ((*ptr)->right == NULL) { /* 左非 NULL,右为 NULL,释放根并将左节点作为根节点 */
        temp = *ptr;
        *ptr = (*ptr)->left;
        free(temp);
    } else {    /* 左右非 NULL,先找出左子树的最大(即最右叶节点 X),再将右子树节点链接到 X 的右节点,根节点指针偏移到左节点,删除根节点*/
        for (temp = (*ptr)->left; temp->right != NULL; temp = temp->right)
            continue;
        temp->right = (*ptr)->right;
        temp = *ptr;
        *ptr = (*ptr)->left;
        free(temp);
    }
}

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

    if (root != NULL) {
        pright = root->right;
        DeleteAllNodes(root->left); /* 删除根节点左子树 */
        free(root);                 /* 删除根节点 */
        DeleteAllNodes(pright);     /* 删除根节点右子树 */
    }
}
/*****************************************************/

/* main.c */
/**
 * @file 该程序以菜单的形式提供选择:向俱乐部成员花名册添加宠物、显示成员列表、
 *       报告成员数量、核实成员及退出。
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include "datatype.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");
                break;
        }
    }
    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;
        if (strchr("alrfndq", ch) == NULL)
            puts("Please enter an a, l, n, f, 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 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 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 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 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;
}
/*****************************************************/

二叉查找树在编程实现重要部分是节点的添加与删除,添加要判断是在根节点的左边还是右边,如此递归直到满足条件;删除要看是删除叶节点、带有单个子树的节点或是具有左右子树的节点,特别是最后一种要考虑删除之后左右节点如何拼接,另外删除节点时要以左节点-根节点-右节点的顺序删除。同时 tree.c 中局部函数也至关重要,它们是对增删细节操作的封装。在查找时根据根节点作为判断标准,将查找范围缩小到左边或是右边,其中实现涉及到递归以简化这种重复操作。

树的思想

二叉查找树的缺点,只有在满员(或平衡)时效率最高。当后续节点都挂到父节点的右节点上,这就构成不平衡的树。对于这种不平衡的串形树,需重新排列节点使之恢复平衡。俄国数学家 Adel’son-Vel’skii 和 Landis 发明一种算法解决不平衡的问题,根据这种算法所创建的树称为 AVL 树

要徒手实现一个链表或数这种 ADT 比较困难,很容易犯错,而插件库则提供了一种可选方法。通过本章的学习,能更好地认识这样的数据类型的实现。