(总结源自《大话数据结构》,初学数据结构推荐此书)
目录
栈
栈(stack)是限定仅在表尾进行插入和删除操作的线性表
我们把允许插入和删除的一端称为栈顶(top),另一端称为栈底(bottom),不含任何数据元素的栈称为空栈。
栈又称为后进先出(Last In First Out)的线性表,简称LIFO结构。
出栈只能是栈顶元素。
栈的顺序存储结构
既然栈只能由一端进行插入和删除,那么用哪一段呢?
下标为0的一端作为栈底比较好,因为首元素都存在栈底,变化最小,所以它作栈底。
ok,我们定义一个top变量来指示栈顶元素在数组中的位置,而它必须小于栈的长度StackSize。
空栈的判断条件是top=-1,意味着栈存在一个元素时,top=0。
//栈的结构定义
typedef int SElemType;
typedef struct
{
SElemType data[MAXSIZE];
int top; //用于栈顶指针
}SqStack;
进栈操作
如图,进栈操作的思想就是:① 、栈顶指针+1,②、待插入元素赋值给栈顶空间
Status Push(SqStack *s,SElemType e)
{
if(S->top == MAXSIZE-1)
{
return ERROR; //栈满,无法添加
}
S->top++; //① 、栈顶指针+1
S->data[S->top]=e;//②、待插入元素赋值给栈顶空间
return OK;
}
出栈操作
出栈操作的思想很简单:栈顶指针-1即可
Status Pop(SqStack *S)
{
if(S->top ==-1)
return ERROR;
S->top--;
return OK;
}
两栈共享空间
栈很方便,因为不存在线性表的插入和删除时需要移动元素的问题。但它同样有缺陷,因为必须事先确定数组存储空间的大小。对于两个相同类型的栈,我们可以最大限度的利用开辟的存储空间,怎么用呢?就是我们的两栈共享!
怎么个共享法呢?共享共享,一定是多个对象共同使用同一个对象。我们知道,数组有两端,而栈只有一个栈底,那么能不能两端各安一个栈底,往数组之间延伸呢?这就是我们的两栈共享。
typedef struct
{
SElemType data[MAXSIZE];
int top1; //栈1的栈顶指针
int top2; //栈2的栈顶指针
}SqDoubleStack;
ok,现在两个栈共享一个数组空间,那插入的时候如何知道插入到哪个栈呢?所以我们需要有个小东西来执行一个职能:插入之前告诉我,你这个元素要插入到哪个栈,即stackNumber
//插入操作
Status Push(SqDoubleStack *s,SElemType e,int stackNumber)
{
if(S->top1+1==S->top2)
return ERROR; //此时栈满
if(stackNumber==1)
S->data[++S->top1]=e; //往top1+1位置处插入元素
else if(stackNumber==2)
S->data[--S->top2]=e; //往top2-1位置处插入元素
return OK;
}
//删除操作
//若栈不空,删除S的栈顶元素,用e返回其值,并返回OK,否则返回ERROR
Status Pop(SqDoubleStack *S, SElemType *e,int stackNumber)
{
if(stackNumber==1)
{
if(S->top1 ==-1)
retrun ERROR;
*e=S->data[S->top1--];
}
if(stackNumber==2)
{
if(S->top2 ==MAXSIZE)
retrun ERROR;
*e=S->data[S->top2++];
}
return OK;
}
那么有个问题来了:共享归共享,它还是在数组里面,数组还是固定大小的呀,那有什么用呢?所以呀,这种数据结构一般用于两个栈的空间需求有相反关系时,即一个增长时另一个在缩短,若两个都在不停增长,那很快就会因为栈满而溢出。
栈的链式存储结构
我们把栈顶放在单链表的头部,删去头结点,因为栈的链式结构中不需要头结点,没有意义。
//链栈结构代码如下
typedef struct StackNode
{
SElemType data;
struct StackNode *next;
}StackNode,*LinkStackPtr;
typedef struct LinkStack
{
LinkStackPtr top;
int count;
}LinkStack;
进栈操作
进栈的思路:既然栈顶指针代替了头结点,那么栈顶指针肯定是要发挥作用的。
开辟一个新的结点,数据域放新的数据元素,再让它的next指向现在的栈顶指针所指的元素,随后再让栈顶指针指向新结点即可。
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;
}
出栈操作
出栈思路:用一个变量p指向栈顶空间,栈顶指针下移,free掉p
//若栈不空,则删除S的栈顶元素,用e返回其值,并返回OK,否则返回ERROR
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;
}
顺序栈与链栈的进栈和出栈都是O(1)的时间复杂度,而顺序栈长度固定,链栈有指针域,二者都存在内存空间浪费问题。若栈的使用过程中,元素变化不可预料,时大时小,最好用链栈,若变化在可控范围内,建议用顺序栈。