栈(Stack)

概念

栈(stack)限定仅在表尾进行插入和删除操作的线性表。允许插入和删除的一端称为栈顶(top),另一端称为栈底(bottom),不含任何数据元素的栈成为空栈,栈又被称为后进先出(Last In First Out)的线性表,简称LIFO结构。

栈的插入操作叫做进栈/压栈/入栈;栈的删除操作叫做出栈/弹栈。

栈的抽象数据类型


ADT 栈(stack)

Data
   同线性表。元素具有相同类型,相邻元素具有前驱和后继关系。
Operation
   InitStack(*S):初始化操作,建立一个空栈。
   DestoryStack(*S):若栈存在,则销毁它。
   ClearStack(*S):将栈清空。
   StackEmpty(S):若栈为空,返回true,否则返回false。
   GetTop(S,*e):若栈存在且非空,用e返回栈顶元素。
   Push(*S,e):若栈S存在,插入新元素e到栈S中并成为栈顶元素。
   Pop(*S,*e):删除栈顶S中的栈顶元素,并用e返回其值。
   SrackLength(S):返回栈S的元素个数。
 endADT  

顺序存储结构

栈是线性表的特例,用数组实现栈的顺序存储结构,选取下标为0的一端作为栈底,定义一个top变量来指示栈顶元素在数组中的位置,相当于游标,游标可以来回移动,但是不能超出数组范围。当栈存在一个元素时,top=0,所以将空栈的判定条件设定为top等于-1(数组下标从0开始)。

结构定义如下所示:

typedef int SElemType; //根据实际情况确定,这里设置为int
typedef struct{
   SElemType data[MAXSIZE];
   int top; //用于指示栈顶元素位置
}SqStack;

进栈操作

/*插入元素e作为新的栈顶元素*/
Status Push(SqStack *S,SElemType e){
   if(S->top >= MAXSIZE-1) return ERROR; //栈满
   //注意栈顶指针指向栈顶元素,当前是有数据的,所以要先上移指针。
   S->top++; //先将栈顶指针上移,再插入新的元素
   S->data[S->top]=e;
   return OK;
}

出栈操作

/*若栈不为空,则删除S的栈顶元素,用e返回其值,并返回OK;否则返回ERROR*/
Status Pop(SqStack *S,SElemType *e){
   if(S->top == -1) return ERROR; //栈为空
   *e=S->data[S->top]; //读取栈顶元素的值
   S->top--; //下移栈顶指针
   return OK;
}

扩展–两栈共享空间

用一个数组来实现两个栈,一个栈的栈底在数组的始端(下标为0),另一个栈的栈底在数组的末端(下标为n-1),用两个top指针来指示栈顶位置。(位于两端,向中间靠拢)

栈1为空:top1等于-1 ; 栈2为空:top2等于n ;

栈满:两指针之间相差1,top1+1==top2.

两栈共享空间的结构和相关方法的代码如下:

/*两栈共享空间结构*/
typedef struct{
   SElemType data[MAXSIZE];
   int top1;  //栈1栈顶指针
   int top2;  //栈2栈顶指针
}SqDoubleStack;

/*插入元素e为新的栈顶元素,压栈*/
Status Push(SqDoubleStack *S,SElemType e,int stackNumber){
   //添加判定参数stackNumber来判断栈1还是栈2

   if(S->top1+1 == S->top2) return ERROR; //栈满

   if(stackNumber==1){//栈1入栈
      //可简写为:S->data[++S->top1]=e;
      S->top1++;
      S->data[S->top1]=e;
   }else if(stackNumber== 2){//栈2入栈
      //可简写为:S->data[--S->top]=e;
      S->top2--;
      S->data[S->top]=e;
   }
   return OK;
}

/*若栈不为空,删除S的栈顶元素,用e返回其值,并返回OK;否则返回ERROR*/
Status Pop(SqDoubleStack *S,SElemType *e,int stackNumber){    
   if(stackNumber==1){ //栈1出栈,首先判断是否为空,再进行出栈操作
      if(S->top1==-1)  return ERROR; //栈为空
      *e=S->data[S->top1--]; //先取栈顶元素,在变更栈顶指针
   }else 
   if(stackNumber== 2){//栈1出栈,首先判断是否为空,再进行出栈操作
      if(S->top2==MAXSIZE) return ERROR; //栈为空
      *e=S->data[S->top2++]; //先取栈顶元素,在变更栈顶指针
   }
   return OK;
}

小结:当两个栈的空间需求有相反关系时,即一个出栈一个入栈,例如买卖。针对两个相同类型的栈的设计技巧,不同类型的话使用该方法会变得更复杂,不适用。

链式存储结构

将头指针与栈顶指针合并成栈顶指针,所以将栈顶放置于链表头部。判断空栈的条件是:top==NULL。

结构定义如下所示:

typedef struct StackNode{
   SElemType data;
   struct StackNode *next;
}StackNode,*LinkStackPtr;

typedef struct LinkStack{ //栈顶指针,栈元素总数
   LinkStackPtr top;
   int count;
}LinkStack;

进栈操作

/*插入元素e作为新的栈顶元素*/
Status Push(LinkStack *S,SElemType e){
   //先生成一个结点,再进行压栈操作
   LinkStackPtr s=(LinkStackPtr)malloc(size(StackNode));
   s->data=e;  //新结点值
   s->next=S->top;  //新结点后继指针
   S->top=s;  //修改栈顶指针位置
   S->count++;  //栈中元素总数+1
   return OK;
}

出栈操作

/*若栈不为空,则删除S的栈顶元素,用e返回其值,并返回OK;否则返回ERROR*/
Status Pop(LinkStack *S,SElemType *e){
   LinkStackPtr p; //保存要删除的结点
   //判断栈是否为空 S->top->next==NULL
   if(StackEmpty(*S)) return ERROR;  
   *e=S->top->data;  //读取栈顶元素值
   p=S->top;  //删除结点
   S->top=S->top->next;  //移动栈顶指针
   free(p);  //释放结点
   S->count--;  
   return OK;
}

顺序栈和链表栈的对比: 时间复杂度一样,均为 O(1) 顺序栈需要事先确定固定长度,可能产生空间浪费问题,但是它存取时定位方便链表栈要求元素都有指针域,增加内存开销,但是对长度无限制

小结:若栈的使用过程中元素的变化不可预料,有时小有时大,最好使用链表栈,反之,若变化在可控范围内,使用顺序栈会更好些。

你可能感兴趣的:(数据结构)