栈和队列

本文详细介绍了栈和队列这两种基本数据结构,包括它们的定义、操作、存储表示和实现。栈遵循后进先出(LIFO)原则,主要应用于数制转换和表达式求值等问题;队列则遵循先进先出(FIFO)原则,常见于模拟排队场景。文中还探讨了顺序栈和链栈的实现,以及循环队列在解决假溢出问题上的应用。
摘要由CSDN通过智能技术生成


栈和队列是两种重要的线性结构。从数据结构的角度看,栈和队列是操作受限的线性表;

3.1 栈*

抽象数据类型栈的定义:栈(stack):先进后出(FILO)或后进先出(LIFO)的线性表。
仅在表尾进行插入和删除操作
栈顶(top): 线性表的表尾端,即可操作端。
栈底(bottom): 线性表的表头。
在这里插入图片描述
抽象数据类型栈的定义:
ADT Stack {
数据对象:D ={ai | ai∈Elemset,(i=1,2,…,n, n≥0)}
数据关系:R1 = {<ai-1,ai>|ai-1,ai ∈ D,(i=2,3,…,n)}
约定an为栈顶, a1为栈底。
基本操作:
InitStack(&S); DestroyStack(&S);
ClearStack(&S); StackEmpty(S);
StackLength(S) ; GetTop(S, &e);
Push(&S, e); Pop(&S, &e);
StackTraverse(S, visit ( ))
}ADT Stack
栈的基本操作(一)
InitStack(&S)
操作结果:构造一个空的栈S。
DestroyStack(&S)
初始条件: 栈S已经存在。
操作结果: 销毁栈S。
ClearStack(&S)
初始条件: 栈S已经存在。
操作结果: 将栈S重置为空栈。
StackEmpty(S)
初始条件: 栈S已经存在。
操作结果: 若栈S为空栈,则返回TURE;否则返回FALSE。
StackLength(S)
初始条件: 栈S已经存在。
操作结果: 返回栈S中的数据元素个数。
GetTop(S, &e)
初始条件: 栈S已经存在且非空。
操作结果: 用e返回栈S中栈顶元素的值。
Push(&S, e) /入栈操作/
初始条件: 栈S已经存在。
操作结果: 插入元素e为新的栈顶元素。
Pop(&S, &e) /出栈操作/
初始条件: 栈S已经存在且非空。
操作结果: 删除S的栈顶元素并用e返回其值。
StackTraverse(S, visit ())
初始条件: 栈S已经存在且非空。
操作结果: 从栈底到栈顶依次对S的每个元素调用函数visit ()。一旦visit ()失败,则操作失败。
栈的两种存储表示和实现
栈的顺序表示与实现— (顺序栈).
#define STACK_INIT_SIZE 100
#define STACKINCREMENT 10

typedef struct{
SElemType base;
/
在栈构造之前和销毁之后,
base的值为NULL*/
SElemType top; / 栈顶指针 /
int stacksize; /
当前已分配的存储空间,
以元素为单位*/
}SqStack;
顺序栈示意图
在这里插入图片描述

顺序栈的操作—— InitStack                               /* 构造一个空的栈S*/
Status InitStack(SqStack &s) 
{   s.base=( SElemType *)malloc
      (STACK_INIT_SIZE * sizeof(SElemType));
  if(!s.base) exit(OVERFLOW);   
  s.top = s.base;   
  s.stacksize = STACK_INIT_SIZE;   
  return OK;
} /* InitStack */

顺序栈的操作——GetTop /栈S非空, 则用e返回栈S中栈顶元素的值,并返回OK;否则,返回ERROR。/
Status GetTop (SqStack s, SElemType &e)
{ if (s.top == s.base)return ERROR;
e = (s.top-1);
return OK;
} /
GetTop /
在这里插入图片描述
顺序栈的操作——Push插入新的栈顶元素时,栈变化示意
在这里插入图片描述
顺序栈的操作——Push Push /插入元素e为新的栈顶元素/
Status Push(SqStack &s, SElemType e)
{ if (s.top – s.base >= s.stacksize)
/
栈满,追加存储空间 /
{ s.base = (SElemType
)realloc
(s.base,(s.stacksize+STACKINCREMENT)*sizeof(SElemType));
if (!s.base) exit(OVERFLOW);
s.top = s.base + s.stacksize;
s.stacksize += STACKINCREMENT;
}
(s.top++) = e; return OK;
} /
Push */
顺序栈的操作——Pop /栈S非空, 则删除S的栈顶元素, 用e返回栈S中栈顶元素的值,并返回OK;否则,返回ERROR。/
Status Pop(SqStack &s, SElemType &e)
{ if (s.top == s.base)return ERROR;
e = (–s.top);
return OK;
} /
Pop */
在这里插入图片描述
栈的链式表示与实现–(链栈)
typedef struct stacknode{
datatype data;
struct stacknode *next;
}stacknode;
在这里插入图片描述

