c语言 数据结构 list、queue、tree抽象数据类型的定义与实现 详尽代码和注释

本文使用c语言定义并实现list、queue、tree抽象数据类型,代码有详尽注释,可以通过代码熟悉原理并运用数据结构。

0.ADT基础知识

类型包括两类信息,属性和操作。在编程时,根据编程问题匹配合适的数据类型。

定义一个新的数据类型,有三个步骤:

  1. 提供类型属性和相关操作的抽象描述。描述不依赖于特定实现和特定编程语言。
  2. 开发一个实现ADT的编程接口。描述如何表示数据、描述实现ADT操作的函数,在c中,可以提供结构定义和操控该结构的函数原型。
  3. 编写代码实现接口。

1.list(链表)的实现

根据三个步骤,定义一个list:
1.建立抽象:

  • 类型名:简单链表。
  • 类型属性:可存储一系列项。
  • 类型操作:初始化链表为空、确定链表为空、确定链表已满、确定链表中的项数、在链表末尾添加项、遍历链表,处理链表中的项、清空链表。

2.建立接口:见list.h

建立接口后,可以使用这个接口编写程序,而不必知道具体实现细节,见film3.c

3.实现接口:见list.c

1.1 list.h

/* list.h -- header file for a simple list type */
#ifndef LIST_H_
#define LIST_H_
#include <stdbool.h>     /* C99 feature         *//* 特定程序的声明 */#define TSIZE      45    /* 存储电影名的数组大小  */
struct film
{char title[TSIZE];int rating;
};/* 一般类型定义 */typedef struct film Item;
/* 如果以后需要其他数据形式的链表,*/
/* 可以重新定义Item类型,不必更改其余的接口定义 */typedef struct node
{Item item;struct node * next;
} Node;/* 每一个链节叫做节点(Node) */typedef Node * List;
/* 指向链表开始处的指针,List作为该类型的指针名*//* 函数原型 */
/* 前提条件:调用该函数前应具备的条件 */
/* 后置条件:执行完该函数后的情况 *//* 操作:        初始化一个链表                          */
/* 前提条件:    plist 指向一个链表                     */
/* 后置条件:    链表初始化为空           */
void InitializeList(List * plist);
/* 该函数的参数是指向链表的指针,*/
/* 如果函数想要修改一个参数,那么该参数的类型应该是指向相应类型的指针 */
/* 调用时InitializeList(&movies) *//* operation:        确定链表是否为空                 */
/*                   plist 指向一个已初始化的链表        */
/* postconditions:   如果链表为空,函数返回true     */
/*                   否则返回false               */
bool ListIsEmpty(const List *plist);/* operation:        确定链表是否已满                  */
/*                   plist points to an initialized list        */
/* postconditions:   function returns True if list is full      */
/*                   and returns False otherwise                */
bool ListIsFull(const List *plist);/* operation:        确定链表中的项数          */
/*                   plist points to an initialized list        */
/* postconditions:   函数返回链表中的项数   */
unsigned int ListItemCount(const List *plist);/* operation:        在链表的末尾添加项                    */
/* preconditions:    item 是一个待添加至链表的项        */
/*                   plist 指向一个已初始化的链表        */
/* postconditions:   如果可以, 函数在链表末尾添加一个项     */
/*                   且返回True; 否则返回False    */                     
bool AddItem(Item item, List * plist);/* operation:        把函数作用于链表中的每一项      */
/*                   plist 指向一个已初始化的链表        */
/*                   pfun 指向一个函数,该函数接受一个    */
/*                   Item类型的参数,且无返回值      */
/* postcondition:    pfun指向的函数作用于链表中的每个项一次         */
void Traverse (const List *plist, void (* pfun)(Item item) );/* operation:        释放已分配的内存(如果有的话)            */
/*                   plist points to an initialized list        */
/* postconditions:   释放了为链表分配的所有内存 */
/*                   链表设置为空               */
void EmptyTheList(List * plist);#endif

1.2 list.c

