图文并茂 ,栈的实现 —— C语言

栈的实现 —— C语言

在这里插入图片描述


每博一文案

有位作家说:”我的人生里,没有那么多时间和精力去揣测别人对我的看法和评论。

因为该做的事,太多了。

李宗盛说过:“每一个努力过的脚印,都是相连的。它一步一步带我到今天

,成功就今天的我,人生没有白走的路,每一步都算数。”

每个阳光下开怀大笑的人,都有一段沉默的时光。

​ —————— 一禅心灵庙语



创建项目的注意事项

  • 首先我们要明白一点就是不存在没有完完全全BUG的项目,就像人无完人一样 我们只能不断地对项目进行优化,减少BUG 的出现,但是我们好像并不能完全的消除所有的BUG
  • 而消除bug 的最好的方式就是运行测试+调试
  • 而我们在运行测试+调试 的时候,需要我们控制一定的 ,我们需要把握住这个 ,这一点是十分重要的,这样有助于我们快速的发现问题,也就是bug 的所在,从而提高我们的效率,减少我们的bug 的出现
  • 具体的方法,我个人有以下建议:
  1. 首先我们需要对项目进行合理的分类,那个类,那个文件实现什么样的功能: 比如:一个类是用于定义什么类型,方法的 ,另一个类是用于什么头main 的,再另外一个类是用于对于什么功能上的实现的 ,合理的分类,可以提高我们项目的可读性,让我们自身不会因为自己随着代码量的增加,对应功能上的增加而导致我们越敲越乱,越写越蒙
  2. 对于量上我们可以,每实现了两三个 功能就运行测试看看,是否存在错误,如果存在出现了错误,我们可以在一定的量(范围内),快速的找出哪里出现了问题,从而对它进行调试 ,解决问题,而不是,把项目整体写完了或者是已经实现了好几十个功能,才开始运行测试项目,结果一运行,bug,警告 冒出一大坨,我们先不说,让不让人,抓头发,就是找出问题,恐怕都需要不时间上的浪费吧
  3. 对于代码上的注释,没事多写明白一点,或许有一天,你会感想曾经,那个写了注释的自己或同事
  4. 对于bug 不要害怕,一点一点的调试看看,找出问题的所在,慢慢来,要有耐心,要明白一点现在bug 写的多,以后bug 跟你说拜拜,现在bug ,不解决,bug 天天对你说 明天见

栈的介绍

栈: 一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作一端称为 栈顶 ,另一端称为 栈底 。不含任何数据的栈称为 空栈 ,栈中的数据元素遵守: 后进先出LIFO(Last In First Out) 的原则。

压栈 :栈的插入操作 叫作压栈,也称为进栈,入栈、入数据在栈顶

出栈 :栈的删除操作叫作 出栈,也称为弹栈,出数据也是在栈顶

在这里插入图片描述


在这里插入图片描述


栈对于顺序表与链表的选择

的实现实现一般可以使用数组或者链表来实现的

使用数组 实现 的结构图:

在这里插入图片描述

从上面的数组实现栈的话,我们对于进栈可以通过对栈顶的下标位置插入数据,而出栈 我们可以使用栈顶 后移动的方式,对于删除数据不需要移动大量的元素,因为 的特点只有栈顶 一端可以进出数据,在时间复杂度上是为O(1)的,


使用链表实现

对于使用链表 ,我们可以使用单链表 以及双向链表 ,比较方便

单链表 的实现,结构图:

在这里插入图片描述

如果以单链表的尾插中的尾节点 作为 栈顶 ,使用这种单链表实现的话,我们可以十分明显的发现进栈容易出栈难 ,因为我们进栈 的话,只要直接拿到尾节点的地址拼接上就可以了,十分容易,但是,如果我们是出栈 删除数据的话,需要找到前一个节点的地址才可以删除数据的,所以从出栈 删除数据上的时间复杂度为O(n),但是我们可以使用 双向链表 解决这个找到前一个节点的问题:结构图:如下

在这里插入图片描述

双向链表就解决了,找到前一个节点的地址的问题了,可以在时间复杂度上为O(1),上解决出栈 问题了

其实我们使用单链表实现栈,在出栈上也是可以到达时间复杂度为O(1)

我们这样设计链表栈 ,使用头插法 的方式进栈 ,以头节点 作为栈顶 ,这样对于出栈 ,我们可以将栈顶置为下一个节点就可以了,在时间复杂度上是为 O(1)的,设计结构如图下:

在这里插入图片描述


栈的具体代码的实现

