写在最前,
写文章的初衷只是为了复习与记录自己的成长,笔者本人也还是学生,文章中难免会出现许多问题与错误,文章内容仅供参考,有不足的地方还请大家多多包涵并指正,谢谢~
第三章:限定性线性表——栈与队列
栈与队列是两种重要的抽象数据类型,是一类操作受限制的线性表,其特殊性在于限制了插入和删除等操作的位置。栈与队列的共同特点是在线性表端点进行操作。从数据结构角度看,它们都是线性结构,应用面较广
3.1 栈
ADT Stack {数据元素:可以是任意类型的数据,但必须属于同一个数据对象。结构关系:栈中数据元素之间是线性关系。基本操作:① InitStack(S):操作前提:S为未初始化的栈。操作结果:将S初始化为空栈。② ClearStack(S):操作前提:栈S已经存在。操作结果:将栈S置成空栈。③ IsEmpty(S):操作前提:栈S已经存在。操作结果:判栈空函数。若S为空栈,则函数返回TRUE,否则返回FALSE。④ IsFull(S):操作前提:栈S已经存在。操作结果:判栈满函数。若S栈已满,则函数返回TRUE,否则返回FALSE。⑤Push(S,x):操作前提:栈S已经存在。操作结果:在S的顶部插入(亦称压入)元素x。若S栈未满,将x插入栈顶位置,并返回TRUE;若栈已满,则返回FALSE,表示操作失败。⑥Pop(S,x):操作前提:栈S已经存在。操作结果:删除(亦称弹出)栈S的顶部元素,并用x带回该值,返回TRUE;若栈为空,返回值为FALSE,表示操作失败。⑦GetTop(S,x):操作前提:栈S已经存在。操作结果:取栈S的顶部元素赋给x所指向的单元,也称读栈顶。与Pop(S,x)不同之处在于GetTop(S,x)不改变栈顶的位置。若栈为空,返回值为FALSE,表示操作失败。}ADT Stack;
/*顺序栈利用一组地址连续的存储单元依次存放自栈底到栈顶的数据元素,
同时由于栈操作的特殊性,还必须附设一个位置指针top来动态地指示栈顶元素在顺序栈中的位置。
通常以top=-1表示空栈*/
//顺序栈的存储结构可以用一维数组来表示
#define Stack_Size 50 //设栈中元素个数为50
typedef struct{
StackElementType elem[Stack_Size]; //用来存放栈中元素的一维数组
int top; //存放栈顶元素下标,top为-1表示空栈
}SeqStack;
//初始化
void InitStack(SeqStack *S){
S->top=-1; //构造一个空栈
}
//进栈
int Push(SeqStack *S,StackElementType x){
//首先判断当前栈是否已满,如果栈已满,再进栈会上溢
if(S->top==Stack_Size-1) return (FALSE);
S->top++; //修改栈顶指针
S->elem[S->top]=x; //x进栈,置入S栈新栈顶
return(TRUE);
}
//出栈,将S栈顶元素弹出,放到x所指的存储空间中带出
int Pop(SeqStack *S,StackElementType *x){
//首先判断当前栈是否为空,如果栈空,再出栈会下溢
if(S->top==-1)
return(FALSE);
else{
*x=S->elem[S->top]; //栈顶元素赋值给x
S->top--; //修改栈顶指针
return(TRUE);
}
}
//读取栈顶元素,将栈S栈顶元素读出,放到x所指的存储空间中,栈顶指针不变
int GetTop(SeqStack *S,StackElementType *x){
if(S->top==-1)
return(FALSE);
else{
*x=S->elem[S->top]; //栈顶元素赋给x
return(TRUE);
}
}
/*注:参数说明SeqStack *S可改为SeqStack S,也就是将传地址方式改为传值方式。
传值比传地址容易理解,但传地址比传值更节省时间和空间*/
//双端栈
#define M 100
typedef struct{
StackElementType Stack[M]; //Stack[M]为栈区
StackElementType top[2]; //top[0]和top[1]分别为两个栈顶指示器
}DqStack;
//初始化
void InitStack(DqStack *S){
S->top[0]=-1;
S->top[1]=M;
}
//进栈,把数据元素x压入i号堆栈
int Push(DqStack *S,StackElementType x,int i){
if(S->top[0]+1==S->top[1]) return(FALSE); //栈已满
switch (i) {
case 0: //0号栈
S->top[0]++;
S->Stack[S->top[0]]=x;
break;
case 1: //1号栈
S->top[1]--;
S->Stack[S->top[1]]=x;
break;
default: //参数错误
return(FALSE);
}
return(TRUE);
}
//出栈,从i号堆栈中弹出栈顶元素并送到x中
void Pop(DqStack *S,StackElementType *x,int i){
switch (i) {
case 0:
if(S->top[0]==-1) return(FALSE);
*x=S->Stack[S->top[0]];
S->top[0]--;
break;
case 1:
if(S->top[1]==M) return(FALSE);
*x=S->Stack[S->top[1]];
S->top[1]++;
break;
default:
return(FALSE);
}
return(TRUE);
}
top为栈顶指针,时钟指向当前栈顶元素前面的头结点。若top->next==NULL则代表栈空。
链栈的结构定义及实现如下:
typedef struct node{
StackElementType data;
struct node *next;
}LinkStackNode;
typedef LinkStackNode *LinkStack;
//进栈
int Push(LinkStack top,StackElementType x){
LinkStackNode *temp;
temp=(LinkStackNode *)malloc(sizeof(LinkStackNode));
if(temp==NULL) return(FALSE); //申请空间失败
temp->data=x;
temp->next=top->next;
top->next=temp; //修改当前栈顶指针
return(TRUE);
}
//出栈
int Pop(LinkStack top,StackElementType x){
LinkStackNode *temp;
temp=top->next;
if(temp==NULL) return(FALSE); //栈为空
top->next=temp->next;
*x=temp->data;
free(temp); //释放存储空间
return(TRUE);
}
#define M 10 //M个链栈
typedef struct node{
StackElementType data;
struct node *next;
}LinkStackNode,*LinkStack;
LinkStack top[M];
top[0],top[1],top[2],...top[M-1]分别是M个链栈的栈顶指针,分别指向M个不同的链栈,下图是多链栈的示意图:
3.2 队列
队列(Queue)是另一种限定性的线性表,只允许在表的一端插入元素,而在另一端删除元素,所以队列具有先进先出(First In First Out,FIFO)的特性。在队列中,允许插入的一端称为队尾(Raar),允许删除的一端则称为队头(Front)。
下面是队列的抽象数据类型定义:
ADT Queue{
数据元素:可以是任意类型的数据,但必须属于同一个数据对象。
结构关系:队列中数据元素之间是线性关系。
基本操作:
①InitQueue(Q):
操作前提:Q为未初始化的队列。
操作结果:将Q初始化为一个空队列。
②IsEmpty(Q):
操作前提:队列Q已经存在。
操作结果:若队列为空,则返回TRUE,否则返回FALSE。
③IsFull(Q):
操作前提:队列Q已经存在
操作结果:若队列为满,则返回TRUE,否则返回FALSE。
④EnterQueue(Q,x):
操作前提:队列Q已经存在
操作结果:在队列Q的队尾插入x。操作成功,返回值为TRUE,否则返回值为FALSE。
⑤DeleteQueue(Q,x):
操作前提:队列Q已经存在
操作结果:将队列Q的队头元素出队,并用x带回其值。操作成功,返回值为TRUE,否则返回值为FALSE。
⑥GetHead(Q,x):
操作前提:队列Q已经存在
操作结果:取队列Q的队头元素(该元素不出队),并用x带回其值。操作成功,返回TRUE,否则返回值为FALSE。
⑦ClearQueue(Q):
操作前提:队列Q已经存在
操作结果:将队列Q置为空队列
}ADT Queue;
通常将队头指针和队尾指针封装在一个结构体中,并将该结构体类型重新命名为链队列类型。定义及实现如下:
typedef struct Node{
QueueElementType data; //数据域
struct Node *next; //指针域
}LinkQueueNode;
typedef struct{
LinkQueueNode *front; //队列头指针
LinkQueueNode *rear; //队列尾指针
}LinkQueue;
//初始化
int InitQueue(LinkQueue *Q){
Q->front=(LinkQueueNode *)malloc(sizeof(LinkQueueNode));
if(Q->front!=NULL){
Q->rear=Q->front;
Q->front->next=NULL;
return(TRUE);
}
else return(FALSE); //溢出
}
//入队,将数据元素x插入到队列Q中
int EnterQueue(LinkQueue *Q,QueueElementType x){
LinkQueueNode *NewNode;
NewNode=(LinkQueueNode *)malloc(sizeof(LinkQueueNode));
if(NewNode!=NULL){
NewNode->data=x;
NewNode->next=NULL;
Q->rear->next=NewNode;
Q->rear=NewNode;
return(TRUE);
}
else return(FALSE); //溢出
}
//出队,将队列Q的队头元素出队,并存放到x所指的存储空间中
int DeleteQueue(LinkQueue *Q,QueueElementType x){
LinkQueueNode *p;
if(Q->front==Q->rear) return(FALSE);
p=Q->front->next;
//队头元素出队
Q->front->next=p->next;
if(Q->rear==p) Q->rear=Q->front; //如果队中只有一个元素,则p出队后成为空队
*x=p->data;
free(p); //释放存储空间
return(TRUE);
}
在上图(c)所示循环队列中,队列头元素是e3,队列尾元素是e5,当e6、e7和e8相继入队后,队列空间均被占满,如图(b)所示,此时队尾指针追上队头指针,所以有front==rear。反之,若e3、e4和e5相继从图(c)的队列中删除,则得到空队列,如图(a)所示,此时队头指针追上队尾指针,也存在关系式front==rear。可见,只凭front==rear无法判别队列的状态是“空”还是“满”。对于这个问题,可有两种处理方法。
一种方法是损失一个元素空间的方法。当队尾指针所指向的空单元的后继单元是队头元素所在的单元时,则停止人队。这样来,队尾指针永远追不上队头指针,所以队满时不会有front==rear。队列“满”的条件变为(rear+1)mod MAXSIZE==front,判队空的条件不变,仍是rear=front。
另一种方法是增设一个标志量tag,以区别队列是“空"还是“满”。初始化操作即产生一个空的循环队列,设Q->front=Q->rear=0,tag=0;当tag==0且Q->front==Q->rear时,表示队空。当tag=1且Q->front==Q->rear时,表示队满。这种方法不损失空间。
//循环队列的类型定义
#define MAXSIZE 50 //队列的最大长度
typedef struct{
QueueElementType element[MAXSIZE]; //队列的元素空间
int front; //头指针指示器
int rear; //尾指针指示器
}SeqQueue;
//初始化,将*Q初始化为一个空的循环队列
void InitQueue(SeqQueue *Q){
Q->front=Q->rear=0;
}
//入队,将元素x入队
int EnterQueue(SeqQueue *Q,QueueElementType x){
if((Q->rear+1)%MAXSIZE==Q->front) //尾指针加1追上头指针,标志队列已经满了
return(FALSE);
Q->element[Q->rear]=x;
Q->rear=(Q->rear+1)%MAXSIZE; //重新设置队尾指针
return(TRUE);
}
//出队,删除队列的队头元素,用x返回其值
int DeleteQueue(SeqQueue *Q,QueueElementType *x){
if(Q->front==Q->rear) return(FALSE); //队列为空
*x=Q->element[Q->front];
Q->front=(Q->front+1)%MAXSIZE; //重新设置队头指针
return(TRUE);
}
写在最后,
因本系列文章主要为复习,故重点关注数据结构概念知识与理论知识,本章内容涉及到应用举例由于篇幅原因就不再展开,后续单独有文章《栈的应用举例和栈与递归的实现》《队列的的应用举例》说明,笔记仅作为参考,若读者发现内容有误请私信指正,谢谢!