数据结构-栈(stack)

数据结构-栈的实现与应用

一.栈的基本概念

        栈(Stack)是一种线性数据结构,遵循**后进先出(LIFO, Last In First Out)**原则。最后插入的元素最先被访问或删除。看栈顶等)。

二.栈的核心操作

设计结构体并初始化

入栈(Push)操作:

        将元素添加到栈顶。若栈已满(固定容量实现时即顺序栈),此操作需要检查栈满否则可能导致栈溢出(Stack Overflow)。


出栈(Pop)操作:

        移除并返回栈顶元素。若栈为空,此操作需要检查栈是否为空,否则可能导致栈下溢(Stack Underflow)。


查看栈顶(Top)操作

        返回栈顶元素但不移除它。也需要判断栈是否为空,因为空栈时操作无效。

三.栈的实现方式

1.顺序表实现栈

        结构体设计
struct seqStack    //定义结构体类型
{
    int *data;     //存储入栈的数据
    int size;      //顺序栈的容量
    int top;       //栈顶位置
};
       初始化

        顺序栈(seqStack)的初始化函数 initStack,为栈结构分配内存并设置初始状态。

        以下是逐步分析:

  • 动态分配 struct seqStack 类型的内存空间,并将指针赋给 stack。若分配失败,malloc 返回 NULL

  • 若栈结构内存分配成功,继续为栈的存储数组 data 分配空间。这里使用 calloc 分配 size 个 int 大小的空间,并初始化为 0。

  • 若 data 分配失败,释放之前分配的 stack 内存,避免内存泄漏,并返回 NULL 表示初始化失败。

  • 初始化栈的容量 size 和栈顶指针 top(-1 表示空栈)。

  • 最终返回栈指针 stack(为 NULL 表示失败)。

//初始化栈,传入栈的容量
struct seqStack * initStack(int size)
{
    struct seqStack *stack = malloc(sizeof(struct seqStack));   //申请内存空间
    if(stack != NULL)                                           //确保申请成功
    {
        stack->data = calloc(size, sizeof(int));                //申请数据存储空间
        if(stack->data == NULL)                                 //申请失败释放空间,返回空避免内存泄漏
        {
            free(stack);
            return NULL;
        }
        stack->size = size;                                     //容量赋初值
        stack->top = -1;                                        //栈顶指针赋初值
    }
    return stack;
}
        入栈

        实现顺序栈的入栈操作(push)

        以下是逐步分析:

        入栈操作前,要检查栈满

        isFULL函数实现

  • 判断栈满,通过栈顶指针(top),当栈顶指针等于栈的容量-1则栈满(初始栈顶指针为-1,从零开始,因此这里需要减1)

  • //判断栈满
    bool isFULL(struct seqStack *stack)
    {
        return stack->top >=stack->size-1;  //0-size计数
    }

        push函数实现:

  • 函数返回布尔类型,表示操作是否成功;参数stack是指向顺序栈结构的指针;参数data是要入栈的整型数据。

  • 调用isFULL函数检查栈是否已满。如果栈满,输出错误信息并通过perror打印错误,并返回false表示入栈失败。

  • 栈顶指针top先自增,指向新的栈顶位置然后将数据data存储在新的栈顶位置data[stack->top]。这里使用的是前自增操作符++,确保先移动指针再存储数据。

  • 操作成功完成,返回true