这里我们使用动态顺序表 实现 ,因为相对而言数组的结构实现更优一些。因为数组在尾上
插入数据的代价比较小

创建栈的结构体类型

这里我们使用了 的方式定义了我们需要扩容的倍数 ,方便后续的必要修改,使用关键字 typedef 定义别名我们的栈的数据类型,同样是方便为了后续需要改变存放的数据的类型时,只要修改这里即可,有利于项目的维护管理

#define OCE 2              // 扩容的倍数
typedef int STDatatype;    // 设定栈数据的类型


// 创建有关栈的结构体类型
typedef struct Stack
{
	STDatatype* data;      // 动态顺序表
	int top;               // 栈顶的标识
	int copacity;          // 栈容量

}ST;


初始化栈

首先我们需要判断一个该传递过来的栈是否为 NULL ,这里我们为空,使用断言 处理,assert(ps) 意思是:如果 ps == NULL 则断言,中止程序,在该位置处报错告诉你,

这里我们使用的是动态顺序表 ,在堆区 上开辟空间,需要判断动态内存的开辟是否成功,如果失败,使用关键字perror 一个错误报告提醒一下,并中止程序 exit(-1)

函数名: exit()
所在头文件:stdlib.h
功 能: 关闭所有文件,终止正在执行的进程。
exit(1)表示异常退出.这个1是返回给操作系统的。
exit(x)(x不为0)都表示异常退出
exit(0)表示正常退出
exit()的参数会被传递给一些操作系统,包括UNIX,Linux,和MS DOS,以供其他程序使用。


栈顶位置的标记存在着两种方式:一个是在下标为 0 的位置,一个是在下标为 -1 的位置,标记的位置的不同其意义也是不一样的,如下图 所示:

在这里插入图片描述


在这里插入图片描述


void Stacklnit(ST* ps)
{
	assert(ps);        // 断言,判断一下栈是否为 NULL,
	
	ps->data = (STDatatype*)malloc(sizeof(STDatatype) * 6);     // 动态内存(堆区)开辟空间

	// 判断动态内存开辟空间是否成功,
	if (NULL == ps->data)
	{
		// 动态内存开辟失败
		perror("ps->data,动态内存开辟失败");        // 错误报告提醒
		exit(-1);                  // -1 程序非正常中止
	}

	ps->copacity = 6;               // 栈的容量
	ps->top = 0;                    // 栈顶位置的标记

}

销毁栈

断言一下,防止栈为 NULL(空),释放动态内存(堆区)开辟的空间使用关键字 free( ) ,并置为 NULL ,防止非法访问,

// 销毁栈
void StackDestor(ST* ps)
{
	assert(ps);    // 断言,防止栈为 NULL 空

	free(ps->data);   // 释放 ps->data 内存空间
	ps->data = NULL;  // 手动置为 NULL,防止非法访问

	ps->copacity = 0;  // 栈容量置为 0 
	ps->top = 0;       // 栈顶标志也为 0 
}

栈顶入栈

同样断言,判断一下,防止栈为NULL(空),在数据入栈 前需要判断容量是否足够存放数据,不够扩容,使用关键字realloc( ),注意在计算类型的大小上,尽管你知道对应类型的大小,还是建议使用操作符 sizeof( ) ,原因提高兼容性,因为类型的大小,在不同的位数的 PC 中,类型的大小是不一样的,如:32位int类型 的大小是 2个字节,而64位int类型 的大小是 4个字节,所以存在误差,而如果使用操作符 sizeof( ) 的话无论是 多少位数的字节个数上都是统一的,提高了项目的兼容性,同样对于动态内存(堆区),也是需要判断扩容是否成功的,成功后,移交 扩容后的地址的主权位置,栈容量 增加,还有一般扩容是在原来的基础上增加 1.5倍或 2倍,防止空间上的浪费 ,你扩容了那么多,却只用一点点。栈顶入栈

// 栈顶入栈
void StackPush(ST* ps, STDatatype x)
{
	assert(ps);       // 断言,防止栈为 NULL,

	// 判断栈是否满了,满了扩容
	if (ps->top == ps->copacity)
	{
		// 扩容
		STDatatype* tmp = (STDatatype*)realloc(ps->data, ps->copacity * OCE*sizeof(STDatatype));
		
		// 判断扩容是否成功
		if (NULL == tmp)
		{
			perror("tmp 扩容");    // 失败提醒
			exit(-1);             // 非正常退出程序

		}
		else
		{
			// 扩容成功,交换主权
			ps->data = tmp;
			ps->copacity *= OCE; // 扩容成功,栈容量扩大

		}
	}

	// 数据入栈
	ps->data[ps->top] = x;
	ps->top++;     // 移动栈顶标识

}

