第4章 栈和队列
栈(stack)是限定仅在表尾进行插入和删除操作的线性表。
我们把允许插入和删除的一端称为栈顶(top),另一端称为栈底(bottom),不含任何数据元素的栈称为空栈。栈又称为后进先出(Last In First Out)的线性表,简称LIFO结构。
栈的插入操作,叫作进栈,也称压栈、入栈。类似子弹入弹夹。栈的删除操作,叫作出栈,也有的叫作弹栈。如同弹夹中的子弹出夹。
4.1栈的抽象数据类型
对于栈来讲,理论上线性表的操作特性它都具备,可由于它的特殊性,所以针对它在操作上会有些变化。特别是插入和删除操作,我们改名为push和pop,英文直译的话是压和弹,更容易理解。你就把它当成是弹夹的子弹压入和弹出就好记忆了,我们一般叫进栈和出栈。
ADT栈(stack)
Data
同线性表。元素具有相同的类型,相邻元素具有前驱和后继关系
Operation
InitStack (*S):初始化操作,建立一个空栈S。
Destroystack (*s):若栈存在,则销毁它。Clearstack(*s):将栈清空。
stackEmpty(s):若栈为空,返回true,否则返回false。
GetTop (s,*e):若栈存在且非空,用e返回S的栈顶元素。
Push(*s,e):若栈S存在,插入新元素e到栈S中并成为栈顶元素。
Pop (*s,*e):删除栈s中栈顶元素,并用e返回其值。
StackLength(S):返回栈S的元素个数。
endADT
4.2栈的顺序存储结构及实现
4.2.1栈的顺序存储结构
既然栈是线性表的特例,那么栈的顺序存储其实也是线性表顺序存储的简化,我们简称为顺序栈。线性表是用数组来实现的,想想看,对于栈这种只能一头插入删除的线性表来说,用数组哪一端来作为栈顶和栈底比较好?
对,没错,下标为0的一端作为栈底比较好,因为首元素都存在栈底,变化最小,所以让它作栈底。
来看栈的结构定义
typedef int SElemType; /* SElemType类型根据实际情况而定,这里假设为int */
typedef struct
{
SElemType data[MAXSIZE];
int top; /* 用于栈顶指针 */
}SqStack;
4.2.2 进栈操作
/* 插入元素e为新的栈顶元素 */
Status Push(SqStack *S,SElemType e)
{
if(S->top == MAXSIZE -1) /* 栈满 */
{
return ERROR;
}
S->top++; /* 栈顶指针增加一 */
S->data[S->top]=e; /* 将新插入元素赋值给栈顶空间 */
return OK;
}
4.2.2 出栈操作
/* 若栈不空,则删除S的栈顶元素,用e返回其值,并返回OK;否则返回ERROR */
Status Pop(SqStack *S,SElemType *e)
{
if(S->top==-1)
return ERROR;
*e=S->data[S->top]; /* 将要删除的栈顶元素赋值给e */
S->top--; /* 栈顶指针减一 */
return OK;
}
4.3 两栈共享空间
#include "stdio.h"
#include "stdlib.h"
#include "math.h"
#include "time.h"
#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
#define MAXSIZE 20 /* 存储空间初始分配量 */
typedef int Status;
typedef int SElemType; /* SElemType类型根据实际情况而定,这里假设为int */
/* 两栈共享空间结构 */
typedef struct
{
SElemType data[MAXSIZE];
int top1; /* 栈1栈顶指针 */
int top2; /* 栈2栈顶指针 */
}SqDoubleStack;
Status visit(SElemType c)
{
printf("%d ",c);
return OK;
}
/* 构造一个空栈S */
Status InitStack(SqDoubleStack *S)
{
S->top1=-1;
S->top2=MAXSIZE;
return OK;
}
/* 把S置为空栈 */
Status ClearStack(SqDoubleStack *S)
{
S->top1=-1;
S->top2=MAXSIZE;
return OK;
}
/* 若栈S为空栈,则返回TRUE,否则返回FALSE */
Status StackEmpty(SqDoubleStack S)
{
if (S.top1==-1 && S.top2==MAXSIZE)
return TRUE;
else
return FALSE;
}
/* 返回S的元素个数,即栈的长度 */
int StackLength(SqDoubleStack S)
{
return (S.top1+1)+(MAXSIZE-S.top2);
}
/* 插入元素e为新的栈顶元素 */
Status Push(SqDoubleStack *S,SElemType e,int stackNumber)
{
if (S->top1+1==S->top2) /* 栈已满,不能再push新元素了 */
return ERROR;
if (stackNumber==1) /* 栈1有元素进栈 */
S->data[++S->top1]=e; /* 若是栈1则先top1+1后给数组元素赋值。 */
else if (stackNumber==2) /* 栈2有元素进栈 */
S->data[--S->top2]=e; /* 若是栈2则先top2-1后给数组元素赋值。 */
return OK;
}
/* 若栈不空,则删除S的栈顶元素,用e返回其值,并返回OK;否则返回ERROR */
Status Pop(SqDoubleStack *S,SElemType *e,int stackNumber)
{
if (stackNumber==1)
{
if (S->top1==-1)
return ERROR; /* 说明栈1已经是空栈,溢出 */
*e=S->data[S->top1--]; /* 将栈1的栈顶元素出栈 */
}
else if (stackNumber==2)
{
if (S->top2==MAXSIZE)
return ERROR; /* 说明栈2已经是空栈,溢出 */
*e=S->data[S->top2++]; /* 将栈2的栈顶元素出栈 */
}
return OK;
}
Status StackTraverse(SqDoubleStack S)
{
int i;
i=0;
while(i<=S.top1)
{
visit(S.data[i++]);
}
i=S.top2;
while(i<MAXSIZE)
{
visit(S.data[i++]);
}
printf("\n");
return OK;
}
int main()
{
int j;
SqDoubleStack s;
int e;
if(InitStack(&s)==OK)
{
for(j=1;j<=5;j++)
Push(&s,j,1);
for(j=MAXSIZE;j>=MAXSIZE-2;j--)
Push(&s,j,2);
}
printf("栈中元素依次为:");
StackTraverse(s);
printf("当前栈中元素有:%d \n",StackLength(s));
Pop(&s,&e,2);
printf("弹出的栈顶元素 e=%d\n",e);
printf("栈空否:%d(1:空 0:否)\n",StackEmpty(s));
for(j=6;j<=MAXSIZE-2;j++)
Push(&s,j,1);
printf("栈中元素依次为:");
StackTraverse(s);
printf("栈满否:%d(1:否 0:满)\n",Push(&s,100,1));
ClearStack(&s);
printf("清空栈后,栈空否:%d(1:空 0:否)\n",StackEmpty(s));
return 0;
}
4.4 栈的链式存储和实现
简称栈链。
#include "stdio.h"
#include "stdlib.h"
#include "math.h"
#include "time.h"
#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
#define MAXSIZE 20 /* 存储空间初始分配量 */
typedef int Status;
typedef int SElemType; /* SElemType类型根据实际情况而定,这里假设为int */
/* 链栈结构 */
typedef struct StackNode
{
SElemType data;
struct StackNode *next;
}StackNode,*LinkStackPtr;
typedef struct
{
LinkStackPtr top;
int count;
}LinkStack;
Status visit(SElemType c)
{
printf("%d ",c);
return OK;
}
/* 构造一个空栈S */
Status InitStack(LinkStack *S)
{
S->top = (LinkStackPtr)malloc(sizeof(StackNode));
if(!S->top)
return ERROR;
S->top=NULL;
S->count=0;
return OK;
}
/* 把S置为空栈 */
Status ClearStack(LinkStack *S)
{
LinkStackPtr p,q;
p=S->top;
while(p)
{
q=p;
p=p->next;
free(q);
}
S->count=0;
return OK;
}
/* 若栈S为空栈,则返回TRUE,否则返回FALSE */
Status StackEmpty(LinkStack S)
{
if (S.count==0)
return TRUE;
else
return FALSE;
}
/* 返回S的元素个数,即栈的长度 */
int StackLength(LinkStack S)
{
return S.count;
}
/* 若栈不空,则用e返回S的栈顶元素,并返回OK;否则返回ERROR */
Status GetTop(LinkStack S,SElemType *e)
{
if (S.top==NULL)
return ERROR;
else
*e=S.top->data;
return OK;
}
/* 插入元素e为新的栈顶元素 */
Status Push(LinkStack *S,SElemType e)
{
LinkStackPtr s=(LinkStackPtr)malloc(sizeof(StackNode));
s->data=e;
s->next=S->top; /* 把当前的栈顶元素赋值给新结点的直接后继,见图中① */
S->top=s; /* 将新的结点s赋值给栈顶指针,见图中② */
S->count++;
return OK;
}
/* 若栈不空,则删除S的栈顶元素,用e返回其值,并返回OK;否则返回ERROR */
Status Pop(LinkStack *S,SElemType *e)
{
LinkStackPtr p;
if(StackEmpty(*S))
return ERROR;
*e=S->top->data;
p=S->top; /* 将栈顶结点赋值给p,见图中③ */
S->top=S->top->next; /* 使得栈顶指针下移一位,指向后一结点,见图中④ */
free(p); /* 释放结点p */
S->count--;
return OK;
}
Status StackTraverse(LinkStack S)
{
LinkStackPtr p;
p=S.top;
while(p)
{
visit(p->data);
p=p->next;
}
printf("\n");
return OK;
}
int main()
{
int j;
LinkStack s;
int e;
if(InitStack(&s)==OK)
for(j=1;j<=10;j++)
Push(&s,j);
printf("栈中元素依次为:");
StackTraverse(s);
Pop(&s,&e);
printf("弹出的栈顶元素 e=%d\n",e);
printf("栈空否:%d(1:空 0:否)\n",StackEmpty(s));
GetTop(s,&e);
printf("栈顶元素 e=%d 栈的长度为%d\n",e,StackLength(s));
ClearStack(&s);
printf("清空栈后,栈空否:%d(1:空 0:否)\n",StackEmpty(s));
return 0;
}
4.5 栈的应用
二进制转换
#include <iostream>
#define ElemType int
#define INIT_SIZE 10 //初始容量
#define INCRE_SIZE 20 //自增量
//定义了一个顺序存储的栈
typedef struct
{
ElemType *base; //栈底指针
ElemType *top; //栈顶指针
int stackSize; //最大容量
} sqStack;
//初始化
void initStack(sqStack *s)
{
s->base = (ElemType *)malloc(INIT_SIZE * sizeof(ElemType));
if (!s->base)
{
exit(0);
}
s->top = s->base; //初始化,栈顶就是栈底
s->stackSize = INIT_SIZE;
}
void push(sqStack *s, ElemType e)
{
if (s->top - s->base == s->stackSize)
{ //判断栈满,追加空间
//realloc 将原来的内存复制到一片新的内存中
s->base = (ElemType *)realloc(s->base, (s->stackSize + INCRE_SIZE) * sizeof(ElemType));
if (!s->base)
{
exit(0);
}
s->top = s->base + s->stackSize; //设置新的栈顶
s->stackSize = s->stackSize + INCRE_SIZE; //重置最大值
}
*(s->top)++ = e;
}
ElemType pop(sqStack *s)
{
if (s->top == s->base)
{
std::cout << "栈为空" << std::endl;
return -1;
}
return *(--s->top);
}
//清空栈,让栈顶回到栈底即可
void clearStack(sqStack *s)
{
s->top = s->base;
}
//销毁栈(和清空不同)
void destroyStack(sqStack *s)
{
s->top = s->base;
//释放为此栈所分配的内存空间,
//在malloc时,系统会记住申请连续空间的起始地址和大小,
//free时,只要把起始地址告诉系统,系统知道会free多大空间
free(s->base);
s->stackSize = 0;
}
ElemType size(sqStack *s){
return s->top-s->base;
}
int main()
{
sqStack s;
initStack(&s);
std::cout << "请输入2进制数,#号结尾" << std::endl;
char c;
scanf("%c",&c);
while(c!='#'){
push(&s,c-48); //0的ascii码是48 所以1-0=1
scanf("%c",&c);
}
getchar();
int n =1,sum=0;
while(s.base!=s.top){
sum += pop(&s) * n;
n *= 2;
}
std::cout << "十进制数为:" << sum << std::endl;
return 0;
}
(逆波兰)四则运算表达式求值
20世纪50年代,波兰逻辑学家Jan Eukasiewicz,想到了一种不需要括号的后缀表达法,我们也把它称为逆波兰(Reverse Polish Notation,RPN)表示。我想可能是他的名字太复杂了,所以后人只用他的国籍而不是姓名来命名。这种后缀表示法,是表达式的一种新的显示方式,非常巧妙地解决了程序实现四则运算的难题。
对于“9+(3-1)×3+10÷2”,如果要用后缀表示法应该是什么样子:“9 3 1 - 3 * + 10 2 / +”,这样的表达式称为后缀表达式,叫后缀的原因在于所有的符号都是在要运算数字的后面出现 。显然,这里没有了括号。对于从来没有接触过后缀表达式的同学来讲,这样的表述是很难受的。
int main()
{
sqStack s;
initStack(&s);
std::cout << "请按逆波兰表达式输入数据,数字以空格隔开,#号结尾" << std::endl;
char c;
ElemType p,q;
scanf("%c",&c);
while(c!='#'){
if(c != ' '){
switch(c){
case '+':
p = pop(&s);
q = pop(&s);
push(&s,q+p);
break;
case '-':
p = pop(&s);
q = pop(&s);
push(&s,q-p);
break;
case '*':
p = pop(&s);
q = pop(&s);
push(&s,q*p);
break;
case '/':
p = pop(&s);
q = pop(&s);
push(&s,q/p);
break;
default:
push(&s,c-48);
}
}
scanf("%c",&c);
}
getchar();
std::cout << "后缀表达式的计算结果为:" << pop(&s) << std::endl;
return 0;
}
//改写程序:可以计算小数
typedef double ElemType;
int main()
{
sqStack s;
initStack(&s);
std::cout << "请按逆波兰表达式输入数据,数字以空格隔开,#号结尾" << std::endl;
char c;
char str[5];//假设最大4位字符
int i=0;
ElemType p, q, d;
scanf("%c", &c);
while (c != '#')
{
while (isdigit(c) || c == '.')
{ //过滤数字或小数
str[i++] = c;
str[i] = '\0'; //结尾是0,下一次还是数字会覆盖
if(i>=5){
printf("输入数字过大");
exit(0);
}
scanf("%c", &c);
if (c == ' ')
{
push(&s, atof(str));
i = 0;
break;
}
}
if (c != ' ')
{
switch (c)
{
case '+':
p = pop(&s);
q = pop(&s);
push(&s, q + p);
break;
case '-':
p = pop(&s);
q = pop(&s);
push(&s, q - p);
break;
case '*':
p = pop(&s);
q = pop(&s);
push(&s, q * p);
break;
case '/':
p = pop(&s);
q = pop(&s);
push(&s, q / p);
break;
}
}
scanf("%c", &c);
}
getchar();
std::cout << "计算结果为:" << pop(&s) << std::endl;
return 0;
}
我们把平时所用的标准四则运算表达式,即“9+(3-1)×3+10÷2”叫做中缀表达式。因为所有的运算符号都在两数字的中间,现在我们的问题就是中缀到后缀的转化。9+(3-1)*3+10/2 —> 9 3 1 - 3 * + 10 2 / +
总结规则:从左到右遍历中缀表达式的每个数字和符号,若是数字则直接输出,若是符号,则判断其与栈顶符号的优先级,是右括号或者优先级低于栈顶符 号,栈顶元素依次出栈并输出,直到遇到左括号或栈空才将最后的那个符号入栈。
typedef char ElemType; //栈存符号
int main()
{
sqStack s;
initStack(&s);
std::cout << "请输入中缀表达式,#号结尾" << std::endl;
char c;
int i = 0;
ElemType p, q, d;
scanf("%c", &c);
while (c != '#')
{
while (c >= '0' && c <= '9')
{
printf("%c", c);
scanf("%c", &c);
if (!isdigit(c))
{
printf(" ");
}
}
if (c == '#')
{
break;
}
if (c == ')')
{
ElemType e = pop(&s);
printf("%c ", e);
pop(&s);
}
if (c == '+' || c == '-')
{
if (s.top == s.base)
{ //如果栈空,直接push,因为+-优先级最低
push(&s, c);
}
else
{
ElemType e;
do
{
e = pop(&s);
if (e == '(') //如果是左括号在栈顶,继续push
{
push(&s, c);
}
else //如果是其他符号,一直输出直到栈空或再遇左括号
{
printf("%c ", e);
}
} while (s.top != s.base && e != '(');
push(&s, c);
}
}
if (c == '*' || c == '/' || c == '(')
{
push(&s, c);
}
scanf("%c", &c);
}
getchar();
while (s.base != s.top)//最后还有表达式,全pop
{
c = pop(&s);
printf("%c ", c);
}
return 0;
}
4.6 队列的定义
队列(queue)是只允许在一端进行插入操作,而在另一端进行删除操作的线性表。
队列是一种先进先出(First In First Out)的线性表,简称FIFO。允许插入的一端称为队尾,允许删除的一端称为队头。假设队列是q=( a1,a2,……,an),那么a1就是队头元素,而an是队尾元素。这样我们就可以删除时,总是从 a1开始,而插入时,列在最后。
4.7 队列的抽象数据类型
ADT队列(queue)
Data
同线性表。元素具有相同的类型,相邻元素具有前驱和后继关系
Operation
InitQueue (*o):初始化操作,建立一个空队列Q。
DestroyQueue (*o):若队列Q存在,则销毁它。
ClearQueue (*o):将队列Q清空。
QueueEmpty (o):若队列Q为空,返回true,否则返回false。
GetHead(o,*e):若队列Q存在且非空,用e返回队列Q的队头元素。
EnQueue (*o,e):若队列Q存在,插入新元素e到队列Q中并成为队尾元素。
DeQueue (*o, *e):删除队列Q中队头元素,并用e返回其值。
QueueLength(o):返回队列Q的元素个数
endADT
4.8 循环队列
为了避免当只有一个元素时,队头和队尾重合使处理变得麻烦,所以引入两个指针,front 指针指向队头元素,rear指针指向队尾元素的下一个位置,这样当front 等于rear时,此队列不是还剩一个元素,而是空队列。我们把队列的这种头尾相接的顺序存储结构称为循环队列。
队列满的条件是(rear+1) %QueueSize == front(取模“%”的目的就是为了整合rear 与front大小为一个问题)。
#include "stdio.h"
#include "stdlib.h"
#include "math.h"
#include "time.h"
#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
#define MAXSIZE 20 /* 存储空间初始分配量 */
typedef int Status;
typedef int QElemType; /* QElemType类型根据实际情况而定,这里假设为int */
/* 循环队列的顺序存储结构 */
typedef struct
{
QElemType data[MAXSIZE];
int front; /* 头指针 */
int rear; /* 尾指针,若队列不空,指向队列尾元素的下一个位置 */
}SqQueue;
Status visit(QElemType c)
{
printf("%d ",c);
return OK;
}
/* 初始化一个空队列Q */
Status InitQueue(SqQueue *Q)
{
Q->front=0;
Q->rear=0;
return OK;
}
/* 将Q清为空队列 */
Status ClearQueue(SqQueue *Q)
{
Q->front=Q->rear=0;
return OK;
}
/* 若队列Q为空队列,则返回TRUE,否则返回FALSE */
Status QueueEmpty(SqQueue Q)
{
if(Q.front==Q.rear) /* 队列空的标志 */
return TRUE;
else
return FALSE;
}
/* 返回Q的元素个数,也就是队列的当前长度 */
int QueueLength(SqQueue Q)
{
return (Q.rear-Q.front+MAXSIZE)%MAXSIZE;
}
/* 若队列不空,则用e返回Q的队头元素,并返回OK,否则返回ERROR */
Status GetHead(SqQueue Q,QElemType *e)
{
if(Q.front==Q.rear) /* 队列空 */
return ERROR;
*e=Q.data[Q.front];
return OK;
}
/* 若队列未满,则插入元素e为Q新的队尾元素 */
Status EnQueue(SqQueue *Q,QElemType e)
{
if ((Q->rear+1)%MAXSIZE == Q->front) /* 队列满的判断 */
return ERROR;
Q->data[Q->rear]=e; /* 将元素e赋值给队尾 */
Q->rear=(Q->rear+1)%MAXSIZE;/* rear指针向后移一位置, */
/* 若到最后则转到数组头部 */
return OK;
}
/* 若队列不空,则删除Q中队头元素,用e返回其值 */
Status DeQueue(SqQueue *Q,QElemType *e)
{
if (Q->front == Q->rear) /* 队列空的判断 */
return ERROR;
*e=Q->data[Q->front]; /* 将队头元素赋值给e */
Q->front=(Q->front+1)%MAXSIZE; /* front指针向后移一位置, */
/* 若到最后则转到数组头部 */
return OK;
}
/* 从队头到队尾依次对队列Q中每个元素输出 */
Status QueueTraverse(SqQueue Q)
{
int i;
i=Q.front;
while((i+Q.front)!=Q.rear)
{
visit(Q.data[i]);
i=(i+1)%MAXSIZE;
}
printf("\n");
return OK;
}
int main()
{
Status j;
int i=0,l;
QElemType d;
SqQueue Q;
InitQueue(&Q);
printf("初始化队列后,队列空否?%u(1:空 0:否)\n",QueueEmpty(Q));
printf("请输入整型队列元素(不超过%d个),-1为提前结束符: ",MAXSIZE-1);
do
{
/* scanf("%d",&d); */
d=i+100;
if(d==-1)
break;
i++;
EnQueue(&Q,d);
}while(i<MAXSIZE-1);
printf("队列长度为: %d\n",QueueLength(Q));
printf("现在队列空否?%u(1:空 0:否)\n",QueueEmpty(Q));
printf("连续%d次由队头删除元素,队尾插入元素:\n",MAXSIZE);
for(l=1;l<=MAXSIZE;l++)
{
DeQueue(&Q,&d);
printf("删除的元素是%d,插入的元素:%d \n",d,l+1000);
/* scanf("%d",&d); */
d=l+1000;
EnQueue(&Q,d);
}
l=QueueLength(Q);
printf("现在队列中的元素为: \n");
QueueTraverse(Q);
printf("共向队尾插入了%d个元素\n",i+MAXSIZE);
if(l-2>0)
printf("现在由队头删除%d个元素:\n",l-2);
while(QueueLength(Q)>2)
{
DeQueue(&Q,&d);
printf("删除的元素值为%d\n",d);
}
j=GetHead(Q,&d);
if(j)
printf("现在队头元素为: %d\n",d);
ClearQueue(&Q);
printf("清空队列后, 队列空否?%u(1:空 0:否)\n",QueueEmpty(Q));
return 0;
}
从这一段讲解,大家应该发现,单是顺序存储,若不是循环队列,算法的时间性能是不高的,但循环队列又面临着数组可能会溢出的问题,所以我们还需要研究一下不需要担心队列长度的链式存储结构。
4.9 队列的链式存储结构及实现
队列的链式存储结构,其实就是线性表的单链表,只不过它只能尾进头出而已,我们把它简称为链队列。为了操作上的方便,我们将队头指针指向链队列的头结点,而队尾指针指向终端节点。空队列时,front和rear都指向头结点。
#include "stdio.h"
#include "stdlib.h"
#include "math.h"
#include "time.h"
#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
#define MAXSIZE 20 /* 存储空间初始分配量 */
typedef int Status;
typedef int QElemType; /* QElemType类型根据实际情况而定,这里假设为int */
typedef struct QNode /* 结点结构 */
{
QElemType data;
struct QNode *next;
}QNode,*QueuePtr;
typedef struct /* 队列的链表结构 */
{
QueuePtr front,rear; /* 队头、队尾指针 */
}LinkQueue;
Status visit(QElemType c)
{
printf("%d ",c);
return OK;
}
/* 构造一个空队列Q */
Status InitQueue(LinkQueue *Q)
{
Q->front=Q->rear=(QueuePtr)malloc(sizeof(QNode));
if(!Q->front)
exit(OVERFLOW);
Q->front->next=NULL;
return OK;
}
/* 销毁队列Q */
Status DestroyQueue(LinkQueue *Q)
{
while(Q->front)
{
Q->rear=Q->front->next;
free(Q->front);
Q->front=Q->rear;
}
return OK;
}
/* 将Q清为空队列 */
Status ClearQueue(LinkQueue *Q)
{
QueuePtr p,q;
Q->rear=Q->front;
p=Q->front->next;
Q->front->next=NULL;
while(p)
{
q=p;
p=p->next;
free(q);
}
return OK;
}
/* 若Q为空队列,则返回TRUE,否则返回FALSE */
Status QueueEmpty(LinkQueue Q)
{
if(Q.front==Q.rear)
return TRUE;
else
return FALSE;
}
/* 求队列的长度 */
int QueueLength(LinkQueue Q)
{
int i=0;
QueuePtr p;
p=Q.front;
while(Q.rear!=p)
{
i++;
p=p->next;
}
return i;
}
/* 若队列不空,则用e返回Q的队头元素,并返回OK,否则返回ERROR */
Status GetHead(LinkQueue Q,QElemType *e)
{
QueuePtr p;
if(Q.front==Q.rear)
return ERROR;
p=Q.front->next;
*e=p->data;
return OK;
}
/* 插入元素e为Q的新的队尾元素 */
Status EnQueue(LinkQueue *Q,QElemType e)
{
QueuePtr s=(QueuePtr)malloc(sizeof(QNode));
if(!s) /* 存储分配失败 */
exit(OVERFLOW);
s->data=e;
s->next=NULL;
Q->rear->next=s; /* 把拥有元素e的新结点s赋值给原队尾结点的后继,见图中① */
Q->rear=s; /* 把当前的s设置为队尾结点,rear指向s,见图中② */
return OK;
}
/* 若队列不空,删除Q的队头元素,用e返回其值,并返回OK,否则返回ERROR */
Status DeQueue(LinkQueue *Q,QElemType *e)
{
QueuePtr p;
if(Q->front==Q->rear)
return ERROR;
p=Q->front->next; /* 将欲删除的队头结点暂存给p,见图中① */
*e=p->data; /* 将欲删除的队头结点的值赋值给e */
Q->front->next=p->next;/* 将原队头结点的后继p->next赋值给头结点后继,见图中② */
if(Q->rear==p) /* 若队头就是队尾,则删除后将rear指向头结点,见图中③ */
Q->rear=Q->front;
free(p);
return OK;
}
/* 从队头到队尾依次对队列Q中每个元素输出 */
Status QueueTraverse(LinkQueue Q)
{
QueuePtr p;
p=Q.front->next;
while(p)
{
visit(p->data);
p=p->next;
}
printf("\n");
return OK;
}
int main()
{
int i;
QElemType d;
LinkQueue q;
i=InitQueue(&q);
if(i)
printf("成功地构造了一个空队列!\n");
printf("是否空队列?%d(1:空 0:否) ",QueueEmpty(q));
printf("队列的长度为%d\n",QueueLength(q));
EnQueue(&q,-5);
EnQueue(&q,5);
EnQueue(&q,10);
printf("插入3个元素(-5,5,10)后,队列的长度为%d\n",QueueLength(q));
printf("是否空队列?%d(1:空 0:否) ",QueueEmpty(q));
printf("队列的元素依次为:");
QueueTraverse(q);
i=GetHead(q,&d);
if(i==OK)
printf("队头元素是:%d\n",d);
DeQueue(&q,&d);
printf("删除了队头元素%d\n",d);
i=GetHead(q,&d);
if(i==OK)
printf("新的队头元素是:%d\n",d);
ClearQueue(&q);
printf("清空队列后,q.front=%u q.rear=%u q.front->next=%u\n",q.front,q.rear,q.front->next);
DestroyQueue(&q);
printf("销毁队列后,q.front=%u q.rear=%u\n",q.front, q.rear);
return 0;
}
4.10 总结回顾
这一章讲的是栈和队列,它们都是特殊的线性表,只不过对插入和删除操作做了限制。
栈(stack)是限定仅在表尾进行插入和删除操作的线性表。
队列(queue)是只允许在一端进行插入操作,而在另一端进行删除操作的线性表。
它们均可以用线性表的顺序存储结构来实现,但都存在着顺序存储的一些弊端。因此有各自解决技巧。对于栈来说,如果是两个相同数据类型的栈,则可以用数组的两端作栈底的方法来让两个栈共享数据,这就可以最大化地利用数组的空间。对于队列来说,为了避免数组插入和删除时需要移动数据,于是就引入了循环队列,使得队头和队尾可以在数组中循环变化。解决了移动数据的时间损耗,使得本来插入和删除是O(n)的时间复杂度变成了0(1)。
它们也都可以通过链式存储结构来实现,实现原则上与线性表基本相同
- 栈
- 顺序栈
- 两栈共享空间
- 栈链
- 顺序栈
- 队列
- 顺序队列
- 循环队列
- 链队列
- 顺序队列