栈的应用—括号匹配问题
eg:(((())))
在程序中:最后出现的左括号最先被匹配
----->栈的特性:先进后出,后进先出
每出现一个右括号,就消耗(出栈)一个左括号
遇到左括号就入栈,遇到右括号就消耗一个左括号出栈
程序流程图:
#include<stdio.h>
#include<stdlib.h>
#define MaxSize 10 //定义栈中元素的最大个数
typedef struct {
char data[MaxSize]; //静态数组存放栈中元素
int top; //栈顶指针
}SqStack;
//初始化栈
void InitStack(SqStack &S){
S.top=-1; //初始化栈顶指针
}
//判空操作
bool StackEmpty(SqStack S){
if(S.top==-1)
return true; //栈空
else
return false; //不空
}
//入栈操作
bool Push(SqStack &S,char x){
if(S.top==MaxSize-1) //栈满报错
return false;
S.top=S.top+1; //指针先加1
S.data[S.top]=x; //新元素入栈
//S.data[++S.top]=x;
}
//出栈操作,并用x返回值
bool Pop(SqStack &S,char &x){
if(S.top==-1) //栈空,报错
return false;
x=S.data[S.top]; //栈顶元素先出栈
S.top=S.top-1; //指针再减1
// x=S.data[--S.top];
return true;
}
bool test(char str[],int length){
SqStack S;
InitStack(S); //初始化一个栈
for(int i=0;i<length;i++){
if(str[i]=='('||str[i]=='['||str[i]=='{'){
Push(S,str[i]); //扫描到左括号入栈
} else{
if(StackEmpty(S)) //扫描到右括号当前栈空
return false; //匹配失败
char topElem;
Pop(S,topElem); //栈顶元素出栈
if(str[i]==')'&&topElem!='(')
return false;
if(str[i]==']'&&topElem!='[')
return false;
if(str[i]=='}'&&topElem!='{')
return false;
}
}
if( StackEmpty(S)) //检索完所有括号后,栈空则匹配成功
return true;
else
return false;
}
int main(void){
char str[4]={'(','(',')',')'}; //建立一个存放括号的数组
if(test(str,4)){
printf("true");
}else{
printf("false");
}
return 0;
}
栈的应用----表达式求值
- **三种算术表达式:**前缀,中缀,后缀表达式
- 中缀表达式转后缀表达式,后缀表达式求值 - 中缀表达式转前缀表达式,前缀表达式求值
操作表达式由三部分组成:
- 操作数
- 运算符
- 界限符:括号,表明运算的顺序
波兰数学家的灵感:不用界限符也可以无歧义的表达运算顺序
- 逆波兰表达式:后缀表达式
- 波兰表达式:前缀表达式
中缀,前缀,后缀表达式
- 中缀表达式:a+b,a+b-c
- 后缀表达式:ab+,ab+c-或abc-+
- 前缀表达式:+ab,-+abc
中缀表达式转后缀表达式的手算
- 确定中缀表达式各个运算符的运算顺序
- 选择下一个运算符,按【做操作数,右操作数,运算符】的方式组合成一个新的操作数
- 如果还有运算符没有被处理,则继续第二步
左优先原则
为了保证手算与机算的结果相同,只要左边的优先符能先算,就先算左边的
后缀表达式的计算
从左往右进行扫描,每遇到一个运算符,就让运算符前面最近的两个操作数执行对应运算,合体为一个操作数,需要注意两个操作数的左右顺序
后缀表达式的机算
注意操作数的特性,后进先出,先出栈的是右操作数
- 对一个算式开始从左向右扫描
- 扫描到操作数就压入栈中
- 扫描的运算符则从栈中取出两个数,注意后进的数先出,且在运算符的右边
- 之后扫到操作数再重复第二步
后缀表达式适用于基于栈的编程语言
中缀转前缀表达式
- 确定中缀表达式中各个运算符的运算顺序
- 选择下一个元运算符,按照【运算符 做操作数 右操作数】的方式组合为一个新的操作数
- 如果还有运算符没有处理,就继续第二步
右优先原则:优先让右边的运算符生效
用计算机实现前缀表达式计算时
- 从右往左进行扫描元素,直到处理完所有元素
- 若扫描到操作数压入栈,并会到第一步,否则执行第三步
- 若扫描到运算符,则弹出栈顶两个元素,执行相应运算,运算结果压回栈顶,回到第一步
与后缀表达式机算不同的是,在前缀表达式中,先出栈的是左操作数
后缀表达式在计算机中使用更多
机器实现
中缀表达式转后缀表达式流程
1.初始化一个栈,用于保存暂时还不能确定运算顺序的运算符
2.从左到右处理各个元素,可能遇到三种情况
- 遇到操作数,直接加入后缀表达式
- 遇到界限符。遇到(直接入栈,遇到(则依次弹出栈内运算符并加入后缀表达式,直到弹出(为止,注意(不加入后缀表达式
- 遇到运算符。则依次弹出栈中优先级高于或等于当前运算符的所有运算符,并加入后缀表达式,若碰到(或栈空则停止。之后再把当前运算符入栈
3.按上述方法处理完所有字符后,将栈中剩余运算符依次弹出,并加入后缀表达式
中缀表达式的计算(用栈实现)
中缀转后缀+后缀表达式的求值----两种算法的结合
用栈实现中缀表达式的计算
- 初始化两个栈,操作数栈和运算符栈
- 若扫描到操作数压入操作数栈
- 若扫描到运算符或界限符,则按照中缀转后缀相同的逻辑压入运算符栈(期间也会弹出运算符,每当弹出一个运算符时,就需要再弹出两个操作数栈的栈顶元素并执行相应运算,运算结果再压回操作数栈)
栈的应用----递归
void main(){
int a,b,c;
//...
func1(a,b);
c=a+b;
//...
}
void func1(int a,int b){
int x;
//...
func2(x);
x=x+10086;
//...
}
void func2(int x){
int m,n
//...
}
函数调用的特点:==最后调用的函数最先执行结束(后进先出)
- 函数调用时需要调用一个栈存储
1.调用返回地址
2.实参
3.局部变量
适合用递归算法解决:可以把原始问题转化为属性相同,但规模较小的问题
1.计算正整数的求值n!
factoria(n):n*factorial(n-1)
2.求斐波那契数列:fib(n):fib(n-1)+fib(n-2)
递归调用时,函数调用栈可称为“递归工作栈”
- 每进入一层递归,就将递归调用所需信息压入栈顶
- 每退出一层递归,就从栈顶弹出相应信息
- 缺点:太多层递归可能会导致栈溢出
- 缺点:可能包含很多重复计算
队列的应用
1.树的层次遍历:一层一层的遍历树里的各个结点
2.图的广度优先遍历:
3.队列再操作系统中的应用:多个进程争抢这使用有限的系统资源时,FCFS(FIrst Come First Service)是一种常用策略,可用队列来实现
特殊矩阵的压缩存储
矩阵可以很容易的使用数组表示
一维数组
ElemType a[10]; //ElemType型一维数组
起始地址:LOC
- 各数组元素大小相同,且物理上连续存放
- 数组元素a[i]的存放地址=LOC+i*sizeof(ElemType)
- 数组下标默认从0开始
二维数组
ElemType b[2][4] //两行四列的二维数组
逻辑视角:
内存视角:在内存中有两种模式:行优先,列优先
普通矩阵的存储
- 可用二维矩阵进行存储注意描述矩阵元素时,行列通常从1开始,然而描述数组时,通常下标从0开始
对于某些特殊的矩阵可以采用特殊的存储方式来节约存储空间
对称矩阵的压缩存储
def:若n阶方针中每一个元素aij都有aij=aji,则该矩阵是对称矩阵
- 主对角线:i=j
- 上三角区:i《j
- 下三角区:i》j
由于上三角区域与下三角区域相同
压缩存储策略:
- 只存储主对角线+下三角线
- 主对角线+上三角区
**实现方法:**按行优先的原则将各元素存入一维数组中
主要问题:
1.数组的大小:
1+2+3+。。。+n
2.对称矩阵压缩存储后怎样才能方便使用
可以实现一个映射函数:矩阵下标——》一维数组下标
eg:aij是第(1+2+3+i-1)+j个元素,所以它对应的下标再-1
三角矩阵的压缩存储
下三角矩阵:除了主对角线和下三角区,其余的元素都相同
上三角矩阵:
**压缩存储策略:**按行优先的原则将不是常量的区域存入一维数组。并在最后一个位置储存常量C。
三对角矩阵的压缩存储:
def:三对角矩阵,又称为带状矩阵,当|i-j|>1时aij=0;
存储策略: 按行优先的原则,只存储带状部分。
第一行和最后一行有两个元素,其他每行都有三个元素
- 一维数组的长度为3n-2
- 数组下标为k的元素在第几行第几列
j=k-2i+3
稀疏矩阵的压缩存储
def:稀疏矩阵的非零元素远远小于矩阵元素的个数
压缩存储策略
- 顺序存储:三元组<行,列,值>
可以定义一个struct,在其中存储i,j,v,再定义一个struct的数组
2.链式存储:十字链表法