数据结构之栈

本篇目标:

1.了解栈的思想

2.掌握栈的增删操作

3.学会运用栈到算法题中

一.初步认识栈

1.基本概念

线性表 :是由相同类型的n(n>=0)个数据元素组成的有限序列,若用L命名为线性表,则⼀般表

示:为L=(a1 ,a2 ,...,aN).。

那么什么是栈呢?

栈(stack)是限定仅在⼀端进行插入或删除操作的线性表 。因此,对栈来说进行插⼊删除数据的这⼀

端称为栈顶(top),另⼀端称为栈底(bottom)不含元素的空表称为空栈 。栈中的数据元素遵循

进先出(LIFOLastInFirstOut)的特性。

注意:栈插入和删除数据都在栈顶端,插入数据⼀般叫做入栈/进栈,删除数据⼀般叫做出栈。

我们也可以通过下面的示意图来理解:

现实中⼿枪弹夹和⽻⽑球桶就是⼀个栈结构,如图:

2.入栈出栈顺序

假设入栈顺序为a1,a2,a3,a4,a5,那么可能的出栈顺序有哪些呢?

最常见的思路是所有元素先入栈再依次出栈,即a5,a4,a3,a2,a1。但实际上,出栈顺序可以有很多变化。例如:

  1. 每个元素入栈后立即出栈,顺序就是a1,a2,a3,a4,a5
  2. 先入栈a1,a2,a3,然后依次出栈a3,a2,接着入栈a4,a5并出栈a5,a4,最后出栈a1,形成a3,a2,a5,a4,a1

通过不同的入栈和出栈时机组合,可以产生多种合法的出栈顺序。

示意图:

3.栈的存储

3.1.栈的分类

栈是一种特殊线性表,仅允许在一端进行元素的插入和删除操作。由于线性表具有顺序和链式两种

存储结构,栈同样可以采用这两种实现方式。需要注意的是,若使用单链表实现栈,入栈时需要遍

历到尾节点,效率较低。因此,采用双链表实现栈会比单链表更高效。本文主要介绍顺序存储的实

现方式,关于链式存储的内容将在后续补充。

可以通过如图了解:

3.2.栈的结构

跟顺序表类似,栈的顺序存储实现可以使用静态数组,我们称为静态顺序栈 ,也可以使用堆上动态

申请数组实现 ,我们称为动态顺序栈。静态顺序栈的缺陷跟静态顺序表类似,只适用于确定知道最

多需要多少空间的场景,那么我们后续重点讲解动态数组实现的版本

静态顺序栈的结构:

cpp 复制代码
#define MAX_SIZE 10 
typedef int STDataType;

typedef struct Stack
{
	STDataType arr[MAX_SIZE];  // 指向栈数组空间的指针
	int top;          // 栈顶位置
}Stack;      

动态顺序栈的结构:

cpp 复制代码
typedef int STDataType;

typedef struct Stack
{
	STDataType* arr;  // 指向栈数组空间的指针
	int top;          // 栈顶位置
	int capacity;     // 容量

}Stack;

3.3.易错点

在实现栈结构时,首先需要明确的是初始化时top的取值问题:应该设为0还是-1

两种初始化方式的区别在于:

  1. top=-1时,直接指向栈顶元素。此时入栈操作需要先执行++top,再将数据存入top位置。
  2. top=0时,指向栈顶元素的下一个空位。此时入栈操作需要先将数据存入top位置,再执行++top。

需要注意的是,其他接口函数(如判空、获取栈顶元素等)的实现细节也需要与选择的初始化方式保持一致。这两种方式在实际使用中各有特点,选择哪种完全取决于个人习惯,没有绝对的优劣之分。

为何初始化时top=0,不能代表top指向栈顶元素呢?那我们要思考栈为空和栈只有⼀个数据时,

top都是0,如何区分呢?

通过图片来理解:

本次我采用的是top为0。

二.代码实现

1.主要实现

在Stack.h中,

cpp 复制代码
#pragma once

#include <stdio.h> 
#include <stdlib.h> 
#include <assert.h> 
#include <stdbool.h> 
typedef int STDataType;

typedef struct Stack
{
	STDataType* arr;  // 指向栈数组空间的指针
	int top;          // 栈顶位置
	int capacity;     // 容量

}Stack;

// 栈的初始化
void StackInit(Stack* st);

// 栈的销毁
void StackDestroy(Stack* st);

// x元素⼊栈(进栈)
void StackPush(Stack* st, STDataType x);

// 将栈顶元素出栈,并⽤返回栈顶元素
STDataType StackPop(Stack* st);

// 获取栈顶元素并返回
STDataType StackTop(Stack* st);

// 获取栈中有效元素个数
int StackSize(Stack* st);

// 检测栈是否为空,如果是空返回真,否则返回假
bool StackEmpty(Stack* st);

2.接口实现

在Stack.c中,

2.1.初始化

