【数据结构】栈(顺序存储、链式存储)

本文介绍了栈的基本概念,包括其后进先出的特点和两种存储结构:顺序存储和链式存储。顺序栈利用数组实现,进栈和出栈操作通过调整栈顶指针完成。链栈则采用链表结构,进栈是将新节点插入链表前端,出栈则是删除栈顶节点。此外,还讨论了两栈共享空间以提高空间利用率的方法。

开发板推荐:天空星STM32F407VET6开发板

超高性价比 STM32主控 | 超高主频 | 一板兼容百芯 | 比赛神器 | 沉金彩色丝印



前言

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

栈的插入操作,叫做进栈,也称为压栈、入栈

栈的删除操作,叫做出栈,也称为弹栈

进出栈



栈的顺序存储结构

顺序存储就是用数组来实现的栈,是一种特殊的线性表,可以简称为顺序栈

栈的结构定义如下:

#define MAXSIZE (100) //栈的最大存储数据量

typedef struct STACK
{
    int data[MAXSIZE]; //用于存储数据的数组
    int top; //栈顶指针
}SqStack;

进栈操作如下:

(1)检查是否满栈,满栈则退出并返回错误;

(2)栈顶指针top向上移动一位,将要插入的数据赋给栈顶。

typedef enum
{
    FALSE = 0,
    TRUE
}bool;

bool Push(SqStack *S, int data)
{
    if (S->top == MAXSIZE - 1) //满栈
    {
        return FALSE;
    }

    S->top++; //栈顶指针加一
    S->data[S->top] = data; //将数据赋给栈顶
    return TRUE;
}

出栈操作如下:

(1)检查是否为空栈,若是则退出并返回错误;

(2)将栈顶数据赋给*data函数参数的传递是必须要用指针的,C++最好用引用),栈顶指针向下移动一位。

bool Pop(SqStack *S, int *data)
{
    if (S->top == -1) //空栈
    {
        return FALSE;
    }

    *data = S->data[S->top]; //将栈顶数据赋给data
    S->top--; //栈顶指针减一
    return TRUE;
}

验证一下:

int main(void)
{
    SqStack S;
    S.top = -1;
    int data1;

    for (int i = 0; i < 10; i++)
    {
        Push(&S, i); //向栈中放入0-9的数
    }


    while (Pop(&S, &data1)) //取出栈中所有数据,并打印
    {
        printf("%d ", data1);
    }

    return 0;
}

栈例程1



两栈共享空间

将数组的两端作为两个栈的栈底,栈顶向中间靠拢,那么相比定义两个数组,可以提高空间的利用率。

共享栈

栈的结构定义如下:

typedef struct DOUBLESTACK
{
    int data[MAXSIZE];
    int top1; // 栈1栈顶指针
    int top2; // 栈2栈顶指针
}SqDoubleStack;

进栈操作如下:

(1)当top1top2相邻时,满栈,退出并返回错误;

(2)StackNo是选择将数据放入栈1还是栈2。

bool Push_Double(SqDoubleStack *S, int data, int StackNo)
{
    if (S->top1 + 1 == S->top2) //满栈
    {
        return FALSE;
    }

    if (1 == StackNo) // 判断是放入那个栈中
    {
        S->top1++;
        S->data[S->top1] = data;
    }
    else
    {
        S->top2--;
        S->data[S->top2] = data;
    }

    return TRUE;
}

出栈操作如下:

(1)根据StackNo判断从栈1或栈2出栈;

(2)判断栈1或栈2是否为空栈,若为空,则返回错误,否则将栈顶元素赋给*data,并向栈底移动top指针。

bool Pop_Double(SqDoubleStack *S, int *data, int StackNo)
{
    if (1 == StackNo)
    {
        if (-1 == S->top1) //栈1是空栈
        {
            return FALSE;
        }
        *data = S->data[S->top1];
        S->top1--;
    }
    else
    {
        if (MAXSIZE == S->top2) //栈2是空栈
        {
            return FALSE;
        }
        *data = S->data[S->top2];
        S->top2++;
    }

    return TRUE;
}

测试程序:

int main(void)
{
    SqDoubleStack DS;
    /* 栈顶指针初始化 */
    DS.top1 = -1; 
    DS.top2 = MAXSIZE;
    
    int data1;
    int data2;
    
    for (int i = 0; i < 10; i++)
    {
        Push_Double(&DS, i, 1);
        Push_Double(&DS, -i, 2);
    }

    printf("Stack1:\n");
    while (Pop_Double(&DS, &data1, 1))
    {
        printf("%d ", data1); //打印栈1内所有数据
    }

    printf("\nStack2:\n");
    while (Pop_Double(&DS, &data2, 2))
    {
        printf("%d ", data2); //打印栈2内所有数据
    }

    return 0;
}

栈例程2



栈的链式存储结构

线性表有顺序存储和链式存储结构,栈是一种特殊的线性表,那么也有链式存储结构,简称链栈。
将链表的头指针作为栈的栈顶指针,因为有栈顶指针,就不需要链表的头结点了。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xHfxBA84-1651738453603)(\栈\链栈.png)]
链栈

链栈的结构定义如下:

typedef struct StackNode //结点
{
    int data;
    struct StackNode *next;
}StackNode; 

typedef struct LinkStack //栈顶指针和结点数
{
    StackNode *top;
    int count;
}LinkStack;

进栈操作如下:

(1)申请一个新结点,存储进栈数据;

(2)将新结点插入到链表的最前端,使其next指向原先的top结点,最后使top指向新结点。

bool Push_Link(LinkStack *S, int data)
{
    StackNode *SNode = (StackNode *)malloc(sizeof(StackNode)); //申请一个结点大小的内存

    SNode->data = data; //将数据赋给结点

    /* 将新结点插入链表中 */
    SNode->next = S->top;
    S->top = SNode;

    S->count++; //链表长度+1

    return TRUE;
}

出栈操作如下:

(1)检查是否为空栈,若是,则返回错误,否则将栈顶指针指向的数据赋给*data

(2)删除栈顶结点:定义一个指针p,指向栈顶结点,将栈顶指针top指向下一个结点,删除原来的栈顶结点。

bool Pop_Link(LinkStack *S, int *data)
{
    if (S == NULL)
    {
        return FALSE;
    }

    *data = S->top->data; //将栈顶数据赋给*data

    StackNode *p;
    p = S->top; //定义一个指针p,指向栈顶结点
    S->top = S->top->next; //将栈顶指针指向下一个结点
    free(p); //删除原来的栈顶结点
    S->count--;

    return TRUE;
}

测试程序:

int main(void)
{
    /* 链栈定义并初始化 */
    LinkStack LS;
    LS.count = 0;
    LS.top = NULL;
    int data1;

    /* 依次进栈0-9 */
    for (int i = 0; i < 10; i++)
    {
        Push_Link(&LS, i);
    }

    /* 出栈并打印所有元素 */
    printf("链栈(长度:%d):\n", LS.count);
    while(LS.count > 0)
    {
        Pop_Link(&LS, &data1);
        printf("%d ", data1);
    }

    return 0;
}

栈例程3


顺序栈和链栈的时间复杂度是一样的,都是O(1),如果栈内的元素数量变化小且可控,可以使用顺序栈,链栈因为每个结点都有指针域,增加了一定的内存开销;如果栈内的元素数量不可控,最好使用链表,根据实际需求变化长度,不会造成大量的空间浪费。

开发板推荐:天空星STM32F407VET6开发板

超高性价比 STM32主控 | 超高主频 | 一板兼容百芯 | 比赛神器 | 沉金彩色丝印

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值