一、什么是抽象数据类型?用来做什么?
ADT是指一个数学模型以及定义在该模型上的操作(即数学模型+数学模型的操作)。难理解,那就先说说类型。一个类型指两类信息:一个属性集+一个操作集),比如,int类型的属性表示它是一个整数,它允许的操作包括加减乘除,取模,改变符号:1+1,那么1是类型,+ - * /等是操作。假设想定义一个新的数据类型,第一,需要提供数据存储的方式;第二,需要提供数据的操作方式。那么,在创建一个新的数据类型需要哪些步骤呢?
1.为类型的属性和可对类型的操作提供一个抽象的描述,比如1是对整数的抽象描述,+ - * /是对类型操作的抽象描述;
2.开发一个实现ADT的编程接口:比如定义一个结构和写一个操作结构的函数;
3.具体编写代码来实现这个接口。
二、构造、使用&实现接口
1.列表:列表是按照一定的线性顺序排列而成的数据。简单列表的接口有两部分:描述数据和描述实现ADT操作的函数。
1.1代码(代码来自《C Primer Plus》):
(1)构造接口:
/* list.h 接口头文件 */
#ifndef LIST_H_ //防止重复包含list.h文件
#define LIST_H_
#include <stdbool.h> /* C99 feature */
#define TSIZE 45
struct film
{
char title[TSIZE];
int rating;
};
typedef struct film Item;
typedef struct node
{
Item item;
struct node * next;
} Node;
typedef Node * List; //取个别名,List是指向Node节点的指针
void InitializeList(List * plist);
//初始化列表
bool ListIsEmpty(const List *plist);
//判断列表是否为空,不想因为判断而改变列表的内容,用const修饰List
bool ListIsFull(const List *plist);
//判断列表是否为满
unsigned int ListItemCount(const List *plist);
//列表项目计数,返回值为int型
bool AddItem(Item item, List * plist);
//往列表中增加项目,增加成功返回true,失败返回false
void Traverse (const List *plist, void (* pfun)(Item item) );
//把函数作用于列表的每一项,pfun指向一个函数,该函数接受Item类型的参数
void EmptyTheList(List * plist);
//清空列表
#endif
(2)使用接口:
/* 与list.c 一块编译 */
#include <stdio.h>
#include <stdlib.h> /* 为 exit()提供原型 */
#include "list.h" /* list.h定义lIst,Item,提供接口函数原型*/
void showmovies(Item item);
int main(void)
{
List movies;
Item temp;
InitializeList(&movies); //传递movies的地址,movies本
//身是一个指向Node的指针
if (ListIsFull(&movies))
{
fprintf(stderr,"No memory available! Bye!\n");
exit(1);
}
puts("Enter first movie title:");
while (gets(temp.title) != 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);
}
(3)实现接口:
#include <stdio.h>
#include <stdlib.h>
#include "list.h"
static void CopyToNode(Item item, Node * pnode);
//使用static函数具有内部链接属性
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;
}
1.2部分重点代码分析
struct film{
char title[TSIZE];
int rating;
}; //定义一个项目,项目包含题目和评分
typedef struct film Item; //给结构取个别名Item
typedef struct node{
Item item;
struct node *next;
}Node; //定义一个节点,节点包含项目和指向下一个节点的指针
typedef Node *List; //给节点取个别名*List,这样以后List定义的变量
//就是指向节点的指针,稍后会对此着重分析
列表增加项目步骤:
(1)为新节点分配空间;
(2)把项目复制到新节点,新节点的next指针设置为NULL;
(3)如果新节点是添加到列表的第一项,将列表头指针指向新节点;否则,遍历列表找到当前列表最后一个节点(其成员next为NULL),将next改为指向新节点。
bool AddItem(Item item,List *plist)
/*因为List为指向节点的指针的类型,plist则为指向这种类型的指针*/
{
Node *pnew; //定义一个新节点
List *scan=*plist; //scan用来遍历列表
pnew=(Node *)malloc(sizeof(Node); //为新节点分配内存
if(pnew==NULL)
return false;
CopyToNode(item,pnew); //复制项目到新节点中
pnew->next=NULL; //新节点的next指针指向NULL,表明为最后一个节点
if(scan==NULL)
*plist=pnew; //*plist为指向节点的指针
else
{
while(scan->next!=NULL) //遍历列表,直到当前列表最后一个项目
scan=scan->next;
scan->next=pnew; //当前列表最后节点的指针指向新节点
}
return true;
}
注意到在头文件 list.h中,函数的原型声明:
void InitializeList(List * plist);
bool ListIsEmpty(const List *plist);
再来看看函数在代码段中的应用:
InitializeList(&movies);
ListIsEmpty(&movies);
都是传递movies的地址,但是bool ListIsEmpty(const List *plist)加了const,而void InitializeList(List * plist)没加,其原因是初始化List需要修改List的内容,而判断List是否为空并不需要修改内容。
2.队列:队列有两个属性,一增加项目只能在队尾,二删除项目只能在队首,即“先进先出”。
2.1代码:
(1)构造接口:
/* queue.h -- 队列接口 */
#pragma c9x on
#ifndef _QUEUE_H_
#define _QUEUE_H_
#include <stdbool.h>
typedef struct item {
int gumption;
int charisma;
} Item;
#define MAXQUEUE 10 //队列的最大长度
typedef struct node { //定义结点
Item item;
struct node * next;
} Node;
typedef struct queue { //定义队列
Node * front; //指向队列首的指针
Node * rear; //指向队列尾的指针
int items; //队列中项目的个数
} Queue;
// 描述:初始化队列,将一个队列初始化为空队列
// 输入:pq:指向一个队列‘
// 返回:无
void InitQueue (Queue * pq);
// 描述:检查队列是否已满
// 输入:pq:指向一个已初始化后的队列
// 返回:ture: 已满 false: 未满
bool QueueIsFull (Queue * pq);
// 描述:检查队列是否为空
// 输入:pq:指向一个已初始化后的队列
// 返回:ture: 已空 false: 未空
bool QueueIsEmpty (Queue * pq);
// 描述:确定队列中项目的个数
// 输入:pq:指向一个已初始化后的队列
// 返回:队列中项目的个数
int QueueItemCount (Queue * pq);
// 描述:向队列尾端添加一个项目
// 输入:pq:指向一个已初始化后的队列 item:项目
// 返回:ture: 成功 false: 失败(队列已满或其它)
bool EnQueue (Queue * pq, Item item);
// 描述:从队列首端删除一个项目
// 输入:pq:指向一个已初始化后的队列 *pitem:删除时将待删除的项目保存到*pitem中
// 输出:ture: 成功(如果队列为空 则将该队列重置为空队列) false: 失败(队列未改变)
bool DeQueue (Queue * pq, Item *pitem);
// 描述:清空队列
// 输入:pq:指向一个已初始化后的队列
// 输出:无
void EmptyTheQueue (Queue * pq);
#endif
(2)实现接口:
/* queue.c -- 队列类型的实现文件 */
#include <stdio.h>
#include <stdlib.h>
#include "queue.h"
static void CopyToNode (Item item, Node * pn); //将项目中的内容保存到结点中
static void CopyToItem (Item *pi, Node * pn); //将结点中的内容保存到项目中
void InitQueue (Queue * pq)
{
pq->front = pq->rear = NULL;
pq->items = 0;
}
bool QueueIsFull (Queue * pq)
{
return pq->items == MAXQUEUE;
}
bool QueueIsEmpty (Queue * pq)
{
return pq->items == 0;
}
int QueueItemCount (Queue * pq)
{
return pq->items;
}
bool EnQueue (Queue * pq, Item item)
{
Node *pnew;
if(QueueIsFull(pg))
return false;
pnew=(Node *)malooc(sizeof(Node));
if(pnew==NULL)
{
fprintf(stderr,"Unable to allocate menmory!n");
eixt(1);
}
CopyToNode(item,pnew);
pnew->next=NULL;
if(QueueIsEmpty(pg))
pg->front=pnew;
else
pg->rear->next=pnew;
pg->rear=pnew;
pg->items++;
retunrn true;
}
bool DeQueue (Queue * pq, Item *pitem)
{
Node * pt;
if(QueueIsEmpty(pq))
return false;
CopyToItem(pitem,pq->front);
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(pq,&dummy);
}
static void CopyToNode (Item item, Node * pn)
{
pn->item = item;
}
static void CopyToItem (Item *pi, Node * pn)
{
*pi = pn->item;
}
2.2部分代码分析:
typedef struct item {
int gumption;
int charisma;
} Item;
typedef struct node { //定义结点
Item item;
struct node * next;
} Node;
typedef struct queue { //定义队列
Node * front; //指向队列首的指针
Node * rear; //指向队列尾的指针
int items; //队列中项目的个数
} Queue;
向队列增加项目步骤:
(1)新建一个节点;
(2)将项目复制到新节点;
(3)将新节点的next指针置为NULL,表明该节点是最后一个节点;
(4)将当前节点的next指针指向新节点,把新节点链接到队列中;
(5)如果队列为空,队列的front指针和rear指针都指向新节点;不为空,rear指针指向新节点;
(6)项目数加1。
bool EnQueue(Item item,Queue *pg)
{
Node *pnew;
if(QueueIsFull(pg)) //如果队列是的,返回false,增加失败
return false;
pnew=(Node *)malooc(sizeof(Node)); //给新节点分配内存
if(pnew==NULL)
{
fprintf(stderr,"Unable to allocate menmory!n");
eixt(1);
}
CopyToNode(item,pnew); //将项目复制到新节点中
pnew->next=NULL; //新节点的next置为NULL
if(QueueIsEmpty(pg)) //如果队列为空队列首指针指向新节点
pg->front=pnew;
else
pg->rear->next=pnew; //不为空next指针指向新节点
pg->rear=pnew; //rear指针指向新节点,表明队尾
pg->items++; //项目数加1
retunrn true;
}
从队列删除项目步骤:
(1)将要删除项目复制到给定变量中;
(2)释放头指针指向节点的内存;
(3)将头指针指向队列下一个节点;
(4)如果删除的是最后一项,头尾指针都置为NULL。
bool DeQueue(Item *pitem,Queue *pg)
{
Node *pt;
if(QueueIsEmpty(pg)) //如果队列为空,返回false删除失败
return false;
CopyToItem(pg->front,pitem); //要删除项目复制到给定变量
pt=pg->front;//头结点指针复制到pt中以释放内存,即删除节点
pg->front=pg->front->next;//头结点front指针指向下一个节点
free(pt); //释放原来头结点的内存
pg->items--; //项目数减一
if(pg->items==0) //如果最后一项被删除,尾指针置NULL
pg->rear=NULL;
return true;
}
3.二叉树:二叉树有两个特点:(1)频繁插入和删除元素,(2
)快速访问元素。
二叉树的实现:
首先定义二叉树:
typedef struct item{
char petname[20];
char petkind[20];
}Item;
typedef struct node{
Item item;
Node *left;
Node *right;
}Node;
typedef struct tree{
Node *root;
int size;
}Tree;
3.1添加项目:
(1)检查:a.二叉树是否有空位给新节点;b.二叉树中是否有已经有添加的项目;
(2)创建新节点,将项目复制到新节点中,将新节点的左右指针置为NULL;
(3)更新Tree结构的size成员,以记录增加了一个项目;
(4)找出新节点在树中的位置:如果树为空,将根节点指针指向新节点;否则,在树中查找到放置新节点的位置。
bool AddItem(const Item *pi,Tree *ptree)
{
Node *new_node;
if(TreeIsFull(ptree))
{
fprintf(stderr,"Tree is full\n");
return false;
}
if(SeekItem(pi,ptree)!=NULL)
{
fprintf(stderr,"Attempted to add duplicate items\n");
retunr false;
}
new_node=Makenode(pi);
ptree->size++;
if(TreeIsEmpty(ptree))
ptree->root=new_node;
else
AddNode(new_node,ptree->root);
return true;
}
static Node *Makenode(const Item *pi)
{
Node *new_node;
new_node=(Node *)malloc(sizeof(Node));
if(new_node!=NULL)
{
new_node->item=*pi;
new_node->left=NULL;
new_node->rignt=NULL;
}
return new_node;
}
增加的节点有三个可能:向左去,向右去和增加节点失败。假设新增加的节点向左去,如果当前节点的左子树为空,则把左指针指向新节点,否则将递归执行AddNode()这个函数直到遇到左子树为空的情况。节点向右去的情况也类似。
static void AddNode(Node *new_node,Node *root)
{
if(ToLeft(&new_node->item,&root->item))
{
if(root->left==NUll)
root->left=new_node;
else
AddNode(new_node,root->left);
}
else if(ToRignt(&new_node->item,&root->item))
{
if(root->right==NUll)
root->right=new_node;
else
AddNode(new_node,root->rignt);
}
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)z)<0)
return true;
else if((comp1==0 && strcmp(i1->petkind,i2->petkind))<0)
return true;
else
return false;
}
3.2查找项目:
typedef pair struct{
Node *parent;
Node *child;
}Pair;
static Pair SeekItem(const Item *pi,const Tree *ptree)
{
Pair look;
look.prent=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(ToLeft(pi,&(look.child->item))
{
look.parent=look.child;
look.child=look.child->right;
}
else
break;
}
return look;
}
3.3删除项目:
(1)删除节点
删除节点有三种情况:a.待删除的节点无左子树;b.无右子树;c.有两个子树。
static void DeleteNode(Node **ptr)
{
Node *temp;
if((*ptr)->left==NULL)
{
temp=*ptr;
*ptr=(*ptr)->rignt;
free(temp);
}
}