3.1栈【顺序栈和链栈】

14 篇文章 0 订阅
10 篇文章 0 订阅

3.1.1 栈的定义

是一种只能在一端进行插入或删除操作的线性表。

栈只能选取同一个端点进行插入和删除操作。

栈的几个概念

  • 允许进行插入、删除操作的一端称为栈顶。
  • 表的另一端称为栈底。
  • 当栈中没有数据元素时,称为空栈。
  • 栈的插入操作通常称为进栈或入栈。
  • 栈的删除操作通常称为退栈或出栈。

栈的主要特点是“后进先出”,即后进栈的元素先出栈。栈也称为后进先出表

示例:

    设一个栈的输入序列为a,b,c,d,则借助一个栈所得到的输出序列不可能是(  )。     A. c,d,b,a        B. d,c,b,a       C. a,c,d,b        D. d,a,b,c     

        

        答案为D

    【例3.3】一个栈的入栈序列为1,2,3,…,n ,其出栈序列是p1,p2,p3,…,pn。若p1=3,则p2可能取值的个数是多少?    

A.n-3        B.n-2      C.n-1       D. 无法确定 

        

        答案为B

 栈抽象数据类型=逻辑结构+基本运算(运算描述) 

栈的几种基本运算如下:    

  InitStack(&s):初始化栈。构造一个空栈s。

  DestroyStack(&s):销毁栈。释放栈s占用的存储空间。

  StackEmpty(s):判断栈是否为空:若栈s为空,则返回真;否则返回假。

  Push(&S,e):进栈。将元素e插入到栈s中作为栈顶元素。

  Pop(&s,&e):出栈。从栈s中退出栈顶元素,并将其值赋给e。

  GetTop(s,&e):取栈顶元素。返回当前的栈顶元素,并将其值赋给e。 

3.1.2 栈的顺序存储结构及其基本运算实现

 栈中元素逻辑关系与线性表的相同,栈可以采用与线性表相同的存储结构。 

假设栈的元素个数最大不超过正整数MaxSize,所有的元素都具有同一数据类型ElemType,则可用下列方式来声明顺序栈类型SqStack:    

typedef struct 
{  ElemType data[MaxSize]; 
   int top;		//栈顶指针
}  SqStack;

 (1)初始化栈InitStack(&s)    建立一个新的空栈s,实际上是将栈顶指针指向-1即可。

void InitStack(SqStack *&s)
{  s=(SqStack *)malloc(sizeof(SqStack));
   s->top=-1;
} 

(2)销毁栈DestroyStack(&s)      释放栈s占用的存储空间。 

void DestroyStack(SqStack *&s)
{
  free(s);
}

(3)判断栈是否为空StackEmpty(s)      栈S为空的条件是s->top==-1。

bool StackEmpty(SqStack *s)
{
  return(s->top==-1);
}

 (4)进栈Push(&s,e)    在栈不满的条件下,先将栈指针增1,然后在该位置上插入元素e

bool Push(SqStack *&s,ElemType e)
{  if (s->top==MaxSize-1) 	//栈满的情况,即栈上溢出
	return false;
   s->top++;		   	//栈顶指针增1
   s->data[s->top]=e;	   	//元素e放在栈顶指针处
   return true;
}

(5)出栈Pop(&s,&e)      在栈不为空的条件下,先将栈顶元素赋给e,然后将栈指针减1。

bool Pop(SqStack *&s,ElemType &e)
{  if (s->top==-1)		//栈为空的情况,即栈下溢出
	return false;
   e=s->data[s->top];		//取栈顶指针元素的元素
   s->top--;			//栈顶指针减1
   return true;
}

(6)取栈顶元素GetTop(s,&e)      在栈不为空的条件下,将栈顶元素赋给e。

bool GetTop(SqStack *s,ElemType &e)
{   if (s->top==-1)	   	//栈为空的情况,即栈下溢出            
       return false;
    e=s->data[s->top];   	//取栈顶指针元素的元素
    return true;
}

 ·顺序栈的应用算法设计

   【例3.4】 设计一个算法利用顺序栈判断一个字符串是否是对称串。所谓对称串是指从左向右读和从右向左读的序列相同。 

 

bool symmetry(ElemType str[])
{  int i;  ElemType e; 
   SqStack *st;
   InitStack(st);			//初始化栈

   for (i=0;str[i]!='\0';i++)		//将串所有元素进栈
      Push(st,str[i]);		//元素进栈

   for (i=0;str[i]!='\0';i++)
   {  Pop(st,e);			//退栈元素e
      if (str[i]!=e)	 		//若e与当前串元素不同则不是对称串
      {  DestroyStack(st);		//销毁栈
	  return false;
      }
   }

   DestroyStack(st);	 		//销毁栈
   return true;
}

 设计一个算法,利用栈将一个正十进制整数转换为二进制数并输出。

