数据结构 | 栈与队列和数组

本文详细介绍了栈和队列这两种数据结构的基础概念、基本操作及其在程序设计中的应用。包括栈的顺序存储和链式存储实现,队列的顺序存储和链式存储实现,以及它们在括号匹配、表达式求值和递归中的应用。同时探讨了共享栈和双端队列的概念。
摘要由CSDN通过智能技术生成

文章目录

栈的基本概念

在这里插入图片描述

栈的定义

在这里插入图片描述
在这里插入图片描述

栈的基本操作

在这里插入图片描述

栈的基本操作代码实现

//栈的基本操作

//初始化一个栈
InitStack(&S)

//销毁栈
DestroyStack(&S)

//进栈
Push(&S,x)

//出栈
Pop(&S,&x)

//读取栈顶元素
GetTop(S,&x)

//判断栈是否为空
StackEmpty(S)

栈的常考题型

在这里插入图片描述
在这里插入图片描述

栈的顺序存储实现

在这里插入图片描述

顺序栈的定义

在这里插入图片描述

初始化操作

在这里插入图片描述

进栈操作

在这里插入图片描述
在这里插入图片描述

出栈操作

在这里插入图片描述

读栈顶元素操作

在这里插入图片描述

另一种方式

在这里插入图片描述
在这里插入图片描述

共享栈

在这里插入图片描述
在这里插入图片描述

顺序栈存储实现代码实现

#include<stdio.h>

//顺序栈的定义
#define MaxSize 10 //定义栈中元素的最大个数
typedef struct{
  int 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;
  }
}

//新元素入栈
bool Push(SqStack &S,int x){
  if(S.top == MaxSize-1){ //栈满,报错
    return false;
  }
  S.data[++S.top] = x; //指针加1,新元素入栈
  return true;
}

//出栈操作
bool Pop(SqStack &S,int &x){
  if(S.top == -1){ //栈空,报错
    return false;
  }
  x = S.data[S.top--]; //栈顶元素出栈,指针减1
  return true;
}

//读取栈顶元素
bool GetTop(SqStack S,int &x){
  if(S.top == -1){ //栈空,报错
    return false;
  }
  x = S.data[S.top]; //x记录栈顶元素
  return true;
}

//共享栈的定义
#define MaxSize 10 //定义栈中元素的最大个数
typedef struct{
  int data[MaxSize]; //静态数组存放栈中元素
  int top0; //0号栈栈顶指针
  int top1; //1号栈栈顶指针
}ShStack;

//共享栈的初始化操作
void InitStack(ShStack &S){
  S.top0 = -1; //初始化0号栈栈顶指针
  S.top1 = MaxSize; //初始化1号栈栈顶指针
}

//共享栈判断栈满
bool FullStack(ShStack S){
  return S.top0 + 1 == S.top1;
}

栈的链式存储实现

在这里插入图片描述

链栈的定义

在这里插入图片描述

链栈的基本操作

  • 用链式存储实现的栈它的本质上也是一个单链表
  • 只不过我们规定我们只能在头结点这一端进行插入和删除操作
  • 也就是把链头的这一段看成是我们栈顶的这一端
  • 所以链栈的定义和单链表的定义几乎没有区别,只是把名字稍微改了下而已
    在这里插入图片描述
    在这里插入图片描述

在这里插入图片描述

链栈的代码实现

#include<stdio.h>

//链栈的定义
typedef struct LinkNode{
  int data; //数据域
  struct LinkNode *next; //指针域
}*LinkStack; //栈类型定义

//链栈的初始化
bool InitStack(LinkStack *L){
  L->next = NULL;
  return true;
}

//链栈进栈操作
bool Push(LinkStack &L,int e){
  if(L == NULL){
    return false;
  }
  LinkStack *s = (LinkStack *)malloc(sizeof(LinkStack));
  if(s == NULL){
    return false;
  }
  s->data = e;
  s->next = L->next;
  L = s;
  return true;
}

//链栈的出栈操作
bool Pop(LinkStack &L,int &e){
  if(L == NULL){
    return false;
  }
  LinkStack *q = L;
  e = q->data;
  L = q->next;
  free(q);
  return true;
}

//链栈获取栈顶元素
bool GetTop(LinkStack L,int &x){
  if(L == NULL){
    return false;
  }
  x = L->data;
  return true;
}

