第三章栈、队列和数组
3.1 栈
定义
只允许在一端进行插入或删除操作的线性表
特点:先进后出,后进先出
栈顶、栈底、空栈
基本操作
InitStack (&S):初始化栈。构造一个空栈 S,分配内存空间。
DestroyStack (&S):销毁栈。销毁并释放栈 S 所占用的内存空间。
Push (&S, x):进栈,若栈 S 未满,则将 x 加入使之成为新栈顶。
Pop (&S,&x):出栈,若栈 S 非空,则弹出栈顶元素,并用 x 返回。
GetTop (S, &x):读栈顶元素。若栈 S 非空,则用 x 返回栈顶元素
栈的顺序存储结构
实现
-
栈顶指针: S.top 栈顶元素: S.data[S.top]
-
进栈: 指针先加 1, 再送值到栈顶元素
-
出栈: 先取栈顶元素值, 再将栈顶指针减 1
共享栈
-
定义: 将两个栈的栈底设置在共享空间的两端, 两个栈顶向中间延伸
-
判空: top 0=-1 top 1=MaxSize
-
判满: top 1-top 0=1
-
进栈: top 0 先加 1 再赋值, top 1 先减 1 再赋值, 出栈相反
栈的链式存储结构
优点: 便于多个栈共享储存空间, 提高其效率, 不会栈满上溢
特点:所有操作在表头进行, 通常没有头结点, 将头指针作为栈顶指针, 便于结点插入 / 删除
3.2 队列
定义
队列(Queue)是只允许 在一端进行插入(入队),在另一端删除(出队) 的线性表
队头、队尾、空队列
特点:先进先出
队列的基本操作
InitQueue (&Q):初始化队列,构造一个空队列 Q。
DestroyQueue (&Q):销毁队列。销毁并释放队列 Q 所占用的内存空间。
EnQueue (&Q, x):入队,若队列 Q 未满,将 x 加入,使之成为新的队尾。
DeQueue (&Q,&x):出队,若队列 Q 非空,删除队头元素,并用 x 返回。
GetHead (Q,&x):读队头元素,若队列 Q 非空,则将队头元素赋值给 x
队列的顺序存储结构
实现
-
两个指针: front 指示队头元素, rear 指向队尾元素下一个位置
-
初始状态 (队空): Q.front Q.rear0
-
进队: 先送值到队尾元素, 再将队尾指针加 1
-
出队: 先取队头元素值, 再将队头指针加 1
-
存在假溢出
队列的链式存储结构
适合数据元素变动较大的情形, 不存在队满溢出, 多个队列不存在存储分配不合
双端队列
只允许从两端插入、两端删除的线性表
输入受限的双端队列:只允许从一端插入、两端删除的线性表
输出受限的双端队列:只允许从两端插入、一端删除的线性表
3.3 栈和队列的应用
3.3.1 栈在括号匹配中的应用
最后出现的左括号最先被匹配
-
设置一个空栈, 顺序读入括号
-
若为 ) , 与栈顶 ( 配对出栈或者不合法
-
若为 ( , 作为新的更急迫的期待压入栈中
-
算法结束, 栈为空, 否则括号序列不匹
3.3.2 栈在表达式求值中的应用
中缀转后缀
中缀转后缀的手算方法
① 确定中缀表达式中各个运算符的运算顺序
② 选择下一个运算符,按照 「左操作数右操作数运算符」 的方式组合成一个新的操作数
③ 如果还有运算符没被处理,就继续 ②
“左优先” 原则:只要左边的运算符能先计算,就优先算左边的可保证运算顺序唯一
后缀表达式的手算方法: 从左往右扫描,每遇到一个运算符,就让运算符前面最近的两个操作数执行对应运算,合体为一个操作数
中缀转后缀的机算方法
初始化一个栈,用于保存暂时还不能确定运算顺序的运算符。
从左到右处理各个元素,直到末尾。可能遇到三种情况:
① 遇到操作数。直接加入后缀表达式。
② 遇到界限符。遇到 “(” 直接入栈;遇到 “)” 则依次弹出栈内运算符并加入后缀表达式,直到弹出 “(” 为止。注意:“(”不加入后缀表达式。
③ 遇到运算符。依次弹出栈中优先级高于或等于当前运算符的所有运算符,并加入后缀表达式,若碰到 “(” 或栈空则停止。之后再把当前运算符入栈
后缀表达式计算(算法实现)
用栈实现后缀表达式的计算:
①从左往右扫描下一个元素,直到处理完所有元素
②若扫描到操作数则压入栈,并回到①;否则执行③
③若扫描到运算符,则弹出两个栈顶元素(先出栈的为右操作数),执行相应运算,运算结果压回栈顶,回到①
中缀表达式计算(栈实现) 中缀转后缀 + 后缀表达式求值
- 初始化两个栈,操作数栈和运算符栈
- 若扫描到操作数,压入操作数栈
- 若扫描到运算符或界限符,则按照 “中缀转后缀” 相同的逻辑压入运算符栈(期间也会弹出运算符,每当弹出一个运算符时,就需要再弹出两个操作数栈的栈顶元素并执行相应运算,运算结果再压回操作数栈)
中缀转前缀
中缀转前缀的手算方法:
① 确定中缀表达式中各个运算符的运算顺序
② 选择下一个运算符,按照 「运算符左操作数右操作数」 的方式组合成一个新的操作数
③ 如果还有运算符没被处理,就继续 ②
“右优先” 原则:只要右边的运算符能先计算,就优先算右边的
前缀表达式计算(算法实现)
用栈实现前缀表达式的计算:
①从右往左扫描下一个元素,直到处理完所有元素
②若扫描到操作数则压入栈,并回到①;否则执行③
③若扫描到运算符,则弹出两个栈顶元素(先弹出的为左操作数),执行相应运算,运算结果压回栈顶,回到①
3.3.3 栈在递归中的应用
函数调用的特点:最后被调用的函数最先执行结束(LIFO)
函数调用时,需要用一个栈存储: ① 调用返回地址 ② 实参 ③ 局部变量
递归 :可以把原始问题转换为属性相同,但规模较小的问题
两个条件 1. 递归表达式 (递归体) 2. 边界条件 (递归出口)
递归调用时,函数调用栈可称为 “递归工作栈” 每进入一层递归,就将递归调用所需信息压入栈顶每退出一层递归,就从栈顶弹出相应信
缺点:效率低,太多层递归可能会导致栈溢出;可能包含很多重复计算
3.3.4 队列的应用
在层次遍历中的应用
- 树的遍历
- 图的广度优先遍历
在计算机系统中的应用
-
FCFS 先来先服务
-
解决主机与外部设备之间速度不匹配的问题
-
解决由多用户引起的资源竞争问题
3.4 数组和特殊矩阵
3.4.1 数组
数组 :由 n(n>=1)个相同类型的数据元素构成的有限序列,每个数据元素称为一个数组元素
数组是线性表的推广
数组地址计算
- 一维数组
- 二维数组–行优先
- 二维数组–列优先
3.4.2 特殊矩阵的压缩存储
压缩存储: 多个值相同的元素只分配一个空间, 0 不分配空间
对称矩阵的压缩存储
若 n 阶方阵中任意一个元素 ai, j 都有 ai, j = aj, i 则该矩阵为对称矩阵
三角矩阵的压缩存储
下三角矩阵:除了主对角线和下三角区,其余的元素都相同
上三角矩阵:除了主对角线和上三角区,其余的元素都相同
三对角矩阵的压缩存储
稀疏矩阵的压缩存储
压缩存储策略:
- 顺序存储——三元组 <行,列,值>
- 链式存储——十字链表法