/* list.c -- 支持链表操作的函数 */
#include <stdio.h>
#include <stdlib.h>
#include "list.h"/* 局部函数原型 */
/* 该函数是实现的一部分,不是接口的一部分,*/
/* 使用static存储类别说明符将其隐藏在list.c文件夹中 */
static void CopyToNode(Item item, Node * pnode);/* 接口函数   */
/* 把链表设置为空 */
void InitializeList(List * plist)
{* plist = NULL;
}/* 如果链表为空,返回true */
bool ListIsEmpty(const List * plist)
{if (*plist == NULL)return true;elsereturn false;
}/* 如果链表已满,返回True */
bool ListIsFull(const List * plist)
{Node * pt;bool full;/*  添加一个节点,分配一次内存 */pt = (Node *) malloc(sizeof(Node));if (pt == NULL)/*  系统把内存分配完了,此时表示链表满了 */full = true;elsefull = 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;
}/* 创建存储项的节点 */
/* 并将其添加至由plist指向的链表末尾  */
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;         /* pnew 放在链表的开头 */else{while (scan->next != NULL)scan = scan->next;  /* 找到链表的末尾    */scan->next = pnew;      /* 把pnew添加到链表的末尾     */}return true;
}/* 访问每个节点并执行pfun指向的函数 */
void Traverse  (const List * plist, void (* pfun)(Item item) )
{Node * pnode = *plist;    /* 设置链表的开始   */while (pnode != NULL){(*pfun)(pnode->item); /* 把函数应用于链表中的项 */pnode = pnode->next;  /* 前进到下一项   */}
}/* 释放由malloc()分配的内存 */
/* 设置链表指针为 NULL          */
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;  /* 拷贝结构 */
}

1.3 film3.c

使用接口编写程序,但是不必知道具体的实现细节。

/* films3.c -- using an ADT-style linked list */
/* compile with list.c                        */
#include <stdio.h>
#include <stdlib.h>    /* prototype for exit() */
#include "list.h"      /* defines List, Item   */
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;if (AddItem(temp, &movies)==false){fprintf(stderr,"Problem allocating memory\n");break;}if (ListIsFull(&movies)){puts("The list is now full.");break;}puts("Enter next movie title (empty line to stop):");}/* 显示         */if (ListIsEmpty(&movies))printf("No data entered. ");else{printf ("Here is the movie 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("Movie: %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)                  // 如果地址不是NULL,*find = '\0';          // 在此处放置一个空字符elsewhile (getchar() != '\n')continue;          // 处理输入行的剩余内容}return ret_val;
}

2.queue(队列)的实现

队列是具有两个特殊属性的链表,1.新项只能添加到链表的末尾,2.只能从链表的开头移除项。 先进先出的数据形式。

根据三个步骤,定义一个list:
1.建立抽象:

  • 类型名:队列。
  • 类型属性:可存储一系列项。
  • 类型操作:初始化队列为空、确定队列为空、确定队列已满、确定队列中的项数、在队列末尾添加项、在队列开头删除或恢复项、清空队列。

2.建立接口:见queue.h

3.实现接口:见queue.c

4.测试接口:见use_q.c

5.使用队列:见mall.c

2.1 queue.h

