当前位置: 首页 > 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.dtcms.com/a/348722.html

相关文章:

  • 生成模型 | DDPM -> Imrpoved DDPM -> DDIM
  • LIANA | part2 results部分
  • QML Charts组件之坐标轴示例
  • LangGraph 与 MCP 模型上下文协议简介与演示
  • 教育系统搭建攻略:线上知识付费与线下消课排课全解析
  • 【Java开发日记】我们来讲一讲 Channel 和 FileChannel
  • Polkadot - JAM
  • 美股期权历史市场数据波动特性分析
  • 【中文教材】14. 汇率计算
  • 人工智能-python-深度学习-tensor基操
  • 数学建模(摸索中……)
  • CUDA安装,pytorch库安装
  • 如何实现模版引擎
  • Shell 学习笔记 - Shell 三剑客篇
  • unity热更新总结
  • 【如何使用Redis实现分布式锁详解讲解】
  • [快乐数](哈希表)
  • 解决编译osgEarth中winsocket2.h找不到头文件问题
  • 基于Spark的热门旅游景点数据分析系统的设计-django+spider
  • Spring Boot测试陷阱:失败测试为何“传染”其他用例?
  • 【追涨抄底关注】副图指标 紫色主力线上行表明资金介入明显 配合价格突破时可靠性更高
  • deepseek连接solidworks设计一台非标设备 (part1)
  • 阿里云ECS服务器搭建ThinkPHP环境
  • 互联网大厂AI/大模型应用开发工程师面试剧本与解析
  • 阿里云云数据库RDS PostgreSQL管控功能使用
  • 基于SpringBoot的婚纱影楼服务预约平台【2026最新】
  • Spring AI 学习笔记(2)
  • GitHub 热榜项目 - 日榜(2025-08-24)
  • Wireshark USRP联合波形捕获(下)
  • windows上如何实现把指定网段的流量转发到指定的端口,有哪些界面化的软件用来配置完成,类似于 Linux中的iptables规则实现