栈顶出栈

栈顶出栈,判断是否为(NULL)空栈,栈不可以没有数据了还进行出栈操作,出栈:移动栈顶标识后移,注意: 不可以使用 ps->data[ps->top] = 0 ,将元素数据置为 0 的方式,因为:有可能其中存放的数值就是 0 呢,却被当作出栈处理了

// 栈顶出栈
void StackPop(ST* ps)
{
	assert(ps);      // 防止栈为 NULL

	if (ps->top < 0)
	{
		printf("栈中没有数据了不可在删除出栈了\n");
		return;       // 退出程序
	}

	ps->top--;      // 栈顶标志后移一个,出栈

}

取出栈顶元素

判断是否为空栈,空栈无法获取栈顶元素,top 的位置的指向在,初始化栈 中已经详细介绍了是栈顶元素的下一个位置,所以这里的栈顶位置是 top -1 的下标位置

// 栈顶取栈顶元素
STDatatype StackTop(ST* ps)
{
	assert(ps);   // 防止栈为 NULL

	if (ps->top <= 0)
	{
		printf("栈为空不可以取栈顶元素\n");
		exit(-1);    // 退出程序
	}
    else
	{
		return ps->data[ps->top - 1];  // top 指向的是栈顶元素的下一个位置
	}

}

取栈的有效数据个数

判断栈是否为空,直接返回 ps->top 的数值就可以了,因为:数组的下标是从 0 开始的,但是我们计数是从 1 开始的,所以需要 + 1,刚好其数值就是等于 ps->top 的,

// 获取栈中的有效数据的个数
int StackSize(ST* ps)
{
	assert(ps);    // 防止栈为空

	return ps->top;       
}

判断栈是否为空栈

这里判断栈是否为空,使用了 布尔值 bool ,需要引入头文件 #include<stdbool.h> ,才可以使用布尔值,其中布尔值 true 真就是数值 1,布尔值 false 假就是数值 0 ,关于C语言 中的布尔值,大家可以移步到 🔜🔜🔜 你真的了解C语言 if - else 、bool(布尔值)、浮点数损失吗 ,栈为空,返回 true,栈不为空,返回 false

// 判断栈是否为空栈
bool StackEmpty(ST* ps)
{
	assert(ps);   // 断言,防止栈为空

	if (ps->top == 0)
	{   // 栈为空,返回 true 真就是 1 
		return true;
	}
	else
	{
		// 栈不为空,返回 false 键就是 0 
		return false;
	}

}

栈的实现的完整代码:

这里我们使用的是 分布式文件设计

Stack.h

存放有关栈中的,函数的声明,变量,宏、结构体,声明使用 extern 关键字、声明是不会开辟空间的,定义才会开辟空间,而开辟空间不是目的,存放数据(二进制)才是目的,因为存放数据需要开辟空间

#pragma once

#include<stdio.h>
#include<assert.h>    // 断言头文件
#include<stdbool.h>   // bool 布尔值头文件
#include<stdlib.h>

#define OCE 2             // 扩容的倍数
typedef int STDatatype;    // 设定栈数据的类型


// 创建有关栈的结构体类型
typedef struct Stack
{
	STDatatype* data;      // 动态顺序表
	int top;               // 栈顶的标识
	int copacity;          // 栈容量

}ST;



// 使用 extern 声明函数和变量
extern void Stacklnit(ST* ps);                         // 初始化栈
extern void StackDestor(ST* ps);                       // 销毁栈
extern void StackPush(ST* ps, STDatatype x);           // 栈顶入栈
extern void StackPop(ST* ps);                          // 栈顶出栈
extern STDatatype StackTop(ST* ps);                    // 栈顶取栈顶元素
extern int StackSize(ST* ps);                          // 获取栈中有效数据的个数
extern bool StackEmpty(ST* ps);                        // 判断栈是否为空栈
extern void StackTest(ST* ps);                         // 栈的测试

Stack.c

存放栈中的功能

#define  _CRT_SECURE_NO_WARNINGS  1


#include"Stack.h"


