当前位置: 首页 > news >正文

【数据结构】栈和队列——栈

目录

  • 栈和队列
      • 栈的基本概念
      • 栈的顺序存储实现
        • 栈的定义与初始化
        • 入栈操作
        • 出栈操作
        • 读取栈顶元素
        • 判空和判满操作
        • 栈的销毁操作
        • 操作集合

栈和队列

栈的基本概念

栈的定义:

  • 栈(Stack) 是一种线性表,它限定了数据元素的插入和删除操作只能在表的一端进行
  • 允许操作的一端称为 栈顶(Top),另一端称为 栈底(Bottom)

所以,栈就是 “先进后出(FILO, First In Last Out)” 的数据结构。

栈的特点:

  • 受限性:只能在栈顶进行插入(push)和删除(pop)。
  • 线性结构:元素有先后次序。
  • FILO 特性:最先入栈的元素最后出栈。

形象理解:就像一摞盘子,先放进去的盘子在最下面,拿的时候只能从最上面一个个拿走。

栈的基本操作:

  • Push(入栈):在栈顶插入一个新元素。
  • Pop(出栈):删除栈顶元素,并返回该元素。
  • GetTop(读栈顶元素):仅仅读取栈顶元素,不删除。
  • InitStack(初始化栈):建立一个空栈。
  • StackEmpty(判空):检查栈是否为空。
  • StackFull(判满,顺序栈时需要):判断栈是否已满。

栈的存储结构:

  1. 顺序栈:
    • 用数组存储栈元素。
    • 优点:实现简单,存取快。
    • 缺点:容量固定,可能溢出(上溢/下溢)。
      在这里插入图片描述
  2. 链式栈
    • 用链表存储,栈顶一般在链表的表头。
    • 优点:容量不受限制。
    • 缺点:需要额外指针,操作相对慢。
      在这里插入图片描述

栈的顺序存储实现

栈的定义与初始化

顺序栈是栈的一种顺序存储结构,用数组(连续内存)来存放数据元素。特点:

  1. 栈顶指针(top)指向栈顶元素的位置(或空元素的下一个位置)。
  2. 栈容量固定(静态)或可扩展(动态)。
  3. 支持的基本操作:
    • 初始化栈 InitStack
    • 入栈 Push
    • 出栈 Pop
    • 判空 IsEmpty
    • 获取栈顶元素 GetTop

顺序栈top 指针的指向在不同教材中有差异:

  1. top 指向栈顶元素(经典写法,大部分算法书和面试中用的)
    • top 是数组下标
    • 空栈时:top = -1
    • 入栈:先 top++,再存元素
    • 出栈:取 data[top],再 top--
    • 特点:top 总是指向有效元素的位置
  2. top 指向栈顶下一个空位(部分教材/视频中用)
    • top 是指针
    • 空栈时:top = 0
    • 入栈:直接存到 data[top],然后 top++
    • 出栈:先 top--,再取 data[top]
    • 特点:top 表示栈中元素数量,也就是下一个可用位置

两种方法逻辑不同,但功能一样,只是 top 的含义不同

静态顺序栈:

  • 定义:
    #define MAXSIZE 100
    typedef int SElemType;  // 假设存储 int 类型typedef struct {SElemType *base;   // 栈底指针,指向数组首地址SElemType *top;    // 栈顶指针,指向栈顶元素的下一个位置int stacksize;     // 栈容量
    } SqStack;
    
  • 初始化:
    // 初始化
    void InitStack(SqStack *S) {S->base = data;      // base 指向数组首地址S->top = S->base;    // 空栈,top 指向第一个空位S->stacksize = MAXSIZE;
    }
    
    特点
    • 容量固定,无法扩容
    • top 指向栈顶元素的下一个位置
    • 入栈/出栈时用 *(S->top++) = e / *e = *(--S->top)