顺序栈
利用一组地址连续的存储单元依次存放自栈底到栈顶的数据元素。
基本运算
栈空时:S.top=S.base
栈满时:S.top-S.base>=S.stacksize
Pop操作、GetTop操作:先判断栈是否为空
Push操作:先判断栈是否已满

3.2栈的应用举例

例一:数制转换问题**
十进制数N和其它d进制数的转换算法基于下列原理: N = (N div d)×d + N mod d 其中,div 相除取整,mod 相除取余。
转换结果各位的产生顺序与通常输出顺序正好相反!
解决方法:利用栈!将转换过程中得到的每一位8进制数入栈保存;转换结束时,依次将这些数出栈。

void conversion() {
    // 对于输入的任意一个非负十进制整数,打印输出与其等值的八进制数
    InitStack(S);   // 构造空栈S
    scanf ("%d", &N);
    while (N) {
          Push(S, N%8);
          N = N/8;
    }
    while ( !StackEmpty(S))  {
          Pop(S, e);
          printf ( "%d", e);
    }
} // conversion

例二:表达式求值
表达式:由操作数、运算符和界符组成的合法式子。

表达式求值:高级语言编译中的一个最基本问题,即把一个表达式翻译成正确求值的一个机器指令序列,或者直接对表达式求值。

这里仅讨论:包含+、-、*、/ 4种运算符和左右括号的简单算术表达式。
在计算机中表达式的表示形式有:
前缀表示法
中缀表示法
后缀表示法
设 Exp = S1 + OP + S2
则称 OP + S1 + S2 为前缀表示法
S1 + OP + S2 为中缀表示法
S1 + S2 + OP 为后缀表示法
在这里插入图片描述
在这里插入图片描述
例如,求解算术表达式:

                   4+2 ×3-10/5
                   算术四则运算规则: 先乘除、后加减;
                   从左算到右;
                   先括号内、再括号外。

在这里插入图片描述
表达式求值过程:设立运算符栈OPTR,
操作数或运算结果栈OPND;
设表达式的结束符为“#”,预设运算符栈的栈底为“#”,操作数栈为空栈;
依次读入表达式中每个字符,若是操作数,则进OPND栈;若是运算符,则与OPTR栈的栈顶运算符比较优先权后作相应操作,直至整个表达式求值完毕(即OPTR栈的栈顶元素和当前读入的字符均为‘#’)。

OperandType EvaluateExpression()
{//  设OPTR和OPND分别为运算符栈和运算数栈,OP为运算符集合。
  InitStack(OPTR); Push(OPTR,'#');
  initStack(OPND); c=getchar();
  while(c!='#'||GetTop(OPTR)!='#')
  {
    if(!In(c,OP)) {Push(OPND,c); c=getchar(); //不是运算符则进栈
    else
         switch(Precede(GetTop(OPTR),c))
        {  case ‘<’: Push(OPTR,c); c=getchar();   break; //栈顶元素优先权低           
           case ‘=’: Pop(OPTR,x);  c=getchar();   break;
                          //脱括号并接收下一字符          
           case '>':                              //退栈并将运算结果入栈
               Pop(OPTR,theta);
               Pop(OPND,b); Pop(OPND,a);
               Push(OPND,Operate(a,theta,b));
               break;
       }
   } 
     return GetTop(OPND);
}

后缀表达式的求值
例如,求解算术表达式:
4+2 ×3-10/5
先把中缀表达式转换成等价的后缀表达式:
423 ×+105/-
然后对后缀表达式求值
后缀表达式求值的实现:仅使用一个操作数栈,在从左向右扫描表达式时,每遇到一个操作数就送入栈中保存,每遇到一个运算符就从栈中取出两个操作数进行当前的运算,然后把结果入栈,…直到整个表达式结束,这时栈顶的值就是计算的结果。

3.3补充内容——顺序栈的另一种存储表示及操作实现

1、栈中的数据元素用一个预设的足够长度的一维数组来存储: datatype data[MAXSIZE];
2、栈底位置可以设置在数组的任一个端点;
3、栈顶是随着插入和删除而变化的,用一个int top来作为栈顶的指针,指明当前栈顶的位置。
4、将data和top封装在一个结构体中。
顺序栈的另一种存储表示及操作实现
顺序栈的类型描述如下:
#define MAXSIZE 1024
typedef int datatype;
typedef struct
{ datatype data[MAXSIZE];
int top;
}SeqStack;
定义一个指向顺序栈的指针:
SeqStack *s;
通常0下标端设为栈底,空栈时栈顶指针top =-1;
入栈时,栈顶指针加1,即s->top++;
出栈时,栈顶指针减1,即s->top–;
在这里插入图片描述
在上述存储结构上基本操作的实现::
(1)置空栈:首先建立栈空间,然后初始化栈顶指针。
SeqStack *Init_SeqStack()
{ SeqStack s;
s=(SeqStack
)malloc(sizeof(SeqStack));
s->top= -1;
return s;
}
(2)判空栈
int Empty_SeqStack(SeqStack *s)
{ if(s->top==-1) return 1;
else return 0;
}
3)入栈
int Push_SeqStack(SeqStack *s,datatype x)
{ if(s->top==MAXSIZE-1) return 0;
else {
s->top++;
s->data[s->top]=x;
return 1;
}
}
4)出栈
int Pop_SeqStack(SeqStack *s,datatype &x)
{ if(Empty_SeqStack(s)) return 0;
else {
x=s->data[s->top];
s->top–;
return 1;
}
}
5)取栈顶元素
datatype Top_SeqStack(SeqStack *s)
{
if(Empty_SeqStack(s)) return 0;
else return (s->data[s->top]);
}
例一:数制转换问题
算法1:
void conversion(int N, int r)
{ SeqStack *s;
datatype x;
s=Init_SeqStack();
while(N) {
Push_SeqStack(s, N % r);
N = N/r;
}
while (!Empty_SeqStack(s))
{
Pop_SeqStack(s, &x);
printf("%d", x);
}
}
算法2:
#define L 10
void conversion(int N, int r)
{ int s[L], top; /*定义顺序栈 */
int x;
top = -1; /*初始化栈 */
while(N) {
s[++top] = N % r;
N = N/r;
}
while(top != -1)
{
x = s[top–];
printf("%d", x);
}
}