//链栈如何判空/判满
bool IsEmpty(LinkStack L){
  if(L == NULL){
    return true;
  }else{
    return false;
  }
}

队列的基本概念

在这里插入图片描述

队列的定义

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

队列的基本操作

在这里插入图片描述

//队列的基本操作

//初始化队列
InitQueue(&Q)

//销毁队列
DestroyQueue(&Q)

//入队
EnQueue(&Q,x)

//出队
DeQueue(&Q,&x)

//获取队头元素
GetHead(&Q,&x)

//判断队列是否为空
IsQueueEmpty(Q)

在这里插入图片描述

队列的顺序实现

在这里插入图片描述

定义

在这里插入图片描述

初始化操作

在这里插入图片描述

入队操作

在这里插入图片描述

循环队列

在这里插入图片描述

  • rear指针指向下一个可以插入数据的位置的循环队列中队列满的条件为什么会空一块出来?
  • 我们是通过front指针和rear指针是否指向同一个位置来判断队列是否为空,如果不留一个空则无法判断队列此时是满还是为空,只能牺牲掉一个数据单元
入队操作

在这里插入图片描述

出队操作

在这里插入图片描述

方案一:判断队列已满/已空(牺牲一片存储空间)

在这里插入图片描述

方案二:判断队列已满/已空(size)

在这里插入图片描述

方案三:判断队列已满/已空(tag)

  • tag == 0 表示最近执行的是一次删除操作
  • tag == 1 表示最近执行的是一次插入操作
    在这里插入图片描述

其他出题方法

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

队列顺序实现代码实现

#include<stdio.h>

//顺序链表的定义
#define MaxSize 10 //定义队列中元素的最大个数
typedef struct{
  int data[MaxSize]; //用静态数组存放队列元素
  int front,rear; //队头指针和队尾指针
}SqQueue;

//顺序队列的初始化操作
void InitQueue(SqQueue &Q){
  //初始化时,队头和队尾指针指向0
  Q.front = 0;
  Q.rear = 0;
}

//入队操作
bool EnQueue(SqQueue &Q,int x){
  if((Q.rear + 1) % MaxSize == Q.front){ //队列满则报错
    return false;
  }
  Q.data[Q.rear] = x;
  Q.rear = (Q.rear + 1) % MaxSize;
  return true;
}

//出队操作
bool DeQueue(SqQueue &Q,int &x){
  if(Q.rear == Q.front){ //队空则报错
    return false;
  }
  x = Q.data[Q.front];
  Q.front = (Q.front + 1) % MaxSize;
  return true;
}

//获取队头元素的值并用x值返回
bool GetHead(SqQueue &Q,int &x){
  if(Q.rear == Q.front){
    return false;
  }
  x = Q.data[Q.front];
  return true;
}

//判断队列是否为空
bool IsQueueEmpty(SqQueue Q){
  if(Q.rear == Q.front){
    return true;
  }else{
    return false;
  }
}

队列的链式实现

在这里插入图片描述

定义

在这里插入图片描述

初始化(带头结点)

在这里插入图片描述

初始化(不带头结点)

在这里插入图片描述

入队(带头结点)

在这里插入图片描述

入队(不带头结点)

在这里插入图片描述

出队(带头结点)

在这里插入图片描述

出队(不带头结点)

在这里插入图片描述

队列满的条件

在这里插入图片描述

在这里插入图片描述

队列链式实现代码实现

#include<stdio.h>

//链队列的定义
typedef struct LinkNode{
  int data;
  struct LinkNode *next;
}LinkNode;

typedef struct{
  LinkNode *front,*rear;
}LinkQueue;

//初始化(带头结点)
void InitQueue(LinkQueue &Q){
  //初始化时,front和rear指针都指向头结点
  LinkQueue *s = (LinkNode *)malloc(sizeof(LinkNode));
  Q.front = s;
  Q.rear = s;
}

//判断队列是否为空(带头结点)
bool IsQueueEmpty(LinkQueue Q){
  if(Q.front == Q.rear){
    return true;
  }else{
    return false;
  }
}

//初始化(不带头结点)
void InitQueue(LinkQueue &Q){
  Q.front = NULL;
  Q.rear = NULL;
}

//判断队列是否为空(不带头结点)
bool IsQueueEmpty(LinkQueue Q){
  if(Q.front == NULL){
    return true;
  }else{
    return false;
  }
}