动态顺序栈: 动态顺序栈可以在栈满时自动扩容。

  • 定义:
    typedef int SElemType;typedef struct {SElemType *base;   // 栈底指针SElemType *top;    // 栈顶指针,指向栈顶元素的下一个位置int stacksize;     // 当前容量
    } SqStack;
    
  • 初始化:
    // 初始化
    int InitStack(SqStack *S, int initSize) {S->base = (SElemType *)malloc(initSize * sizeof(SElemType));if (!S->base) return 0;   // 分配失败S->top = S->base;         // 空栈S->stacksize = initSize;return 1;
    }
    

动态顺序栈可以在 Push 时判断容量是否够,如果不够就 realloc 扩容。

#include <stdio.h>
#include <stdlib.h>typedef int SElemType;  // 统一元素类型typedef struct {SElemType *base;    // 栈底指针SElemType *top;     // 栈顶指针(指向栈顶元素的下一个位置)int stacksize;      // 当前容量
} SqStack;// 初始化栈
int InitStack(SqStack *S, int initSize) {S->base = (SElemType *)malloc(initSize * sizeof(SElemType));if (!S->base) return 0;  // 分配失败S->top = S->base;        // 空栈:栈顶=栈底S->stacksize = initSize;return 1;
}// 入栈
int Push(SqStack *S, SElemType x) {// 判断栈满,需要扩容if (S->top - S->base >= S->stacksize) {int newSize = S->stacksize * 2;SElemType *newBase = (SElemType *)realloc(S->base, newSize * sizeof(SElemType));if (!newBase) return 0;  // 扩容失败// 更新指针和容量S->top = newBase + (S->top - S->base);  // 调整栈顶指针(原偏移量不变)S->base = newBase;S->stacksize = newSize;printf("栈扩容到 %d\n", newSize);}*(S->top++) = x;  // 先赋值,再移动栈顶指针return 1;
}// 出栈(将栈顶元素存入e,成功返回1,失败返回0)
int Pop(SqStack *S, SElemType *e) {if (S->top == S->base) return 0;  // 空栈*e = *(--S->top);  // 先移动栈顶指针,再取值return 1;
}int main() {SqStack S;if (!InitStack(&S, 3)) {  // 初始容量3printf("栈初始化失败\n");return 1;}// 入栈10个元素(测试扩容)for (int i = 1; i <= 10; i++) {if (Push(&S, i)) {printf("入栈: %d, 当前元素数: %d\n", i, S.top - S.base);} else {printf("入栈 %d 失败\n", i);}}// 出栈所有元素SElemType e;printf("\n出栈顺序:\n");while (Pop(&S, &e)) {printf("出栈: %d\n", e);}free(S.base);  // 释放堆内存return 0;
}
入栈操作

入栈(Push) 就是把一个新元素压入栈顶的操作。
核心逻辑:

  1. 判断栈是否已满(静态栈)或是否需要扩容(动态栈)。
  2. 栈顶指针 top 上移(或计算位置)。
  3. 将元素放到栈顶位置。
  4. 更新 top(或数组索引)。

栈遵循 后进先出(LIFO) 原则,所以新元素总是放在栈顶。

静态顺序栈入栈:

void Push(SqStack *S, int e) {// 判断是否栈满if (S->top - S->base >= S->stacksize) {return 0;   // 入栈失败}*(S->top) = e;  // 把元素放到 top 指向的位置S->top++;       // top 后移,指向下一个空位return 1;       // 入栈成功
}
  • 空栈时top == base
  • 入栈成功后top 始终指向“栈顶元素的下一个位置”。
  • 栈满条件top - base == stacksize
  • 栈顶元素*(top - 1)

动态顺序栈入栈:

// 入栈
void Push(SqStack *S, int e) {if (S->top - S->base >= S->stacksize) {   // 栈满,需要扩容S->base = (ElemType *)realloc(S->base, (S->stacksize + STACKINCREMENT) * sizeof(ElemType));if (!S->base) exit(0); // 内存不足S->top = S->base + S->stacksize;  // 重新定位 topS->stacksize += STACKINCREMENT;   // 更新容量}*(S->top) = e;   // 把元素放到栈顶S->top++;        // top 指针上移
}

动态顺序栈入栈图示:

   初始栈(容量=3)         入栈扩容后(容量=6)
+----+----+----+        +----+----+----+----+----+----+
| 10 | 20 | 30 |        | 10 | 20 | 30 | 40 |    |    |
+----+----+----+        +----+----+----+----+----+----+↑                                ↑top                              top
出栈操作
// 出栈操作:将栈顶元素弹出并返回给 e
int Pop(SqStack *S, int *e) {if (S->top == S->base) return 0;   // 栈空S->top--;                              // 指针下移*e = *(S->top);                        // 取出元素return 1;
}

S->top--;*e = *(S->top); 可以合并为 *e = *(--S->top);

有道例题:写出下列程序段的输出结果

void main(){Stack S;InitStack(S);x='c'; y='k';Push(S,x); Push(S,'a'); Push(S,y);Pop(S,x);  Push(S,'t'); Push(S,x);Pop(S,x);  Push(S,'s');While(!StackEmpty(S)) {Pop(S,y); printf(y);}printf(x);
}

Pop 会将出栈的数据返回,在这题中的重点是 Pop 操作会改变原来 x 中的数据

读取栈顶元素

核心思想

  • 栈顶元素位于 top 的前一个位置(因为 top 指向栈顶元素的下一个空位)。
  • 不改变 top 指针的值,只是返回栈顶元素。
// 取栈顶元素,不出栈
int GetTop(SqStack S, int *e) {if (S.top == S.base) return ERROR; // 空栈*e = *(S.top - 1);                // 栈顶元素return 1;
}
  • top 指向栈顶元素的下一个位置
  • 栈顶元素*(top - 1)
  • GetTop 不改变栈结构,只返回栈顶元素;
判空和判满操作
  1. 判空操作:
    核心思想:栈空时,top == base,因为没有元素,栈顶指针就在栈底。
    // 判空
    int StackEmpty(SqStack S) {if (S.top == S.base) return 1;   // 栈空else return 0;   // 栈非空
    }
    
  2. 判满操作
    核心思想:栈满时,top - base == stacksize,因为栈顶指针指向下一个空位,减去栈底得到元素个数等于容量。
    // 判满
    int StackFull(SqStack S) {if (S.top - S.base == S.stacksize)return 1;   // 栈满elsereturn 0;   // 栈未满
    }
    
栈的销毁操作

核心思想

  • 栈底指针 base 是动态分配的,所以只需要 free(base)
  • 销毁后,将 basetop 置为 NULLstacksize 置为 0,防止野指针。
Status DestroyStack(SqStack *S) {if (S->base) {free(S->base);   // 释放动态分配的内存S->base = NULL;S->top = NULL;S->stacksize = 0;return OK;}return ERROR;         // 栈本身为空或未初始化
}
  • 栈使用 malloc 分配内存后,必须用 free 释放,否则会 内存泄漏
  • 销毁后把 basetop 置为 NULLstacksize 置 0,避免野指针。
  • 对静态顺序栈(数组固定分配)则不需要销毁操作,因为数组在栈上分配,程序结束自动释放。
操作集合
#include <stdio.h>
#include <stdlib.h>#define MAXSIZE 100
#define OK 1
#define ERROR 0typedef int Status;
typedef int SElemType;// 顺序栈定义(老师写法)
typedef struct {SElemType *base;   // 栈底指针SElemType *top;    // 栈顶指针(指向下一个空位)int stacksize;     // 栈容量
} SqStack;// 初始化栈
Status InitStack(SqStack *S) {S->base = (SElemType *)malloc(MAXSIZE * sizeof(SElemType));if (!S->base) return ERROR;S->top = S->base;S->stacksize = MAXSIZE;return OK;
}// 入栈
Status Push(SqStack *S, SElemType e) {if (S->top - S->base == S->stacksize) return ERROR; // 栈满*(S->top) = e;S->top++;return OK;
}// 出栈
Status Pop(SqStack *S, SElemType *e) {if (S->top == S->base) return ERROR; // 空栈S->top--;*e = *(S->top);return OK;
}// 取栈顶元素(不出栈)
Status GetTop(SqStack S, SElemType *e) {if (S.top == S.base) return ERROR; // 空栈*e = *(S.top - 1);return OK;
}// 判空
Status StackEmpty(SqStack S) {return S.top == S.base ? 1 : 0;
}// 判满
Status StackFull(SqStack S) {return (S.top - S.base == S.stacksize) ? 1 : 0;
}// 销毁栈
Status DestroyStack(SqStack *S) {if (S->base) {free(S->base);S->base = NULL;S->top = NULL;S->stacksize = 0;return OK;}return ERROR;
}// 打印栈内元素(从栈底到栈顶)
void PrintStack(SqStack S) {SElemType *p = S.base;while (p < S.top) {printf("%d ", *p);p++;}printf("\n");
}// 测试顺序栈操作
int main() {SqStack S;if (InitStack(&S) == ERROR) {printf("栈初始化失败!\n");return 0;}printf("栈是否为空: %d\n", StackEmpty(S));// 入栈for (int i = 1; i <= 5; i++) {Push(&S, i);printf("入栈: %d, 栈顶元素: %d\n", i, *(S.top - 1));}printf("当前栈: ");PrintStack(S);printf("栈是否已满: %d\n", StackFull(S));// 取栈顶元素SElemType e;if (GetTop(S, &e) == OK) {printf("栈顶元素: %d\n", e);}// 出栈while (!StackEmpty(S)) {Pop(&S, &e);printf("出栈元素: %d\n", e);}printf("栈是否为空: %d\n", StackEmpty(S));// 销毁栈DestroyStack(&S);printf("销毁栈后, 栈容量: %d, base指针: %p\n", S.stacksize, S.base);return 0;
}

程序运行结果如下:
在这里插入图片描述

http://www.xdnf.cn/news/1356643.html

相关文章:

  • MyBatis 和 MyBatis-Plus对比
  • 一个奇怪的问题-Python会替代Java吗?技术语言之争的真相-优雅草卓伊凡
  • 深度学习:CUDA、PyTorch下载安装
  • 用 Bright Data MCP Server 构建实时数据驱动的 AI 情报系统:从市场调研到技术追踪的自动化实战
  • 自由学习记录(87)
  • System.IO.Pipelines 与“零拷贝”:在 .NET 打造高吞吐二进制 RPC
  • 关于 svn无法查看下拉日志提示“要离线”和根目录看日志“no data” 的解决方法
  • 编译Marlin 1.1.9.1固件指南
  • 如何理解“向量”
  • 大数据、hadoop、爬虫、spark项目开发设计之基于数据挖掘的交通流量分析研究
  • 数据挖掘 4.1~4.7 机器学习性能评估参数
  • 【软考架构】云计算相关概念
  • 《CF1120D Power Tree》
  • Implementing Redis in C++ : E(AVL树详解)
  • 深入解析Apache Kafka的核心概念:构建高吞吐分布式流处理平台
  • 自动化运维之k8s——Kubernetes集群部署、pod、service微服务、kubernetes网络通信
  • Linux-函数的使用-编写监控脚本
  • Qt——网络通信(UDP/TCP/HTTP)
  • Linux学习-TCP网络协议
  • Linux shell脚本数值计算与条件执行
  • (计算机网络)JWT三部分及 Signature 作用
  • 如何在 IDEA 中在启动 Spring Boot 项目时加参数
  • [Windows] PDF-XChange Editor Plus官方便携版
  • 海盗王3.0客户端从32位升级64位之路
  • 操作系统文件系统
  • [e3nn] 等变神经网络 | 线性层o3.Linear | 非线性nn.Gate
  • Excel 转化成JSON
  • GPT 模型详解:从原理到应用
  • 第16届蓝桥杯C++中高级选拔赛(STEMA)2024年12月22日真题
  • 以国产IoTDB为代表的主流时序数据库架构与性能深度选型评测