【学习笔记----数据结构05-栈与队列】

来源:互联网 发布:微信公众号对接数据库 编辑:程序博客网 时间:2024/04/30 06:12

栈是限定仅在表尾进行插入和删除操作的线性表。队列是只允许在一端进行插入操作、而在别一端进行删除的操作的线性表。

栈的定义

栈是限定仅在表尾进行插入和删除操作的线性表。我们把允许插入和删除的一端称为栈顶。另一端称为栈底,不含任何数据元素的栈称为空栈。又称后进先出线性表(LIFO结构)

理解:首先它是一个线性表,也就是说,栈元素具有线性关系,即前驱后继关系。只不过它是一种特殊的线性表而已。特殊之处在于限制了这个线性表的插入和删除位置,它始终只在栈顶进行。这也就使得:栈底是固定的,最先栈的只能在栈底。
栈的插入操作,叫作进栈,也称压栈、入栈。
栈的删除操作,也叫出栈,也有叫作弹栈。

栈的抽象数据类型

ADT 栈(stack)
Data
同线性表。元素具胡相同类型,相邻元素具有前驱和后继关系
Operation
InitStack(*S);初始化操作,建立一个空栈S
DestroyStack(*S):若栈存在,则销毁它
ClearStack(S):将栈清空
StackEmpty(S):若栈为空返回true,否则返回false
GetTop(S,*e):若栈存在且非空,用e返回S的栈顶元素
Push(*S,e):若栈存在,输入新元素e到栈S中并成为栈顶元素
Pop(*S,*e):删除栈S中栈顶元素,并用e返回其值
StackLength(S):返回栈S的元素个数
endADT
由于栈本身就是一个线性表,那么前面学到的线性表的顺序存储和链式存储,对于栈来说,也是同样适用的。

栈的顺序存储结构及实现

既然栈是线性表的特例,那么栈的顺序存储其实也是线性表表顺序存储的简化,我们简称为顺序栈。线性表是用数组来实现的,想想看,对于栈这种只能一头插入删除的线性表来说,用数组哪一端来作为栈顶和栈底较好?对没错,下标为0的一端作为栈底比较好,因为首元素都存在栈底,变化最小,所以让它作栈底。我们定义一个top变量来指示栈顶元素在数组中的位置。若存储栈的长度为StackSize,则栈顶位置top必须小于StackSize。当栈存在一个元素时,top等于0,因此通常把空栈的判断条件定为top等于-1;
栈的定义:

typedef    int   SElemTypetypedef   struct{    SElemType   data[MAXSIZE];    int   top;}

进栈操作

Status  Push(SqStack  *S,SElemType  e){   if(S->top==MAXSIZE-1)//栈满    {         return  ERROR;     }     S->top++;     S->data[S->top] = e;     return OK;}

出栈操作

Status Pop(SqStack *S,SElemType  *e){   if(S->top==-1) return ERROR;   *e=S->data[S->top];     S->top--;     return OK;}

进栈出栈没有涉及到任何循环语句,因此时间复杂度均是O(1)

两栈共享空间

如果我们有两个相同类型的栈,我们为它们各自开辟了数组空间,极有可能是第一个栈已经满了,再进栈就举出了,而另一个栈还有很多存储空间空闲。这又何必呢,我们完全可以用一个数组来存储两个栈,只不过需要点小技巧。
数组有两个端点,两个栈有两个栈底,让一个栈底为数组的始端,即为下标0处,另一个栈为数组的末端,即下标为数组长度n-1处。这样,两个栈如果增加元素,就是两个端点向中间延伸。
关键思路是:它们是数组的两端,向中间靠拢。top1和top2是栈1和栈2的栈顶指针,可以想象,只要它们不见面就可以一直使用:
栈1空时,top1等于-1;
栈2空时,top2等于n
极端情况下栈2空,栈1的top1等于n-1时,就是栈1满。反之,当栈1为空时,top2等于0时,为栈2满。但更多的情况,其实就是我刚才说的,两个栈见面之时,也就是两个指针之间相差1时,即top1+1 = top2为栈满
代码如下:
//两栈共享空间结构

typedef  struct{   SElemType  data[MAXSIZE];   int   top1;   int   top2;}SqDoubleStack;

//压栈时,除了插入元素值参数外,还需要有一个判断是栈1还是栈2的栈号参数stackNumber

Status  Push(SqStack  *S,SElemType  e, int  stackNumber){   if(S->top1+1==S->top2)//栈满    {         return  ERROR;     }     if(stackNumber==1){          //栈1          S->data[++S->top1] = e;     }else if(stackNumber==2){          S->data[--S->top2] = e;     }     return OK;}Status Pop(SqDoubleStack  *S,SElemType  *e,int  stackNumber){    if(stackNumber==1){       if(S->top1==-1) return ERROR;       *e = S->data[S->top1--];    }else if(stackNumber==2){       if(S->top2==MAXSIZE) return ERROR;       *e = S->data[S->top2++];    }    return OK;}

栈的链式存储结构及实现

都已经有了栈顶在头部了,单链表中比较常用的头结点也就失去了意义,通常对于链栈来说,不需要头结点的。

对于链栈来说,基本不存在栈满的情况,除非内存已经滑可以使用的空间,如果真的发生,那么此时的计算机系统已面临死机崩溃的情况,而不是这个链栈是否溢出的问题。
代码结构如下:

typedef  struct  StackNode{   SElemType  data;   struct StackNode  *next;} StackNode,*LinkStackPtrtypedef  struct  LinkStack{      LinkStackPtr   top;      int   count;} LinkStack;

链栈—进栈

Status  Push(LinkStack *S,SElemType  e){LinkStackPtr  s = (LinkStackPtr  )malloc(sizeof(StackNode));s->data=e;s->next =S->top;S->top = s;S->count++;return OK;}

链栈—出栈

Status  Pop(LinkStack  *S,SElemType  *e){    LinkStackPtr  p;    if(StackEmpty(*S))       return ERROR;     *e = S->top->data;      p=S->top;      S->top = S->top->next;      free(p);      S->count--;      return OK;}

对于空间性能,顺序栈需要确定一个固定长度,可能会存在内存空间浪费的问题,但它的优势是存取时定位很方便,而链栈则要求每个元素都有指针域,这同时也增加了一些内存的开销,但对于栈的长度无限制。

即:如果栈的使用过程中元素变化不可预料,有时很小,有时很大,那么最好是用链栈,反之,如果它的变化的可控范围内,建议使用顺序栈会更好一些。

0 0
原创粉丝点击