3.4队列

队列(queue)是一种先进先出(FIFO)的线性表,它只允许在表的一端–称为队尾(rear)进行插入操作,而在另一端—称为队头(front)进行删除操作。如图所示:
在这里插入图片描述
队列(queue)的例子:排队取款、排队购物、排队上车、排队打饭等;WEB客户端的请求在服务器端的排队。
抽象数据类型队列的定义
ADT Queue {
数据对象:D= {ai|ai∈ Elemset,(i=1,2,…,n, n≥0)}
数据关系:R1 = {<ai-1, ai>|ai-1,ai∈ D,(i=2,3,…,n)}
约定a1为队头, an为队尾。
基本操作:
InitQueue(&Q); DestroyQueue(&Q);
ClearQueue (&Q); QueueEmpty(Q);
QueueLength(Q) ; GetHead(Q, &e);
EnQueue(&Q, e); DeQueue(&Q, &e);
QueueTraverse(Q, visit ( ))
}ADT Queue
队列的基本操作
InitQueue(&Q)
操作结果:构造一个空的队列Q。
DestroyQueue(&Q)
初始条件: 队列Q已经存在。
操作结果: 销毁队列Q。
ClearQueue (&Q)
初始条件: 队列Q已经存在。
操作结果: 将队列Q清为空队列。
QueueEmpty(Q)
初始条件: 队列Q已经存在。
操作结果:若Q为空队列,则返回TURE;否则返回FALSE。
QueueLength(Q)
初始条件:队列Q已经存在。
操作结果:返回Q中的数据元素个数,即队列长度。
GetHead(Q, &e)
初始条件:队列Q已经存在且非空。
操作结果:用e返回队列Q中的队头元素。
EnQueue(&Q, e) /入队操作/
初始条件:队列Q已经存在。
操作结果:插入元素e为新的队尾元素。
DeQueue(&Q, &e) /出队操作/
初始条件:队列Q已经存在且非空。
操作结果:删除Q的队头元素,并用e返回其值。
QueueTraverse(Q, visit ( ))
初始条件:队列Q已经存在且非空。
操作结果:从队头到队尾依次对Q的每个元素调用函数visit ()。一旦visit ()失败,则操作失败。
注:队列的数据元素类型在应用程序中定义。
队列的两种存储表示和实现
链队列—队列的链式表示和实现
在这里插入图片描述
在这里插入图片描述
链队列类型的模块说明: --ADT Queue的链式表示与实现
/ — 单链队列–队列的链式存储结构 ----

typedef struct QNode {
QElemType data;
struct Qnode *next;
}Qnode, *QueuePtr;