#include "sqstack1.cpp"     //包含整数栈的基本运算函数
void trans(int n)
{
  int e;
  SqStack *st;
  InitStack(st);
  while (n>0)
  {  Push(st,n%2);
     n=n/2;
  }
  while (!StackEmpty(st))   //输出对应的二进制数
  {  Pop(st,e);
     printf("%d",e);
  }
  printf("\n");
  DestroyStack(st);
}

示例

            假设输入序列是1、2、…、n。设计一个算法判断通过一个栈能否得到由a[0..n-1](为1、2、…、n的某个排列)指定出栈序列。 

        例如,n=3,

        a[]={2,3,1}是合法出栈序列

        a[]={3,1,2}不是合法出栈序列

bool Validseq(int a[],int n)
{  int i,e,k=0;			//k扫描a的元素
   bool flag;
   SqStack *st;
   InitStack(st);
   for(i=1;i<=n;i++)			//处理输入序列1、2、…、n
   {  Push(st,i);			//i进栈
      while (!StackEmpty(st))	 	//栈不空循环
      {  GetTop(st,e);		//取栈顶元素e
         if (a[k]==e)			//匹配的情况
         {  Pop(st,e);
            k++;
	  }
	  else break;			//不匹配时退出while循环
      }
   }
   flag=StackEmpty(st);
   DestroyStack(st);
   return flag;
}

 共享栈

    若需要用到两个相同类型的栈,可用一个数组data[0..MaxSize-1]来实现这两个栈,这称为共享栈。

 

共享栈类型:

typedef struct
{  ElemType data[MaxSize];	//存放共享栈中元素
   int top1,top2;		//两个栈的栈顶指针
} DStack;	

共享栈的4要素:

  1. 栈空条件,栈1空:top1==-1;栈2空:top2==MaxSize;
  2. 栈满条件:top1==top2-1;
  3. 元素x进栈操作,进栈1操作:top1++;data[top1]=x;进栈2操作:top2--;data[top2]=x;
  4. 出栈x操作,出栈1操作,x=data[top1];top1--;出栈2操作,x=data[top2];top2++;

3.1.3 栈的链式存储结构及其基本运算的实现

采用链表存储的栈称为链栈,这里采用带头结点的单链表实现。

链栈的4要素: 

  1.  栈空条件:s->next=NULL  
  2. 栈满条件:不考虑  
  3. 进栈e操作:将存放e的结点插入到头结点之后  
  4. 退栈操作:取出头结点之后结点的元素并删除之

链栈中数据结点的类型LinkStNode声明如下:  

typedef struct linknode
{   ElemType data;		//数据域
    struct linknode *next;	//指针域
}  LinkStNode;

 在链栈中,栈的基本运算算法如下:

(1)初始化栈initStack(&s)       建立一个空栈s。实际上是创建链栈的头结点,并将其next域置为NULL。

void InitStack(LinkStNode *&s)
{  s=(LinkStNode *)malloc(sizeof(LinkStNode)); 
   s->next=NULL;
}

 (2)销毁栈DestroyStack(&s)      释放栈s占用的全部存储空间。

void DestroyStack(LinkStNode *&s)
{  LinkStNode *p=s,*q=s->next;
    while (q!=NULL)
    {	free(p);
	p=q;
	q=p->next;
    }
    free(p);	//此时p指向尾结点,释放其空间
}

 (3)判断栈是否为空StackEmpty(s)      栈S为空的条件是s->next==NULL,即单链表中没有数据结点。

bool StackEmpty(LinkStNode *s)
{
    return(s->next==NULL);
}

 (4)进栈Push(&s,e)     将新数据结点插入到头结点之后。

void Push(LinkStNode *&s,ElemType e)
{  LinkStNode *p;
   p=(LinkStNode *)malloc(sizeof(LinkStNode));
   p->data=e;		//新建元素e对应的结点p
   p->next=s->next;	//插入p结点作为开始结点
   s->next=p;
}

 (5)出栈Pop(&s,&e)      栈非空时,将头结点后继数据结点的数据域赋给e,然后将其删除。

bool Pop(LinkStNode *&s,ElemType &e)
{  LinkStNode *p;
   if (s->next==NULL)		//栈空的情况
     return false;
   p=s->next;			//p指向开始结点
   e=p->data;
   s->next=p->next;		//删除p结点
   free(p);			//释放p结点
   return true;
}