//入队(带头结点)
void EnQueue(LinkQueue &Q,int x){
  LinkNode *s = (LinkNode *)malloc(sizeof(LinkNode));
  s->data = x;
  s->next = NULL;
  Q.rear->next = s; //新结点插入到rear之后
  Q.rear = s; //修改表尾指针
}

//入队(不带头结点)
void EnQueue(LinkQueue &Q,int x){
  LinkNode *s = (LinkNode *)malloc(sizeof(LinkNode));
  s->data = x;
  s->next = NULL;
  if(Q.front == NULL){
    Q.front = s;
    Q.rear = s;
  }else{
    Q.rear->next = s; //新结点插入到rear之后
    Q.rear = s; //修改表尾指针
  }
}

//出队(带头结点)
bool DeQueue(LinkQueue &Q,int &x){
  if(Q.front == Q.rear){
    return false;
  }
  LinkNode *p = Q.front->next;
  x = p->data;
  Q.front->next = p->next;
  if(Q.rear == p){
    Q.rear == Q.front;
  }
  free(q);
  return false;
}

//入队(不带头结点)
bool DeQueue(LinkQueue &Q,int &x){
  if(Q.front == NULL){
    return false;
  }
  LinkNode *p = Q.front;
  x = p->data;
  Q.front = p->next;
  if(Q.rear == p){
    Q.front == NULL;
    Q.rear == NULL;
  }
  free(q);
  return false;
}

双端队列

定义

在这里插入图片描述
在这里插入图片描述

考点:判断输出序列合法性

在这里插入图片描述

输入受限的双端队列
  • 栈中合法的序列,在双端队列中也一定合法
  • 由于只能在一端进行输入,所以在序号较大的元素出队之前,其他的序号较小的元素已经可以确定它们在队列里面的相对位置
  • 绿色的和有下划线的都是合法的,红色的无下划线的是非法的
    在这里插入图片描述
输出受限的双端队列
  • 栈中合法的序列,双端队列中也一定合法
  • 我们在对这些序列进行验证的时候,很重要的一点就是如果你在输出序列当中看到了某一个序号的元素,那么在这个元素输出之前意味着它之前的所有元素肯定都已经输入到这个队列里面了
    在这里插入图片描述
    在这里插入图片描述

栈在括号匹配中的应用

括号匹配问题

在这里插入图片描述
在这里插入图片描述

算法演示

  • 遇到左括号就入栈

  • 遇到右括号,就“消耗”一个左括号

  • case_1:

在这里插入图片描述

  1. 前面3个括号都是左括号,分别压入栈中
  2. 遇到了一个右括号③,栈顶弹出一个左括号③和其匹配
  3. 遇到了一个右括号②,栈顶弹出一个左括号②和其匹配

在这里插入图片描述

  1. 遇到了一个左括号④,压入栈中
  2. 遇到了一个右括号④,栈顶弹出一个左括号④和其匹配
  3. 遇到了一个右括号①,栈顶弹出一个左括号①和其匹配
    在这里插入图片描述
  • case_2:
  1. 前面都是左括号,分别压入栈中
    在这里插入图片描述
  2. 遇到了一个右括号③,栈顶弹出一个左括号③和其匹配
  3. 遇到了一个右括号②,栈顶弹出一个左括号②和其匹配
    在这里插入图片描述
  4. ②括号匹配失败,本次括号匹配结束
    在这里插入图片描述
  • case_3:
    在这里插入图片描述
  • case_4:
    在这里插入图片描述

算法实现详细过程

在这里插入图片描述

算法的代码实现

在这里插入图片描述
在这里插入图片描述

栈在表达式求值中的应用

在这里插入图片描述

大家熟悉的算术表达式

在这里插入图片描述

波兰数学家的灵感

在这里插入图片描述

中缀、后缀、前缀表达式

在这里插入图片描述

中缀表达式转后缀表达式(手算)

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

后缀表达式的计算(手算)

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

后缀表达式的计算(机算)

在这里插入图片描述

  • 扫描到A、B两个操作数,直接压入栈中
    在这里插入图片描述
  • 扫描到的是+操作符,依次弹出两次栈顶元素B、A,执行A+B(注意这里先弹出的是后操作数,后弹出的是前操作数)
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • 类似的,这种算法思想不仅仅可以求值,而且还可以实现后缀表达式转化为带括号的中缀表达式