/* queue.h -- interface for a queue */
#ifndef _QUEUE_H_
#define _QUEUE_H_
#include <stdbool.h>// 在这里插入Item类型的定义
// FOR EXAMPLE,
//typedef int Item;  // for use_q.c
// OR typedef struct item {int gumption; int charisma;} Item;
// OR  (for mall.c)
/**/typedef struct item{long arrive;      // 一位顾客加入队列的时间int processtime;  // 该顾客咨询时花费的时间} Item;
/**/#define MAXQUEUE 10typedef struct node
{Item item;struct node * next;
} Node;typedef struct queue
{Node * front;  /* 指向队列首项的指针  */Node * rear;   /* 指向队列尾项的指针   */int items;     /* 队列中的项数   */
} Queue;/* operation:        初始化队列                       */
/* 前提条件:     pq 指向一个队列                       */
/* 后置条件:     队列被初始化为空        */
void InitializeQueue(Queue * pq);/* operation:        检查队列是否已满                    */
/* precondition:     pq 指向之前被初始化的队列           */
/* postcondition:    如果队列已满则返回true,否则返回false   */
bool QueueIsFull(const Queue * pq);/* operation:        检查队列是否为空                    */
/* precondition:     pq 指向之前被初始化的队列  */
/* postcondition:    如果队列为空则返回true,否则返回false */
bool QueueIsEmpty(const Queue *pq);/* operation:        确定队列中的项数         */
/* precondition:     pq points to previously initialized queue  */
/* postcondition:    返回队列中的项数          */
int QueueItemCount(const Queue * pq);/* operation:        在队列末尾添加项                  */
/* precondition:     pq 指向之前被初始化的队列  */
/*                   item是要被添加在队列末尾的项      */
/* postcondition:    如果队列不为空,item将被添加在队列的末尾,   */
/*                   函数返回true;         */
bool EnQueue(Item item, Queue * pq);/* operation:        从队列的开头删除项            */
/* precondition:     pq 指向之前被初始化的队列  */
/* postcondition:    如果队列不为空,     */
/*                   队列首端的item将被拷贝到*pitem中, */
/*                   并被删除,且函数返回True   */
/*                   如果该操作使队列为空,  */
/*                   则重置队列为空   */
/*                   如果队列在操作前为空,     */
/*                   该函数返回false                     */
bool DeQueue(Item *pitem, Queue * pq);/* operation:        清空队列                            */
/* precondition:     pq指向之前被初始化的队列  */
/* postconditions:   队列被清空                         */
void EmptyTheQueue(Queue * pq);#endif

2.2 queue.c

/* queue.c -- the Queue type implementation*/
#include <stdio.h>
#include <stdlib.h>
#include "queue.h"/* local functions */
static void CopyToNode(Item item, Node * pn);
static void CopyToItem(Node * pn, Item * pi);void InitializeQueue(Queue * pq)
{pq->front = pq->rear = NULL;pq->items = 0;
}bool QueueIsFull(const Queue * pq)
{return pq->items == MAXQUEUE;
}bool QueueIsEmpty(const Queue * pq)
{return pq->items == 0;
}int QueueItemCount(const Queue * pq)
{return pq->items;
}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;           /* 项位于队列首端     */elsepq->rear->next = pnew;      /* 链接到队列尾端   */pq->rear = pnew;                /* 记录队列尾端位置 */pq->items++;                    /* 队列项数加1 */return true;
}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;//首指针和尾指针都置为NULLreturn true;
}/* 清空队列                */
void EmptyTheQueue(Queue * pq)
{Item dummy;while (!QueueIsEmpty(pq))DeQueue(&dummy, pq);
}/* Local functions                 */static void CopyToNode(Item item, Node * pn)
{pn->item = item;
}static void CopyToItem(Node * pn, Item * pi)
{*pi = pn->item;
}

2.3 use_q.c

/* use_q.c -- driver testing the Queue interface */
/* compile with queue.c                          */
#include <stdio.h>
#include "queue.h"  /* defines Queue, Item       */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')   /* ignore other input */continue;if ( ch == 'a'){printf("Integer to add: ");scanf("%d", &temp);if (!QueueIsFull(&line)){printf("Putting %d into queue\n", temp);EnQueue(temp,&line);}elseputs("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;
}

2.4 mall.c

有一个提供快乐的摊位,顾客可以购买1分钟、2分钟或3分钟的快乐。为确保交通顺畅,每个摊位前排队等待的顾客最多为10人。假设顾客随机出现,并且他们花费的时间也是随机选择的(1\2\3分钟),那么该摊位平均每小时接待多少名顾客?每位顾客平均花多长时间?排队等待的顾客平均有几人?

