当前位置: 首页 > news >正文

数据结构与算法 第三章 栈和队列

3.1 栈的基本概念

栈是只允许在一端进行插入或删除操作的 线性表

栈顶:允许插入和删除的一端

栈底:不允许插入和删除的一端

基本操作

InitStack(&S):初始化栈

DestroyStack(&S):销毁栈

Push(&S,x):x进栈

Pop(&S,&x):栈顶元素出栈,并由x返回

GetTop(S,&x):读栈顶元素,用x返回

StackEmpty(S):判断栈是否空



3.2 栈的顺序存储实现

顺序栈的定义
#define MaxSize 10		// 定义栈中元素的最大个数
typedef struct{ElemType data[MaxSize];		// 静态数组存放栈中元素int top;	// 栈顶指针
}SqStack;void testStack(){SqStack S;	// 声明一个顺序栈(分配空间)
}

在这里插入图片描述

初始化操作
void InitStack(SqStack &S){S.top=-1;	// 初始化栈顶指针
}// 判断栈空
bool StackEmpty(SqStack S){return (S.top==-1);
}
进栈操作
bool Push(SqStack &S,ElemType x){if(S.top==MaxSize-1)	// 栈满,报错return false;S.top=S.top+1;		// 指针先加1S.data[S.top]=x;	// 新元素入栈return true;
}// 第4行与第5行的写法等价于:
S.data[++S.top]=x;
出栈操作
bool Pop(SqStack  &S, ElemType &x){if (S.top==-1)	// 栈空,报错return false;x=S.data[S.top];	// 栈顶元素先出栈S.top=S.top-1;	// 指针再减1return true;
}// 第4行与第5行的写法等价于:
x=S.data[S.top--];

出栈时,栈顶指针只是减了1,所以数据还残留在内存中,但是逻辑上被删除了

读栈顶操作
bool GetTop(SqSatck S, ElemType &x){if (S.top==-1)return false;x=S.data[S.top];return true;
}

初始化时,也可以将栈顶指针设为0,此时相关操作会有细微差别,但是逻辑上的实现都一样。

共享栈

就是两个栈共享同一片存储空间,一个指针从上开始指,另一个栈指针从下开始指

在这里插入图片描述

#define MaxSize 10	
typedef struct{ElemType data[MaxSize];	int top0;	// 0号栈栈顶指针int top1;	// 1号栈栈顶指针
}ShStack;// 初始化栈
void InitStack(ShStack &S){S.top1=-1;S.top2=MaxSize;
}

栈满的条件:top0+1==top1



3.3 栈的链式存储实现

typedef struct Linknode{ElemType data;	// 数据域struct Linknode *next;	// 指针域
} *LiStack;		// 栈类型定义


3.4 队列的基本概念

队列:只允许在一端进行插入,在另一端删除的线性表

队头:允许删除的一端

队尾:允许插入的一端

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

基本操作

InitQueue(&Q):初始化队列

DestroyQueue(&Q):销毁队列

EnQueue(&Q,x):入队

DeQueue(&Q,x):出队

GetHead(Q, &x):读队头元素

QueueEmpty(Q):判队列空



3.5 队列的顺序实现

初始化操作

这里我们将front指向队头元素,rear指向队尾元素的后一个位置。

void InitQueue(SeQueue &Q){Q.rear=Q.front=0;
}
入队操作
bool EnQueue(SqQueue &Q,ELemType x){if(队列已满)return false;Q.data[Q.rear]=x;Q.rear=(Q.rear+1)%MaxSize;	return true;
}

通过取模运算,将存储空间在逻辑上变成了环状

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

队列中的元素个数=(rear+MaxSize-front)%MaxSize

判断队列空/满的三种方案

1
#define MaxSize 10	// 定义队列中元素的最大个数
typedef struct{ElemType data[MaxSize];		// 用静态数组存放队列元素int front,rear;		// 队头指针和队尾指针
}SqQueue;void InitQueue(SeQueue &Q){Q.rear=Q.front=0;
}

初始化时头指针和尾指针都指向0,说明队空时头尾指针指向同一个

由于rear指针总是指向队尾元素的最后一个位置,说明队满时,rear再向后走一步就是front

bool QueueEmpty(SqQueue Q){return (Q.rear==Q.front);
}bool QueueFull(SqQueue Q){return ((Q.rear+1)%MaxSize==Q.front)
}

在这里插入图片描述

在这里插入图片描述

下面两种方案针对不浪费队满时rear指向的那片存储空间

2

使用size变量来记录队列大小

#define MaxSize 10	// 定义队列中元素的最大个数
typedef struct{ElemType data[MaxSize];		// 用静态数组存放队列元素int front,rear;		// 队头指针和队尾指针int size;
}SqQueue;
bool QueueEmpty(SqQueue Q){return (Q.size==0);
}bool QueueFull(SqQueue Q){return (Q.size==MaxSize)
}
3

使用tag变量标记最近执行的操作是删除/插入

每次删除操作成功时,都令tag=0(只有删除操作,才可能导致队空)

