数据结构之美(六)栈的顺序存储结构和链式存储结构

(总结源自《大话数据结构》,初学数据结构推荐此书)

目录

栈的顺序存储结构

进栈操作

出栈操作

两栈共享空间

栈的链式存储结构

进栈操作

出栈操作



栈(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)的时间复杂度,而顺序栈长度固定,链栈有指针域,二者都存在内存空间浪费问题。若栈的使用过程中,元素变化不可预料,时大时小,最好用链栈,若变化在可控范围内,建议用顺序栈。

发布了38 篇原创文章 · 获赞 6 · 访问量 1907

猜你喜欢

转载自blog.csdn.net/weixin_43827227/article/details/100856860