第三章 栈和队列

第三章 栈和队列

一、栈的基本概念
1、栈的定义:

:只允许在一段进行插入和删除的限定性线性表。
栈顶:线性表允许进行插入和删除的那一端。
栈低:不允许插入和删除的那一端。
空栈:不含任何元素的空表。
根据上述定义,每次进栈的元素都被放在原栈顶元素之上而成为新的栈顶,而每次出栈的总是当前栈中“最新”的元素,即最后进栈的元素。在下图所示的栈中,元素是以a1,a2,a3,…,an的顺序进栈的,而退栈的次序却是an,…,a3,a2,a1。栈的修改是按后进先出的原则进行的。因此,栈又称为后进先出的线性表,简称为LIFO表。

2、栈的抽象数据类型

栈的抽象数据类型定义
ADT Stack
数据元素:可以是任意类型的数据,但必须属于同一个数据对象。
结构关系:栈中数据元素之间是线性关系。
基本操作:
(1)InitStack(S)
操作前提:S为未初始化的栈。
操作结果:将S初始化为空栈。
(2)ClearStack(S)
操作前提:栈S已经存在。
操作结果:将栈S置成空栈。
(3)IsEmpty(S)
操作前提:栈S已经存在。
操作结果:判栈空函数,若S为空栈则函数值为“TRUE”,否则为“FALSE”。
(4)IsFull(S)
操作前提:栈S已经存在。
操作结果:判栈满函数,若S栈已满,则函数值为“TRUE”,否则为“FALSE”。

二、栈的顺序存储结构
1、顺序栈的定义

采用顺序存储结构的栈称为顺序栈。

2、顺序栈的C语言描述
#define MaxSize 50
typedef int ElemType; 
//栈的顺序存储类型描述
typedef struct{
    ElemType data[MaxSize];
    int top;
}SqStack;
//栈的初始化
void InitStack(SqStack *S){
    S->top=-1;
}
//判栈为空
bool StackEmpty(SqStack S){
    if(S->top==-1){
        return true;
    }else{
        return false;
    }
}
//(4)进栈
bool Push(SqStack *S,ElemType x){
    if(S->top==MaxSize-1)
        return false;
    S->top++;
    S->data[S->top]=x;
    return true;
}
//出栈操作
bool Pop(SqStack *S,ElemType *x){     //出栈
    if(S->top==-1)
        return false;
    *x=S->data[S->top];
    S->top--;
    return true;
}
//读取栈顶元素
bool GetTop(SqStack *S,ElemType *x){
    if(S->top==-1)
        return false;
    *x=S->data[S->top];
    return true;
    }
}

3、顺序栈的共享

让多个栈共享一个足够大的数组空间,通过利用栈的动态特性来使 其存储空间互相补充,这就是多栈共享技术。在顺序栈的共享技术中最常用的是两个栈的共享技术即双端栈:它主要利用了栈“栈底位置不变,而栈顶位置动态变化”的特性。
顺序栈的共享

(1)共享栈的C语言描述

