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

139.DS--第三章

栈 队列 数组

迟来原因是因为后续会大跳步的连续总结
近来或许是出现了一些疲倦感 不过这就是人的本性
在此下放慢一下脚步 顶住枯燥 坚持 因为我不止一次经历这种险阻

如标题所示 三个东西搞懂即可
会涉及一些算法的设计题

一.栈

这个在编程中太常见了
image
一端进行插入和删除的线性表

  • 栈顶:进行插入和删除的地方
  • 有空栈的出现

对于那些栈的基本操作我不再阐述 直接在算法设计中看

栈的顺序存储

关于此处后续也是 先概述思想设计 然后伪代码

image
初始栈顶设为-1 然后先指针+1 再放入
这就是push操作

当然入栈需要判断栈是否满了 满了入个蛋 条件S.top==MaxSize-1

bool Push(SqStack &S,ElemType x){if(S.top==MaxSize-1)       //栈满return false;S.data[++S.top]=x; //S.data[S.top++]=x;return true;
}

没错我们商讨的是top指针初始化-1 如果为0 那么就是先放入再+往上

pop 出栈
image
先出去 然后指针往下-1

bool Pop(SqStack &S,ElemType x){if(S.top==-1)       //栈空return false;x=S.data[S.top--];  //x=S.data[--S.top];return true;
}

同样出栈 如果空那就出个屁

还有一个简单的读栈顶元素

bool GetTop(SqStack S,ElemType &x){if(S.top==-1) return false;  //栈空x=S.data[S.top]return true;
}

共享栈
image

栈的链式存储

因为链栈的特性 动态分配存储空间 不存在栈满的问题
同样插入删除会很方便 因为全在头操作

image

我们用一个入栈简单讲一下过程

image

