数据之间具有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]); }