分节目录
数据结构(完结)
数据结构Part1 绪论与线性表
数据结构Part2 栈和队列
数据结构Part3 串
数据结构Part4 树与二叉树
数据结构Part5 图
数据结构Part6 查找
数据结构Part7 排序
第三章 栈和队列
栈和队列就是插入或删除操作收限制的线性表。
数据结构 | 队首/栈顶 | 队尾/栈底 |
---|---|---|
栈 | 插入&删除 | / |
队列 | 插入 | 删除 |
双端队列 | 插入&删除 | 插入&删除 |
输出受限的双端链表 | 插入&删除 | 插入 |
输入受限的双端链表 | 插入&删除 | 删除 |
注意:
1.经过确定操作后,栈/队列的状态。
2.给定输入序列,求可能的输出序列。n个元素入栈可能的输出序列共有(2*n)C(n)/(n+1) [C:组合符号]
3.循环队列的判空问题,注意初始化时头尾指针的位置,是否需要/可以牺牲一个存储单元。
1. 栈的基本操作
InitStack(&S) //初始化栈。构造一个空栈s,分配内存空间。
DestroyStack(&L) //销毁栈。销毁并释放栈s所占用的内存空间。
Push(&S,x) //进栈,若栈s未满,则将x加入使之成为新栈顶。
Pop(&S,&x) //出栈,若栈s非空,则弹出栈顶元素,并用x返回。
GetTop(S,&x) //读栈顶元素。若栈s非空,则用x返回俄顶元素
StackEmpty(S) //判断一个栈s是否为空。若s为空,则返回true,否则返回false。
2. 队列的基本操作
lnitQueue(&Q) //初始化队列,构造一个空队列Q。
DestroyQueue(&Q) //销毁队列。销毁并释放队列Q所占用的内存空间。
EnQueue(&Qk) //入队,若队列Q未满,将x加入,使之成为新的队尾。
DeQueue(&Q,&x) //出队,若队列Q非空,删除队头元素,并用x返回。
GetHead(Q,&x) //读队头元素,若队列Q非空,则将队头元素赋值给x。
用栈实现队列
class MyQueue {
public:
MyQueue() {}
void push(int x) {
s1.push(x);
}
int pop() {
fun();
int ans = s2.top();
s2.pop();
return ans;
}
int peek() {
fun();
return s2.top();
}
bool empty() {
if(s1.empty() && s2.empty()) return true;
else return false;
}
private:
stack<int> s1;
stack<int> s2;
void fun(){
if(s2.empty()){ //如果s2是空的,那么把s1都注入s2中
while(!s1.empty()){
s2.push(s1.top());
s1.pop();
}
}
}
};
3. 栈和队列的应用
3.1 栈在括号匹配中的应用
使用一个栈。
遇到左括号则入栈,遇到右括号则出栈,可以记录错误的数量。
i.初始设置一个空栈,顺序读入括号序列;
ii.若是右括号,则弹出栈顶元素进行比较;
ii.若是左括号,则压入栈内;
iv.若括号序列读完后,栈为空栈,则完全匹配。
typedef struct
{
char data[MaxSize];
int top = 0;
} SqStack;
3.2 栈在表达式求值中的应用
表达式的组成:操作数(数字)、运算符(符号)、界限符(括号)
种类 | 特点 | 举例 |
---|---|---|
中缀表达式 | 界限符必要 | (a+b)*(c-d) |
前缀表达式/波兰表达式 | 界限符不必要 | *+ab+cd |
后缀表达式/逆波兰表达式 | 界限符不必要 | ab+cd+* |
常用(常考)后缀表达式。
表达式类型转换方法(手操)
i.确定中缀表达式中各个运算符的运算顺序
ii.选择下一个运算符,按照「左操作数右操作数运算符」的方式组合成一个新的操作数
iii.如果还有运算符没被处理,就继续ii
中缀表达式:((15/ (7-(1+1)))*3)-(2+(1+1))
后缀表达式:15 7 1 1 + - / 3 * 2 1 1 + + -
前缀表达式:- * / 15 - 7 + 1 1 3 + 2 + 1 1
后/前缀表达式中运算符出现的次序与中缀表达式中运算符进行的顺序是一致的,但不唯一
中->后
在左优先原则的情况下,可保证运算顺序是唯一的
左优先原则:只要左边的运算符能先计算,就优先计算左边的运算符
中->前
在右优先原则的情况下,可保证运算顺序是唯一的
右优先原则:只要右边的运算符能先计算,就优先计算右边的运算符
表达式计算
后缀表达式:
1.初始设置一个空栈,从左往右顺序读入表达式;
2.若是操作数,压入栈内;
3.若是运算符$,则弹出栈顶两个数p,q(先弹出p),计算q $ p = r,r入栈;
4.当表达式序列读取结束后,栈中只有一个元素,即为表达式的值。
前缀表达式:
1.初始设置一个空栈,从右往左顺序读入表达式;
2.若是操作数,压入栈内;
3.若是运算符$,则弹出栈顶两个数p,q(先弹出p),计算p $ q = r,r入栈;
4.当表达式序列读取结束后,栈中只有一个元素,即为表达式的值。
中缀表达式转后缀表达式
初始化一个栈,用于保存暂时还不能确定运算顺序的运算符。从左到右处理各个元素,直到末尾。可能遇到三种情况:
i.遇到操作数。直接加入后缀表达式。
ii.遇到界限符。遇到“(”直接入栈;遇到“)”则依次弹出栈内运算符并加入后缀表达式,直到弹出“(”为止。注意:“(”不加入后缀表达式。
iii.遇到运算符。依次弹出栈中优先级高于或等于当前运算符的所有运算符,并加入后缀表达式,若碰到“(”或栈空则停止。
iv.之后再把当前运算符入栈。按上述方法处理完所有字符后,将栈中剩余运算符依次弹出,并加入后缀表达式。
注意:括号不需要加入后缀表达式,注意比较各运算符的优先级。答题时可采用伪代码或模拟入栈出栈操作的过程(表格)
*中缀表达式求值(机算)
使用两个栈。
i.初始化两个栈,操作数栈和运算符栈若扫描到操作数,压入操作数栈
ii.若扫描到运算符或界限符,则按照“中缀转后缀”相同的逻辑压入运算符栈(期间也会弹出运算符,每当弹出一个运算符时,就需要再弹出两个操作数栈的栈顶元素并执行相应运算,运算结果再压回操作数栈)
两个栈分别对应着**“中缀转后缀”和“后缀表达式求值(机算)”**两个算法。
3.3 栈在递归中的应用
递归函数就是函数调用的过程(自己调用自己)。
递归的适用范围:将问题转换为属性相同但是规模更小的问题,即an+1 = f(an)
函数调用栈:函数调用时,需要用一个栈存储调用返回地址、实参、局部变量三类内容。
递归调用时,函数调用栈可称为“递归工作栈”每进入一层递归,就将递归调用所需信息压入栈顶每退出一层递归,就从栈顶弹出相应信息。
递归算法会创造较大的函数调用栈,存在栈溢出的可能。
递归改非递归:通过栈模拟递归。
注意:重点是理解递归的逻辑,和递归转非递归的逻辑,不是代码实现。
3.4 队列的层次遍历中的应用
树的层次遍历
i.根节点入队;
ii.若队为空(所有节点已经处理完毕),则遍历结束。否则执行iii;
iii.队列第一个节点出队,并将其子节点从左至右依次入队,返回ii。
图的广度优先遍历(Breadth First Search,bfs)与这个类似,注意要标记已经遍历过的节点。
3.5 队列在操作系统中得应用
进程排序:多个进程争抢有限得系统资源时,可以采用先来先服务(First Come First Service, FCFS)的策略。
4.特殊矩阵的压缩存储
4.1 数组的存储结构
一维数组:各数组元素大小相同,物理上连续存放,数组下标默认从0开始。
ElemType a[MaxSize]; //ElemType型一维数组,数组大小为MaxSize
二维数组:
行优先,b[i][j]的存储地址= LOC+ (i*N + j)* sizeof(ElemType)
列优先,b[i][j]的存储地址= LOC+ (j*M + i)* sizeof(ElemType)
ElemType a[xSize][ySize]; //ElemType型一维数组,数组大小为xSize*ySize
4.2 特殊矩阵
描述矩阵时行号和列号默认从1开始。
对称矩阵:上三角与下三角数据相同,可以只存储主对角线+下三角区的数据。
按照行优先的原则将元素存入一维数组B中。n阶对称矩阵的一位数组大小为(1+n)*n/2。
构建一个映射函数将a[i][j]映射到数组B[k]上。
k
=
{
i
(
i
+
1
)
2
+
j
−
1
,
i
≥
j
j
(
j
−
1
)
2
+
i
−
1
,
i
<
j
大
概
是
这
样
不
用
记
k= \begin{cases} \cfrac {i(i+1)}{2} +j-1, & i\ge j\\ \cfrac {j(j-1)}{2} +i-1, & i\lt j \end{cases} 大概是这样不用记
k=⎩⎪⎨⎪⎧2i(i+1)+j−1,2j(j−1)+i−1,i≥ji<j大概是这样不用记
三角矩阵:上三角区或下三角区全是常量,对称矩阵压缩+1个位置存放常量
三对角矩阵:又称带状矩阵,沿对角线呈条带状。当|i - j| > 1时,a[i][j] = 0
共需存储3*n-2个元素。k = 2*i+j-3
a[i][j] -> k:前i-1行共3*(i-1)-1个元素,a是第i行第j-i+2个元素,a[i][j] 是第2*i+j-2个元素
k -> a[i][j]:前i-1行共3*(i-1)-1个元素,前i行共3*i-1个元素,则3*(i-1)-1<k+1<=3*i-1,则i = [(k + 2) / 3] + 1。
要注意边界问题的处理。
稀疏矩阵:非零元素个数远远少于矩阵元素个数,零元素过多。
可采用一个三元组**<行,列,值>**保存,但是这样会失去随机查找的特性。
或采用十字链表法:向下域指向第j列的第一个元素,向右域指向第i行的第一个元素