//入栈
bool push(struct seqStack *stack, int data)
{
    if(isFULL(stack))
    {
        perror("isfull");return false;
    }
        
    stack->data[++stack->top] = data;   //存储传入的数据,修改栈顶指针的位置
    return true;
}

            出栈

            出栈和取栈顶元素需要判断栈空

            isEmpty函数实现:

    //判断栈空
    bool isEmpty(struct seqStack *stack)
    {
        return stack->top == -1;
    }

            top函数实现:

    • 参数struct seqStack *stack:指向顺序栈结构的指针;

    • 参数int *m:用于存储获取到的栈顶元素的指针;

            1.调用isEmpty(stack)检查栈是否为空 如果栈为空,输出错误信息"isempty"并返回false

            2.当栈不为空时,通过stack->data[stack->top]访问栈顶元素 将栈顶元素的值赋给指针m所指向的内存位置,最后返回true表示操作成功。

    //取栈顶元素
    bool top(struct seqStack *stack, int *m)
    {
        if(isEmpty(stack))
        {
            perror("isempty\n");
            return false;
        }
            
        *m = stack->data[stack->top];
        return true;
    }
    取栈顶元素

    pop函数实现:

    接收两个参数:指向栈结构的指针 stack 和用于返回栈顶元素的指针 m

    调用 top 函数检查栈是否为空并获取栈顶元素。

    如果栈为空,top 返回 falsepop 也直接返回 false

    如果栈不为空,栈顶指针 top 减一,完成出栈操作,并返回 true 表示出栈成功。

    //出栈
    bool pop(struct seqStack *stack, int *m)
    {
        if(!top(stack, m))
        {
            
            return false;
        }
            
        stack->top--;
        return true;
    }
    主函数
    //主函数
    int main(void)
    {
        struct seqStack *stack = initStack(10);
        if(stack == NULL)
        {
            perror("初始化顺序栈失败\n");
            exit(0);
        }
        else
        printf("初始化成功\n");
    
        push(stack, 3);
        push(stack, 4);
        push(stack, 5);
        printf("\n");
        while(!isEmpty(stack))
        {
            int m;
            pop(stack, &m);
            printf("%d\n", m);
        }
        return 0;
    
    完整代码:
    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <stdbool.h>
    
    
    struct seqStack    //定义结构体类型
    {
        int *data;     //存储入栈的数据
        int size;      //顺序栈的容量
        int top;       //栈顶位置
    };
    
    //初始化栈,传入栈的容量
    struct seqStack * initStack(int size)
    {
        struct seqStack *stack = malloc(sizeof(struct seqStack));   //申请内存空间
        if(stack != NULL)                                           //确保申请成功
        {
            stack->data = calloc(size, sizeof(int));                //申请数据存储空间
            if(stack->data == NULL)                                 //申请失败释放空间,返回空避免内存泄漏
            {
                free(stack);
                return NULL;
            }
            stack->size = size;                                     //容量赋初值
            stack->top = -1;                                        //栈顶指针赋初值
        }
        return stack;
    }
    
    //判断栈满
    bool isFULL(struct seqStack *stack)
    {
        return stack->top >=stack->size-1;  //0-size计数
    }
    
    //入栈
    bool push(struct seqStack *stack, int data)
    {
        if(isFULL(stack))
        {
            perror("isfull");return false;
        }
            
        stack->data[++stack->top] = data;   //存储传入的数据,修改栈顶指针的位置
        return true;
    }
    
    //判断栈空
    bool isEmpty(struct seqStack *stack)
    {
        return stack->top == -1;
    }
    
    
    //取栈顶元素
    bool top(struct seqStack *stack, int *m)
    {
        if(isEmpty(stack))
        {
            perror("isempty\n");
            return false;
        }
            
        *m = stack->data[stack->top];
        return true;
    }
    
    //出栈
    bool pop(struct seqStack *stack, int *m)
    {
        if(!top(stack, m))
        {
            
            return false;
        }
            
        stack->top--;
        return true;
    }
    
    //主函数
    int main(void)
    {
        struct seqStack *stack = initStack(10);
        if(stack == NULL)
        {
            perror("初始化顺序栈失败\n");
            exit(0);
        }
        else
        printf("初始化成功\n");
    
        push(stack, 3);
        push(stack, 4);
        push(stack, 5);
    
        printf("\n");
        while(!isEmpty(stack))
        {
            int m;
            pop(stack, &m);
            printf("%d\n", m);
        }
        return 0;
    }
    顺序栈的优缺点
    优点
    • 内存连续:顺序栈基于数组实现,内存连续访问效率高,CPU缓存命中率更高。

    • 操作简单:入栈(push)和出栈(pop)仅需操作栈顶指针,时间复杂度为 O(1)。

    • 节省空间:无需存储额外指针,仅需一个数组和栈顶指针即可实现。

    缺点
    • 固定容量:栈大小需预先定义,可能导致空间浪费或栈溢出(需动态扩容,但扩容有性能开销)。

    • 扩容成本高:动态扩容需重新分配内存并复制数据,时间复杂度为 O(n)。


    链表实现栈

            栈是一种后进先出(LIFO)的数据结构,链表是一种动态数据结构,通过指针连接节点。用链表实现栈时,栈顶通常是链表的头节点,入栈和出栈操作均在链表头部进行,保证时间复杂度为 O(1)。

     结构体设计
    • 链式存储:通过next指针实现动态内存分配,无需预先定义栈容量。
    • 组合设计:将栈顶指针top和栈大小size封装在linkstack结构中,便于状态管理。
    • 指针类型别名:使用typedefstruct linkstack*定义为Linkstack,简化代码书写。
    struct nodestack            //节点设计
    {
        int data;
        struct nodestack* next;
    };
    
    typedef struct linkstack    //栈结构设计
    {
        struct nodestack* top;
        int size;
    }*Linkstack;
       
     初始化

            Linkstack initlinkstack()实现初始化链式栈。

    内存分配

    • 使用malloc动态分配struct linkstack大小的内存空间

    • 检查分配是否成功(stack!=NULL

    初始化成员

    • 将栈顶指针top初始化为NULL,表示空栈

    • 将栈大小size初始化为0,表示没有元素

    //初始化链式栈
    Linkstack initlinkstack()
    {
        Linkstack stack = malloc(sizeof(struct linkstack));
        if(stack!=NULL)
        {
            stack->top = NULL;
            stack->size = 0;
        }
        
        return stack;
    }
            入栈

            实现链式栈的入栈操作(push)

            push函数实现:

            动态分配新节点内存
            通过 malloc 创建新节点 node,并检查内存分配是否成功, 处理分配失败的情况。

            节点数据赋值
            将参数 data 存入新节点的 data 字段,完成数据初始化。

            调整链表指针
            新节点的 next 指针指向当前栈顶节点 stack->top,建立链表连接关系。

            更新栈顶指针
            栈的 top 指针指向新节点,使新节点成为新的栈顶元素。

            栈大小维护
            递增 stack->size ,保持栈的当前元素数量同步更新。

    //入栈,创建新的节点,链接链表
    bool push(Linkstack stack, int data)
    {
        struct nodestack *node = malloc(sizeof(struct nodestack));
        if (node == NULL) 
            return false;
        node->data = data;
        node->next = stack->top;
        stack->top = node;
        stack->size++;
        return true;
    }

              出栈

              出栈和取栈顶元素需要判断栈空

              isEmpty函数实现:

      //判断栈空
      bool isEmpty(Linkstack stack)
      {
          return stack->size == 0;
      }

              top函数实现:

      • 检查栈是否为空(调用 isEmpty(stack)),若非空则执行下一步;
      • 通过 stack->top->data 获取栈顶节点的数据,并将其赋值给 *data
      • 返回 true 表示操作成功;
      • 若栈为空,直接返回 false 表示操作失败。
      //取栈顶元素
      bool top(Linkstack stack, int *data)
      {
          if(!isEmpty(stack))
          {
              *data = stack->top->data;
              return true;
          }
          return false;
      }
      取栈顶元素

      pop函数实现:

      1. 空栈检查
        调用 isEmpty(stack) 判断栈是否为空,避免对空栈执行出栈操作。

      2. 出栈操作

        • 临时指针 p 保存当前栈顶节点。
        • 将 stack->top 指向下一个节点(stack->top->next)。
        • 释放 p 指向的节点内存。
        • 更新栈大小(stack->size--)。
        • 返回值

          • 成功出栈返回 true
          • 栈空时返回 false
        //出栈
        bool pop(Linkstack stack)
        {
            if(!isEmpty(stack))
            {
                    struct nodestack* p = stack->top;        //记录出栈指针,以便后续释放
                    stack->top = stack->top->next;           //指针移位,指向释放结构体指针的后面
                    free(p);                                 //出栈需要释放内存
                    stack->size--;
                    return true;
            }
            return false;
        }
        主函数
        int main(void)
        {
            Linkstack stack = initlinkstack();
            if(stack == NULL)printf("初始化失败\n");
            else printf("初始化成功\n");
            printf("请输入一个数\n");
            int n=0;
            scanf("%d", &n);
            while(n)//n != 0
            {
                push(stack, n--);
            }
           while(1)
            {
                int data = 0;
                if(!top(stack, &data))
                break;
                printf("%d ",data);
                printf("\n");
                pop(stack);
            }
            return 0;
        }
        完整代码:
        #include <stdio.h>
        #include <stdlib.h>
        #include <unistd.h>
        #include <stdbool.h>
        
        struct nodestack            //节点设计
        {
            int data;
            struct nodestack* next;
        };
        
        typedef struct linkstack    //栈结构设计
        {
            struct nodestack* top;
            int size;
        }*Linkstack;
        
        //初始化链式栈
        Linkstack initlinkstack()
        {
            Linkstack stack = malloc(sizeof(struct linkstack));
            if(stack!=NULL)
            {
                stack->top = NULL;
                stack->size = 0;
            }
            
            return stack;
        }
        
        //入栈,创建新的节点,链接链表
        bool push(Linkstack stack, int data)
        {
            struct nodestack *node = malloc(sizeof(struct nodestack));
            if (node == NULL) 
                return false;
            node->data = data;
            node->next = stack->top;
            stack->top = node;
            stack->size++;
            return true;
        }
        
        //判断栈空
        bool isEmpty(Linkstack stack)
        {
            return stack->size == 0;
        }
        
        //取栈顶元素
        bool top(Linkstack stack, int *data)
        {
            if(!isEmpty(stack))
            {
                *data = stack->top->data;
                return true;
            }
            return false;
        }
        
        //出栈
        bool pop(Linkstack stack)
        {
            if(!isEmpty(stack))
            {
                    struct nodestack* p = stack->top;        //记录出栈指针,以便后续释放
                    stack->top = stack->top->next;           //指针移位,指向释放结构体指针的后面
                    free(p);                                 //出栈需要释放内存
                    stack->size--;
                    return true;
            }
            return false;
        }
        
        int main(void)
        {
            Linkstack stack = initlinkstack();
            if(stack == NULL)printf("初始化失败\n");
            else printf("初始化成功\n");
            printf("请输入一个数\n");
            int n=0;
            scanf("%d", &n);
            while(n)//n != 0
            {
                push(stack, n--);
            }
           while(1)
            {
                int data = 0;
                if(!top(stack, &data))
                break;
                printf("%d ",data);
                printf("\n");
                pop(stack);
            }
            return 0;
        }
        链式栈的优缺点
        优点
        • 动态大小:基于链表实现,可动态增长,无需预先定义容量。

        • 灵活内存:节点分散存储,适合内存碎片化场景。

        • 无溢出风险:只要内存足够,理论上可无限扩展。

        缺点
        • 额外开销:每个节点需存储指针,占用更多内存。

        • 访问效率低:内存非连续,访问节点时缓存命中率低,操作速度略慢于顺序栈。

        • 代码复杂度:需处理链表节点的动态分配与释放,实现稍复杂。

        对比总结

        特性顺序栈链式栈
        内存布局连续非连续
        容量限制固定(可动态扩容)动态增长
        时间效率$O(1)$(无扩容时)$O(1)$(但常数项更高)
        空间效率更优(无指针开销)较差(需存储指针)
        适用场景已知最大容量或频繁操作容量不可预知或内存碎片多

        四.栈的应用场景

        • 函数调用栈:保存函数调用时的上下文。
        • 表达式求值:处理括号匹配、运算符优先级。
        • 浏览器历史:后退功能基于栈实现。
        • 撤销操作:文本编辑器的撤销机制。
        评论
        成就一亿技术人!
        拼手气红包6.0元
        还能输入1000个字符
         
        红包 添加红包
        表情包 插入表情
         条评论被折叠 查看
        添加红包

        请填写红包祝福语或标题

        红包个数最小为10个

        红包金额最低5元

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

        抵扣说明:

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

        余额充值