typedef struct {
QueuePtr front; //队头指针
QueuePtr rear; //队尾指针
}LinkQueue;
链队列类型的模块说明: --ADT Queue的链式表示与实现
// — 基本操作的函数原型说明 —
Status InitQueue(LinkQueue &Q)
// 构造一个空队列Q
Status DestroyQueue(LinkQueue &Q)
// 销毁队列Q
Status ClearQueue(LinkQueue &Q)
// 将队列清为空队列
Status QueueEmpty(LinkQueue Q)
// 若队列为空队列,则返回TRUE,否则返回FALSE
int QueueLength(LinkQueue Q)
// 返回Q的元素个数,即队列的长度
Status GetHead(LinkQueue Q, QElemType &e)
// 若队列不空,则用e返回Q的队头元素,并返回OK;否则返回ERROR
Status EnQueue(LinkQueue &Q, QElemType e)
// 插入元素e为Q的新的队尾元素
Status DeQueue(LinkQueue &Q, QElemType &e)
// 若队列不空,则删除Q的队头元素,用e返回其值,并返回OK;否则返回ERROR
Status QueueTraverse(LinkQueue Q, visit())
// 从队头到队尾依次对队列Q中每个元素调用函数visit()。一旦visit失败,则操作失败。
链队列类型的模块说明: --ADT Queue的链式表示与实现
// — 基本操作的算法描述(部分) —

Status InitQueue(LinkQueue &Q) {
// 构造一个空队列Q
Q.front = Q.rear = (QueuePtr )malloc(sizeof(Qnode));
if(!Q.front) exit(OVERFLOW); //存储分配失败
Q.front->next = NULL;
return OK;
}
Status DestroyQueue(LinkQueue &Q) {
// 销毁队列Q
while(Q.front) {
Q.rear = Q.front->next;
free(Q.front);
Q.front = Q.rear;
}
return OK;
}
Status EnQueue(LinkQueue &Q, QElemType e) {
// 插入元素e为Q的新的队尾元素
p = (QueuePtr )malloc(sizeof(Qnode));
if(!p) exit(OVERFLOW);
p->data = e; p->next = NULL;
Q.rear->next = p;
Q.rear = p;
return OK;
}
Status DeQueue(LinkQueue &Q, QElemType &e) {
// 若队列不空,则删除Q的队头元素,用e返回其值,并返回OK;
// 否则返回ERROR
if(Q.front == Q.rear) return ERROR;
p = Q.front->next;
e = p->data;
Q.front->next = p->next;
if(Q.rear == p) Q.rear = Q.front;
free§;
return OK;
}
3.4.3 循环队列—队列的顺序表示和实现
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
循环队列类型的模块说明: --ADT Queue的顺序表示与实现
// — 循环队列–队列的顺序存储结构 —
#define MAXSIZE 100 //最大队列长度
typedef struct {
QElemType *base; //初始化的动态分配存储空间
int front; //头指针,若队列不空,则指向队列头元素
int rear; //尾指针,若队列不空,则指向队列尾元素的下一个位置
}SqQueue;
// — 循环队列的基本操作的算法描述(部分) —
Status InitQueue(SqQueue &Q) {
// 构造一个空队列Q
Q.base = (QElemType *)malloc(MAXSIZE *
sizeof(QElemType));
if(!Q.base) exit(OVERFLOW); //存储分配失败
Q.front = Q.rear = 0;
return OK;
}
Status QueueLength(SqQueue Q) {
// 返回Q的元素个数,即队列的长度
return (Q.rear - Q.front + MAXSIZE) % MAXSIZE;
}

Status EnQueue(SqQueue &Q, QElemType e) {
// 插入元素e为Q的新的队尾元素
if((Q.rear + 1) % MAXSIZE == Q.front) return ERROR;
Q.base[Q.rear] = e;
Q.rear = (Q.rear + 1) % MAXSIZE;
return OK;
}
Status DeQueue(SqQueue &Q, QElemType &e) {
// 若队列不空,则删除Q的队头元素,用e返回其值,并返回OK;
// 否则返回ERROR
if(Q.front == Q.rear) return ERROR;
e = Q.base[Q.front];
Q.front = (Q.front + 1) % MAXSIZE;
return OK;
}
理解顺序队列中的“假溢出”;
循环队列可以解决“假溢出”,但无法通过等式Q.front=Q.rear判断队列空间是“空”还是“满”。
两种处理方法:
(1)设一个标志位区别队列“空”还是“满”
(2)少用一个存储空间,约定队头指针在队尾指针的下一位置上作为队列“满”的标志。

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

cai-4

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值