[大话数据结构2]栈与队列

栈和队列

栈是一种仅允许在表尾进行插入和删除操作的线性表;
队列是只允许在一端进行插入操作、在另一端进行删除操作的线性表;

一、栈

(一)栈的定义

  • 栈是一种重要的线性结构,可以这样讲,栈是前面讲过的线性表(顺序表、链表)的一种具体的特殊的形式
  • 栈(Stack)是一个后进先出(Last in first out,LIFO)的线性表,它要求只在表尾进行删除和插入操作
  • 对于栈来说,这个表尾称为栈的栈顶(top),相应的表头称为栈底(bottom)。

(二)栈的存储结构

栈本质上是一个线性表,因此栈分为栈的顺序存储结构和栈的链式存储结构;但是一般都是使用栈的顺序存储结构;
使用数组表示的话:下标为0的一端作为栈底比较好;
空栈的判定条件:top = -1;

在这里插入图片描述

  • 顺序存储结构的定义方案一:
// ElemType 元素类型可以通过Typedef进行定义
typedef int ElemType;
typedef  struct
{
  ElemType  *base;  // 栈底指针变量
  ElemType  *top;  // 栈顶指针变量
  int  stackSize;  // 栈当前可以使用的最大容量
}sqStack;

对应的结构图:

在这里插入图片描述

  • 声明方式二:
typedef  int  ElemType;

typedef  struct
{
  ElemType data[MAXSIZE];
  int top; // 用于标注栈顶的位置
  int  stackSize;
}

对应的结构图: 这里的top先+1,然后在对应位置赋值
在这里插入图片描述

栈的基本操作

  • 创建一个栈
#define STACK_INIT_SIZE = 100

initStack(sqStack *s)
{
  s->base = (ElemType  *)malloc( STACK_INIT_SIZE * sizeof(ElemType) );

  if( !s->base ){
    exit(0);
  }
s->top = s->base; // 最开始,栈顶就是栈底
s->stackSize = STACK_INIT_SIZE;
}
  • 入栈(压栈)
    栈的插入操作(Push),叫做进栈,也称为压栈,入栈。类似子弹放入弹夹的动作。
    首先压栈必须在栈顶进行,每次向栈中压入一个数据,然后栈顶的top指针+1,直到栈满为止;

方案一:

//插入新元素e为新的栈顶元素;

Status Push(SqStack *S, SElemType e){
  if(s->top == MAXSIZE -1){
    return ERROR;
    }

  S->top++;
  S->data[S->top] = e;    //将新插入的元素赋值给栈顶空间
  return  OK;
}

方案二:

#define SATCKINCREMENT 10

Push(sqStack *s, ElemType e)
{
    // 如果栈满,追加空间
    if( s->top – s->base >= s->stackSize )
    {
        s->base = (ElemType *)realloc(s->base, (s->stackSize + STACKINCREMENT) * sizeof(ElemType));
        if( !s->base )
            exit(0);

        s->top = s->base + s->stackSize;     // 设置栈顶
        s->stackSize = s->stackSize + STACKINCREMENT; // 设置栈的最大容量
    }

    *(s->top) = e;
    s->top++;
}
  • 出栈

栈的删除操作(Pop),叫做出栈,也称为弹栈。如同弹夹中的子弹出夹。
从栈顶取出数据,然后将栈顶的指针-1,同时栈当前的容量-1;

Pop(sqStack  *s, ElemType  *e)
{

  if( s->top == s->base ) // 栈已空空是也
    return;
    
  *e = *--(s->top);  //栈的top值先--然后指针

}

上面代码等价于:

Status Pop(SqStack *S, SElemType *e){
  if(S->top == -1){
    return ERROR;
  }
  *e = S->data[S->top];   //将要删除的元素赋值给e;
  S->top--;
  return OK;
}
  • 题目:利用栈的特点,将用户输入的二进制数转换为十进制数。

  • 清空一个栈

•就是将栈中的元素全部作废,但栈本身物理空间并不发生改变(不是销毁)。

因此我们只要将s->top的内容赋值为s->base即可,这样s->base等于s->top,也就表明这个栈是空的了。