#define M 100
typedef int StackElementType;
//两栈共享的数据结构定义
typedef struct{
    StackElementType Stack[M];
    int top[2];
}DqStack;
//初始化操作
void InitStack(DqStack *S){
    S->top[0]=-1;
    S->top[1]=M;
}
//进栈操作
int Push(DqStack *S,StackElementType x,int i){
    if(S->top[0]+1==S->top[1])
        return false;
    switch(i){
    case 0:
        S->top[0]++;
        S->Stack[S->top[0]]=x;
        break;
    case 1:
        S->top[M]--;
        S->Stack[S->top[M]]=x;
        break;
    default:
        return false;
    }
}
//出栈操作
int Pop(DqStack *S,StackElementType *c,int i){
    switch(i){
    case 0if(S->top[0]==-1)
            return false;
        *x=S->Stack[S->top[1]];
        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;
}

三、栈的链式存储结构——链栈
1、链栈的定义

​ 链栈即采用链表作为存储结构实现的栈。由于栈的插入和删除操作仅限制在表头位置进行,所以链表的表头指针就作为栈顶指针,如下图所示。
链栈的定义

​ 在上图中,top 为栈顶指针,始终指向当前栈顶元素前面的头结点。若top->next=NULL,则代表栈空。采用链栈不必预先估计栈的最大容量,只要系统有可用空间,链栈就不会出现溢出。

2、链栈结构的C语言描述
typedef struct node
{
StackElementType data;
struct node *next;
}LinkStackNode;
typedef LinkStackNode *LinkStack;
3、链栈的基本操作
//进栈操作
int Push(LinkStack top, StackElementType x)
/* 将数据元素 x 压入栈 top 中 */
{
 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)
{ /* 将栈 top 的栈顶元素弹出,放到 x 所指的存储空间中 */
LinkStackNode * temp;
temp=top->next;
if(temp==NULL) /*栈为空*/
return(FALSE);
top->next=temp->next;
*x=temp->data;
free(temp); /* 释放存储空间 */
return(TRUE);
}

四、栈的应用
1、括号匹配问题

【算法描述】在检验算法中可设置一个栈,每读入一个括号,若是左括号,则直接入栈,等待相匹配的同类右括号;若读入的是右括号,且与当前栈顶的左括号同类型,则二者匹配,将栈顶的左括号出栈,否则属于不合法的情况。另外,如果输入序列已读尽,而栈中仍有等待匹配的左括号,或者读入了一个右括号,而栈中已无等待匹配的左括号,均属不合法的情况。当输入序列和栈同时变为空时,说明所有括号完全匹配。

【C语言描述】

void BracketMatch(char *str)
/* str[]中为输入的字符串,利用堆栈技术来检查该字符串中的括号是否匹配*/
{
Stack S; int i; char ch;
InitStack(&S);
For(i=0; str[i]!=’\0; i++) /*对字符串中的字符逐一扫描*/
{
 switch(str[i]){
 case(:
case[:
case{:
 Push(&S,str[i]);
 break;
 case):
 case]:
 case}:
if(IsEmpty(&S))
 { printf("\n 右括号多余!"); return;}
else
{
 GetTop (&S,&ch);
if(Match(ch,str[i])) /*用 Match 判断两个括号是否匹配*/
 Pop(&S,&ch); /*已匹配的左括号出栈*/
 else
 { printf("\n 对应的左右括号不同类!"); return;}
}
}/*switch*/
}/*for*/
if(IsEmpty(&S))
printf("\n 括号匹配!");
else
printf("\n 左括号多余!");
}
2、表达式求值问题

例 3-1 在实现A/B↑C+D* E的运算过程中,栈区变化情况如图3.9所示。为运算方便,在表达式后面加上一个结束符#,并将其视为一个优先级最低的特殊运算符,所以实际输入的表达式为:A/B↑C+D * E#。

【算法思想】

(1)规定运算符的优先级表

(2)设置两个栈:OVS(运算数栈)、OPTR(运算符栈)

(3)自左向右扫描,进行如下处理:

①遇到运算数则进OVS栈;

②遇到运算符则与OPTR栈的栈顶运算符进行优先级比较:

A.如果当前运算符>OPTR栈顶运算符,则当前运算符进OPTR栈;

B.如果当前运算符 ≤OPTR 栈顶运算符,则 OPTR退栈一次,得到栈顶运算符θ,OVS连续退栈两次,得到运算数a、运算数b,对a,b执行θ运算,得到结果T(i),将T(i)进OVS栈。
表达式求值问题

五、队列
(一)队列的定义和实现

队列是一种先进先出(FIFO)的限定性线性表,它只允许在表的一端进行插入,在另一端删除。在队列中允许插入的一段叫做队尾,允许删除的一段叫做对头。

(二)链队列的表示和实现

链队列的表示和实现

【链队列的C语言描述】

// 链队列定义如下:
typedef struct Node
{
 QueueElementType data; /*数据域*/
 struct Node *next; /*指针域*/
}LinkQueueNode;
typedef struct
{
 LinkQueueNode * front;
 LinkQueueNode * rear;
}LinkQueue;
// 初始化操作
int InitQueue(LinkQueue * Q)
{ /* 将 Q 初始化为一个空的链队列 */
 Q->front=(LinkQueueNode *)malloc(sizeof(LinkQueueNode));
 if(Q->front!=NULL)
{
Q->rear=Q->front;
Q->front->next=NULL;
return(TRUE);
}
else return(FALSE); /* 溢出!*/
}
// 入队操作
int EnterQueue(LinkQueue *Q, QueueElementType x)
{ /* 将数据元素 x 插入到队列 Q 中 */
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); /* 溢出!*/
}
// 出队操作
int DeleteQueue(LinkQueue * Q, QueueElementType *x)
{ /* 将队列 Q 的队头元素出队,并存放到 x 所指的存储空间中 */
LinkQueueNode * p;
if(Q->front==Q->rear)
 return(FALSE);
p=Q->front->next;
Q->front->next=p->next; /* 队头元素 p 出队 */
if(Q->rear==p) /* 如果队中只有一个元素 p,则 p 出队后成为空队 */
Q->rear=Q->front;
*x=p->data;
free(p); /* 释放存储空间 */
return(TRUE);
}