中缀表达式转前缀表达式(手算)

在这里插入图片描述
在这里插入图片描述

前缀表达式的计算

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

中缀表达式转后缀表达式(机算)

在这里插入图片描述

不带界限符(括号)的例子
  • 依次扫描,遇到操作数A直接加入后缀表达式
    在这里插入图片描述

  • 往下扫描,遇到操作符+,由于此时栈内为空,直接把操作符压入栈内
    在这里插入图片描述

  • 往下扫描,遇到操作数B直接加入后缀表达式
    在这里插入图片描述

  • 往下扫描,遇到操作符-,由于此时-的优先级和+是同一个优先级的,所以我们要先把栈里面的+弹出栈(这里这样操作的依据是在+后面扫面到了一个-。这就说明+-中间肯定是加进去了一个操作数,扫到-就说明中间加入的这个数肯定要进行加法运算也要进行减法运算,而由于这两个运算的优先级是相等的,根据上面“左优先”的原则,我们要先执行左边的运算符所代表的运算,后缀表达式中各运算符出现的先后顺序就是它们生效的顺序)
    在这里插入图片描述

  • 然后把A+B看作一个整体继续往后扫描,C是一个操作数可以直接输出
    在这里插入图片描述

  • 再往后是一个操作符 *,经过检查发现此时栈顶的元素是一个-,这意味着我们扫描到的C这个操作数前面是个减法运算,后面是个乘法运算,根据先乘除后加减,所以就不能先把-给弹出栈,但是我们也不能直接把*直接加入表达式让乘法先运行,因为我们不确定后面会不会出现括号,所以我们这里先将*给压入栈中
    在这里插入图片描述

  • 再往后扫描是一个操作数D,直接加入表达式中
    在这里插入图片描述

  • 往下扫描,扫描到了一个操作符/,意味着这个/左边的操作数D既要进行乘法操作也要进行除法操作,由于除法和乘法它们的优先级是相等的,所以我们可以让左边的乘法先生效,先把这个*给弹出栈,这个时候我们需要把CD*AB+都看成一个整体

  • 此时栈顶元素是-,意味着此时/左边的操作数CD*既要进行减法也要进行除法操作,虽然除法此时的优先级是比减法要高的,但是我们此时不能确定/后面会不会跟着类似于(E+F)这样的小括号,所以我们此时也不能确定此时可不可以让除法先生效,所以此时还是需要把/给压入栈里面
    在这里插入图片描述

  • 继续往后是一个操作数E,可以直接输出
    在这里插入图片描述

  • 再往后是一个操作符+,由于此时的栈顶元素是一个/,意味着+左边的操作数既需要除法也需要加法,没有括号,此时我们就可以确定应该让除法先生效,我们就可以大胆地把优先级更高的/给弹出栈中,此时意味着我们需要把CD*E/看作一个整体
    在这里插入图片描述

  • 此时的栈顶元素是-,意味着+中间的这个操作数既要减法也要加法,根据优先级以及左优先,我们让-弹出栈,加入表达式当中
    在这里插入图片描述

  • 再往后是一个操作数F,直接加入表达式,此时的字符都处理完了,我们把栈里的运算符依次弹出加入表达式
    在这里插入图片描述