// 初始化栈
void Stacklnit(ST* ps)
{
	assert(ps);        // 断言,判断一下栈是否为 NULL,
	
	ps->data = (STDatatype*)malloc(sizeof(STDatatype) * 6);     // 动态内存(堆区)开辟空间

	// 判断动态内存开辟空间是否成功,
	if (NULL == ps->data)
	{
		// 动态内存开辟失败
		perror("ps->data,动态内存开辟失败");        // 错误报告提醒
		exit(-1);                  // -1 程序非正常中止
	}

	ps->copacity = 6;               // 栈的容量
	ps->top = 0;                    // 栈顶位置的标记

}



// 销毁栈
void StackDestor(ST* ps)
{
	assert(ps);    // 断言,防止栈为 NULL 空

	free(ps->data);   // 释放 ps->data 内存空间
	ps->data = NULL;  // 手动置为 NULL,防止非法访问

	ps->copacity = 0;  // 栈容量置为 0 
	ps->top = 0;       // 栈顶标志也为 0 
}



// 栈顶入栈
void StackPush(ST* ps, STDatatype x)
{
	assert(ps);       // 断言,防止栈为 NULL,

	// 判断栈是否满了,满了扩容
	if (ps->top == ps->copacity)
	{
		// 扩容
		STDatatype* tmp = (STDatatype*)realloc(ps->data, ps->copacity * OCE*sizeof(STDatatype));
		
		// 判断扩容是否成功
		if (NULL == tmp)
		{
			perror("tmp 扩容");    // 失败提醒
			exit(-1);             // 非正常退出程序

		}
		else
		{
			// 扩容成功,交换主权
			ps->data = tmp;
			ps->copacity *= OCE; // 扩容成功,栈容量扩大

		}
	}

	// 数据入栈
	ps->data[ps->top] = x;
	ps->top++;     // 移动栈顶标识

}



// 栈顶出栈
void StackPop(ST* ps)
{
	assert(ps);      // 防止栈为 NULL

	if (ps->top < 0)
	{
		printf("栈中没有数据了不可在删除出栈了\n");
		return;       // 退出程序
	}

	ps->top--;      // 栈顶标志后移一个,出栈

}


// 栈顶取栈顶元素
STDatatype StackTop(ST* ps)
{
	assert(ps);   // 防止栈为 NULL

	if (ps->top <= 0)
	{
		printf("栈为空不可以取栈顶元素\n");
		exit(-1);    // 退出程序
	}
	else
	{
		return ps->data[ps->top - 1];  // top 指向的是栈顶元素的下一个位置
	}

}



// 获取栈中的有效数据的个数
int StackSize(ST* ps)
{
	assert(ps);    // 防止栈为空

	return ps->top;       
}



// 判断栈是否为空栈
bool StackEmpty(ST* ps)
{
	assert(ps);   // 断言,防止栈为空

	if (ps->top == 0)
	{   // 栈为空,返回 true 真就是 1 
		return true;
	}
	else
	{
		// 栈不为空,返回 false 键就是 0 
		return false;
	}

}


Test.c

main 方法中对于栈的测试

栈只有一端栈顶 ,才可以进栈,入栈的,遵循先进后出 的原则的,取了栈顶元素,还要伴随出栈才行,不然你是无法获取到后面的元素数据的,是单向的

#define  _CRT_SECURE_NO_WARNINGS  1

#include"Stack.h"

int main()
{
	ST st;   // 定义栈结构体类型

	StackTest(&st);   // 注意是传地址

	return 0;
}



// 栈的测试
void StackTest(ST* ps)
{
	Stacklnit(ps);    // 初始化栈

	StackPush(ps, 1);
	StackPush(ps, 2);
	StackPush(ps, 3);     // 数据入栈
	StackPush(ps, 4);
	printf("%d\n", StackTop(ps));  // 取栈顶元素
	StackPop(ps);                  // 出栈
	StackPush(ps, 5);
	StackPush(ps, 6);
	StackPush(ps, 7);

	printf("栈是否为空:%d\n", StackEmpty(ps));    // 判断栈是否为空栈
	printf("栈中的元素个数:%d\n", StackSize(ps));  // 获取栈中的有效可数


	while (0 == StackEmpty(ps))
	{
		printf("%d\n", StackTop(ps));    // 取栈顶元素
		StackPop(ps);                    // 栈顶出栈
	}
	printf("\n");
	/*

	//StackPop(ps);
	printf("%d\n",StackSize(ps));
	printf("%d\n",StackTop(ps));
	printf("%d\n",StackEmpty(ps));

	*/

	StackDestor(ps);    // 销毁栈 

}

运行结果

在这里插入图片描述


最后

限于自身水平,其中存在的错误,希望大家给予指教,韩信点兵——多多益善,谢谢大家,后会有期,江湖再见!


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值