栈和队列
栈
定义
栈是限定仅在表尾进行插入或删除操作的线性表。因此,对栈来说,表尾端有其特珠含义,称为栈顶(top),相应地,表头端称为栈底(batcom)。不含元素的空表称为空栈。
特点
假设栈S=(a1,a2,…,an),则称a1为栈底元素,an为校顶元素。栈中元素按a1,a2,…,an的次序进栈,退栈的第一个元素应为栈顶元素。换句话说,栈的修改是按后进先出的原则进行的,如下图所示。
因此,栈又称为后进先出的线性表,它的这个特点可用下图所示的铁路调度站形象地表示。
顺序栈的表示和操作的实现
存储结构
顺序栈是指利用顺序存储结构实现的栈,即利用一组地址连续的存储单元依次存放自栈底到栈顶的数据元素,同时附设指针top指示栈顶元素在顺序栈中的位置。因此设指针base指示栈底元素在顺序栈中的位置。当top和base的值相等时,表示空栈。
#define MAXSIZE 100 //顺序栈存储空间的初始分配量
typedef struct
{
SElemType* base; //栈底指针
SElemType* top; //栈顶指针
int stacksize;
}SqStack;
初始化
顺序栈的初始化操作就是为顺序栈动态分配一个预定义大小的数组空间。
SqStack InitStack(SqStack& S)
{//构造一个空栈S
S.base = new SElemType[MAXSIZE]; //为顺序栈动态分配一个最大容量为MAXSI2E的数组空间
if (!S.base) exit(OVERFLOW); //存储分配失败
S.top = S.base; //top初始为base,空栈
S.stacksize = MAXSIZE; //stacksize置为栈的最大容量MAXSIZEreturn
return 1;
}
入栈
入栈操作是指在栈顶插入新的元素。
SqStack Pop(SqStack& S, SElemType& e)
{//删除S的栈顶元素,用e返回其值
if (S.top == S.base) return 0; //栈空
e = *--S.top; //栈顶指针减1,将栈顶元素赋给e
return 1;
}
出栈
出栈操作是指将栈顶元素删除。
Status Pop(SqStack &S,SElemType &e)
(//删除S的栈顶元素,用e返回其值
if(S.top==s.base) return ERROR; //栈空
e=*--S.top; //栈顶指针减1,将栈顶元素赋给e
return OK;
}
取栈顶元素
当栈非空时,此操作返回当前栈顶元素的值,栈顶指针保持不变。
SElemType GetTop(SqStack S)
{//返回s的栈顶元素,不修改栈顶指针
if (S.top != S.base) //栈非空
return *(S.top - 1); //返回栈顶元素的值,栈顶指针不变
}
由于顺序栈和顺序表一样,受到最大空间容量的限制,虽然可以在“满
员”时重新分配空间扩大容量,但工作量较大,应该尽量避免。因此在应用程序无法预先估计栈可能达到的最大容量时,还是应该使用下面介绍的链栈。
链栈的表示和操作的实现
存储结构
链栈是指采用链式存储结构实现的栈。通常链栈用单链表来表示,如下图所示。栈的节点结构与单链表的结构相同,在此用StackNode表示,定义如下:
typedef struct StackNode
{
ElemType data;
struct StackNode *next;
) StackNode, *LinkStack;
由于对栈的主要操作是在栈顶插入和删除元素,显然以链表的头部作为栈顶是最方便的,而且没必要像单链表那样为了操作方便附加一个头节点。
初始化
链栈的初始化操作就是构造一个空栈,因为没必要设头节点,所以直接将栈顶指针置空即可。
typedef struct StackNode
{
SElemType data;
struct StackNode* next;
} StackNode, * LinkStack;
入栈
和顺序栈的入栈操作不同的是,链栈在入栈前不需要判断栈是否满,只需要为入栈元素动态分配一个节点空间,如图所示。
StackNode Pop(LinkStack& S, SElemType& e)
{//删除S的栈顶元素,用e返回其值
if (S == NULL) return ERROR; //栈空
e = S->data; //将栈顶元素赋给e
LinkStack& p = S; //用p临时保存栈顶元素空间,以备释放
S = S->next; //修改栈顶指针
delete p; //释放原栈顶元素的空间
return OK;
}
出栈
和顺序栈一样,链栈在出栈前也需要判断栈是否为空,不同的是,链栈在出栈后需要释放出栈元素的栈顶空间,如图所示。
StackNode Pop(LinkStack& S, SElemType& e)
{//删除S的栈顶元素,用e返回其值
if (S == NULL) return ERROR; //栈空
e = S->data; //将栈顶元素赋给e
LinkStack& p = S; //用p临时保存栈顶元素空间,以备释放
S = S->next; //修改栈顶指针
delete p; //释放原栈顶元素的空间
return OK;
}
取栈顶元素
与顺序栈一样,当栈非空时,取栈顶元素操作返回当前栈顶元素的值,栈顶指针S保持不变。
SElemType GetTop(LinkStack S)
{//返回S的栈顶元素,不修改栈顶指针
if (S != NULL) //栈非空
return S->data; //返回栈顶元素的值,栈顶指针不变
}
队列
定义
和栈相反,队列是一种先进先出的线性表。它只允许在表的一端进行插入,而在另一端删除元素。这和日常生活中的排队是一致的,最早进入队列的元素最早离开。在队列中,允许插入的一端称为队尾(rear),允许删除的一端则称为队头。
特点
假设队列为q=(a1,a2,…,an),那么,a1就是队头元素,an则是队尾元素。队列中的元素是按a1,a2,…,an的顺序进入的,退出队列也只能按照这个次序依次退出,也就是说,只有在a1,a2,…,a(n-1)都离开队列之后,an才能退出队列。下图所示为队列的示意。
循环队列——队列的顺序表示和操作的实现
存储结构
和顺序栈相类似,在队列的顺序存储结构中,除了用一组地址连续的存储单元依次存放从队头到队尾的元素之外,尚需附设两个整型变量front和rear分别指示队头元素及队尾元素的位置(后面分别称为头指针和尾指针)。队列的顺序存储结构表示如下:
#define MAXQSIZE 100 //队列可能达到的最大长度
typedef struct
typedef struct
{
ElemType* base; //存储空间的基地址
int front; //头指针
int rear; //尾指针
} SqQueue;
初始化创建空队列时,令front=rear=0,每当播入新的队尾元素时,尾指针rear增1;每当删除队头元素时,头指针front增1。因此,在非空队到中,头指针始终指向队头元素,而尾指针始终指向队尾元素的下一个位置,如下图所示。
初始化
循环队列的初始化操作就是动态分配一个预定义大小为MAXQSIZE的数组空间。
SqQueue InitQueue(SqQueue& Q)
{//构造一个空队列
Q.base = new ElemType[MAXQSIZE]; //为队列分配一个最大容量为MAXQSIZE的数组空间
if (!Q.base) exit(OVERFLOW); //存储分配失败
Q.front = Q.rear = 0; //将头指针和尾指针置为0,队列为空
return OK;
}
求队列长度
对于非循环队列,尾指针和头指针的差值便是队列长度;而对于循环队列,差值可能为负数,所以需要将差值加上MAXOSIZE,然后与MAXQSIZE求余。
int QueueLength(SqQueue Q)
{//返回Q的元素个数,即队列的长度
return(Q.rear-Q.front+MAXQSIZE)/MAXQSIZE;
}
入队
入队操作是指在队尾插入一个新的元素。
SqQueue StatusEnQueue(SqQueue& Q, ElemType e)
{//插入元素e为Q的新的队尾元素
if ((Q.rear + 1) & MAXQSIZE == Q.front) //若尾指针在循环意义上加1后等于头指针,表明队满
return ERROR;
Q.base[Q.rear] = e; //新元素插入队尾
Q.rear = (Q.rear + 1)/MAXQSIZE; // 队尾指针加 1
return OK;
}
出队
出队操作是指将队头元素删除。
SqQueue DeQueue(SqQueue &Q, ElemType& e)
{//删除Q的队头元素,用e返回其值
if (Q.front == Q.rear) return ERROR; //队空
e = Q.base[Q.front]; //保存队头元
Q.front = (Q.front + 1) % MAXQSIZE; //队头指针加
return OK;
}
取队头元素
当队列非空时,此操作返回当前队头元素的值,队头指针保持不变。
ElemType GetHead(SqQueue Q)
{//返回Q的队头元素,不修改队头指针 // 队列非空
if (Q.front != Q.rear)
return Q.base[Q.front]; //返回队头元素的值,队头指针不变 队头元素
}
如果用户的应用程序中设有循环队列,则必须为它设定一个最大队列长度;若用户无法预估所用队列的最大长度,则宜采用链队列。
链队列——队列的链式表示和实现
存储结构
链队列是指采用链式存储结构实现的队列。通常链队列用单链表来表示,下图所示。
一个链队列显然需要两个分别指示队头和队尾的指针(分别称为头指针和尾指针)才能唯一确定。这里和线性表
队列的单链表一样,为了操作方便起见,给链队列添加一个头节点,头指针始终指向头节点。队列的链式存储结构表示如下:
typedef struct QNode
{
QElemType data;
struct QNode *next;
)QNode,*QueuePtr;
typedef struct
{
QueuePtr front;//队头指针
QueuePtr rear; //队尾指针
}LinkQueue;
链队列的操作即为单链表插入和删除操作的特殊情况,只是需进一步修改尾指针或头指针。下面给出链队列初始化、入队、出队操作的实现。
初始化
链队的初始化操作就是构造一个只有一个头节点的空队,如图(a)所示。
LinkQueue InitQueue(LinkQueue& Q)
{//构造一个空队列Q
Q.front = Q.rear = new QNode;//生成新节点作为头节点,队头和队尾指针指向此节点
Q.front->next = NULL; //头节点的指针域置空
return OK;
}
入队
和循环队列的入队操作不同的是,链队列在人队前不需要判断队是否满,只需要为入队元素动态分配一个节点空间,如图(b)和图©所示。
LinkQueue EnQueue(LinkQueue& Q, QElemType e)
{//插入元素e为Q的新的队尾元素
LinkQueue &p = new QNode; //为入队元素分配节点空间,用指针p指向
p->data = e; //将新节点数据域置为e
p->next = NULL;
Q.rear->next = p; //将新节点插入队尾
Q.rear = p; //修改队尾指针
return OK;
}
出队
和循环队列一样,链队在出队前也需要判断队列是否为空,不同的是,链队列在出队后需要释放队头元素所占的空间,如图3.16(d)所示。
LinkQueue DeQueue(LinkQueue& Q,QElemType& e)
{//删除Q的队头元素,用e返回其值
if (Q.front == Q.rear) return ERROR; //若队列为空,则返回ERROR
p = Q.front->next; //p指向队头元素
e = p->data;//e保存队头元素的值
Q.front->next = p->next; //修改头节点的指针域
if (Q.rear == p) Q.rear = Q.front; //最后一个元素被删,队尾指针指向头节点
delete p; //释放原队头元素的空间
return OK;
}
需要注意的是,在链队列的出队操作中还要考虑当队列中最后一个元素被删后,队尾指针也会丢失,因此需对队尾指针重新赋值(指向头节点)。
取队头元素
与循环队列一样,当队列非空时,此操作返回当前队头元素的值,队头指针保持不变。
QElemType GetHead(LinkQueue Q)
{//返回Q的队头元素,不修改队头指针
if (Q.front != Q.rear) // 队列非空
return Q.front->next->data; //返回队头元素的值,队头指针不变
}
完整代码
顺序栈
#include <stdlib.h>
#include <corecrt_math.h>
#define MAXSIZE 100 //顺序栈存储空间的初始分配量
typedef int SElemType;
typedef struct
{
SElemType* base; //栈底指针
SElemType* top; //栈顶指针
int stacksize;
}SqStack;
SqStack InitStack(SqStack& S)
{//构造一个空栈S
S.base = new SElemType[MAXSIZE]; //为顺序栈动态分配一个最大容量为MAXSI2E的数组空间
if (!S.base) exit(OVERFLOW); //存储分配失败
S.top = S.base; //top初始为base,空栈
S.stacksize = MAXSIZE; //stacksize置为栈的最大容量MAXSIZEreturn
return 1;
}
SqStack Push(SqStack& S, SElemType e)
{//插入元素e为新的栈顶元素
if (S.top - S.base == S.stacksize) return 0; //栈满
*S.top++ = e;//将元素e压入栈顶,栈顶指针加1
return 1;
}
SqStack Pop(SqStack& S, SElemType& e)
{//删除S的栈顶元素,用e返回其值
if (S.top == S.base) return 0; //栈空
e = *--S.top; //栈顶指针减1,将栈顶元素赋给e
return 1;
}
SElemType GetTop(SqStack S)
{//返回s的栈顶元素,不修改栈顶指针
if (S.top != S.base) //栈非空
return *(S.top - 1); //返回栈顶元素的值,栈顶指针不变
}
链栈
#include <stdlib.h>
#define MAXSIZE 100 //顺序栈存储空间的初始分配量
typedef int SElemType;
typedef struct StackNode
{
SElemType data;
struct StackNode* next;
} StackNode, * LinkStack;
StackNode Push(LinkStack &S, SElemType e)
{//在栈顶插入元素e
LinkStack& p=new StackNode; //生成新节点
p->data=e; //将新节点数据域置为e
p->next=S; //将新节点插入栈顶
S=p; //修改栈顶指针为p
return OK;
}
StackNode Pop(LinkStack& S, SElemType& e)
{//删除S的栈顶元素,用e返回其值
if (S == NULL) return ERROR; //栈空
e = S->data; //将栈顶元素赋给e
LinkStack& p = S; //用p临时保存栈顶元素空间,以备释放
S = S->next; //修改栈顶指针
delete p; //释放原栈顶元素的空间
return OK;
}
SElemType GetTop(LinkStack S)
{//返回S的栈顶元素,不修改栈顶指针
if (S != NULL) //栈非空
return S->data; //返回栈顶元素的值,栈顶指针不变
}
顺序队列
#include <stdlib.h>
#include <corecrt_math.h>
#define MAXQSIZE 50 //定义队列中元素的最大个数
typedef int ElemType;
typedef struct
{
ElemType* base; //存储空间的基地址
int front; //头指针
int rear; //尾指针
} SqQueue;
SqQueue InitQueue(SqQueue& Q)
{//构造一个空队列
Q.base = new ElemType[MAXQSIZE]; //为队列分配一个最大容量为MAXQSIZE的数组空间
if (!Q.base) exit(OVERFLOW); //存储分配失败
Q.front = Q.rear = 0; //将头指针和尾指针置为0,队列为空
return OK;
}
int QueueLength(SqQueue Q)
{//返回Q的元素个数,即队列的长度
return(Q.rear - Q.front + MAXQSIZE) / MAXQSIZE;
}
SqQueue StatusEnQueue(SqQueue& Q, ElemType e)
{//插入元素e为Q的新的队尾元素
if ((Q.rear + 1) & MAXQSIZE == Q.front) //若尾指针在循环意义上加1后等于头指针,表明队满
return ERROR;
Q.base[Q.rear] = e; //新元素插入队尾
Q.rear = (Q.rear + 1)/MAXQSIZE; // 队尾指针加 1
return OK;
}
SqQueue DeQueue(SqQueue &Q, ElemType& e)
{//删除Q的队头元素,用e返回其值
if (Q.front == Q.rear) return ERROR; //队空
e = Q.base[Q.front]; //保存队头元
Q.front = (Q.front + 1) % MAXQSIZE; //队头指针加
return OK;
}
ElemType GetHead(SqQueue Q)
{//返回Q的队头元素,不修改队头指针 // 队列非空
if (Q.front != Q.rear)
return Q.base[Q.front]; //返回队头元素的值,队头指针不变 队头元素
}
链队列
#include <stdlib.h>
#define MAXSIZE 100 //顺序栈存储空间的初始分配量
typedef int QElemType;
typedef struct QNode
{
QElemType data;
struct QNode* next;
}QNode, * QueuePtr;
typedef struct
{
QueuePtr front;//队头指针
QueuePtr rear; //队尾指针
}LinkQueue;
LinkQueue InitQueue(LinkQueue& Q)
{//构造一个空队列Q
Q.front = Q.rear = new QNode;//生成新节点作为头节点,队头和队尾指针指向此节点
Q.front->next = NULL; //头节点的指针域置空
return OK;
}
LinkQueue EnQueue(LinkQueue& Q, QElemType e)
{//插入元素e为Q的新的队尾元素
LinkQueue &p = new QNode; //为入队元素分配节点空间,用指针p指向
p->data = e; //将新节点数据域置为e
p->next = NULL;
Q.rear->next = p; //将新节点插入队尾
Q.rear = p; //修改队尾指针
return OK;
}
LinkQueue DeQueue(LinkQueue& Q,QElemType& e)
{//删除Q的队头元素,用e返回其值
if (Q.front == Q.rear) return ERROR; //若队列为空,则返回ERROR
LinkQueue& p = Q.front->next; //p指向队头元素
e = p->data;//e保存队头元素的值
Q.front->next = p->next; //修改头节点的指针域
if (Q.rear == p) Q.rear = Q.front; //最后一个元素被删,队尾指针指向头节点
delete p; //释放原队头元素的空间
return OK;
}
QElemType GetHead(LinkQueue Q)
{//返回Q的队头元素,不修改队头指针
if (Q.front != Q.rear) // 队列非空
return Q.front->next->data; //返回队头元素的值,队头指针不变
}