每次插入操作成功时,都令tag=1(只有插入操作,才有可能导致队满)

#define MaxSize 10	// 定义队列中元素的最大个数
typedef struct{ElemType data[MaxSize];		// 用静态数组存放队列元素int front,rear;		// 队头指针和队尾指针int tag;	// 最近进行的是删除/插入
}SqQueue;void InitQueue(SeQueue &Q){Q.rear=Q.front=0;Q.tag=0;
}
bool QueueEmpty(SqQueue Q){return (Q.front==Q.rear&&Q.tag==0);
}bool QueueFull(SqQueue Q){return (Q.front==Q.rear&&Q.tag==1)
}


3.5 队列的链式实现

typedef struct LinkNode{	// 链式队列结点ElemType data;struct LinkNode *next;
}LinkNode;typedef struct{		// 链式队列LinkNode *front, *rear;		// 队列的队头和队尾指针
}LinkQueue;
初始化(带头结点)
void InitQueue(LinkQueue &Q){// 初始时front、rear都指向头结点Q.front=Q.rear=(LinkNode*)malloc(sizeof(LinkNode));Q.front->next=NULL;
}
// 判断队列是否为空
bool IsEmpty(LinkQueue Q){return (Q.front==Q.rear);// return Q.front.next==NULL;// return Q.rear.next==NULL;
}
初始化(不带头结点)
void InitQueue(LinkQueue &Q){// 初始时front、rear都指向NULLQ.front=NULL;Q.rear=NULL;
}
// 判断队列是否为空
bool IsEmpty(LinkQueue Q){return (Q.front==NULL);
}
入队(带头结点)
void EnQueue(LinkQueue &Q, ElemType x){LinkNode *s=(LinkNode *)malloc(sizeof(LinkNode));s->data=x;s->next=NULL;Q.rear->next=s;		// 新结点插入到rear之后Q.rear=s;		// 修改表尾指针
}
入队(不带头结点)
void EnQueue(LinkQueue &L,ElemType 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;Q.rear=s;}
}
出队(带头结点)
bool DeQueue(LinkQueue &Q,ElemType &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(p);return true;
}
出队(不带头结点)
bool DeQueue(LinkQueue &Q,ElemType &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(p);return true;
}


3.6 双端队列

双端队列:只允许从两段插入、两端删除的线性表。

若只使用其中一端的插入、删除操作,则效果等同于栈

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



3.7 栈在括号匹配中的应用

用栈实现括号匹配:

依次扫描所有字符,遇到左括号入栈,遇到右括号则弹出栈顶元素,检查是否匹配

匹配失败情况:

  1. 左括号单身
  2. 右括号单身
  3. 左右括号不匹配

在这里插入图片描述

bool bracketCheck(char str[], int length){SqStack S;InitStack(S);	// 初始化一个栈for (int i=0;i<length;i++){if(str[i]=='(' || str[i]=='[' || str[i]=='{'){Push(S,str[i]);		// 扫描到左括号,入栈}else{if(StackEmpty(S))	// 扫描到右括号,且当前栈空return false;	// 匹配失败char topElem;Pop(S,topElem);		// 栈顶元素出栈if(str[i]==')' && topElem!='(')return false;if(str[i]==']' && topElem!='[')return false;if(str[i]=='}' && topElem!='{')return false;}}return StackEmpty(S);	// 检索完全部括号后,栈空说明匹配成功
}


3.8 栈在表达式求值中的应用

前缀表达式:运算符在两个操作数中间

后缀表达式:运算符在两个操作数后面

前缀表达式:运算符在两个操作数前面

中缀表达式转后缀表达式(手算)
  1. 确定中缀表达式中各个运算符的运算顺序
  2. 选择下一个运算符,按照[左操作数,右操作数,运算符]的方式组合成一个新的操作数
  3. 如果还有运算符没被处理,继续2

注意: 为了使结果唯一,采取左优先原则

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

用栈实现

  1. 左往右 扫描下一个元素,直到处理完所有元素
  2. 若扫描到操作数则压入栈,并回到1,否则执行3
  3. 若扫描到运算符,则弹出两个栈顶元素,执行相应运算,运算结果压回栈顶,回到1

注意: 先弹出的为右操作数,后一个弹出的为左操作数

最终栈存储的最后一个元素则是最终结果

中缀表达式转前缀表达式(手算)
  1. 确定中缀表达式中各个运算符的运算顺序
  2. 选择下一个运算符,按照[运算符,左操作数,右操作数]的方式组合成一个新的操作数
  3. 如果还有运算符没被处理,继续2

注意: 为了使结果唯一,采取右优先原则

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

用栈实现

  1. 右往左 扫描下一个元素,直到处理完所有元素
  2. 若扫描到操作数则压入栈,并回到1,否则执行3
  3. 若扫描到运算符,则弹出两个栈顶元素,执行相应运算,运算结果压回栈顶,回到1

注意: 先弹出的为左操作数,后一个弹出的为右操作数

最终栈存储的最后一个元素则是最终结果

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