(6)取栈顶元素GetTop(s,e)      在栈不为空的条件下,将头结点后继数据结点的数据域赋给e。

bool GetTop(LinkStNode *s,ElemType &e)
{  if (s->next==NULL)		//栈空的情况
     return false;
   e=s->next->data;
   return true;
}

    【例3.5】编写一个算法判断输入的表达式中括号是否配对(假设只含有左、右圆括号)。

算法设计思路

   一个表达式中的左右括号是按最近位置配对的。所以利用一个栈来进行求解。这里采用链栈。 

 

bool Match(char exp[],int n)
{  int i=0; char e;  

   bool match=true; 

   LinkStNode *st;

   InitStack(st);			//初始化栈
   while (i<n && match)		//扫描exp中所有字符
   { 
      if (exp[i]=='(')
          Push(st,exp[i]);


      else if (exp[i]==')') 		//当前字符为右括号
      {  if (GetTop(st,e)==true)
	  {  if (e!='(')	   	//栈顶元素不为'('时不匹配
		match=false;
	     else
		Pop(st,e);    	//将栈顶元素出栈
	  }
	  else match=false;  		//无法取栈顶元素时不匹配
      }

      i++;				//继续处理其他字符
  }

    if (!StackEmpty(st))	
	match=false;

    DestroyStack(st);	     	//销毁栈
    return match;
}

 注意:只有在表达式扫描完毕且栈空时返回true。

3.1.4 栈的应用 

栈和队列都是存放多个数据的容器。通常用于存放临时数据:

如果后放入的数据先处理,则使用

如果先放入的数据先处理,则使用。 

1. 简单表达式求值 

问题描述      :

    这里限定的简单表达式求值问题是:用户输入一个包含“+”、“-”、“*”、“/”、正整数和圆括号的合法算术表达式,计算该表达式的运算结果。简单表达式采用字符数组exp表示,其中只含有“+”、“-”、“*”、“/”、正整数和圆括号。    为了方便,假设是合法的算术表达式,例如,exp=“1+2*(4+12)”;    在设计相关算法中用到栈,这里采用顺序栈存储结构。

    运算符位于两个操作数中间的表达式称为中缀表达式。例如,"1+2*3"就是一个中缀表达式。

    中缀表达式的运算规则:“先乘除,后加减,从左到右计算,先括号内,后括号外”。     因此,中缀表达式不仅要依赖运算符优先级,而且还要处理括号。

    算术表达式的另一种形式是后缀表达式或逆波兰表达式,就是在算术表达式中,运算符在操作数的后面。     如"1+2*3"的后缀表达式为"1 2 3 * +"。

 后缀表达式特点:

  1. 没有括号,已考虑了运算符的优先级。
  2. 只有操作数和运算符,而且越放在前面的运算符来越优先执行。 

 中缀表达式的求值过程:

  1. 将中缀算术表达式转换成后缀表达式。
  2. 对该后缀表达式求值。 

 

while (从exp读取字符ch,ch!='\0')
{  ch为数字:将后续的所有数字均依次存放到postexp中,
            并以字符'#'标志数值串结束;
   ch为左括号'(':将此括号进栈到Optr中;
   ch为右括号')':将Optr中出栈时遇到的第一个左括号'('以前的运算符依次出
            栈并存放到postexp中,然后将左括号'('出栈;
   ch为其他运算符:
       if (栈空或者栈顶运算符为'(') 直接将ch进栈;
       else if (ch的优先级高于栈顶运算符的优先级)
	     直接将ch进栈;
       else
	     依次出栈并存入到postexp中,直到栈顶运算符优先级小于ch的
            优先级,然后将ch进栈;
}
若exp扫描完毕,则将Optr中所有运算符依次出栈并存放到postexp中。

针对简单表达式exp: 

while (从exp读取字符ch,ch!='\0')
{   ch为数字:将后续的所有数字均依次存放到postexp中,
             并以字符'#'标志数值串结束;
    ch为左括号'(':将此括号进栈到Optr中;
    ch为右括号')':将Optr中出栈时遇到的第一个左括号'('以前的运算符
             依次出栈并存放到postexp中,然后将左括号'('出栈;
    ch为'+'或'-':出栈运算符并存放到postexp中,直到栈空或者栈顶为'(',
             然后将'+'或'-'进栈;
    ch为'*'或'/':出栈运算符并存放到postexp中,直到栈空或者栈顶
             为'('、'+'或'-',然后将'+'或'-'进栈;
}
若exp扫描完毕,则将Optr中所有运算符依次出栈并存放到postexp中。