ClearStack(sqStack  *s){

s->top = s->base;

}
  • 销毁一个栈
    相当于释放掉该栈所占据的物理内存空间;
DestroyStack(sqStack  *s){
  int  i, len;  
  len = s->stackSize;
  
  for( i=0; i < len; i++ ){  
    free( s->base );    
    s->base++;
  }
  
  s->base = s->top = NULL;  
  s->stackSize = 0;
}
  • 计算栈当前的容量

即是计算栈中元素的个数,只要返回s.top - s.base即可;
注意与栈的最大容量相区分,栈的最大容量是指该栈占据内存空间的大小,大小是s.stackSize

int  StackLen(sqStack s)
{
  return(s.top – s.base); // 初学者需要重点讲解
  // 指针之间的相减即是地址的相减,得到的是指针指向的数据类型的数据量,就是个数,前提是两个指针指向的数据类型是一致的
}

4.5 两栈共享空间

当两个栈的空间需求是相反的关系时候,例如股票操作,并且只针对两个具有相同数据类型的栈的设计;

在这里插入图片描述

一个栈的栈底为数组下标为0处,另一个栈的栈底为数组下标为n-1处,栈满的条件为栈顶指针top1 + 1 == top2

  • 两栈共享空间的结构的代码如下:
typedef struct{
  SElemType data[MAXSIZE];
  int top1; //栈1的栈顶指针;
  int top2;
  }SqDoubleStack;                                                                                        
  • 插入新元素e:
Status Push(SqDoubleStack *S, SElemType e, int stackNumber){
//stackNumber  =1 or 2 表示对栈1or2进行插入操作;
  if(S->top1 +1 == S->top2){
    return ERROR;
    }
  if(stackNumber == 1){
    S->top1++;
    S->data[S->top1] = e;
    }

  if(stackNumber == 2){
    S->top2--;
    S->data[S->top2] = e;
    }

  return OK;
}

4.6 栈的链式存储结构及实现

如果栈的使用过程中元素变换不可预料,有时很小,有时非常大,那么最好是用链栈;

4.6.1 栈的链式存储结构

将栈顶放在单链表的头部,同时合并掉头结点(即不在需要头结点)
基本不存在栈满的情况,除非内存没有空间;同时空链栈为:top = NULL

在这里插入图片描述

typedef struct StackNode{
  SElemType data;
  struct StackNode *next;
}StackNode, *LinkStackPtr;

typedef struct LinkStack{
  LinkStackPtr top;
  int count;
}LinkStack;

4.6.2 进栈操作

在这里插入图片描述

//插入元素e为新的栈顶元素

Status Push(LinkStack *S, SElemType e){
  LinkStackPtr s = (LinkStackPtr)malloc(sizeof(StackNode));
  s->data = e;
  s->next = S->top;
  S->top = 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;
  S->top = S->top->next;
  free(p);
  S->count--;
  return OK;
}

4.8 栈的应用-递归

递归含义:
直接调用自己或通过一系列的调用语句间接地调用自己的函数,称做递归函数。
每个递归定义必须至少有一个条件,满足时递归不再进行,即不再引用自身而是返回值退出;

迭代和递归的区别:
迭代使用的是循环结构,递归使用的是选择结构;迭代则不需要反复调用函数和占用额外的内存;递归使程序根据简洁,但是大量的递归调用会耗费大量的时间和内存;

递归与栈的关系说明

4.8.1 斐波那契数列实现

【1,1,2,3,5,8,13,21,34,55,89,144】
一般用法:迭代

int main(){
  int i;
  int a[40];
  a[0] = 0;
  a[1] = 1;
  printf("%d",a[0]);
  printf("%d",a[1]);
  for(i = 2;i < 40;i++){
    a[i] = a[i-1] + a[i-2];
    printf("%d",a[i]);
  }
  return 0;
}

使用递归函数:

//斐波那契数列的递归函数
int Fbi(int i){
  if(i < 2){
    return i == 0?0:1;
  }
  return Fbi(i-1)+Fbi(i-2);// 这里Fbi就是函数自己,它在调用自己;
}