初始化一个栈,用于保存暂时还不能确定运算顺序的运算符

从左到右处理各个元素,直到末尾,可能遇到三种情况:

  1. 遇到操作数。直接假如后缀表达式
  2. 遇到界限符。遇到"("直接入栈;遇到")"则依次弹出栈内运算符并加入后缀表达式,直到弹出"("为止。
  3. 遇到运算符。依次弹出栈中优先级高于或等于当前运算符的所有运算符,并加入后缀表达式,若碰到"("或栈空则停止。之后再把当前运算符入栈
中缀表达式的计算(用栈实现)

初始化一个栈,操作数栈和运算符栈

若扫描到操作数,压入操作数栈

若扫描到运算符或界限符,则按照”中缀转后缀“相同的逻辑压入运算符栈(期间也会弹出运算符,每当弹出一个个运算符时,就需要再弹出两个操作数栈的栈顶元素并执行相应计算,运算结果再压回操作数栈)



3.9 栈在递归中的应用

在这里插入图片描述

上面层层调用的函数有个特点:最后被调用的函数最先执行结束,符合栈这种结构

所以,函数调用时,需要用一个栈存储:

  1. 调用返回地址
  2. 实参
  3. 局部变量

在这里插入图片描述

函数调用背后的过程

在这里插入图片描述

栈在递归中的应用

适合用递归算法解决:可以把原始问题转换为属性相同,但规模较小的问题

在这里插入图片描述

在这里插入图片描述

缺点:效率低,可能包含很多重复计算



3.10 特殊矩阵的压缩存储

一维数组的存储结构

在这里插入图片描述

数组a[i]的存放地址 = LOC + i * sizeof(ElemType) (0<=i<10)

二维数组的存储结构

在这里插入图片描述

M行N列的二维数组b[M][N]中,

若按行优先存储,则b[i][j]的存储地址=LOC+(i*N+j)*sizeof(ElemType)

若按列优先存储,则b[i][j]的存储地址=LOC+(j*N+i)*sizeof(ElemType)

对称矩阵的压缩存储

若n阶方阵中任意一个元素ai,j都有ai,j=aj,i,则该矩阵为对称矩阵

在这里插入图片描述

压缩存储策略:只存储主对角线+下三角区(或主对角线+上三角区)

e.g:存储主对角线+下三角区,按行优先原则将各元素存储一维数组中:

在这里插入图片描述

三角矩阵的压缩存储

在这里插入图片描述

下三角矩阵压缩存储策略:按行优先原则将橙色区元素存储一维数组,并在最后一个位置存储常量c。

在这里插入图片描述

上三角矩阵存储策略:按行优先原则将绿色区元素存储一维数组中,并在最后一个位置存储常量c

在这里插入图片描述

三对角矩阵的压缩存储

在这里插入图片描述

三对角矩阵,又称带状矩阵:当|i-j|>1时,有ai,j=0(1<=i,j<=n)

压缩存储策略:按行优先(或列优先)原则,只存储带状部分

在这里插入图片描述

稀疏矩阵的压缩存储

在这里插入图片描述

在这里插入图片描述

http://www.dtcms.com/a/264944.html

相关文章:

  • 第一章 快速入门
  • DPI深度检索原理和架构
  • 人脸活体识别3:C/C++实现人脸眨眼 张嘴 点头 摇头识别(可实时检测)
  • 创客匠人解构知识付费爆单密码:产品力打造与 IP 变现的深度耦合
  • Kafka高级特性深度解析:构建企业级流处理平台的核心技术
  • IP地理定位技术综述:理论、方法与应用创新(三)
  • pdf 合并 python实现(已解决)
  • Qt Quick 与 QML(五)qml中的布局
  • 基于图神经网络的ALS候选药物预测模型设计与实现
  • Point Transformer V3(PTv3)
  • AI:什么是Agent
  • mysql查看数据库
  • 自主/智能的本质内涵及其相互关系
  • QT6 源(145)模型视图架构里的表格视图 QTableView 篇一:先学习属性,再 public 权限的成员函数
  • 胡兵全新时尚生活频道上线,开启多维生活美学新篇
  • 胡兵创立时尚生活频道《HUBING SELECTS胡兵智选》担任主编深耕智选生活
  • Ragflow 前后端登录逻辑
  • 存储过程在现代编程中的作用与演变:衰退与重塑
  • 网络编程学习路线
  • MySQL使用C语言连接
  • 全球双G品牌LOGO深度解码:从经典交织到科技赋能的符号革命
  • 大语言模型(LLM)专业术语汇总
  • 公用LCU屏的功能、应用场景
  • 【Java面试】Redis的poll函数epoll函数区别?
  • 优雅草蜻蜓T语音会议系统私有化部署方案与RTC技术深度解析-优雅草卓伊凡|clam
  • 【数据结构与算法】哈希表拾遗
  • npm install安装的node_modules是什么
  • 开源计算机视觉的基石:OpenCV 全方位解析
  • RabbitMQ 高级特性之消息确认
  • 【Java面试】讲讲Redis的Cluster的分片机制