抽象数据类型(ADT)

一、什么是抽象数据类型?用来做什么?
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      /* 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 
#include      /* 为 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 


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

}

你可能感兴趣的:(抽象数据类型(ADT))