【学习总结】《大话数据结构》- 第4章-栈与队列
【学习总结】《大话数据结构》- 总
第4章栈与队列-代码链接
启示:
-
栈与队列
栈是限定仅在表尾进行插入和删除操作的线性表
队列是只允许在一端进行插入操作,而在另一端进行删除操作的线性表
目录
- 4.1 开场白
- 4.2 栈的定义
- 4.3 栈的抽象数据类型
- 4.4 栈的顺序存储结构及实现
- 4.5 两栈共享空间
- 4.6 栈的链式存储结构及实现
- 4.7 栈的作用
- 4.8 栈的应用--递归
- 4.9 栈的应用--四则运算表达式求值
- 4.10 队列的定义
- 4.11 队列的抽象数据类型
- 4.12 循环队列
- 4.13 队列的链式存储结构及实现
- 4.14 总结回顾
- 4.15 结尾语
========================================
4.1 开场白
- 一些可以略过的场面话...
- 以左轮手枪和弹夹式手枪的区别引入
========================================
4.2 栈的定义
-
栈的引入:
-
栈的常见应用:
软件中的撤销(undo)操作,浏览器历史纪录,Android中的最近任务,Activity的启动模式,CPU中栈的实现,Word自动保存,解析计算式,解析xml/json
-
-
定义:栈(stack)是限定仅在表尾进行插入和删除操作的线性表
-
栈首先是一个线性表,栈元素具有线性关系,即前驱后继关系。
-
栈顶(top):允许插入和删除的一端称为栈顶
-
栈底(bottom):另一端称为栈底
-
定义所述“表尾插入删除”,表尾指的是栈顶,而不是栈底
-
特殊的线性表:特殊在于限制了插入删除的位置,始终在栈顶进行,栈底是固定的
-
空栈:不含任何数据元素的栈称为空栈
-
LIFO:last in first out,栈又称为后进先出的线性表,简称LIFO结构
-
栈的插入操作:进栈,也称压栈、入栈
-
栈的删除操作:出栈,也称弹栈
-
-
进栈出栈变化形式
-
举例来说,三个int型数字元素1,2,3依次进栈,出栈次序有5种
- 321:123进,321出
- 123:1进1出,2进2出,3进3出
- 213:12进,21出,3进3出
- 132:1进1出,23进,32出
- 231:12进,2出,3进,31出
-
求出栈序列个数:卡特兰数公式:C(2n,n)/(n+1)
-
- ### 其中,卡特兰数前几项为: 1, 2, 5, 14, 42, 132, 429, 1430, 4862, 16796
-
栈的应用之n阶汉诺塔盘问题(Hanoi)
-
规则:
-
在游戏中,总共有n个金属盘片的塔叫做n阶汉诺塔
-
-
若要完成n阶汉诺塔,则最少要移动2^n -1次
(没搞懂原理,也没搞懂代码,但是先把结论记下来)
========================================
4.3 栈的抽象数据类型
-
栈作为线性表,理论上线性表的操作特性都具备
-
针对特殊性,插入和删除改名为push和pop,即压和弹
========================================
4.4 栈的顺序存储结构及实现
-
1、栈的顺序存储结构
-
栈的顺序存储也是线性表顺序存储的简化,简称为顺序栈
-
数组中,用下标为0的一端作为栈底,因为首元素都存在栈底,变化小
-
栈顶top:若存储栈长度为StackSize,则top必须小于StackSize
-
空栈:栈中有一个元素时top=0,以示区别,空栈的判定条件设为top=-1
-
栈的结构定义:
-
-
栈的图示:
-
2、栈的顺序存储结构--进栈操作push
-
图示:
-
-
代码实现:
-
3、栈的顺序存储结构--出栈操作
-
代码实现:
-
-
时间复杂度:
-
进栈、出栈不涉及任何循环语句,时间复杂度均为O(1)
-
========================================
4.5 两栈共享空间
-
两栈共享空间的关键思路:
-
两个栈底分别位于数组的始端(下标0)和数组的末端(下标数组长度n-1),增加元素即从两端点向中间延伸
-
-
两栈共享空间的前提:
-
针对两个具有相同数据类型的栈
-
-
两栈共享空间的图示:
-
栈满:
-
一般情况:两个栈见面之时,即两个指针相差1,top1+1==top2时
-
极端情况:2空时,top1=n-1,则1满;1空,top2=0,则2满
-
-
两栈共享空间的结构代码:
-
两栈共享空间的push方法代码实现:
除了要插入元素值参数,还需要一个判断是栈1还是2的栈号参数stackNumber
-
两栈共享空间的pop方法代码实现:
参数仅为判断栈1或2的参数stackNumber
========================================
4.6 栈的链式存储结构及实现
-
栈的链式存储结构
-
栈的链式存储结构,简称链栈
-
栈顶放在单链表的头部,且不需要头结点
-
空栈:链表原定义为头指针指向空,故链栈的空是top=NULL
-
图示:
-
-
链栈的结构代码:
-
链栈的操作大部分与单链表类似,只是插入、删除特殊
-
栈的链式存储结构--进栈操作
-
图示:
-
-
代码实现:
-
栈的链式存储结构--删除操作
-
图示:
-
-
代码实现:
-
时间复杂度:
-
链栈的push和pop均不涉及循环操作,时间复杂度为O(1)
-
========================================
4.7 栈的作用
-
类比明明可以靠脚走到世界上任何地方,为什么还要乘坐飞机火车呢
-
栈的引入简化了程序涉及的问题,划分了不同关注层次,使思考范围缩小,更加聚焦于问题核心
(比如像数组之类的,要分散精力去考虑数组的下标增减等细节问题。。) -
现在许多高级语言如java,c#等,都有对栈结构的封装,不用关心实现细节,直接使用Stack的push和pop方法,非常方便
========================================
4.8 栈的应用--递归
-
引入:站在相对的两面镜子中间,A中有B,B中有A,会产生无数个“像中像”
-
斐波那契数列(Fibonacci)
- 但是我不明白为什么兔子问题和斐波那契数列能扯上关系。。。
-
公式:
-
递归代码实现:用递归方式,调用自己
-
图示:
-
递归定义
-
把一个直接调用自己或者通过一系列的调用语句间接地调用自己的函数,称为递归函数。
-
每个递归定义必须至少有一个条件,满足时递归不再进行,即不再引用自身而是返回值退出。
-
迭代和递归的区别:
- 迭代使用循环结构,递归使用选择结构
- 递归使程序的结构更清晰、更简洁
- 递归调用会建立函数的副本,会耗费大量的时间和内存
-
递归与栈结构:
- 递归过程退回的顺序是它前进顺序的逆序。在退回过程中,可能要执行某些动作,包括恢复在前进过程中存储起来的某些数据。
- 这种存储某些数据,并在后面又以存储的逆序恢复这些数据,以提供之后使用的需求,这显然很符合栈这样的数据结构。
-
========================================
4.9 栈的应用--四则运算表达式求值
-
后缀(逆波兰)表示法定义
-
一种不需要括号的后缀表达法,也称为逆波兰(Reverse Polish Notation, RPN)
-
后缀: 所有的符号都是要在运算数字的后面出现的。
-
没了括号,机器可以更好地执行。
-
-
中缀表达式
-
定义:常用的标准四则运算表达式,所有的运算符号都在两数字的中间
-
-
想要让计算机处理常见的中缀表达式,最重要的有两步:
-
1、将中缀表达式转化为后缀表达式(栈用来进出运算的符号)
-
2、将后缀表达式进行运算得出结果(栈用来进出运算的数字)
-
-
中缀表达式转后缀表达式(数字全部输出,只有符号进栈出栈)
-
规则:
-
从左到右遍历中缀表达式的每个数字和符号
-
若是数字就输出,即成为后缀表达式的一部分
-
若是符号,先判断其与栈顶符号的优先级
-
是右括号或优先级低于栈顶符号,则栈顶元素依次出栈并输出,并将当前符号进栈
-
若优先级高于栈顶符号,则不用管栈顶,直接压栈
-
-
一直到最终输出后缀表达式为止
-
-
图示:
-
- ### 注:右括号不进栈,当匹配到左括号时,直接到左括号的部分出栈
-
后缀表达式计算结果(只有数字进栈出栈)
-
规则:
-
从左到右遍历表达式的每个数字和符号
-
遇到数字就进栈
-
遇到符号,就将处于栈顶的两个数字出栈,并运算,且运算结果进栈
-
一直到最终获得结果
-
-
图示:
-
- ### 注:遇运算符取出两数计算,然后需要结果再次进栈,最终结果会出现在栈里
-
示例:
-
中缀表达式:9 + ( 3 - 1 ) * 3 +10 / 2
-
后缀表达式:9 3 1 - 3 * + 10 2 / +
-
========================================
4.10 队列的定义
-
队列的引入:
-
常见应用:电脑卡壳时,过一会儿又自顾自地按顺序把之前的操作执行一遍;客服排队;键盘输入到显示器输出等,都是队列
-
-
定义:队列(queue)-- 只允许在一端进行插入操作,而在另一端进行删除操作的线性表
-
允许插入的一端称为队尾,允许删除的一端称为队头
(先进先出,因此头作为先进的,会先出,因此删除端是队头) -
FIFO:first in first out,先进先出的线性表
-
-
图示:设q = (a1, a2, ... , an)
-
则a1是队头元素,an是队尾元素
-
========================================
4.11 队列的抽象数据类型
-
队列同样是线性表,也有类似线性表的各种操作
-
不同在于:插入只能在队尾,删除只能在队头
========================================
4.12 循环队列
-
注:栈和队列作为特殊的线性表,同样都具有顺序存储和链式存储两种存储方式。
-
队列顺序存储的不足
-
当队头固定设为数组下标为0的位置时:入队O(1),但出队O(n)
-
-
当不限制队头必须在数组下标为0的位置时,可以提高一些性能
-
引入两个指针:front指向队头,rear指向队尾的下一个位置
当空队列:front=rear
-
仍存在问题:假溢出
-
循环队列定义
-
定义:队列的头尾相接的顺序存储结构称为循环队列。
-
重新定义队列的空和满
-
QueueSize:队列的最大尺寸
-
队列空: front == rear
-
队列满: front == (rear + 1) % QueueSize
(修改队列满的条件,保留一个元素空间)
-
-
计算队列长度的公式:(rear - front + QueueSize) % QueueSize
-
-
循环队列的代码实现
-
结构代码:
-
-
初始化代码:
-
求队列长度代码:
-
入队列操作代码:
-
出队列操作代码:
-
循环队列的相关条件和公式:
-
1.队空条件:rear==front
-
2.队满条件:(rear+1) %QueueSIze==front,其中QueueSize为循环队列的最大长度
-
3.计算队列长度:(rear-front+QueueSize)%QueueSize
-
4.入队:rear = (rear+1)%QueueSize
-
5.出队:front = (front+1)%QueueSize
-
========================================
4.13 队列的链式存储结构及实现
-
定义:
-
队列的链式存储结构,其实就是线性表的单链表,但只能尾进头出,简称链队列。
-
-
图示:
-
队头指针指向链队列的头结点,队尾指针指向终端结点
-
-
空队列:front和rear都指向头结点
-
链队列的结构代码:
-
队列的链式存储结构--入队
-
入队,即在链表尾部插入结点
-
图示:
-
-
代码实现:
-
队列的链式存储结构--出队
-
出队,即头结点的后继结点出队,将头结点的后继改为它后面的结点
-
若链表除头结点外,只剩一个元素,则需将rear指向头结点
-
图示:
-
-
代码实现:
-
时间空间复杂度分析 -- 循环队列和链队列对比
-
时间上:都是O(1)
-
空间上:
-
循环队列:事先申请好空间,使用期间不释放
-
链队列:每次申请和释放结点也会存在一些时间开销
-
循环队列:固定长度,故存在存储元素个数和空间浪费的问题
-
链队列:需要指针域,产生一些空间上的开销,在空间上更灵活
-
-
========================================
4.14 总结回顾
========================================