数据之间具有3中基本结构:
线性结构:数据元素之间为1对1 关系
树形结构:数据元素之间为1对多的关系
网状结构:数据元素之间为多对多结构
最简单的线性结构:线性表
下面是对顺序表的一些操作
#include <stdio.h>
#include <string.h>
#define MAXSIZE 100 //定义线性表的最大长度
typedef struct //定义顺序表结构
{
DATA ListData[MAXSIZE+1]; //保存顺序表的数组
int ListLen; //顺序表已存结点 的数量
}SeqListType;
void SeqListInit(SeqListType *SL) //初始化顺序表
{
SL->ListLen=0; //初始化时,设置顺序表长度为0
}
int SeqListLength(SeqListType *SL) //返回顺序表的元素数量
{
return (SL->ListLen);
}
int SeqListAdd(SeqListType *SL,DATA data) //增加元素到顺序表尾部
{
if(SL->ListLen>=MAXSIZE) //顺序表已满
{
printf("顺序表已满,不能再添加结点了!\n");
return 0;
}
SL->ListData[++SL->ListLen]=data;
return 1;
}
int SeqListInsert(SeqListType *SL,int n,DATA data)
{
int i;
if(SL->ListLen>=MAXSIZE) //顺序表结点数量已超过最大数量
{
printf("顺序表已满,不能插入结点!\n");
return 0; //返回0表示插入不成功
}
if(n<1 || n>SL->ListLen-1) //插入结点序号不正确
{
printf("插入元素序号错误,不能插入元素!\n");
return 0; //返回0,表示插入不成功
}
for(i=SL->ListLen;i>=n;i--) //将顺序表中的数据向后移动
SL->ListData[i+1]=SL->ListData[i];
SL->ListData[n]=data; //插入结点
SL->ListLen++; //顺序表结点数量增加1
return 1; //返回成功插入
}
int SeqListDelete(SeqListType *SL,int n) //删除顺序表中的数据元素
{
int i;
if(n<1 || n>SL->ListLen+1) //删除元素序号不正确
{
printf("删除结点序号错误,不能删除结点!\n");
return 0; //返回0,表示删除不成功
}
for(i=n;i<SL->ListLen;i++) //将顺序表中的数据向前移动
SL->ListData[i]=SL->ListData[i+1];
SL->ListLen--; //顺序表元素数量减1
return 1; //返回成功删除
}
DATA *SeqListFindByNum(SeqListType *SL,int n) //根据序号返回数据元素
{
if(n<1 || n>SL->ListLen+1) //元素序号不正确
{
printf("结点序号错误,不能返回结点!\n");
return NULL; //返回0,表示不成功
}
return &(SL->ListData[n]); //返回的是结构体中元素的地址
}
int SeqListFindByCont(SeqListType *SL,char *key) //按关键字查询结点
{
int i;
for(i=1;i<=SL->ListLen;i++)
if(strcmp(SL->ListData[i].key,key)==0) //如果找到所需结点
return i; //返回结点序号
return 0; //遍历后仍没有找到,则返回0
}
可以看到在查找顺序表的时候特别方便,给出一个序号就可以立马查找到,但是当插入一个数据或者删除一个数据的时候,就涉及到对一大段数据进行操作了,这样既麻烦,有可能造成在对大量数据操作时候可能产生的失误,安全性能降低。
这个时候引入链表:
NOTE:链表是采用动态存储分配的一种结构,就是说你可以在你需要的时候申请一块内存来存储数据,而且c语言程序不可以自动回收动态分配的空间,所以在删除某个节点的时候,应该用free函数来释放其占用的内存。
#include <stdlib.h>
typedef struct Node
{
DATA data; //数据域
struct Node *next;
}ChainListType;
#include <string.h>
//返回的是一个ChainListType类型的数据,首先还是要确定ChainListType是
//head这个指针指向的是原来链表末尾节点
ChainListType *ChainListAddEnd(ChainListType *head,DATA data) //添加结点到链表结尾
{
ChainListType *node,*h;
if(!(node=(ChainListType *)malloc(sizeof(ChainListType))))
{
printf("为保存结点数据申请内存失败!\n");
return NULL; //分配内存失败
}
node->data=data; //保存数据
node->next=NULL; //设置结点指针为空,即为表尾
if(head==NULL) //是头指针
{
head=node;
return head;
}
h=head;
while(h->next!=NULL) //查找链表的末尾节点
h=h->next ;
h->next=node;
return head;
}
ChainListType *ChainListAddFirst(ChainListType *head,DATA data)
{
ChainListType *node,*h;
if(!(node=(ChainListType *)malloc(sizeof(ChainListType))))
{
printf("为保存结点数据申请内存失败!\n");
return NULL; //分配内存失败
}
node->data=data; //保存数据
node->next=head; //指向头指针所指结点
head=node; //头指针指向新增结点
return head;
}
ChainListType *ChainListInsert(ChainListType *head,char *findkey,DATA data) //插入结点到链表指定位置
{
ChainListType *node,*node1;
if(!(node=(ChainListType *)malloc(sizeof(ChainListType)))) //分配保存结点的内容
{
printf("为保存结点数据申请内存失败!\n");
return 0; //分配内存失败
}
node->data=data; //保存结点中的数据
node1=ChainListFind(head,findkey);
if(node1) //若找到要插入的结点
{
node->next=node1->next; //新插入结点指向关键结点的下一结点
node1->next=node; //设置关键结点指向新插入结点
}else{
free(node);//释放内存
printf("未找到插入位置!\n");
}
return head;//返回头指针
}
ChainListType *ChainListFind(ChainListType *head,char *key) //按关键字在链表中查找内容
{
ChainListType *h;
h=head; //保存链表头指针
while(h) //若结点有效,则进行查找
{
if(strcmp(h->data.key,key)==0) //若结点关键字与传入关键字相同
return h; //返回该结点指针
h=h->next; //处理下一结点
}
return NULL; //返回空指针
}
int ChainListDelete(ChainListType *head,char *key)
{
ChainListType *node,*h; //node保存删除结点的前一结点
node=h=head;
while(h)
{
if(strcmp(h->data.key,key)==0) //找到关键字,执行删除操作
{
node->next=h->next; //使前一结点指向当前结点的下一结点
free(h); //释放内存
return 1;
}else{
node=h; //指向当前结点
h=h->next; //指向下一结点
}
}
return 0;//未删除
}
int ChainListLength(ChainListType *head)//获取链表结点数量
{
ChainListType *h;
int i=0;
h=head;
while(h) //遍历整个链表
{
i++; //累加结点数量
h=h->next;//处理下一结点
}
return i;//返回结点数量
}
可以看到,链表在进行数据的插入或者删除的时候,所需操作要比顺序表少很多,但是进行查询的时候没有顺序表方便。
队列
队列是一种特殊的线性表,值允许在表的前端进行删除操作,就像一队人,在出队的时候从队首出,入队的时候自动的去找队尾排到队尾。是一种“先来先服务”的结构。
#define QUEUEMAX 15
typedef struct
{
DATA data[QUEUEMAX]; //队列数组
int head; //队头
int tail; //队尾
}SeqQueue;
队头队尾就是队头或者队尾元素的位置的标号。入队的时候需要修改队尾的标号,队尾标号加1。出队的时候修改队首的标号,队首标号加1.
SeqQueue *SeqQueueInit()
{
SeqQueue *q;
if(q=(SeqQueue *)malloc(sizeof(SeqQueue))) //申请保存队列的内存
{
q->head = 0;//设置队头
q->tail = 0;//设置队尾
return q;
}else
return NULL; //返回空
}
void SeqQueueFree(SeqQueue *q) //释放队列
{
if (q!=NULL)
free(q);
}
int SeqQueueIsEmpty(SeqQueue *q) //队列是否为空
{
return (q->head==q->tail);
}
int SeqQueueIsFull(SeqQueue *q)//队列是否已满
{
return (q->tail==QUEUEMAX);
}
int SeqQueueLen(SeqQueue *q) //获取队列长度
{
return(q->tail-q->head);
}
int SeqQueueIn(SeqQueue *q,DATA data)//顺序队列的入队函数
{
if(q->tail==QUEUEMAX)
{
printf("队列已满!\n");
return(0);
}else{
q->data[q->tail++]=data;
return(1);
}
}
DATA *SeqQueueOut(SeqQueue *q)//顺序队列的出队
{
if(q->head==q->tail)
{
printf("\n队列已空!\n");
return NULL;
}else{
return &(q->data[q->head++]);
}
}
DATA *SeqQueuePeek(SeqQueue *q) //获取队头元素
{
if(SeqQueueIsEmpty(q))
{
printf("\n队列为空!\n");
return NULL;
}else{
return &(q->data[q->head]);
}
}
对于队列,同样存在一个问题,当不断的出队之后,明明前面空着许多的位置,但是入队的时候却只能从队尾入,而队尾此时已经达到最大值,这种现象叫做“假溢出”。
为了解决这个问题,引入了
循环队列
循环队列在初始的时候队首队尾标号相同,在入队的时候,如果队尾还未达到最大值,则和普通队列一致,如果超过最大值了,则队尾标号=(tail+1)% maxsize;如果这个时候tail=head,则真的说明队列已经满了,则提示溢出。在出队的时候,head=head+1,同样,这是在head还处于maxsize范围之内的,如果超过了,也应当进行head=(head+1)%maxsize操作。
CycQueue *CycQueueInit()
{
CycQueue *q;
if(q=(CycQueue *)malloc(sizeof(CycQueue))) //申请保存队列的内存
{
q->head = 0;//设置队头
q->tail = 0;//设置队尾
return q;
}else
return NULL; //返回空
}
void CycQueueFree(CycQueue *q) //释放队列
{
if (q!=NULL)
free(q);
}
int CycQueueIsEmpty(CycQueue *q) //队列是否为空
{
return (q->head==q->tail);
}
int CycQueueIsFull(CycQueue *q)//队列是否已满
{
return ((q->tail+1)%QUEUEMAX==q->head);
}
int CycQueueIn(CycQueue *q,DATA data)//入队函数
{
if((q->tail+1)%QUEUEMAX == q->head )
{
printf("队列已满!\n");
return 0;
}else{
q->tail=(q->tail+1)%QUEUEMAX;//求列尾序号
q->data[q->tail]=data;
return 1;
}
}
DATA *CycQueueOut(CycQueue *q)//循环队列的出队函数
{
if(q->head==q->tail) //队列为空
{
printf("队列已空!\n");
return NULL;
}else{
q->head=(q->head+1)%QUEUEMAX;
return &(q->data[q->head]);
}
}
int CycQueueLen(CycQueue *q) //获取队列长度
{
int n;
n=q->tail-q->head;
if(n<0)
n=QUEUEMAX+n;
return n;
}
DATA *CycQueuePeek(CycQueue *q) //获取队定中第1个位置的数据
{
if(q->head==q->tail)
{
printf("队列已经为空!\n");
return NULL;
}else{
return &(q->data[(q->head+1)%QUEUEMAX]);
}
}
后进后出结构
首先数据结构的定义
typedef struct stack
{
DATA data[SIZE+1]; //数据元素
int top; //栈顶
}SeqStack;
入栈操作:判断top+1是不是已经超过了最大SIZE,如果没有贼top上移,top+1,将入栈的数据存放到栈的数据段的top+1位置。
出栈操作:判断top-1是不是小于0;如果是的话说明栈是空的。不是的话,top-1;弹出原来top位置对应的栈顶元素。
SeqStack *SeqStackInit()
{
SeqStack *p;
if(p=(SeqStack *)malloc(sizeof(SeqStack))) //申请栈内存
{
p->top=0; //设置栈顶为0
return p;//返回指向栈的指针
}
return NULL;
}
int SeqStackIsEmpty(SeqStack *s) //判断栈是否为空
{
return(s->top==0);
}
void SeqStackFree(SeqStack *s) //释放栈所占用空间
{
if(s)
free(s);
}
void SeqStackClear(SeqStack *s) //清空栈
{
s->top=0;
}
int SeqStackIsFull(SeqStack *s) //判断栈是否已满
{
return(s->top==SIZE);
}
int SeqStackPush(SeqStack *s,DATA data) //入栈操作
{
if((s->top+1)>SIZE)
{
printf("栈溢出!\n");
return 0;
}
s->data[++s->top]=data;//将元素入栈
return 1;
}
DATA SeqStackPop(SeqStack *s) //出栈操作
{
if(s->top==0)
{
printf("栈为空!");
exit(0);
}
return (s->data[s->top--]);
}
DATA SeqStackPeek(SeqStack *s) //读栈顶数据
{
if(s->top==0)
{
printf("栈为空!");
exit(0);
}
return (s->data[s->top]);
}