不带头结点的链栈
// 入栈操作
bool Push(Stack *S, ElemType x) {Node *p = (Node *)malloc(sizeof(Node));p->data = x;p->next = S->top;S->top = p;return true;
}
// 出栈操作
bool Pop(Stack *S, ElemType x) {if (S->top == NULL) {  //栈空return false;}Node *p = S->top;x = p->data;S->top = p->next;free(p);return true;
}带头结点的链栈
// 入栈操作
bool Push(Stack *S, ElemType x) {Node *p = (Node *)malloc(sizeof(Node));p->data = x;p->next = S->header->next;S->header->next = p;return true;
}
// 出栈操作
bool Pop(Stack *S, ElemType x) {if (S->header->next == NULL) {  //栈空return false;}Node *p = S->header->next;x = p->data;S->header->next = p->next;free(p);return true;
}

还有一个小知识点:

n个不同元素按固定次序入栈 可能出现的出栈序列总数为 (卡特兰数)
image

来几道题完事 真不难

image

image
通过判断队列的入队就知道了 出栈的序列从而判断栈的容量
后续会介绍队

image


二.队列

image

对应的也是操作受限的线性表 不过一端限制插入 一端限制删除

我们同样研究其它数据结构 顺序和链式 不过多了个循环

队列的顺序存储

image

很简单理解 入队就队尾干进去 然后队尾指针+1往上移 出队就干到队头 然后队头指针+1往上移

同样入队需要判断队满了吗 出队判断队空吗

  • 队满:(Q.rear + 1) % MaxSize == Q.front
  • 队空:Q.front == Q.rear
// 入队操作
bool Enqueue(SeqQueue *Q, ElemType 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(SeqQueue *Q, ElemType x) {if (Q->front == Q->rear) {  //队空return false;}x = Q->data[Q->front];Q->front = (Q->front + 1) % MaxSizereturn true;
}

这里有个问题就是判断队满的条件为何是(Q.rear + 1) % MaxSize == Q.front

如果是 Q.rear == MaxSize 发生上溢情况
image

队列的链式存储

没错仍然不用管队满的情况 因为是用到链表了
front就对应队首指针 rear就对应队尾指针
image

写伪代码之前 需要搞懂一点 就是关于带不带头结点影响什么
主要就是空队的判断

  • 不带头结点很好理解:Q.rear == NULL 或 Q.front==NULL
  • 若带头结点 那入队无所谓有个头结点存在 出队就得看看还有结点吗 Q.front==Q.rear
带头结点
初始化:
Q.front=Q.rear=(LinkNode*)malloc(sizeof(LinkNode));
Q.front->next=NULL;
//入队
void EnQueue(LinkQueue &Q,ElemType x){LinkNode *s=(LinkNode*)malloc(sizeof(LinkNode));s->data=x;s->next=NULL;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;
}不带头结点
初始化:
Q.front = NULL;
Q.rear = NULL;
//入队
void EnQueue(LinkQueue &Q,ElemType x){LinkNode *s=(LinkNode*)malloc(sizeof(LinkNode));s->data=x;s->next=NULL;if(Q.rear == NULL){  //队空Q.front = s;Q.rear = s;}else{Q.rear->next=s;Q.rear=s;}
}
//出队
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.front==NULL)  //队列变为空Q.rear=NULL;free(p);return true;
}
循环队列

没错循环你就想到环了
image

  • 队满:(Q.rear + 1) % MaxSize == Q.front
  • 队空:Q.front == Q.rear
  • 队元素个数(队长度):(Q.rear + MaxSize - Q.front) % MaxSize
  • 入队:Q.rear = (Q.rear + 1) % MaxSize
  • 出队:Q.front = (Q.front + 1) % MaxSize
    注意过程会牺牲一个存储单元 这个知识点后续我们会用一道题目来充分理解
初始化:
Q.rear=Q.front=0;
//入队
bool EnQueue(SqQueue &Q,ElemType 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,ElemType &x){if(Q.rear==Q.front)     //队空return false;x=Q.data[Q.front];Q.front=(Q.front+1)%MaxSize;return true;
}

这里还有个补充 双端队列

类似共享栈 其实就是我认为两个队列干在一起
弥补队列缺陷 两端都可以插入和删除
image

  • 输入受限:一端插入删除 一端仅删除
  • 输出受限:一端插入删除 一端仅插入

一道题目就能搞懂
image

其实按照原理来说我的做法不正确 这样理解成了栈了 但对于做题没有任何问题
比如以A为例:左右两端都能插入 所以无所谓的 只有一端能删除 这个是关键 我们以这个删除端往外出得到选项结果解题 再次申明这种方法并不正确符合

image
以B为例 真的没什么难的这些题目

补充之前的知识 关于牺牲一个存储单元的理解

image
比如上面队列最多可以有n个元素 是逻辑最多有效的 需要牺牲一个存储单元为的是安全缓冲区 为队空队满的情况

看一道综合题目:

(2019)
请设计一个队列,要求满足:① 初始时队列为空;② 入队时,允许增加队列占用空间;③ 出队后,出队元素所占用的空间可重复使用,即整个队列所占用的空间只增不减;④ 入队操作和出队操作的时间复杂度始终保持为 O(1) 。请回答下列问题:(1) 该队列是应选择链式存储结构,还是应选择顺序存储结构?(2) 画出队列的初始状态,并给出判断队空和队满的条件。(3) 画出第一个元素入队后的队列状态。(4) 给出入队操作和出队操作的基本过程。

先讲一下我的思路:1点初始为空没啥 2点增加占用空间 这个是顺序存储无法实现的 链式可以动态分配存储空间 4队头操作出队 队尾操作入队 时间复杂度达到O(1)说明跟循环有关系要一下找到 3没想出来 这就是我的初始想法

然后我们参考答案来看:

  • 1.初始为空很好理解没啥
  • 2.允许增加队列占用空间 链式可动态分配空间可以扩容 还可以重复利用
  • 3.内存回收机制
  • 4.链式队列+空间复用机制

错误点在 链式队列入队和出队时间复杂度就是O(1)了 不用循环

关键在于这个理解 用两条链表 一条链式正常存储队列 一条空闲链式队列

image
出队后不是销毁掉 而是将存储空间回收放到空闲链表中
然后入队先看空闲链表中是否有优先用这个空间 没有在malloc创建新的
image
至于队空条件就是front是否为NULL 队满这个链表不存在这个情况 主要看空闲链表是否满了


三.栈和队列的应用

没错还没完呢

栈:

  • 括号匹配
  • 表达式求值
  • 递归
  • 函数调用

队列:

  • 层次遍历
  • 缓冲区
    我们快速过知识点 然后通过题目来理解

括号匹配
image
很简单理解 和最近的也相当于栈顶一个一个弹 找到匹配的
image

表达式这里涉及到中缀表达式和后缀表达式 前缀表达式可以类比学习
1.中缀表达式 转化成-> 后缀表达式
image
2.通过后缀表达式求值
image

当然有规则了:

中缀->后缀1.操作数直接加入后缀表达式
2.遇到界限符:
( 直接入栈
) 不入栈 依次栈顶弹出运算符加入后缀表达式直到遇到(
3.遇到运算符:
若优先级>栈顶运算符 直接入栈
若栈顶为( 直接入栈
若优先级<=栈顶运算符 依次弹出运算符加入后缀表达式 直到遇到( 或 栈空 或 优先级更低的运算符
后缀表达式求值1.操作数 直接入栈
2.操作符 弹出栈顶两个操作数进行运算 结果入栈(注意后出来的是操作符前面者)

看题理解

image

image
这个问的是转换过程中 也就是栈中可存操作符最大数目 分析过程即可

至于递归和函数调用更为常见

image

image
image

这个之前JVM也深入总结过

image
很好理解就是队列的应用

至于队列在层次遍历应用也就是二叉树的层次遍历 后续会总结
还有计算机系统中的应用:

  • 解决主机和外部设备之间速度不匹配问题
  • 解决多用户引起的资源竞争问题

四.数组和特殊矩阵

数组没啥好介绍的
就简单看一个二维数组的算地址下标

求一个行号列号i j的元素地址
image
h1和h2分别是数组范围内最大的行号和列号

看题:

image
套公式 然后求解就行

特殊矩阵的压缩存储

前面简单介绍一下这些东西 然后其实就是几个公式

  • 特殊矩阵:拥有大量相同元素 且分布有规律
  • 压缩存储:为多个值元素只分配一个存储空间 对0元素不分配存储空间

1.对称矩阵
image
这个容易理解 对角线两边对称 选择一边进行压缩存储

接下来讨论的都是其中某个元素(知行号列号 i j)存储在一维数组中算下标

image
如果越界对调对称

2.三角矩阵
image
将三角区域进行压缩存储

image
跟对称矩阵一样 若越界则0

3.三对角矩阵
image
非0元素集中在以主对角线为中心的的三条对角线区域

image

上述总结的下标都是从0开始的 若从1开始 那就在后面+1

稀疏矩阵

一句话大部分元素都是0
存储的是什么呢:

  • 三元组
  • 矩阵行号
  • 矩阵列号

三元组存储的就是非0元素(因为大部分元素都是0)

image
可以看出来三元组存储的就是非0元素的行号列号和值

看题补充:

image

image

大致这个总结在此 回见

http://www.jsqmd.com/news/683755/

相关文章:

  • TRAE如何导入java项目
  • 告别编译报错!手把手教你用VS2022命令行编译curl静态库(附完整测试代码)
  • 手把手教你排查SSH登录失败:当OpenSSH的UsePAM设为yes后,我踩过的那些坑
  • 别再只用ReLU了!PyTorch中PReLU激活函数实战:从参数学习到图像分类效果对比
  • 用 Go 写了一个极简 API Key 管理工具,两个字母搞定一切
  • 股市学习心得-固态电池核心上市公司
  • Nature 图表复现 | 样本分布图
  • OpenClaw35万Star-AI编程进入多智能体协同时代
  • 2026年山东到哈萨克斯坦物流公司最新推荐:山东到吉尔吉斯斯坦物流、山东到塔吉克斯坦物流、山东到乌兹别克斯坦物流、山东到土库曼斯坦物流公司选择指南 - 海棠依旧大
  • Logback日志格式实战:解决特殊字符与多行日志采集的5个坑
  • 别再手动写packages了!用setuptools的find_packages()自动打包你的Python多模块项目
  • 展讯A16摄像头插值到非代码中预设值时处理方法
  • 网络安全实战干货:从个人防护到企业防护,全场景避坑指南
  • 告别IP盲猜:为你的STM32设备加上“网络身份证”(基于LwIP 2.1.2的HostName与DHCP深度集成教程)
  • 2026年如何部署OpenClaw?8分钟华为云保姆级安装及百炼Coding Plan步骤
  • STM32CubeIDE新手必知的10个快捷键,效率提升不止一倍(附重定义printf避坑指南)
  • Altium Designer 导出Gerber和坐标文件保姆级教程(附常见报错排查)
  • 什么是数据库?什么是关系数据库?什么是非关系型数据库?
  • 告别手动推导噩梦:用Matlab符号工具箱快速搞定球坐标拉普拉斯算子转换
  • 告别Demo版限制:手把手教你搞定CANoe 17.0的License激活与疑难杂症排查
  • 高效构建由对称子矩阵组成的三维数组
  • Claude-Opus-47-VS-GLM-51-2026编程能力王者之争
  • 区块链与AI融合:10大产业变革深度解析
  • Qt信号量QSemaphore避坑指南:tryAcquire非阻塞调用、release过量释放,这些多线程‘暗雷’你踩过吗?
  • 猫抓浏览器扩展:轻松捕获网页媒体资源的终极指南
  • Python变量相关性分析:原理、实现与实战应用
  • 别再写硬编码了!MyBatis-Plus的apply方法,这样用才安全又灵活(附日期查询实战)
  • 1篇5章2节:macOS 必备开源包管理器 Homebrew
  • 生化危机8修改器 风灵月影 支持最新版本
  • Element UI 表格合并踩坑记:从官网示例到真实业务场景的完整避坑指南