【数据结构】栈的定义与实现(附完整运行代码)

目录

一、栈的定义

二、顺序栈 链栈比较

三、栈的实现(顺序栈)

3.1 ❥ 定义栈结构

3.2 ❥ 初始化

3.3 ❥ 销毁

3.4 ❥ 插入(入栈)

3.5 ❥ 删除 (出栈)  

3.6 ❥ 获取栈顶元素

3.7 ❥ 判空

3.8 ❥ 获取数据个数

四、完整运行代码

stack.h

stack.c

test.c


一、栈的定义

栈是一种特殊的线性表,其只允许在固定的一端进行插入删除元素操作。

进行数据插入和删除操作的一端称为栈顶,另一端称为栈底。

栈中的数据元素遵守后进先出LIFO(Last In First Out)的原则。

  • 压栈:栈的插入操作叫做进栈/压栈/入栈,入数据在栈顶。
  • 出栈:栈的删除操作叫做出栈。出数据也在栈顶。

举例:

生活中我们装羽毛球的筒就是后进先出的例子,后放进的羽毛球先拿出来。

二、顺序栈 链栈比较

栈可以用数组实现,也可以用单链表或者双向链表实现。

用数组实现的栈称为顺序栈,用链表实现的栈称为链式栈

现在共有3种结构,选哪种更优呢?

首先排除双向链表。

因为单链表能实现,用双向链表就会浪费空间,少维护一个指针也更方便。


而数组和单链表实现栈各有好处,二者效果等同。

如果非要选择一个,选择数组更优一些。

数组的缺点只有一个:就是扩容。扩容虽然自身有消耗,但影响不大。况且并不是有数据插入就要扩容。

而数组的更优在于:cpu高速缓存命中率更高,所以选数组更好一些。(若是没有缓存率这一点,选链表)

三、栈的实现(顺序栈)

3.1 ❥ 定义栈结构

代码如下:

typedef int STDataType;

//定义栈结构
typedef struct stack
{
	STDataType* a;  //指向数组元素的指针
	int top;	   //有效数据个数
	int capacity;  //容量大小
}ST;

3.2 ❥ 初始化

为了防止使用出现错误,首先我们要对栈进行初始化操作,构造一个空栈。

思路:

  • 先将结构体变量的地址传给初始化函数
  • 然后将结构体里的数组指针初始化为NULL
  • 最后再把数据个数和容量大小都初始化为0

代码如下:

//初始化
void STInit(ST* ps)//传的是实参的地址,因为形参是实参的一份临时拷贝
{
	ps->a = NULL;
	ps->top = 0;
	ps->capacity = 0;
}

易错点:关于top指针指向栈顶元素还是指向栈顶元素的下一个位置?

这里取决于自己定义。

  • top如果指向栈顶元素,那初始化top的时候就不能为0 (因为top为0时无法确定到底是有数据还是没有数据)
  • top如果指向栈顶元素的下一个,那初始化top的时候可以指向0

两种初始化方式想取哪一种都可以,但要搞清楚它们之间的关系(前后要求匹配)

3.3 ❥ 销毁

销毁的目的是:当我们使用完栈后,就要释放栈所占用的内存空间,还给操作系统

思路:

  • 首先进行断言,防止传入空指针(空地址)
  • 释放动态开辟的空间,并把指针置空,防止野指针发生未定义行为
  • 最后把容量和数据个数置为0(也可以不管,但是一般为了规范,都会把所有的成员做清理,除非是在销毁函数中做访问操作会出现错误)

代码如下:

//销毁
void STDestory(ST* ps)
{
	assert(ps);

	free(ps->a);
	ps->a = NULL;

	ps->top = ps->capacity = 0;
}

3.4 ❥ 插入(入栈)

思路:

  • 我们要进行插入操作,就要动态申请空间,申请空间前,要先进行断言,防止传入空指针
  • 动态开辟一块空间,进行插入操作。为了防止开辟空间不够,我们需要持续扩容,所以用realloc函数,且每次开辟空间为原来的2倍
  • realloc前应先判断是否为首次开辟,因为若是头一次开辟的话,0*2一直为0,就等于没有开辟
  • 所以这里我们用三目运算,若是头一次开辟,直接给4个空间大小;若不是,就2倍增长。
  • 开辟完之后我们还要进行指针判断,防止开辟失败传入空指针
  • 若开辟成功,我们将数据入栈(也就是写入数组)

代码如下:

//插入(入栈)
void STPush(ST* ps, STDataType x)
{
	assert(ps);

	//扩容 开辟空间
	if (ps->capacity == ps->top)
	{
		// 三目运算符  等于0开辟4个字节 不等于0原空间大小*2
		int newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
		STDataType* tmp = (STDataType*)realloc(ps->a, newcapacity * sizeof(STDataType));
		
		//开辟失败
		if (tmp == NULL)
		{
			perror("realloc fail");
			exit(1);
		}
		
		//开辟成功
		ps->capacity = newcapacity;
		ps->a = tmp;

	}	

	//入栈
	ps->a[ps->top] = x;
	ps->top++;

}