// mall.c -- use the Queue interface
// compile with queue.c
#include <stdio.h>
#include <stdlib.h>    // for rand() and srand()
#include <time.h>      // for time()
#include "queue.h"     // change Item typedef
#define MIN_PER_HR 60.0bool newcustomer(double x);   // 是否有新顾客来?
Item customertime(long when); // 设置顾客参数int main(void)
{Queue line;Item temp;                // 新的顾客数据int hours;                // 模拟的小时数int perhour;              // 每小时平均多少位顾客long cycle, cyclelimit;   // 循环计数器、 计数器的上限long turnaways = 0;       // 因队列已满被拒的顾客数量long customers = 0;       // 加入队列的顾客数量long served = 0;          // 在模拟期间到访过摊位的顾客数量long sum_line = 0;        // 累计的队列总长int wait_time = 0;        // 从当前到摊位空闲所需时间double min_per_cust;      // 顾客到来的平均时间long line_wait = 0;       // 队列累计的等待时间 InitializeQueue(&line);srand((unsigned int) time(0)); // rand()随机初始化puts("Case Study: Sigmund Lander's Advice Booth");puts("Enter the number of simulation hours:");scanf("%d", &hours);cyclelimit = MIN_PER_HR * hours;puts("Enter the average number of customers per hour:");scanf("%d", &perhour);min_per_cust = MIN_PER_HR / perhour;for (cycle = 0; cycle < cyclelimit; cycle++)//每次迭代对应一分钟的行为{if (newcustomer(min_per_cust))//如果1分钟内有顾客到来{if (QueueIsFull(&line))turnaways++;//被拒的顾客数量else{customers++;temp = customertime(cycle);/*设置temp结构中的arrive和processtime成员*/EnQueue(temp, &line);}}if (wait_time <= 0 && !QueueIsEmpty(&line)){//如果队列不为空且前面的顾客没有咨询,则删除队列首端的项DeQueue (&temp, &line);wait_time = temp.processtime;//该顾客咨询时花费的时间line_wait += cycle - temp.arrive;//到目前为止,队列中所有顾客的等待总时间served++;//咨询过的顾客数量}if (wait_time > 0)//完成当前顾客的需求所需的时间大于0wait_time--;sum_line += QueueItemCount(&line);//目前为止统计的队列长度}if (customers > 0){printf("customers accepted: %ld\n", customers);printf("  customers served: %ld\n", served);printf("       turnaways: %ld\n", turnaways);printf("average queue size: %.2f\n",(double) sum_line / cyclelimit);printf(" average wait time: %.2f minutes\n",(double) line_wait / served);}elseputs("No customers!");EmptyTheQueue(&line);puts("Bye!");return 0;
}// x = 顾客到来的平均时间(单位:分钟) 
// 如果1分钟内有顾客到来,则返回true
bool newcustomer(double x)
{if (rand() * x / RAND_MAX < 1)return true;elsereturn false;
}// when是顾客到来的时间
// 该函数返回一个Item结构,该顾客到达的时间设置成when
// 咨询时间设置为1 - 3的随机值
Item customertime(long when)
{Item cust;cust.processtime = rand() % 3 + 1;cust.arrive = when;return cust;
}

3.tree(二叉查找树)的实现

访问元素有两种,随机访问:使用数组下标直接访问该数组中的任意元素;顺序访问:对于链表而言,必须从链表首节点开始,逐个节点移动到要访问的节点。
用数组可以实现二分查找,因为可以使用数组下标确定数组中任意部分的中点。但是链表只支持顺序访问,不提供跳至中间节点的方法,所以链表中不能使用二分查找。

有一种数据形式的需求,既支持频繁插入和删除项,又支持频繁查找。此时需要二叉查找树。

根据三个步骤,定义一个二叉树:

该定义假设二叉树不包含相同的项。

1.建立抽象:

类型名:二叉查找树。

类型属性:

  • 二叉树要么是空节点的集合(空树),要么是有一个根节点的节点集合。
  • 每个节点都有两个子树,叫做左子树和右子树
  • 每个子树本身也是一个二叉树,也可能是空树
  • 二叉查找树是一个有序的二叉树,每个节点包含一个项。
  • 左子树的所有项都在根节点项的前面,右子树所有项都在根节点项的后面