(三)队列的顺序表示

​ 队列的顺序实现是指分配一块连续的存储单元存放队列中的元素,并附设两个指针 frontrear 分别指向队头和队尾元素的位置。设队头指针指向队头元素,队尾指针指向队尾元素的下一个位置。

// 队列的顺序存储类型定义
# define MaxSize 50
typedef struct{
    ElemType data[MaxSize];
    int front,rear;
}SqQueue

【算法描述】

初始状态(空队列):Q.front == Q.rear==0;

进队操作 :队不满时,先送值到队尾位置,再将队尾指针加1;

出队操作 :队不空时 ,先取队头元素值,再将对头指针加一;

(四)循环队列的表示和实现

算法描述

​ 为解决顺序队列的“假溢出”的缺点,这里引出了循环队列的概念,即把存储队列元素的表从逻辑上看做一个环 ,称为循环队列。当队首指针 Q.front=MaxSize-1 后,在前进一个位置及自动返回0,这可以用取余运算来实现。

​ 初始状态(空队列):Q.front == Q.rear==0;

​ 队首指针进1:Q.front =(Q.front+1)%MaxSize;

​ 队尾指针进1:Q.front =(Q.front+1)% MaxSize;

​ 队列长度:(Q.rear+ MaxSize - Q.front)% MaxSize;

判断队列是否为空或者为满的条件

队列为空 :Q.front == Q.rear==0;

队列为满 :(Q.rear+1)%Maxsize=Q.front;

队列元素的个数 :(Q.rear - Q.front+MaxSize )% MaxSize;

循环队列的C语言实现

// 循环队列的类型定义
#define MAXSIZE 50 /*队列的最大长度*/
typedef struct
{
QueueElementType element[MAXSIZE]; /* 队列的元素空间*/
int front; /*头指针指示器*/
int rear ; /*尾指针指示器*/
}SeqQueue;
// 初始化操作
void InitQueue(SeqQueue * Q)
{ /* 将*Q 初始化为一个空的循环队列 */
Q->front=Q->rear=0;
}
// 入队操作
int EnterQueue(SeqQueue *Q, QueueElementType x)
{ /*将元素 x 入队*/
if((Q->rear+1)%MAXSIZE==Q->front) /*队列已经满了*/
return(FALSE);
Q->element[Q->rear]=x;
Q->rear=(Q->rear+1)%MAXSIZE; /* 重新设置队尾指针 */
return(TRUE); /*操作成功*/
}
// 出队操作
int DeleteQueue(SeqQueue *Q, QueueElementType * x)
{ /*删除队列的队头元素,用 x 返回其值*/
if(Q->front==Q->rear) /*队列为空*/
return(FALSE);
*x=Q->element[Q->front];
Q->front=(Q->front+1)%MAXSIZE; /*重新设置队头指针*/
return(TRUE); /*操作成功*/
}
/*将元素 x 入队*/
if((Q->rear+1)%MAXSIZE==Q->front) /*队列已经满了*/
return(FALSE);
Q->element[Q->rear]=x;
Q->rear=(Q->rear+1)%MAXSIZE; /* 重新设置队尾指针 */
return(TRUE); /*操作成功*/
}
// 出队操作
int DeleteQueue(SeqQueue *Q, QueueElementType * x)
{ /*删除队列的队头元素,用 x 返回其值*/
if(Q->front==Q->rear) /*队列为空*/
return(FALSE);
*x=Q->element[Q->front];
Q->front=(Q->front+1)%MAXSIZE; /*重新设置队头指针*/
return(TRUE); /*操作成功*/
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值