算法:将算术表达式exp转换成后缀表达式postexp。

void trans(char *exp,char postexp[])		
{  char e;  
   SqStack *Optr;			//定义运算符栈指针
   InitStack(Optr);			//初始化运算符栈
   int i=0;				//i作为postexp的下标
   while (*exp!='\0')			//exp表达式未扫描完时循环
   {   switch(*exp)
       {
       case '(':			//判定为左括号
	   Push(Optr,'(');		//左括号进栈
	   exp++;			//继续扫描其他字符
	   break;
        case ')':		   	//判定为右括号
	    Pop(Optr,e);	    	//出栈元素e
	    while (e!='(')	    	//不为'('时循环
	    {  postexp[i++]=e;	//将e存放到postexp中
		Pop(Optr,e);	   	//继续出栈元素e
	    }
	    exp++;		     	//继续扫描其他字符
	    break;
         case '+':				//判定为加或减号
         case '-':
	      while (!StackEmpty(Optr))	//栈不空循环
             {   GetTop(Optr,e);		//取栈顶元素e
                 if (e!='(')			//e不是'('
                 {  postexp[i++]=e;		//将e存放到postexp中
	             Pop(Optr,e);		//出栈元素e
                 }
                 else				//e是'(时退出循环
		      break;
	      }
	      Push(Optr,*exp);		//将'+'或'-'进栈
	      exp++;				//继续扫描其他字符
	      break;

   case '*':				//判定为'*'或'/'号
   case ‘/’:
       while (!StackEmpty(Optr))	//栈不空循环
       {  GetTop(Optr,e);		//取栈顶元素e
	   if (e=='*' || e=='/')
	   {   postexp[i++]=e;	//将e存放到postexp中
	       Pop(Optr,e);		//出栈元素e
	   }
	   else				//e为非‘*’或‘/’时退出循环
	       break;
	}
	Push(Optr,*exp);		//将'*'或'/'进栈
	exp++;				//继续扫描其他字符
	break;
	default:				//处理数字字符
	   while (*exp>='0' && *exp<='9')    //判定为数字字符
	   {  postexp[i++]=*exp;
	      exp++;
	   }
	   postexp[i++]='#';	  	//用#标识一个数值串结束
	}
    }
    while (!StackEmpty(Optr))	  	//此时exp扫描完毕,栈不空时循环
    {  Pop(Optr,e);		 	//出栈元素e
       postexp[i++]=e;	  	//将e存放到postexp中
    }
    postexp[i]='\0';		  	//给postexp表达式添加结束标识
    DestroyStack(Optr);	  	//销毁栈
}

while (从postexp读取字符ch,ch!='\0')
{   ch为'+':从Opnd栈中出栈两个数值a和b,计算c=b+a;将c进栈;
    ch为'-':从Opnd栈中出栈两个数值a和b,计算c=b-a;将c进栈;
    ch为'*':从Opnd栈中出栈两个数值a和b,计算c=b*a;将c进栈;
    ch为'/':从Opnd栈中出栈两个数值a和b,若a不零,计算c=b/a;将c进栈;
    ch为数字字符:将连续的数字串转换成数值d,将d进栈;
}
返回Opnd栈的栈顶操作数即后缀表达式的值;

 算法:计算后缀表达式postexp的值。

double compvalue(char *postexp)	
{  double d, a, b, c, e;
   SqStack1 *Opnd;			//定义操作数栈
   InitStack1(Opnd);			//初始化操作数栈
   while (*postexp!='\0')		//postexp字符串未扫描完时循环
   {  switch (*postexp)
      {
      case '+':			//判定为'+'号
          Pop1(Opnd,a);		//出栈元素a
          Pop1(Opnd,b);		//出栈元素b
          c=b+a;			//计算c
          Push1(Opnd,c);		//将计算结果c进栈
          break;
	case '-':			//判定为'-'号
	   Pop1(Opnd,a);		//出栈元素a
	   Pop1(Opnd,b);		//出栈元素b
	   c=b-a;			//计算c
	   Push1(Opnd,c);		//将计算结果c进栈
	   break;
	case '*':			//判定为'*'号
	   Pop1(Opnd,a);		//出栈元素a
	   Pop1(Opnd,b);		//出栈元素b
	   c=b*a;			//计算c
	   Push1(Opnd,c);		//将计算结果c进栈
	   break;
	case '/':				//判定为'/'号
	   Pop1(Opnd,a);			//出栈元素a
	   Pop1(Opnd,b);			//出栈元素b
	   if (a!=0)
	   {  c=b/a;				//计算c
	      Push1(Opnd,c);			//将计算结果c进栈
	      break;
	   }
	   else
	   {   printf("\n\t除零错误!\n");
	       exit(0);			//异常退出
	   }
	   break;
	default:		//处理数字字符
           d=0;		//转换成对应的数值存放到d中
	    while (*postexp>='0' && *postexp<='9')
	    {	d=10*d+*postexp-'0';
		postexp++;
	    }
	    Push1(Opnd,d);	//将数值d进栈
	    break;
       }
       postexp++;		//继续处理其他字符
   }
   GetTop1(Opnd,e);		//取栈顶元素e
   DestroyStack1(Opnd);	//销毁栈
   return e;			//返回e
}

  设计求解程序

