栈定义
栈(Stack)是限定只能在表的一端(表尾)进行插人或刪除操作的线性表。允许进行插人或删除的这一端称为栈顶(Top);另一端则称栈底(Bottom),不能进行插人或删除。
当栈中没有包含数据元素时,称为空栈。栈非空时,处于栈顶位置的元素称为栈顶元素。向一个栈插人新的元素称为人栈或进栈(Push),此时,插人的元素成为新的栈顶元素;从找中删除一个元素时,只能删除当前的栈顶元素,称为出栈或退栈(Pop)。
由于栈的插人和删除只能在栈顶进行,最先人栈的元素必定最后出栈,最后人栈的元素最先出栈,因此栈又叫做后进先出(Last In First Out,LIFO)线性表。
顺序表的表示与实现
与顺序表类似,顺序栈就是用一组地址连续的存储单元依次存放自栈底到栈顶的数据元素。
实现代码
#include <stdio.h>
#include <stdlib.h>
#define OK 1
#define ERROR 0
#define STACK_INITsize 5
#define STACK_INCREASE 5
typedef struct {
int *base;
int *top;
int stacksize;//分配存储空间
} sqlstack;
int initStack(sqlstack *p){
p->base = malloc(sizeof(int)*STACK_INITsize);//开辟了STACK_INITsize
//个int内存单元,p->base指向第一个内存单元。
if(p->base==NULL){
return ERROR;
}
p->stacksize = STACK_INITsize;
p->top = p->base;//设置为空栈
return OK;
}
int EmptyStack(sqlstack *p){
if(p->base==p->top){
printf("栈是空栈\n");
}else{
printf("栈不是空栈 \n");
}
return OK;
}
int pushStack(sqlstack *p,int value){
if(p->top-p->base>=p->stacksize){
p->base = realloc(p->base,(p->stacksize+STACK_INCREASE)*sizeof(int));
if(p->base==NULL){
return ERROR;
}
p->top = p->base+p->stacksize;
p->stacksize+=STACK_INCREASE;
}
p->top++;//栈顶指针加1, p->top指向的是int的内存单元格,所以每次移动一下,指针地址是加上4.
*(p->top) = value;
return OK;
}
int popStack(sqlstack *p){
if(p->top==p->base){//空栈
return ERROR;
}
p->top--;
printf("出栈一个元素 \n");
return OK;
}
int getTop(sqlstack *p){
if(p->base==p->top){
return ERROR;
}
printf("栈顶的元素: %d \n",*(p->top));
return OK;
}
int main()
{
int choice;
sqlstack *p = malloc(sizeof(sqlstack));
int value;
do{
printf("1 栈的初始化 \n");
printf("2 判断栈是否为空 \n");
printf("3 入栈的操作 \n");
printf("4 出栈的操作 \n");
printf("5 取栈顶元素 \n");
scanf("%d",&choice);
switch(choice){
case 1:
initStack(p);
break;
case 2:
EmptyStack(p);
break;
case 3:
printf("请输入要入栈的值:\n");
scanf("%d",&value);
pushStack(p,value);
break;
case 4:
popStack(p);
break;
case 5:
getTop(p);
break;
}
}while(choice>0);
}
说明:
(1) 对于顺序栈,入栈时,必须判定栈是否满。栈满时,若顺序栈采用静态的存储表示,则不能再做入栈操作;若采用上述的动态可变长的存储表示,则需要先申请增加存储空间,再入栈。
(2) 出栈和取栈顶元素操作,必须判定栈是否为空,栈空时不能进行出栈或取栈顶元素操作,否则产生错误。
栈链的表示和实现
链栈就是采用链式存储实现的栈,通过栈链用单链表表示。
实现代码
#include <stdio.h>
#include <stdlib.h>
#define ERROR 0
#define OK 1
#define LINKSTACK_INITSIZE 5;
typedef struct {
int data;
struct StackNode *next;
} StackNode;
int initLinkStak(StackNode *p){
if(p==NULL){
printf("申请地址失败 \n");
return ERROR;
}
p->next = NULL;
return OK;
}
int EmptyLinkStack(StackNode *p){
if(p->next==NULL){
printf("该链栈为空 \n");
}else{
printf("该链栈不为空 \n");
}
return OK;
}
int pushLinkStack(StackNode *p,int value){
StackNode *newstackNode = malloc(sizeof(StackNode));
if(newstackNode==NULL){
return ERROR;
}
newstackNode->next = p;
newstackNode->data = value;
p = newstackNode;//将新增的节点作为链栈的顶元素
return p;//返回该节点,不然还是之前的栈顶的元素
}
int popLinkStack(StackNode *p){
if(p->next==NULL){
printf("是空链栈");
return OK;
}
StackNode *deleteNode =p;
p = p->next;
free(deleteNode);
return p ;//返回删除后栈顶节点,如果不返回,在调用还是原来的栈顶节点已经删除。
}
int showLinkStack(StackNode *p){
while(p->next!=NULL){
printf("%d",p->data);
p =p->next;
}
}
int main()
{
int chioce;
StackNode *p = malloc(sizeof(StackNode));
int value;
do{
printf("1 初始化链栈 \n");
printf("2 判断是否为空链栈 \n");
printf("3 链栈的入栈的操作 \n");
printf("4 链栈的出栈的操作 \n");
printf("5 链栈的遍历 \n");
scanf("%d",&chioce);
switch(chioce){
case 1:
initLinkStak(p);
break;
case 2:
EmptyLinkStack(p);
break;
case 3:
printf("输入栈的值:\n");
scanf("%d",&value);
p = pushLinkStack(p,value);
break;
case 4:
p = popLinkStack(p);
break;
case 5:
showLinkStack(p);
break;
}
}while(chioce>0);
}
总结:不管是栈还是栈链,都是基于栈顶的插入和删除,把握栈顶元素的变化。
队列
队列(Queue)是限定只能在表的两端分别进行插人或删除操作的线性表。在队列结构中,数据元素只能从一端(表尾)插人,从另一端(表头)删除。允许插人的一端称为队尾(Rear),允许删除的一端称为队头(Front)。当队列中没有包含数据元素时,称为空队。向一个队列插人新的元素称为人队(Enqueue),此时,插人的元素成为新的队尾元素;从队列中删除一个元素时,只能删除当前的队头元素,称为出队(Dequeue)。基于队列的这种“先进先出”的结构特点,因而也称为先进先出(First In First Out,FIFO)线性表。
队列的表示和实现可以采用顺序结构存储和链式结构存储。
顺序队列的表示和实现
#include <stdio.h>
#include <stdlib.h>
#define MAXSIZE 10
#define INCREATE 5
#define OK 1
#define ERROR 0
typedef struct SqQueue{
int *data;
int front;//队头指针front指向当前的对头元素
int rear;//队尾指针rear指向当前队尾元素的下一个位置
//这里书上把它当前指针,实则并不是指针,可以把它当做记录队列中元素的位置信息
} sqQueue;
void InitQueue(sqQueue *p){
p->data = malloc(MAXSIZE*sizeof(int));
if(p->data==NULL){
return ERROR;
}
p->front = p->rear=0;
}
void insertQueue(sqQueue *p,int value){
int position = p->rear;
if(p->rear==MAXSIZE-1){
printf("内存地址开辟不够,需要重新动态分配地址 \n");
p->data = realloc(p->data,((p->rear-1)+INCREATE)*sizeof(int));
if(p->data==NULL){
return ERROR;
}
}
p->data[position] = value;
p->rear++;
return OK;
}
void deleteQueue(sqQueue *p){
if(p->front==p->rear){//空队列不可以出列
return ERROR;
}
p->front++;
return OK;
}
void emptyQueue(sqQueue *p){
if(p->front==p->rear){
printf("这是空队列 \n");
return ERROR;
}else{
printf("不是空队列 \n");
return OK;
}
}
void getHead(sqQueue *p){
if(p->front==p->rear){
printf("这是空队列 \n");
return ERROR;
}
printf("这是队头的元素 %d \n",p->data[p->front]);
}
void getRear(sqQueue *p){
if(p->front==p->rear){
printf("这是空队列 \n");
return ERROR;
}
printf("这是队尾的元素 %d \n",p->data[p->rear-1]);
}
int main()
{
int choice;
int value;
sqQueue *p = malloc(sizeof(sqQueue));
if(p==NULL){
return ERROR;
}
do{
printf("1 初始化队列 \n");
printf("2 插入队列 \n");
printf("3 出列 \n");
printf("4 是否为空队列 \n");
printf("5 这是队首元素 \n");
printf("6 这是队尾元素 \n");
scanf("%d",&choice);
switch(choice){
case 1:
InitQueue(p);
break;
case 2:
scanf("%d",&value);
insertQueue(p,value);
break;
case 3:
deleteQueue(p);
break;
case 4:
emptyQueue(p);
break;
case 5:
getHead(p);
break;
case 6:
getRear(p);
break;
}
}while(choice>0);
}
循环列表
为了解决上面的假溢出的问题,我们有了循环队列。可以看出上面队列,队列中的数组是可以动态分配的,溢出了就会自动分配,所以下面的代码是基于静态数组,提前给出数组容量。
主要的实现思路:将一维数组的最后一个单元和第一个单元连接起来,构成逻辑上的循环数组,此时就称之为循环队列。
如何形成逻辑上的循环,可以去想想24时间制的1点和13点,一个表示白天,一个是夜里,是怎么去记录,时间是12小时一换,所以用时间除以12,看余数。
根据上面的分析,我们就可以知道在上面定义的指针移动就会有变化。
循环列表的实现代码
#include <stdio.h>
#include <stdlib.h>
#define OK 1
#define ERROR 0
typedef struct LoopQueue{
int data[10];//选择数组,已经主动分配地址
int front;
int rear;
} loopqueue;
int initQueue(loopqueue *p){
if(p==NULL){
return ERROR;
}
p->front = p->rear=0;
return OK;
}
int insertQueue(loopqueue *p,int value){
if((p->rear+1)%10==p->front){
printf("队列已经满了 \n");
return ERROR;
}
p->data[p->rear] = value;
p->rear = (p->rear+1)%10;//移动队尾指针
return OK;
}
int deleteQueue(loopqueue *p){
if(p->front==p->rear){
printf("空队列 \n");
return ERROR;
}
p->front =(p->front+1)%10;
}
int getHead(loopqueue *p){
if(p->front==p->rear){
printf("空队列 \n");
return ERROR;
}
printf("这是队首元素 %d\n",p->data[p->front]);
}
int getRear(loopqueue *p){
if(p->front==p->rear){
printf("空队列 \n");
return ERROR;
}
printf("这是队尾元素 %d\n",p->data[p->rear-1]);
}
int EmptyLoopQueue(loopqueue *p){
if(p->front==p->rear){
printf("空队列 \n");
return ERROR;
}else{
printf("不是空队列 \n");
}
}
int main()
{
int choice;
int value;
loopqueue *p=malloc(sizeof(loopqueue));
do{
printf("1 循环队列初始化 \n");
printf("2 插入队列 \n");
printf("3 出列 \n");
printf("4 是否为空队列 \n");
printf("5 这是队首元素 \n");
printf("6 这是队尾元素 \n");
scanf("%d",&choice);
switch(choice){
case 1:
initQueue(p);
break;
case 2:
scanf("%d",&value);
insertQueue(p,value);
break;
case 3:
deleteQueue(p);
break;
case 4:
EmptyLoopQueue(p);
break;
case 5:
getHead(p);
break;
case 6:
getRear(p);
break;
}
}while(choice>0);
}
需要注意的地方:循环队列初始化空列时,front=rear=0;当队列经过多次出队入队操作后,会出现对头指针和队尾指针在次指向同一个单元,此时队列可能是空列也可能是满列,为处理这样的情况,通过空留一个存储单元的方式来区分队列满或是空。
链队
采用链式存储结构的队列,称之为链队。
实现代码
#include <stdio.h>
#include <stdlib.h>
#define OK 1
#define ERROR 0
typedef struct LinkNode{
int data;
struct LinkNode *next;
} LinkNode;
typedef struct LinkQueue{
LinkNode *front;
LinkNode *rear;
} LinkQueue;
int initQueue(LinkQueue *p){
if(p==NULL){
printf("链表初始化失败 \n");
return ERROR;
}
LinkNode *node;
node = malloc(sizeof(LinkNode));
if(node==NULL){
printf("节点申请失败 \n");
return ERROR;
}
p->front =p->rear = node;
node->next=NULL;
return OK;
}
int emptyLinkQueue(LinkQueue *p){
if(p->front==p->rear){
printf("这是空链队 \n");
}else{
printf("这不是空链队 \n");
}
return OK;
}
int insertQueue(LinkQueue *p,int value){
//因为采用的是链式,没有数量限制,你想要多少都可以,
//不需要判断列队是否满
LinkNode *node;
node = malloc(sizeof(LinkNode));
if(node==NULL){
printf("节点申请失败 \n");
return ERROR;
}
p->rear->next = node;//先之前的指针的next指向新的节点
node->data = value;
p->rear = node;//把队列的队尾指针指向新添加的节点,这个
//代码决不能和p->rear->next = node先后顺序有错,否则产生逻辑上的错误。
node->next=NULL;//最后的一个节点的next必须为空,不然后面无法去作为遍历条件
return OK;
}
int deleteLinkQueue(LinkQueue *p){
if(p->rear==p->front){
printf("空链队 \n");
return ERROR;
}
LinkNode *node = p->front->next;
if(p->front->next==p->rear){
p->front->next=NULL;
p->rear=p->front;//将队列设置为空队列
}
p->front->next = node->next;
return OK;
}
void show(LinkQueue *p){
if(p->rear==p->front){
printf("空链队 \n");
return ERROR;
}
LinkNode *node = p->front->next;
while(node!=NULL){
printf("%d \n",node->data);
node = node->next;
}
}
int main()
{
int choice;
int value;
LinkQueue *p=malloc(sizeof(LinkQueue));
do{
printf("1 链队初始化 \n");
printf("2 插入队列 \n");
printf("3 出列 \n");
printf("4 是否为空队列 \n");
printf("5 遍历 \n");
scanf("%d",&choice);
switch(choice){
case 1:
initQueue(p);
break;
case 2:
scanf("%d",&value);
insertQueue(p,value);
break;
case 3:
deleteLinkQueue(p);
break;
case 4:
emptyLinkQueue(p);
break;
case 5:
show(p);
break;
}
}while(choice>0);
}
特别需要注意的就是:在出队列的时候,需要两种考虑,有其他节点的时候,和只有一个节点和头结点时候,出队列之后,就是空队列,p->front==p->rear,应该将队列置于空,否则在进操作时,就会发现队尾指针丢失。