带有界限符(括号)的例子

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

  • 接下来扫到的是一个界限符(,如果遇到(我们要把它直接入栈,但是(是不加入后缀表达式的
    在这里插入图片描述
    在这里插入图片描述
  • 此时扫描到的是一个-,按照规则我们需要弹出栈顶所有优先级高于或者等于-的运算符(遇到左括号(或者栈空则停止)
  • 当前栈中(上面没有任何操作符,所以我们直接把-压进栈中
  • 背后的逻辑是扫描到-的时候,-左边的操作数的左边有一个左括号,显然我们需要优先计算括号里面的内容,但是我们不能确定-后面的操作数后面会不会跟有一个乘法或者除法操作,所以我们不能确定这个减法是否可以立即生效,所以需要把-压入栈中
    在这里插入图片描述
    在这里插入图片描述
  • 当我们遇到右括号)的时候,我们需要依次弹出栈内的运算符并加入后缀表达式,知道弹出左括号(为止,但是注意左括号(不能加入后缀表达式
  • 所以我们这里弹出-,加入后缀表达式,再弹出左括号(,舍弃
  • 背后的逻辑是,扫到右括号)的时候我们已经可以知道括号的作用范围了,我们必须优先计算括号内的内容,所以括号里面的所有内容都可以先生效,所以我们可以大胆地弹出括号里面的内容
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
  • 可以利用下面的表达式自己模拟一下过程:
    在这里插入图片描述

中缀表达式的计算(用栈实现)

实现原理

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

具体实现过程

在这里插入图片描述

  • 扫到A,直接放入操作数栈里面
    在这里插入图片描述
  • 扫到+,按照“中缀转后缀”的逻辑,此时栈是空的,直接把+压入栈中
    在这里插入图片描述
  • 扫描到操作数B,把它压进操作数栈里面
    在这里插入图片描述
  • 扫描到一个-,按照“中缀转后缀”的逻辑,我们已经可以确定栈顶的+已经可以生效了,所以我们需要将运算符栈里面的栈顶元素+弹出栈
  • 运算符+弹出后,我们需要在操作数栈里弹出两个栈顶元素AB并执行运算A+B后再压回操作数栈里
  • 再把当前扫描到的-给压入栈里
    在这里插入图片描述
  • 扫描到的是操作数C,直接入栈
    在这里插入图片描述
  • 扫描到的是**的优先级比-高,所以我们不需要把-弹出,直接把*压入运算符栈中
    在这里插入图片描述
  • 接下来遇到一个操作数D,入栈
    在这里插入图片描述
  • 扫描到一个/,根据”中缀转后缀“的逻辑,我们需要把*弹出栈,此时*运算符生效,我们需要在操作数栈中弹出两个栈顶元素CD并进行计算C*D再把结果压回操作数栈
    在这里插入图片描述
  • 扫描到的是操作数E,直接入栈
    在这里插入图片描述
  • 扫描到的是+,根据”中缀转后缀“的逻辑,此时/运算符生效,我们需要在操作数栈中弹出两个栈顶元素EC*D并进行计算(C*D)/E再把结果压回操作数栈
  • 根据”中缀转后缀“的逻辑,此时-运算符生效,我们需要在操作数栈中弹出两个栈顶元素A+B(C*D)/E并进行计算(A+B)-(C*D)/E再把结果压回操作数栈
  • 再把当前扫描到的运算符+压到运算符栈里面
    在这里插入图片描述
  • 之后扫描到的是操作数F,直接把它压入栈里
    在这里插入图片描述
  • 当我们扫描完所有的东西之后,按照”中缀转后缀“的逻辑,我们需要把运算符栈里面的所有运算符都依次地弹出栈
  • 每当弹出一个运算符的时候就需要让一个运算符生效,由于此时运算符栈里面只剩下+,则让+生效,根据”中缀转后缀“的逻辑,我们需要在操作数栈中弹出两个栈顶元素F(A+B)-(C*D)/E并进行计算(A+B)-(C*D)/E+F再把结果压回操作数栈
  • 最后留在操作数栈里面的内容就是我们所求的表达式的值
    在这里插入图片描述
    在这里插入图片描述

栈在递归中的应用

函数调用背后的过程

在这里插入图片描述

栈在递归当中的应用

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

队列的应用

树的层次遍历

  • 在访问一个结点的时候要分别把其左右孩子依次放到队列的队尾
  • 遍历完的结点可以出队并删除
    在这里插入图片描述

图的广度优先遍历

  • 新建一个队列
  • 当我们遍历一个结点的时候,就需要检查和这个结点相邻的其他结点有没有被遍历过,没有被遍历过的要一次放到队列的队尾
  • 访问处理完的结点就可以让其出队
    在这里插入图片描述

队列在操作系统中的应用

在这里插入图片描述
在这里插入图片描述

特殊矩阵的压缩存储

在这里插入图片描述

一维数组的存储结构

在这里插入图片描述

二维数组的存储结构

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

普通矩阵的存储

在这里插入图片描述

对称矩阵的压缩存储

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

三角矩阵的压缩存储

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

三对角矩阵的压缩存储

  • 三对角矩阵,又称为带状矩阵

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

稀疏矩阵的压缩存储

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

ErizJ

觉得好的话给小弟一点鼓励吧

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

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

打赏作者

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

抵扣说明:

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

余额充值