栈和队列
栈
定义
只允许在一端进行插入或删除操作的线性表
操作
初始化、进栈、出栈、销毁栈等
特征
- 后进先出(非常重要的特征)
实现
-
顺序栈
顺序存储,用静态数组实现,并记录栈顶指针。顺序栈的缺点:栈的大小不可变
-
共享栈
两个栈共享同一片内存空间,两个栈从两左边往中间增长
-
初始化
0号栈栈顶指针初始时top0 = -1; 1号栈栈顶指针初始时top1 = MaxSize;
-
栈满条件
top0+1 == top1
-
-
链栈
- 头插法建立单链表对应进栈,单链表的删除操作对应:出栈。都是只对第一个元素进行操作
若有n个元素进栈,则出栈顺序有1/(n+1) * C(2n,n),其中C(2n,n)为组合公式
队列
定义
只允许在一端进行插入,在另一端进行删除的线性表
操作
增、删、查、改、初始化、销毁
特征
- 先进先出(非常重要的特征)
循环队列
-
注意:我们默认循环队列的front指针指向当前元,和rear指针都是指向下一个地址。默认牺牲一个存储单元不存放元素
-
确定判空的条件
Q.rear==Q.front
-
确定判满的条件
(Q.rear+1)%MaxSixe==Q.front
-
队列元素个数
(rear + maxSize - front) % maxSize
-
出队操作
Q.front=(Q.front+1)%MaxSize
-
入队操作
Q.rear=(Q.rear+1)%MaxSize
-
注意:循环队列,题目没有说明的情况下,默认使用牺牲一个存储单元的方法。出队、入队、和求队列元素个数都要模maxSize。其中尤其要注意求队列元素,因为rear-front可能会存在负值,要加上maxSize再与maxSize取模
-
如何有效利用所有存储单元
-
牺牲一个存储单元,这是默认使用的一种方式
-
增加辅助变量size,则队空:size=0,队满:size=maxSize
-
增加一个标记变量tag,每次删除操作成功时,都令tag=O;每次插入操作成功时,都令tag=1。只有删除操作,才可能导致队空只有插入操作,才可能导致队满
初始化时 rear=front=0;tag = 0;
队空条件: front==rear && tag ==0
队满条件: front==rear && tag ==1
-
双端队列
-
定义
只允许从两端插入、两端删除的线性表
-
分类
-
输入受限的双端队列
只允许从一端插入、两端删除的线性表
-
输出受限的双端队列
只允许从两端插入、一端删除的线性表
-
-
如何判断输入受限的双端队列的合法顺序
看输出的第一个元素,比如输入顺序时1、2、3、4,而输出顺序是4、2、1、3,这显然不对,因为第一个元素是4,说明队列中的顺序是1,2,3,4了,输出4以后不可能先输出中间的2,如下,输出4以后只能输出1或3。简记:输入受限,看第一个元素。(根据第一个元素的值可以知道已经进去了多少元素,从而可以推导出序列的合法性)如下图:
-
如果4第一个出队,那么说明队列里的元素已经是1、2、3、4.则4输出以后不可能先输出2,所以以4、2开头的序列都是非法的
-
如何判断输出受限的双端队列的合法顺序
根据输出顺序推倒输入顺序,比如输入顺序是1、2、3、4,但是一个输出顺序是4、1、3、2,这显然是不对的,因为1先进入,2后进入,1的旁边一定有2,所以该序列非法。输出受限的双端队列是比较好推导的。简记:输出受限:逆推导(根据输出序列推导输入序列是否合法),如下图:
根据双端队列输出受限的特点,1进入队列以后,2会接着进入队列,因此不管怎么样,2都在1的旁边,要么在左边,要么在右边,因此不可能出现2在1的隔壁。所以4,、2、3、1是非法序列。
他们俩都有一个相似点:如果输出没有受限,只看输出的合法性,不用关心输入的过程。如果输入没有受限,就看输入的合法性,不用关心输出的过程。
栈和队列对比
顺序栈、顺序队列
扩展容量不方便
存储密度高
链栈、链队列
一般不会出现容量不足的问题
存储密度稍低
栈的应用:
1、括号匹配
依次扫描所有字符,遇到左括号入栈,遇到右括号则弹出栈顶元素检查是否匹配
-
匹配失败的三种情况:
左括号单身、有括号单身、左右括号不匹配
2、表达式求值
-
逆波兰表达式又叫后缀表达式 波兰表达式又叫前缀表达式
-
后缀表达式求值
-
中缀转后缀
1、按左优先原则确定运算符的运算次序
2、根据运算次序,依次将各个运算符和与之相邻的两个操作数按<左操作数、右操作数、运算符>的规则合体
-
后缀转中缀
从左往右扫描,每遇到一个运算符,就让运算符前面最近的两个操作数执行对应运算合体为一个操作数。先弹出的为右操作数
-
计算,按左优先原则,先弹出的是右操作数
用栈实现后缀表达式的计算:
-
①从左往右扫描下一个元素,直到处理完所有元素
-
②若扫描到操作数则压入栈,并回到①;否则执行③
-
③若扫描到运算符,则弹出两个栈顶元素,执行相应运算,运算结果压回栈顶,回到①
-
-
前缀表达式求值
-
中缀转前缀
①确定中缀表达式中各个运算符的运算顺序
-
②选择下一个运算符,按照<运算符,左操作数,右操作数的>,组合成一个新的操作数 ③如果还有运算符没被处理,就继续② "右优先"原则:只要右边的运算符能先计算,就优先算右边的
-
计算,按右优先原则,先弹出的为左操作数
从右往左扫描下一个元素,直到处理完所有元素②若扫描到操作数则压入栈,并回到①;否则执行③ ③若扫描到运算符,则弹出两个栈顶元素,执行相应运算,运算结果压回栈顶,回到①
-
3、递归、图的深度优先遍历
队列的应用:
树的层次遍历
图的广度优先遍历
操作系统分配资源(先来先服务算法)
打印数据缓冲区
特殊矩阵在一维数组中的下标计算
只需要计算出当前元素前面有几个元素即可。但是很多时候有的元素是a00开头,有的是a11开头。甚至更复杂的行以0开头,列以1开头。 我们只需要记住一点,以a11开头的,aij前面有i-1行,j-1个元素。即aij前面有(i - 1)* 列数 + j-1
以a00开头的,aij前面有i行,j个元素。即aij前面有 i* 列数 + j
不管哪种方式开头,只需计算出aij前面有K个元素, 一般情况下默认存在一维数组的下标从0开始存放,则当前坐标为a[K],如果以1开始存放则下标为 a[ K+1 ]。从M开始存放,则下标为a[M+K]
可能上面的叙述有点混乱,没关系,只要看两个题你就懂了:
比如一个m*n的二维矩阵,下标以A11开头:
如果把这些元素按行存储方式存入一维数组,则Aij应该存在一维数组的哪个下标?
首先我们知道矩阵是以 1开头,则Aij前面有i-1行(比如,A23前面就有1行),j-1个元素(比如A23前面有2个元素),故A23前面一共有n*1+2个元素。
现在我们已经计算出Aij前面有K个元素了,如果数组下标默认是从0开始,则A23存入的下标就是n*1+2,如果一维数组下标从1开始,则则A23存入的下标就是n*1+2+1。更一般的,如果一维数组下标从M开始,则则A23存入的下标就是n*1+2+M
如果矩阵以A00开头:
首先我们知道矩阵是以 0开头,则Aij前面有i行(比如,A21前面就有2行),j个元素(比如A21前面有1个元素),故A23前面一共有n*2+1个元素。
现在我们已经计算出Aij前面有K个元素了,如果数组下标默认是从0开始,则A21存入的下标就是n*2+1,如果一维数组下标从1开始,则则A21存入的下标就是n*2+1。更一般的,如果一维数组下标从M开始,则则A23存入的下标就是n*2+1+M
稀疏矩阵
定义
在矩阵中,若数值0的元素数目远多于非0元素的数目,并且非0元素分布没有规律
存储方式
三元组表(行、列、值)
十字链表
特殊矩阵
对称矩阵
三角矩阵
三对角矩阵
稀疏矩阵