数据结构之线性表4:栈

431 阅读7分钟

栈:允许进行运算端成为栈顶(其实就是表尾),不允许运算的另一端成为栈底

常见操作:入栈(表尾插入),出栈(表尾删除)

特征:后进先出(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;
}

栈的使用

消除递归:

  • 简单递归直接用循环结构代替,不需要隐形栈的调用(例如阶乘,斐波那契)
  • 基于栈的方式,将递归中的隐含栈转化为可以控制的明显栈,同时方便保存参数