栈
栈:允许进行运算端成为栈顶(其实就是表尾),不允许运算的另一端成为栈底。
常见操作:入栈(表尾插入),出栈(表尾删除)
特征:后进先出(LIFO)
顺序栈
- 用一组连续存储单元依次存放自栈底到栈顶的数据
- 设一个指针top动态指向栈顶位置
- top=-1表示空栈
数据类型:
typedef struct{
StackElementType elem[Stacksize];
int top;
}SeqStack;
初始化操作
将栈顶赋值-1就好了,从此可以开始添加变量
void InitStack(SeqStack *S)
{ /*构建一个空栈*/
S->top=-1;
}
判断是否空栈
查询栈顶指针是否为-1即可
int IsEmpty(SeqStack *S)
{
if(S->top==-1)
return 1;
else
return 0;
}
判断是否满栈(*)
查询栈顶指针是否为Stacksize-1即可
int IsFull(SeqStack *S)
{
if(S->top==Stacksize-1)
return 1;
else
return 0;
}
进栈操作(*)
将新元素x压入栈S栈顶
int Push(SeqStack *S,StackElementType x)
{
if(S->top==Stacksize-1)
return 0; //满栈
S->top++;
S->elem[S->top]=x;
return 1;
}
出栈操作
将栈S的栈顶取出,放进变量x中
int Pop(SeqStack *S,StackElementType *x)
{ if(S->top==-1)
return 0; //已经空栈了
else
{
*x=S->elem[S->top]; //接收栈顶元素
S->top--; //栈顶向下移一个
return 1;
}
}
实际上没有消失,只是逻辑上让他离开整体了
取栈顶操作
读取栈顶元素的值
int GetTop(SeqStack *S,StackElementType *x)
{ if(S->top==-1)
return 0; //已经空栈了
else
{
*x=S->elem[S->top]; //接收栈顶元素
return 1;
}
}
两栈共享
为两个栈申请一个共享的一维数组S[M],将两个栈的栈底分别放在一维数组的两端,下标分别是0和M-1,然后用两个栈顶指针top[0]和top[1]分别指向两个栈顶(同属于一个类型,分别是0号栈和1号栈的栈顶)
数据类型
#define M 100
typedef struct
{
StackElementType Stack[M];
StackElementType top[2];
/*里面包含两个元素,分别是top[0]和top[1]两个栈顶指示器*/
}DqStack;
在栈内存一个仅两元素的数组,分别用来存储两栈栈顶的位置信息
初始化
top是位置信息,指示下标,左边使下标为-1,右边M。
void InitStack(DqStack *S)
{
S->top[0]=-1;
S->top[1]=M;
}
进栈操作
向双栈的i号栈压入数据x
int Push(DqStack *S,StackElementType x,int i)
{ if(S->top[0]+1==S->top[1])
return 0; //栈满了
else
{ switch(i)
{ case 0:S->top[0]++; //压入0号栈,栈顶右移
S->Stack[S->top[0]]=x;
break;
case 1:S->top[1]--;
S->Stack[S->top[1]]=x;
break;
default:return 0; //输入栈号错误
}
}
return 1;
}
出栈操作
不能
if(S->top[0]==-1&&S->top[1]==M)
return 0;
判断是否合法可取,因为如果要取0号栈但0号栈空了,应该返回0,但语句必须同时1号栈空才能返回0,这不应该。
也不能
if(S->top[0]==-1||S->top[1]==M)
return 0;
判断是否合法可取,因为如果要取0号栈,0号栈有值可以取,但是1号栈空了,那么这句话就会判断返回0,即不可取,但实际上0号栈可取。
正确算法
int Pop(DqStack *S,StackElementType x,int i)
{ switch(i)
{ case 0:
if(S->top[0]==-1)
return 0;
*x=S->Stack[S->top[0]];
S->top[0]--; //压入0号栈,栈顶右移
break;
case 1:
if(S->top[1]==M)
return 0;
*x=S->Stack[S->top[0]];
S->top[1]++;
break;
default:return 0; //输入栈号错误
}
}
return 1;
}
链栈
数据类型
typedef struct node
{ StackElementType data;
struct node *next;
}LinkStackNode,*LinkStack;
进栈操作
头插法
int Push(LinkStack top,StackElementType x)
{ LinkStackNode *temp;
temp=(LinkStackNode *)malloc(sizeof(LinkStackNode));
if(temp==NULL)
return 0; //申请失败
temp->data=x; //头插法
temp->next=top->next;
top->next=temp;
return 1;
}
出栈操作
int Pop(LinkStack top,StackElementType *x)
{ LinkStackNode *temp;
temp=top->next;
if(temp==NULL)
return 0; //没有首元结点,不能出栈
top->next=temp->next;
*x=temp->data;
free(temp);
return 1;
}
多栈共享
#define M 10
typedef struct node
{
StackElementType data;
struct node *next;
}LinkStackNode,*LinkStack;
给M个栈每个都使用栈名指针top[0]~top[M-1]确定位置,每个栈元素入栈的话头插法插入即可。
栈的应用
括号匹配问题
若读取到左括号则入栈,等待匹配的右括号;若读取到右括号且与栈顶匹配,则栈顶出栈,否则不合法
for(i=0;str[i]!='\0';i++)
{
switch(str[i]) //仅对左右括号进行判断
{case '(':Push(S,str[i]);break; //发现左括号(,进栈
case '{':Push(S,str[i]);break;
case '[':Push(S,str[i]);break;
case '}':{ if(IsEmpty(S)) //若空栈,肯定没有能跟它对上的
{printf("右括号多余");return ;}
else
{
GetTop(S,c); //获取栈顶并存入c
if(Match(c,str[i])) //将c和现在的 } 进行比较
Pop(S,c); //匹配上了,将栈顶取出
else
{ printf("左右不匹配"); //没匹配上
return ;
}
}
break;
}
case ']':{ if(IsEmpty(S))
{printf("右括号多余");return ;}
else
{
GetTop(S,c);
if(Match(c,str[i]))
Pop(S,c);
else
{ printf("左右不匹配");
return ;
}
}
break;
}
case ')':{ if(IsEmpty(S))
{printf("右括号多余");return ;}
else
{
GetTop(S,c);
if(Match(c,str[i]))
Pop(S,c);
else
{ printf("左右不匹配");
return ;
}
}
break;
}
}
if(IsEmpty(S)) //每次循环都检测,若没有括号进入swich且栈空
printf("匹配成功");
else //若栈里面还有东西但是switch完毕了,那就有多余
printf("左括号多余");
}
表达式求值
根据运算符优先级
无括号情况
建立两个栈,分别存运算数和运算符
具体:www.bilibili.com/video/BV1kx…
栈与递归
递归
特性:
- 分而治之,复杂问题分解为因数更低的简单问题
- 时间效率低
- 如Fortran和basic等语言不支持递归功能
- 一次执行完,处理一些问题时候不合适
递归例题
求m,n两数最大公约数
int fun (int m,int n)
{
int r;
if(n>m)
return (fun(n,m)); //若n>m则调换位置
else if(n==0)
return m; //若n为0,直接返回m
else
{
r=m%n; //r是余数
return (fun(n,r));
}
}
本质是辗转相除法,直到某个余数小于1
更简便,循环更快:
int fun (int m,int n)
{ int r
do{
r=m%n;
m=n;
n=r;
}while(r!=0);
return m;
}
每递归一次都要退一次层
递归类型
单向递归
递归中虽然有多次递归调用,但每次调用之和主调用函数有关,相互之间参数无关,且每次调用语句都在算法最后
- 例:斐波那契数列
递归算法
/*递归计算第i个斐波那契数列,从1算起*/
int fib(int n){
if(n==1||n==2){
return 1;
}
else{
return fib(n-1)+fib(n-2);
}
}
代码解读:第1、第2个斐波那契数列值为1,若i为3,则返回fib(1)+fib(2)这两个值,就是1+1。若i为4,则返回fib(3)+fib(2)这两个值,就是fib(3)+1。这里fib(3)又是由fib(1)+fib(2)得出为2,fib(4)=2+1=3。
非递归算法
不同于上面从1,1,2开始,这里是从0,1,1开始斐波那契数列。
int f(int n)
{
int x,y,z,i;
if(n==0||n==1)
return n;
else
{
x=0;
y=1;
for(i=2;i<=n;i++) //三个数依次是x,y,z eg:2 3 5
{
z=y; //z上存y位置的数 2 3 3
y=x+y; //中间变成前两个之和 2 5 3
x=z; //把最后挪到最前面 3 5 3
}
}
return y;
}
代码解读:z是临时变量。若需要第3位之后,先给最初两位分别为0和1,先将n-1放z存起来(还用得上),将前两个求和,再把z中数值放在最前面(其实就是n)。例子见代码备注
尾递归
递归调用语句只有一个,在算法最后,尾递归是单项递归的特例
- 例:阶乘
递归算法
int Fact(int n)
{ if(n==0);
return 1;
else
return n*Fact(n-1);
}
非递归算法
int fact(int n)
{ int i;
int fac=1;
for(i=1;i<=n;i++){
fac=fac*i;
}
return fac;
}
栈的使用
消除递归:
- 简单递归直接用循环结构代替,不需要隐形栈的调用(例如阶乘,斐波那契)
- 基于栈的方式,将递归中的隐含栈转化为可以控制的明显栈,同时方便保存参数