类型操作:初始化树为空、确定树是否为空、确定树是否已满、确定树中的项数、在树中添加一个项、在树中删除一个项、在树中查找一个项、在树种访问一个项、清空树。

2.建立接口:见tree.h

3.实现接口:见tree.c

4.使用二叉树:见petclub.c

3.1 tree.h

/* tree.h -- binary search tree 二叉查找树                */
/*           树中不允许有重复的项 */
#ifndef _TREE_H_
#define _TREE_H_
#include <stdbool.h>/* 根据情况重新定义Item */
#define SLEN 20
typedef struct item
{char petname[SLEN];char petkind[SLEN];
} Item;#define MAXITEMS 10typedef struct trnode
{Item item;struct trnode * left;  /* 指向左分支的指针  */struct trnode * right; /* 指向右分支的指针  */
} Trnode;typedef struct tree
{Trnode * root;         /* 指向根节点的指针  */int size;              /* 树的项数  */
} Tree;/* 函数原型 *//* 操作:       把树初始化为空          */
/* 前提条件:   ptree 指向一个树        */
/* 后置条件:   树被初始化为空          */
void InitializeTree(Tree * ptree);/* operation:      确定树是否为空          */
/* preconditions:  ptree points to a tree              */
/* postconditions: 如果树为空,函数返回true    */
/*                 否则返回false      */
bool TreeIsEmpty(const Tree * ptree);/* operation:      确定树是否已满           */
/* preconditions:  ptree points to a tree              */
/* postconditions: function returns true if tree is    */
/*                 full and returns false otherwise    */
bool TreeIsFull(const Tree * ptree);/* operation:      确定树的项数                         */
/* preconditions:  ptree points to a tree              */
/* postconditions: 返回树的项数                         */
int TreeItemCount(const Tree * ptree);/* operation:      在树中添加一个项               */
/* preconditions:  pi 是待添加项的地址   */
/*                 ptree 指向一个已初始化的树 */
/* postconditions: 如果可以添加,该函数将在树中添加一个项  */
/*                 并返回true,否则返回false          */
bool AddItem(const Item * pi, Tree * ptree);/* operation:      在树中查找一个项                   */
/* preconditions:  pi指向一个项                */
/*                 ptree 指向一个已初始化的树 */
/* postconditions: 如果在树中找到指定项,该函数返回true */
/*                 否则返回false    */
bool InTree(const Item * pi, const Tree * ptree);/* operation:      从树中删除一个项          */
/* preconditions:  pi 是删除项的地址 */
/*                 ptree 指向一个已初始化的树 */
/* postconditions: 如果在树中成功删除一个项,该函数返回true */
/*                 否则返回false*/
bool DeleteItem(const Item * pi, Tree * ptree);/* operation:      把函数应用于树中的每一项               */
/* preconditions:  ptree 指向一个树              */
/*                 pfun 指向一个函数            */
/*                 该函数接受一个Item类型的参数,并无返回值       */
/* postcondition:  pfun指向的这个函数为树中的每一项执行一次 */
void Traverse (const Tree * ptree, void (* pfun)(Item item));/* operation:      删除树中的所有内容       */
/* preconditions:  ptree指向一个已初始化的树 */
/* postconditions: 树为空                       */
void DeleteAll(Tree * ptree);#endif

3.2 tree.c