易错提醒:

开辟空间的条件这里写的是:ps->capacity==ps->top

原因:

因为我们这里是把top初始化为0了。当top初始化为0时,top跟size的意思一样(注意下标)

  • 若top=-1,top+1=capacity
  • 若top=0,top=capacity

哪种写法都可以,看初始化时top为-1还是0

3.5 ❥ 删除 (出栈)  

思路:

  • 删除前应先进行断言,防止传入空指针
  • 还要断言栈内元素是否为空,如果为空的话,就没办法进行出栈操作
  • 删除只需要将top指针往前挪动一位即可(因为top代表有效元素个数)

代码如下:

// 删除(出栈)
void STPop(ST* ps)
{
	assert(ps);
	assert(ps->top > 0);

	ps->top--;
}

插入删除端的固定:

具体插入删除端在哪里根据自己所选的结构有关。

  • 数组:插入删除端在尾
  • 单链表:插入删除端在头
  • 双向链表:插入删除端在头尾都可以

3.6 ❥ 获取栈顶元素

思路:

  • 获取之前先进行断言是否为空指针
  • 也需要断言栈内是否有元素
  • 然后返回top前一个下标位置(因为top用作下标表示的是栈顶下一个元素的位置)

代码如下:

//获取栈顶元素
STDataType STTop(ST* ps)
{
	assert(ps);
	assert(ps->top > 0);

	return ps->a[ps->top--];
}

3.7 ❥ 判空

思路:

  • 先进行断言
  • 判断top是否为0,为0则栈空,不为0则栈不为空

代码如下:

//判断栈是否为空
bool STEmpty(ST* ps)
{
	assert(ps);

	return ps->top == 0;
}

3.8 ❥ 获取数据个数

思路:

  • 先进行断言
  • 返回top(top表示有效数据个数)

代码如下:

//获取数据个数
int STSize(ST* ps)
{
	assert(ps);

	return ps->top;
}

四、完整运行代码

stack.h

#pragma once

#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h>
#include<assert.h>


typedef int STDataType;

//定义栈结构
typedef struct stack
{
	STDataType* a;  //指向数组元素的指针
	int top;	   //有效数据个数
	int capacity;  //容量大小
}ST;


//初始化
void STInit(ST*ps);  //传的是实参的地址,因为形参是实参的一份临时拷贝

//销毁
void STDestory(ST*ps);

//插入(入栈)
void STPush(ST* ps, STDataType x);//插入端固定,只能在一端进行插入

// 删除(出栈)
void STPop(ST * ps);//删除端也固定,只能在一端进行删除

//获取栈顶元素
STDataType STTop(ST* ps);

//判断栈是否为空
bool STEmpty(ST* ps);

//获取数据个数
int STSize(ST* ps);

stack.c

#define _CRT_SECURE_NO_WARNINGS 1

#include"stack.h"

//初始化
void STInit(ST* ps)
{
	ps->a = NULL;
	ps->top = 0;
	ps->capacity = 0;
}



//销毁
void STDestory(ST* ps)
{
	assert(ps);

	free(ps->a);
	ps->a = NULL;

	ps->top = ps->capacity = 0;
}

//插入(入栈)
void STPush(ST* ps, STDataType x)
{
	assert(ps);

	//扩容 开辟空间
	if (ps->capacity == ps->top)
	{
		// 三目运算符  等于0开辟4个字节 不等于0原空间大小*2
		int newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
		STDataType* tmp = (STDataType*)realloc(ps->a, newcapacity * sizeof(STDataType));
		
		//开辟失败
		if (tmp == NULL)
		{
			perror("realloc fail");
			exit(1);
		}
		
		//开辟成功
		ps->capacity = newcapacity;
		ps->a = tmp;

	}	

	//入栈
	ps->a[ps->top] = x;
	ps->top++;

}

// 删除(出栈)
void STPop(ST* ps)
{
	assert(ps);
	assert(ps->top > 0);

	ps->top--;
}

//获取栈顶元素
STDataType STTop(ST* ps)
{
	assert(ps);
	assert(ps->top > 0);

	return ps->a[ps->top--];
}

//判断栈是否为空
bool STEmpty(ST* ps)
{
	assert(ps);

	return ps->top == 0;
}

//获取数据个数
int STSize(ST* ps)
{
	assert(ps);

	return ps->top;
}

test.c

#define _CRT_SECURE_NO_WARNINGS 1

#include"stack.h"

int main()
{
	//创建结构体变量s
	ST s;

	//初始化
	STInit(&s);

	//插入(入栈)
	STPush(&s, 1);
	STPush(&s, 2);
	STPush(&s, 3);
	STPush(&s, 4);

	// 删除(出栈)
	STPop(&s);

	//获取栈顶元素
	STDataType ret=STTop(&s);

	//判断栈是否为空
	bool h=STEmpty(&s);

	//获取数据个数
	int size=STSize(&s);

	//销毁
	STDestory(&s);

	return 0;
}

  • 28
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值