建立如下主函数调用上述算法: 

int main()
{   char exp[]="(56-20)/(4+2)";	   //可将exp改为键盘输入
    char postexp[MaxSize];
    trans(exp,postexp);
    printf("中缀表达式:%s\n",exp);
    printf("后缀表达式:%s\n",postexp);
    printf("表达式的值:%g\n",compvalue(postexp));
    return 0;
}

2、用栈求解迷宫问题 

  问题描述      

    给定一个M×N的迷宫图、入口与出口、行走规则。求一条从指定入口到出口的路径。     所求路径必须是简单路径,即路径不重复。 

 

在算法中用到的栈采用顺序栈存储结构,即将栈声明为:

typedef struct
{  int i;			//当前方块的行号
   int j;			//当前方块的列号
   int di;			//di是下一可走相邻方位的方位号
} Box;				//定义方块类型
typedef struct
{  Box data[MaxSize];
   int top;			//栈顶指针
}  StType;			//声明顺序栈类型

 

 

用栈求一条迷宫路径的算法: (xi,yi)--》 (xe,ye) 

bool mgpath(int xi,int yi,int xe,int ye)
{  Box path[MaxSize], e; int i,j,di,i1,j1,k;  bool find;
   StType *st;				//定义栈st
   InitStack(st);			//初始化栈顶指针
   e.i=xi; e.j=yi; e.di=-1;		//设置e为入口
   Push(st,e);				//方块e进栈
   mg[xi][yi]=-1;			//入口的迷宫值置为-1避免重复
while (!StackEmpty(st))		//栈不空时循环
{  GetTop(st,e);			//取栈顶方块e
   i=e.i; j=e.j; di=e.di;
   if (i==xe && j==ye)		//找到了出口,输出该路径
   {  printf("一条迷宫路径如下:\n");
      k=0;
      while (!StackEmpty(st))
      {  Pop(st,e);			//出栈方块e
	  path[k++]=e;			//将e添加到path数组中
      }
    while (k>=1)
    {  k--;				         
       printf("\t(%d,%d)",path[k].i,path[k].j);
          if ((k+2)%5==0)  	//每输出每5个方块后换一行
       printf("\n");
    }
    printf("\n");
    DestroyStack(st);    	//销毁栈
    return true;		//输出一条迷宫路径后返回true
  }
   find=false;
   while (di<4 && !find)   //找相邻可走方块(i1,j1)
   {	di++;
	switch(di)
	{
	case 0:i1=i-1; j1=j;    break;
	case 1:i1=i;   j1=j+1;  break;
	case 2:i1=i+1; j1=j;    break;
	case 3:i1=i;   j1=j-1;  break;
	}
	if (mg[i1][j1]==0) find=true;	
	        //找到一个相邻可走方块,设置find为真
   }
    if (find)  		     	//找到了一个相邻可走方块(i1,j1)
    {   st->data[st->top].di=di;  	//修改原栈顶元素的di值
        e.i=i1; e.j=j1; e.di=-1;
        Push(st,e);		     	//相邻可走方块e进栈
        mg[i1][j1]=-1;	
					//(i1,j1)迷宫值置为-1避免重复
    }
    else			//没有路径可走退栈
    {	Pop(st,e);		//将栈顶方块退栈
	mg[e.i][e.j]=0;	
	    			//让退栈方块变为1
    }
  }
  DestroyStack(st);		//销毁栈
  return false;		//表示没有可走路径
}

  设计求解程序

建立如下主函数调用上述算法: 

int main()
{  if  (!mgpath(1,1,M,N))
      printf("该迷宫问题没有解!");
   return 1;
}


本节完. 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

万叶学编程

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值