int main(){
  int i;
  for(int i = 0; i < 40; i++){
    printf("%d", Fbi(i));
  }
  return 0;
}

4.9栈的应用—四则运算表达式求解

一般处理情况:表达式遇到左括号就进栈,遇到右括号就让栈顶的左括号出栈;

4.9.1 后缀表示法的定义:

该表示法是不需要括号的后缀表达法:例如:9+(3-1)*3 + 10/2使用后缀表示法:9 3 1 - 3 * + 10 2 / +

  • 由中缀表达式(标准的四则运算表达式)生成后缀表达式:
    规则:从左到右遍历中缀表达式的每个数字和符号,若是数字就输出,即成为后缀表达式的一部分;若是符号,则判断其与栈顶符号的优先级,是右括号或者优先级不高于栈顶符号(乘除优先于加减)则栈顶元素依次出栈并且输出,并将当前符号进栈,一直到最终输出后缀表达式为止。

  • 后缀表达式计算结果: P106 待补充

4.10队列的定义

队列是只允许在一端进行插入操作,而在另一端进行删除操作的线性表;
先进先出,允许插入的一端称为队尾,允许删除的一端称为队头;
在这里插入图片描述

因此入队列的时间复杂度为O(1),出队列的时间复杂度为O(n);

循环队列

在确定队列长度最大值的情况下,建议使用循环队列,无法估计队列的长度的时候,建议使用链队列;
首先为了解决假溢出的问题,也就是队列的开始不再以0为开始,而是通过头尾指针确认队列的开始和结束为止,这样会造成后面队列满了,而前面还空着的情况;

使用循环列表,也就是rear尾指针可以从最后一个位置满了之后在从第一个位置开始指;

  • 判断队列是否满了:
    因为现在队列空或者满的判断条件均为:head = rear;
    • 法一:加上一个标识变量:flag;
      • 当flag=0且front == rear的时候队列为空;
      • 当flag=1且front == rear的时候队列为满;
    • 法二:队列为满条件改为数组中任有一个空间单元即默认为满;

在这里插入图片描述

计算公式:设队列最大尺寸为:QueueSize
(rear + 1) % QueueSize == front等式成立则为 满;

计算队列长度的公式为:(rear - front + QueueSize)

循环队列的顺序存储结构代码:

typedef int QElemType;
typedef struct{
  QElemType data[MAXSIZE];
  int front; //头指针
  int rear; //尾指针,若队列不空,指向队列尾元素的下一个位置;
}sqQueue;

循环队列的初始化代码:

// 初始化一个空队列Q
Status InitQueue(sqQueue *Q){
  Q->front = 0;  
  Q->rear = 0;
  return OK;
}

循环队列求队列长度代码:

//返回队列的当前长度,返回Q的元素个数
int QueueLength(sqQueue Q){
  return (Q.rear - Q.front + MAXSIZE)%MAXSIZE;
}

循环队列的入队列操作代码:

//若队列未满,则插入元素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指针向后移一个位置,若到最后则转到数组头部; 默认为Q->rear + 1;

return OK;
}

循环队列的出队列操作代码:

//若队列不空,则删除Q中队头元素,用e返回其值;
Status EnQueue(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;
}

4.13 队列的链式存储结构及其实现

本质上是线性表的单链表,只不过是只能尾进头出而已;

在这里插入图片描述
空队列时候相当于:rear 和front都指向头结点;

链队列的结构代码:

typedef int QElemType;

typedef struct QNode{   //结点结构
  QElemType data;
  struct QNode *next;
  }QNode,*QueuePtr;

typedef struct{  //队列的链表结构
  QueuePtr front,rear;  //队头、队尾指针;
}LinkQueue;

入队操作代码:

在这里插入图片描述

//插入元素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设置成队尾结点;
  return OK;
}

出栈操作代码:

在这里插入图片描述

//若队列不空,删除Q的队头元素,用e返回其值,并返回OK,否则返回ERROR;
Status DeQueue(LinkQueue *Qk, 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;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值