/* tree.c -- tree support functions */
#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;elsereturn false;
}bool TreeIsFull(const Tree * ptree)
{if (ptree->size == MAXITEMS)return true;elsereturn 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)      /* 情况 1: 树为空  */ptree->root = new_node;   /* 新节点为树的根节点  */else                          /* case 2: 树不为空      */AddNode(new_node,ptree->root); /* 在树中添加新节点  */return true;                  /* 成功返回      */
}/*在树中查找一个项*/
bool InTree(const Item * pi, const Tree * ptree)
{/*SeekItem函数返回一个结构,该函数可以与结构成员运算符一起使用*/return (SeekItem(pi, ptree).child == NULL) ? false : true;
}bool DeleteItem(const Item * pi, Tree * ptree)
{Pair look;/*SeekItem返回一个结构,*//*内含两个指针,一个指针look.parent指向父节点,*//*一个指针look.child指向包含特定项的节点*/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);/*第三种情况,待删除节点是父节点的右子节点*/elseDeleteNode(&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);/*释放内存*//*处理Tree类型的结构,重置Tree类型结构成员,表明该树为空*/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;/*存储root->right,使其在释放根节点后仍然可用*/DeleteAllNodes(root->left);free(root);DeleteAllNodes(pright);}
}/*确定新节点的位置,添加新节点*/
static void AddNode (Trnode * new_node, Trnode * root)
{/*如果新项应放在左子树中,ToLeft()函数返回true*/if (ToLeft(&new_node->item, &root->item)){if (root->left == NULL)      /* 空子树       */root->left = new_node;   /* 把节点添加到此处    */else/*把新项和左子节点中的项作比较,*//*以确定新项该放在该子节点的左子树还是右子树*/AddNode(new_node, root->left);/* 否则处理该子树*/}/*如果新项应放在右子树中,ToRight()函数返回true*/else if (ToRight(&new_node->item, &root->item)){if (root->right == NULL)root->right = new_node;elseAddNode(new_node, root->right);}else                         /* 不允许有重复项 */{fprintf(stderr,"location error in AddNode()\n");exit(1);}
}/*ToLeft和ToRight函数依赖于Item类型的性质。*/
/*成员名按字母排序,如果名字相同,按其种类排序*/
/*如果种类相同,属于重复项*/
static bool ToLeft(const Item * i1, const Item * i2)
{int comp1;/*第一个字符串在第二个字符串前面,strcmp返回负数*//*两个字符串相同,strcmp返回0*/if ((comp1 = strcmp(i1->petname, i2->petname)) < 0)return true;else if (comp1 == 0 &&strcmp(i1->petkind, i2->petkind) < 0 )return true;elsereturn 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;elsereturn 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;
}/*查找特定项*/
/*返回的结构包含两个指针,*/
/*一个指针指向包含项的节点,如果未找到指定项则为NULL*/
/*一个指针指向父节点(如果该节点为根节点,没有父节点,则为NULL)*/
static Pair SeekItem(const Item * pi, const Tree * ptree)
{Pair look;look.parent = NULL;/*开始时,设置look.child指向树的根节点*/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;//如果没有找到匹配的项,look.child将被设置为NULL}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是指向待删除节点指针的地址  */
/*那么*ptr就是把ptr地址里面的值取出来,就是指向待删除节点的指针*/ 
/*要修改的指针本身是Trnode*类型,指向Trnode的指针*/
/*该函数的参数是该指针的地址,所以参数的类型是Trnode** */
{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    /* 被删除的节点有两个子节点 */{/* 找到重新连接右子树的位置 *//*沿左子树的右支查找到第1个空位*/for (temp = (*ptr)->left; temp->right != NULL;temp = temp->right)continue;/*把待删除节点的右子树与该空位连接*/temp->right = (*ptr)->right;/*处理完之后重置待删除节点的地址*//*重置之前先将*ptr存储在temp中,释放待删除节点的内存*/temp = *ptr;*ptr =(*ptr)->left;free(temp); }
}

3.3 petclub.c

/* 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 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:");elsebreak;}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!");elseTraverse(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;     /* 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");elseprintf("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");elseprintf("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)                  // 如果地址不是 NULL,*find = '\0';          // 在此处放置一个空字符elsewhile (getchar() != '\n')continue;          // 处理输入行的剩余内容}return ret_val;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/560299.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

局域网服务器没显示,无法登录服务器 局域网也不显示连接

什么情况啊 论坛上很多这种情况 客服快来远程帮我解决一下&#xff01;&#xff01;&#xff01;&#xff01;********************************************************************************[星际争霸重置版]客户端到服务器Ping测试&#xff1a;服务器&#xff0c;223.25…

css 回到首页按钮,js+css实现回到顶部按钮(back to top)

本文实例介绍了jscss实现回到顶部按钮的方法&#xff0c;分享给大家供大家参考&#xff0c;具体内容如下效果htmlcssp#back-to-top{position:fixed;bottom:100px;right:80px;}p#back-to-top a{text-align:center;text-decoration:none;color:#d1d1d1;display:block;width:30px;…

Java Set基础知识

Set集合中不包含重复对象。 HashSet&#xff1a; HashSet的底层用哈希散列表来存储对象。其中的散列码是由关键字的信息内容确定的。关键字到散列码的转换是自动执行的。 存储元素时底层要做的判断:1.调用元素的hashCode()方法,直接访问对应的数组元素,有没有这个哈希值,没有就…

c语言基础练习题 字符串输入scanf/gets 字符串赋值、比大小

PTA上C语言程序设计练习题&#xff0c;包含查找书籍、字符串排序。主要用到字符串输入scanf/gets 字符串赋值、比大小等知识。 1.查找书籍 习题9-4 查找书籍 (20 分) 给定n本书的名称和定价&#xff0c;本题要求编写程序&#xff0c;查找并输出其中定价最高和最低的书的名称和…

ajax搜索思路,jquery创建一个ajax关键词数据搜索实现思路

在web开发过程当中&#xff0c;我们经常需要在前台页面输入关键词进行数据的搜索&#xff0c;我们通常使用的搜索方式是将搜索结果用另一个页面显示&#xff0c;这样的方式对于搭建高性能网站来说不是最合适的&#xff0c;今天给大家分享一下如何使用 jquery&#xff0c;MySQL …

esxi 修改服务器配置失败,R720服务器安装 ESXI 5.5update1后出现XXX错误

R720服务器&#xff0c;安装ESXI5.5UPDATE1 (DELL 的版本)后&#xff0c;安装了几个WIN2003及CENTOS的虚拟机。启用了一台2003和一台CENTOS6.5没有安装vCenter Server从vSphere Client端连接到esxi5.5服务器&#xff0c;发现每次重新启动ESXI&#xff0c;启动事件里面有很多类似…

(哈希)两数之和(leetcode 1)

1.题目 给定一个整数数组 nums 和一个整数目标值 target&#xff0c;请你在该数组中找出 和为目标值 target 的那 两个 整数&#xff0c;并返回它们的数组下标。 你可以假设每种输入只会对应一个答案。但是&#xff0c;数组中同一个元素在答案里不能重复出现。 你可以按任意…

丢失的数字

给定一个包含 [0, n] 中 n 个数的数组 nums &#xff0c;找出 [0, n] 这个范围内没有出现在数组中的那个数。 示例 1&#xff1a; 输入&#xff1a;nums [3,0,1] 输出&#xff1a;2 解释&#xff1a;n 3&#xff0c;因为有 3 个数字&#xff0c;所以所有的数字都在范围 [0,3…

ftp服务器收集文件,ftp服务器收集文件

ftp服务器收集文件 内容精选换一换为了对源端服务器进行迁移可行性评估以及为后续目的端服务器的选择和配置提供必要性数据&#xff0c;迁移Agent会收集源端服务器的相关信息并上报到主机迁移服务。收集的Windows操作系统的具体数据如表1所示。收集的Linux操作系统的具体数据如…

STM32-GPIO学习-跑马灯实验和按键实验-寄存器版本和HAL库版本

一、stm32跑马灯实验 a.GPIO general purpose input output 通用输入输出端口&#xff0c;可以做输入也可做输出&#xff0c;GPIO端口可通过程序配置成输入或输出。 STM32FXXXIGT6一共9组IO&#xff1a;PA~ PI&#xff0c;其中PA~ PH每组16个IO&#xff0c;PI只有PI0~PI11。…

STM32-时钟-时钟树-时钟初始化配置

1.STM32时钟 STM32有5个时钟源&#xff1a;HSI、HSE、LSI、LSE、PLL HSI是高速内部时钟&#xff0c;RC振荡器&#xff0c;频率为16MHz&#xff0c;精度不高。可以直接作为系统时钟或者用作PLL时钟输入。 HSE是高速外部时钟&#xff0c;可接石英/陶瓷谐振器&#xff0c;或者接…

pe系统 服务器维护,FirPE 维护系统

前言年更项目更新啦~~~&#xff0c;这次修复了一些BUG&#xff0c;此外还增加了亿点特性。首次使用WinRE注册表&#xff0c;使得WinPE速度更快了。系统简介FirPE 是一款系统预安装环境(Windows PE)&#xff0c;它具有简约、易操作等特点&#xff0c;使用起来十分人性化。以U盘作…

STM32-Systick滴答定时器-延时函数

1.STM32-Systick滴答定时器 Systick定时器&#xff0c;是一个简单的定时器&#xff0c;对于ST的CM3,CM4,CM7内核芯片&#xff0c;都有Systick定时器。 Systick定时器常用来做延时&#xff0c;或者实时系统的心跳时钟。这样可以节省MCU资源&#xff0c;不用浪费一个定时器。比如…

华为服务器更改从系统盘启动不了,华为服务器设置启动项

华为服务器设置启动项 内容精选换一换对于已安装Cloud-Init/Cloudbase-Init的云服务器&#xff0c;首次执行切换/故障切换操作&#xff0c;或者创建容灾演练后&#xff0c;系统第一次启动时会运行Cloud-Init/Cloudbase-Init&#xff0c;为云服务器重新注入初始化数据&#xff0…

STM32-NVIC中断优先级分组-中断优先级设置

STM32-中断优先级管理NVIC 1.NVIC中断优先级分组 NVIC的全称是Nested vectoredinterrupt controller&#xff0c;即嵌套向量中断控制器。STM32F中文参考手册中搜索向量表可以找到相应的中断说明。 CM4/CM7 内核支持256个中断&#xff0c;其中包含了16个内核中断和240个外部中…

hyper-v虚拟服务器内存满了,在Hyper-V Dynamic Memory里设置虚拟内存

Hyper-V R2 SP1版中加入了动态内存技术&#xff0c;关于内存分配的猜想也随之结束。当VMware可以自动进行虚拟内存分配的时候&#xff0c;Hyper-V通过动态内存让用户可以调整虚拟内存的设置。这种灵活性为主机内存分配方式提供了参考&#xff0c;有效提高了精确度和降低难度。通…

STM32-IO引脚复用-原理和使用

1.什么是端口复用&#xff1f; STM32有很多的内置外设(把一些功能ADC\看门狗…集中到芯片里面)&#xff0c;这些外设的外部引脚都是与GPIO复用的。也就是说&#xff0c;一个GPIO如果可以复用为内置外设的功能引脚&#xff0c;那么当这个GPIO作为内置外设使用的时候&#xff0c…

泰拉瑞亚服务器config修改,泰拉瑞亚配置怎么修改_泰拉瑞亚配置修改方法介绍...

泰拉瑞亚配置怎么修改呢&#xff0c;还有很多玩家还不知道的&#xff0c;下面小编为玩家们带来了泰拉瑞亚配置修改方法介绍&#xff0c;一起来看看吧。泰拉瑞亚配置修改方法介绍Config文件(1)Wiki链接http://terraria.gamepedia.com/Config.json_settings(2)关于Configconfig文…

STM32-UART-串口通信框图-波特率计算

1.处理器与外部设备通信的两种方式&#xff1a; 并行通信 传输原理&#xff1a;数据各个位同时传输。优点&#xff1a;速度快缺点&#xff1a;占用引脚资源多 串行通信 传输原理&#xff1a;数据按位顺序传输。优点&#xff1a;占用引脚资源少缺点&#xff1a;速度相对较慢…

java客户端与服务器信息加密,Java AES加密 - 在客户端和服务器之间发送初始化向量...

我在客户端生成初始化向量&#xff0c;其中消息被加密&#xff0c;然后与解密向量一起发送到服务器。Java AES加密 - 在客户端和服务器之间发送初始化向量客户端代码&#xff1a;String key1 "1234567812345678";byte[] key2 key1.getBytes();SecretKeySpec secret…