cpp 复制代码
void StackInit(Stack* st)
{
	assert(st);

	st->arr = (STDataType*)malloc(sizeof(STDataType) * 4);
	if (st->arr == NULL)
	{
		perror("申请空间失败\n");
		exit(1);
	}

	st->top = 0;
	st->capacity = 4;
}

2.2.入栈

cpp 复制代码
// x元素入栈(进栈)
void StackPush(Stack* st, STDataType x)
{
	assert(st&&st->arr);

	//检查一下是否要扩容?
	if (st->top == st->capacity)
	{
		STDataType* tmp = (STDataType*)realloc(st->arr,sizeof(STDataType) * st->capacity * 2);
		if (tmp == NULL)
		{
			perror("扩容失败\n");
			exit(1);
		}

		st->arr = tmp;
		st->capacity *= 2;
	}

	st->arr[st->top++] = x;
}

入栈时,记得要检查一下栈中的容量是不是不够了。

示意图:

2.3.出栈

cpp 复制代码
// 将栈顶元素出栈,并⽤返回栈顶元素
STDataType StackPop(Stack* st)
{
	assert(st&&st->arr);
	//要检查栈不为空的情况
	assert(st->top!=0);

	STDataType x = st->arr[st->top-1];
	st->top--;

	return x;
}

出栈之前要先检查栈是否为空,否则就会导致top减为负数了。

示意图:

2.4.取栈顶元素

cpp 复制代码
// 将栈顶元素出栈,并⽤返回栈顶元素
STDataType StackPop(Stack* st)
{
	assert(st&&st->arr);
	//要检查栈不为空的情况
	assert(st->top!=0);

	STDataType x = st->arr[st->top-1];
	st->top--;

	return x;
}

2.5.销毁

cpp 复制代码
// 栈的销毁
void StackDestroy(Stack* st)
{
	assert(st);

	if (st->arr)
	{
		st->top = 0;
		st->capacity = 0;
		free(st->arr);
		st->arr = NULL;
	}
}

2.6.其他接口

cpp 复制代码
// 检测栈是否为空,如果是空返回真,否则返回假
bool StackEmpty(Stack* st)
{
	assert(st);

	return st->top == 0;
}
cpp 复制代码
// 获取栈中有效元素个数
int StackSize(Stack* st)
{
	assert(st);

	return st->top;
}

3.测试代码

cpp 复制代码
#include "Stack.h"

int main()
{
    Stack st;
    StackInit(&st);

     //入栈,检测栈顶数据元素和栈中有效数据元素是否正确
    StackPush(&st, 1);
    StackPush(&st, 2);
    StackPush(&st, 3);
    StackPush(&st, 4);
    StackPush(&st, 5);

     printf("栈中元素总数为: %d\n", StackSize(&st));
    printf("栈顶元素为: %d\n", StackTop(&st));

    // 出栈
    printf("出栈:栈顶元素为: %d\n", StackPop(&st));
    printf("出栈:栈顶元素为: %d\n", StackPop(&st));
    printf("出栈:栈顶元素为: %d\n", StackPop(&st));
    printf("出栈:栈顶元素为: %d\n", StackPop(&st));
    printf("出栈:栈顶元素为: %d\n", StackPop(&st));

    // 断言报错
    // printf("出栈:栈顶元素为: %d\n", StackPop(&s));
    // printf("栈顶元素为: %d\n", StackTop(&s));

    StackPush(&st, 6);
    StackPush(&st, 7);

    // 出栈
    printf("出栈:栈顶元素为: %d\n", StackPop(&st));
    printf("出栈:栈顶元素为: %d\n", StackPop(&st));

    StackDestroy(&st);

    return 0;
}
相关推荐
wyhwust1 小时前
web应用技术--springboot01
java·开发语言
想你依然心痛1 小时前
数据库技术在电力业务中的核心应用场景
java·开发语言·数据库
nice_lcj5201 小时前
排序(3)-第三篇:交换排序专题——从冒泡排序到快速排序的效率飞跃
java·数据结构·算法·排序算法
草莓熊Lotso1 小时前
【Linux网络】深入理解 TCP 协议(一):报头设计与可靠性基石
linux·运维·服务器·c语言·网络·c++·tcp/ip
ywl4708120871 小时前
数据结构之链表反转算法
数据结构·算法·链表
Shadow(⊙o⊙)1 小时前
QT常用控件3.0,font字体设置,toolTip提示,focusPolicy焦点定位原则,中型控件StyleSheet样式表。
服务器·开发语言·前端·c++·qt
Shadow(⊙o⊙)1 小时前
QT常用控件2.0,windowOpacity窗口透明度,Cursor光标设置
开发语言·c++·qt
cfm_29141 小时前
JVM对象逃逸分析深度详解
java·开发语言·jvm
SilentSamsara1 小时前
LLM API 工程化:OpenAI/DeepSeek/国产模型统一调用层设计
开发语言·人工智能·python