栈与队列的实现

news2024/11/28 0:33:54

前言

本次博客将要实现一下栈和队列,好吧

他们两个既可以使用动态数组也可以使用链表来实现

本次会有详细的讲解

栈的实现

栈的基础知识

什么是栈呢?

栈的性质是后进先出

来画个图来理解

当然可不可以出一个进一个呢,当然可以了

比如先进1 2 3再出一个

还剩下 1 2   再进一个5

最后出完所有元素

那么出的元素为 3 5 2 1

但是进入的顺序为1 2 3 5

所以先进后出也是相对而言的

我们上面的图只是让大家理解后入先出的概念

ok接下来我们开始来选择用什么结构来实现栈

分析

数组实现

很明显我们知道,栈是后入先出,所以它是需要进行尾插尾删的

而数组的尾插尾删,只需要将大小也就是size++或者-- 就可以实现,

在取栈顶的数据时也可以直接调用,取栈底时也可以直接调用

链表实现

当然链表也可以实现,但是普通的链表需要找尾,才可以尾插

我们可以多用一个指针来指向这个尾,这样可以解决问题

但是尾删怎么办?

如果要尾删,我们还是要再加一个指针指向这个尾的前一个才好尾删呀

然后取栈底栈顶可以通过头结点以及尾来找到

这样也可以实现,但是很麻烦

所以这个普通的单链表是不好实现链表的

但我们就是要搞事情

解决方法一

使用双向链表,这个就不多说了,多加个尾,尾插尾删,很简单

解决方法二

反过来看链表

看图吧

是不是实现的方法有很多种

我们这次要干大的,把这三种都敲一敲,当然在敲的过程中一定要注意细节

本次博客还会把细节交代清楚

1数组实现栈

我们先来看看头文件吧

#pragma once
#include<stdlib.h>
#include<stdio.h>
#include<stdbool.h>
#include<assert.h>
//这里是动态数组的定义
typedef int DataType;
typedef struct Stack {
	DataType* a;
	int top;
	int capacity;
}ST;
//初始化栈
void InitStack(ST* st);
//取栈顶
DataType TopStack(ST* st);
//入栈
void PushStack(ST* st, DataType x);
//出栈
void PopStack(ST* st);
//取栈的大小
int SizeStack(ST* st);
//取栈底
DataType BottomStack(ST* st);
//销毁栈
void DestroyStack(ST* st);
//栈的判空
bool EmptyStack(ST* st);

细节1

注意我们这里定义了一个int top;

栈顶 在这里其实可以有两种写法,1 top初始值为0   2 top初始值为-1

第一种是top指向栈顶元素的下一个 第二种为top指向栈顶元素

其实就是因为数组的元素是以0开始的

这两种不同的写法,会导致后序的函数实现有一些差别

比如取栈的大小时,第一种是 返回top的值 第二种是返回top+1

本次博客使用第一种

细节2

在动态开辟数组时还有一个问题

就是一开始要不要给容量的问题,如果不给容量的话

在push操作中就会多一个判断,给容量的话一开始在Init(初始化)就要开辟空间

这也是细节之一这两种写法也有所不同 ,本次博客使用一开始不给容量

另外还有注意在满容量时的扩容,是扩二倍还是扩定量

搞清楚这几个细节就可以敲代码了

我们直接看吧

#include"stack.h"
void InitStack(ST* st)
{
	assert(st);
	st->a = NULL;
	st->capacity = 0;
	st->top = 0;
}
DataType TopStack(ST* st)
{
	assert(st);
	assert(st->top > 0);
	return st->a[st->top - 1];
}
void PushStack(ST* st, DataType x)
{
	assert(st);
	if (st->a == NULL)
	{
		st->a=(DataType*)malloc(sizeof(DataType)*4);
		st->capacity = 4;
		st->a[st->top++] = x;
	}
	else
	{
		if (st->capacity == st->top)
		{
			DataType* temp = (DataType*)realloc(st->a, sizeof(DataType) * 2 * st->capacity);
			if (temp != NULL)
			{
				st->a =temp;
				st->capacity *= 2;
			}
		}
		st->a[st->top++] = x;
	}
}
void PopStack(ST* st)
{
	assert(st);
	assert(st->top > 0);
	st->top--;
}
int SizeStack(ST* st)
{
	return st->top;
}
DataType BottomStack(ST* st)
{
	assert(st);
	assert(st->top > 0);
	return st->a[0];
}
void DestroyStack(ST* st)
{
	free(st->a);
	st->a = NULL;
	st->capacity = 0;
	st->top = 0;
}
bool EmptyStack(ST* st)
{
	return st->top == 0;
}

测试的话,大家自行去看,反正我是已经测试过了

2单链表实现栈

当然先看头文件

再来说说细节之处

#pragma once
#include<stdlib.h>
#include<stdio.h>
#include<stdbool.h>
#include<assert.h>
typedef struct Stack2_ {
	struct Stack2* next;
	DataType value;
}ST2_;
typedef struct Stack2 {
	ST2_* top;
	ST2_* bottom;
	int size;
}ST2;
//初始化
void InitStack2(ST2* st);
//栈顶
DataType TopStack2(ST2* st);
//入栈
void PushStack2(ST2* st, DataType x);
//出栈
void PopStack2(ST2* st);
//返回栈的大小
int SizeStack2(ST2* st);
//栈底
DataType BottomStack2(ST2* st);
//销毁栈
void DestroyStack2(ST2* st);
//判空
bool EmptyStack2(ST2* st);

细节1

我们是使用了嵌套结构体避免了空间的浪费

比如如果使用一个结构体

那么每一个结构体都要有栈底,栈底,大小,还有下一个成员指针next,以及值value

而且对于不带头的单链表来说必须使用二级指针,很麻烦

这里是一定不能带头的

如果带头结点的话,那么我们有怎么头插呢,是不是,所以这里可是可以不写两个

结构体,但是要使用二级指针,来搞定第一个结点的情况

而且,它本质上浪费了空间

OK看看.c文件

void InitStack2(ST2* st)
{
	assert(st);
	st->top = st->bottom = NULL;
	st->size = 0;
}
DataType TopStack2(ST2* st)
{
	assert(st->top);
	return st->top->value;
}
void PushStack2(ST2* st, DataType x)
{
	assert(st);
	ST2_* temp = (ST2_*)malloc(sizeof(ST2_));
	if (temp == NULL)
		return;
	temp->value = x;
	if (st->top == NULL)
	{
		st->bottom = st->top = temp;
		temp->next = NULL;
	}
	else
	{
		temp->next = st->top;
		st->top = temp;
	}
	st->size++;
}
void PopStack2(ST2* st)
{
	assert(st);
	assert(st->top);
	if (st->bottom == st->top)
	{
		free(st->top);
		st->bottom = st->top = NULL;
	}
	else
	{
		ST2_ *temp = st->top->next;
		free(st->top);
		st->top = temp;
	}
	st->size--;
}
int SizeStack2(ST2* st)
{
	return st->size;
}
DataType BottomStack2(ST2* st)
{
	assert(st->top);
	return st->bottom->value;
}
void DestroyStack2(ST2* st)
{
	assert(st);
	while (!EmptyStack2(st))
	{
		PopStack2(st);
	}
	st->size = 0;
}
bool EmptyStack2(ST2* st)
{
	return st->size == 0;
}

再来看看下一个,真挺累的,哈哈哈哈

3双链表实现栈

其实原理是一样的,但是这个结构会使操作更加简单

这个就直接看代码就好


void InitStack3(ST3* st);
DataType TopStack3(ST3* st);
void PushStack3(ST3* st, DataType x);
void PopStack3(ST3* st);
int SizeStack3(ST3* st);
DataType BottomStack3(ST3* st);
void DestroyStack3(ST3* st);
bool EmptyStack3(ST3* st);

void InitStack3(ST3* st)
{
	st->size = 0;
	st->bottom = NULL;
}
DataType TopStack3(ST3* st)
{
	assert(st);
	assert(st->bottom);
	return st->bottom->prev->value;
}
void PushStack3(ST3* st, DataType x)
{
	assert(st);
	ST3_* temp = (ST3_*)malloc(sizeof(ST3_));
	temp->value = x;
	if (st->bottom == NULL)
	{
		st->bottom = temp;
		temp->prev = temp;
		temp->next = temp;
	}
	else
	{
		ST3_* tem = st->bottom->prev;
		tem->next = temp;
		temp->next = st->bottom;
		temp->prev = tem;
		st->bottom->prev = temp;
	}
	st->size++;
}
void PopStack3(ST3* st)
{
	assert(st);
	assert(st->bottom);
	//st->bottom->next == st->bottom->prev
	//只有两个节点时也会有上式成立
	//下面是解决方法
	//st->bottom==st->bottom->next
	//SizeStack3(st)==1
	if (SizeStack3(st) == 1)
	{
		free(st->bottom);
		st->bottom = NULL;
	}
	else
	{
		ST3_* tem = st->bottom->prev;
		st->bottom->prev = tem->prev;
		tem->prev->next = st->bottom;
		free(tem);
	}
	st->size--;
}
int SizeStack3(ST3* st)
{
	return st->size;
}
DataType BottomStack3(ST3* st)
{
	assert(st);
	assert(st->bottom);
	return st->bottom->value;
}
void DestroyStack3(ST3* st)
{
	while (!EmptyStack3(st))
	{
		PopStack3(st);
	}
	st->size = 0;
}
bool EmptyStack3(ST3* st)
{
	assert(st);
	return st->size == 0;
}

哎呀,写的过程还是有些小问题,但都是小问题

OK,下面来看看队列

队列的实现

队列的特性与栈正好相反

他是先入先出

看图吧

当然了

队列的实现与栈的实现大差不差

队列也可以通过数组和链表来实现

分析

首先队列是需要从入口进,出口出的,

对于数组来说,他需要尾插和头删,

尾插很简单,只需要再接一个元素即可

但是头插,很麻烦需要整体左移然后size--时间复杂为o(n)

所以用数组实现有硬伤,因为其顺序结构,所以必然如此

对于链表来说 

我们先使用单链表,要进行尾插以及头删

我们需要使用尾插,一般的单链表进行尾插的时间复杂度为o(n)

头删很简单,复杂度为o(1)

但是,我们可以使用一个指针来指向尾,这样就可以直接使尾插的复杂度变为o(1)

ok,从结构来讲,单链表最适合

但是,咱么都干了!!!

1数组实现队列

直接看代码吧

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>
typedef int DataType;
typedef struct Queue {
	DataType* a;
	int size;
	int capacity;
}Que;
void InitQue(Que*que)
{
	que->a = (DataType*)malloc(sizeof(DataType)*4);
	que->capacity = 4;
	que->size = 0;
}
void QuePush(Que* que, DataType x)
{
	if (que->size == que->capacity)
	{
		DataType* temp = (DataType*)realloc(que->a,sizeof(DataType)*que->capacity*2);
		if (temp == NULL)
		{
			printf("realloc fail\n");
			return;
		}
		else
		{
			que->a = temp;
			que->capacity *= 2;
		}
	}
	que->a[que->size++] = x;
}
void QuePop(Que* que)
{
	assert(que->size != 0);
	for (int i = 1; i < que->size; i++)
	{
		que->a[i - 1] = que->a[i];
	}
	que->size--;
}
bool QueEmpty(Que* que)
{
	return que->size == 0;
}

DataType QueBack(Que* que)
{
	assert(que);
	assert(que->size > 0);
	return que->a[que->size-1];
}

DataType QueFront(Que* que)
{
	return que->a[0];
}
void QueDestroy(Que* que)
{
	free(que->a);
	que->capacity = 0;
	que->size = 0;
}

上面的代码,基本实现的逻辑与栈相差无几,甚至可以说跟简单

OK

看看更加实用的链表实现

2单链表实现队列

这里的注意点与用栈实现栈也是一样的

但是有一个注意点还是要讲一讲的

这里差点漏了

注意点1

就是在pop队列时

如果只有一个结点时,此时请你一定要把tail指针置空

上面的链表实现栈是也会有这个问题,

大家要注意,不然你就会被自己幽默到,看到nullter被调用 哈哈哈哈哈

OK大家看代码呗

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>
typedef int DataType;
typedef struct Queue_2 {
	struct Queue2* next;
	DataType data;
}Que_2;
typedef struct Queue2 {
	Que_2* a;
	Que_2* tail;
	int size;
}Que2;
void InitQue2(Que2* que);
void QuePush2(Que2* que, DataType x);
void QuePop2(Que2* que);
bool QueEmpty2(Que2* que);
DataType QueFront2(Que2* que);
void QueDestroy2(Que2* que);
int QueSize2(Que2* que);
DataType QueBack2(Que2* que);
void InitQue2(Que2* que)
{
	assert(que);
	que->tail=que->a = NULL;
	que->size = 0;
}
void QuePush2(Que2* que, DataType x)
{
	Que_2* temp = (Que_2*)malloc(sizeof(Que_2));
	if (temp == NULL)
	{
		printf("malloc fail\n");
		return;
	}
	temp->next = NULL;
	temp->data = x;
	if (que->a == NULL)
	{
		que->tail=que->a= temp;
	}
	else
	{
		que->tail->next = temp;
		que->tail = temp;
	}
	que->size++;
}
void QuePop2(Que2* que)
{
	assert(que->a);
	Que_2* temp = que->a->next;
	free(que->a);
	que->a = temp;
	if (que->a == NULL)
	que->tail = NULL;
	que->size--;
}
bool QueEmpty2(Que2* que)
{
	return que->a == NULL;
}
DataType QueFront2(Que2* que)
{
	assert(que->a);
	return que->a->data;
}
DataType QueBack2(Que2* que)
{
	assert(que->a);
	return que->tail->next;
}
void QueDestroy2(Que2* que)
{
	assert(que->a);
	while (que->a)
	{
		Que_2* temp = que->a->next;
		free(que->a);
		que->a = temp;
	}
	que->tail = NULL;
	que->size = 0;
}
int QueSize2(Que2* que)
{
	return que->size;
}

总结

额,终于算是肝完了,这里的代码的重复性很高,不要看字很多

很多都是重复的!好吧,栈和队列这个基础数据结构的各项实现方法算是都敲了一遍

当然,我们还有一个循环队列没有实现,对吧

下一次再来,搞一搞oj题

加油!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1668633.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C++笔记(STL标准库)

1.STL六大部件 容器 Containers分配器 Allocators&#xff1a;帮容器分配内存算法 Algorithms迭代器 Iterators&#xff1a;算法通过迭代器操作容器里的数据&#xff0c;是一种泛化的指针适配器 Adapters&#xff1a;修改或扩展已有类或函数的接口以满足特定的需求仿函数 Func…

Python深度学习基于Tensorflow(9)注意力机制

文章目录 注意力机制是怎么工作的注意力机制的类型 构建Transformer模型Embedding层注意力机制的实现Encoder实现Decoder实现Transformer实现 注意力机制的主要思想是将注意力集中在信息的重要部分&#xff0c;对重要部分投入更多的资源&#xff0c;以获取更多所关注目标的细节…

2024最新商业视频打赏系统源码 多套模板 有代理后台 已对接支付

简介&#xff1a; 2024最新商业视频打赏系统源码 多套模板 有代理后台 已对接支付 图片&#xff1a; 源码下载

清华发布Temporal Scaling Law,解释时间尺度对大模型表现的影响

众所周知&#xff0c; 语言模型调参&#xff01; 预训练语言模型调参&#xff01;&#xff01; 预训练大语言模型调参&#xff01;&#xff01;&#xff01; 简直就是一个指数级递增令人炸毛的事情&#xff0c;小编也常常在做梦&#xff0c;要是只训练几步就知道现在的超参…

【优选算法】—Leetcode—11—— 盛最多水的容器

1.题目 11. 盛最多水的容器 给定一个长度为 n 的整数数组 height 。有 n 条垂线&#xff0c;第 i 条线的两个端点是 (i, 0) 和 (i, height[i]) 。 找出其中的两条线&#xff0c;使得它们与 x 轴共同构成的容器可以容纳最多的水。 返回容器可以储存的最大水量。 说明&#…

K邻近算法

简介 介绍了非常简单的算法&#xff1a;K邻近算法&#xff0c;即KNN。 基本介绍 K-近邻算法&#xff08;K-Nearest Neighbors&#xff0c;简称KNN&#xff09;是一种基本且广泛应用的监督学习算法&#xff0c;主要用于分类和回归任务。 工作原理非常简答直观&#xff1a;所谓…

改变浏览器大小,图片(img)内容居中显示img标签,不是背景图

改变浏览器大小,图片&#xff08;img&#xff09;内容居中显示&#xff0c;img标签&#xff0c;不是背景图 效果直接上图&#xff1a; 上代码&#xff1a; <!DOCTYPE html> <html> <head><title>测试图片居中显示&#xff0c;高度不变只变宽度<…

面试集中营—Seata分布式事务

一、分布式事务 本地事务 在计算机系统中&#xff0c;更多的是通过关系型数据库来控制事务&#xff0c;这是利用数据库本身的事务特性来实现的&#xff0c; 因此叫数据库事务&#xff0c;由于应用主要靠关系数据库来控制事务&#xff0c;而数据库通常和应用在同一个服务器&am…

SQLite利用事务实现批量插入(提升效率)

在尝试过SQLite批量插入一百万条记录&#xff0c;执行时长高达20多分钟后&#xff0c;就在想一个问题&#xff0c;这样的性能是不可能被广泛应用的&#xff0c;更不可能出现在真实的生产环境中&#xff0c;那么对此应该如何优化一下呢&#xff1f; 首先分析一下批量插入的逻辑 …

社交媒体数据恢复:脉脉

在使用社交软件脉脉的过程中&#xff0c;可能会遇到数据丢失的情况&#xff0c;如误删了重要信息或者更换手机后数据未能同步等问题。那么如何恢复脉脉中的数据呢&#xff1f;本文将为您提供详细的步骤指导。 注意&#xff1a;以下操作需要在脉脉账户登录状态下进行。 登录脉…

红米K60Pro/K50/K40系列澎湃OS解锁BL降级出厂MIUI14稳定版本方法

最新红米K60/60pro/K50/K50至尊/K40等多个系列手机都已经推送了澎湃OS系统&#xff0c;但新版的系统适配周期短或者等其他原因&#xff0c;导致很多小伙伴希望降级回到MIUI14系统&#xff0c;多个小米售后都拒绝降级服务&#xff0c;并且官方也没有开通1个自助降级的方法&#…

速戳!高考生做近视手术须知,避免错过心仪大学

距离高考还有不到一个月的时间&#xff0c;考生们在紧张复习的同时&#xff0c;不要忘了了解意向专业、院校的视力要求。一些专业和院校录取不仅靠实力,还需要“视力”,考了个好成绩却因视力不达标而被专业、院校退档,这样的结果是我们不想看到的。如果你想圆军旅梦、警校梦、航…

软考--信息系统项目管理师课程笔记

第一章 信息化发展 1.国家信息化&#xff1a;应用&#xff08;上&#xff09;&#xff0c;技术&#xff08;下&#xff09;&#xff0c;人才&#xff08;左&#xff09;&#xff0c;规范&#xff08;右&#xff09; 2.广域网协议包括&#xff1a;ISDN&#xff0c;ASDL&#xf…

Android的视图显示和管理机制:layout view window WindowManager Canvas Surface

在Android系统中&#xff0c;Layout view window WindowManager Canvas Surface SurfaceFlinger这些组件协同工作&#xff0c;以实现图形的绘制和显示。需要搞明白这些组件是什么时候创建的以及他们之间的结构关系。 从上到下的层级关系&#xff1a;用户在View上进行操作&…

SQLite .journal 文件

在之前插入大量数据测试的时候&#xff0c;发现在数据库文件同级目录下会产生一个同名.journal的文件&#xff0c;并且不是一直会存在&#xff0c;而是生成一会就会自动删除&#xff0c;然后继续生成继续删除&#xff0c;直到数据插入完成。 初步猜测&#xff0c;应该是类似 re…

代码-功能-Python-运用bs4技术爬取汽车之家新闻信息

第三方库安装指令&#xff1a; pip install requests -i https://pypi.tuna.tsinghua.edu.cn/simple pip install BeautifulSoup4 -i https://pypi.tuna.tsinghua.edu.cn/simple 运行代码&#xff1a; #这个代码并不完整&#xff0c;有很大的问题&#xff0c;但目前不知道怎么…

05-Fortran基础--Fortran文件操作

05-Fortran基础--Fortran文件操作 0 引言1 文件操作有关的关键字介绍1.1 打开文件&#xff08;OPEN&#xff09;1.2 文件关闭&#xff08;CLOSE&#xff09;1.3 文件查询&#xff08;INQUIRE&#xff09;1.3 文件读写&#xff08;read/write&#xff09;1.4 文件操作符&#xf…

机器学习作业4——朴素贝叶斯分类器

目录 一、理论 一个例子&#xff1a; 二、代码 对于代码的解释&#xff1a; 1.fit函数&#xff1a; 2.predict函数: 三、实验结果 原因分析&#xff1a; 一、理论 朴素贝叶斯分类器基于贝叶斯定理进行分类&#xff0c;通过后验概率来判断将新数据归为哪一类。通过利用贝…

设计模式——结构型模式——代理模式(静态代理、动态代理:JDK、CGLIB)

目录 代理模式 代理模式简介 代理模式的分类 代理模式组成 代理模式的优缺点 静态代理 背景前置 编写代码 JDK动态代理 编写代码 使用Arthas分析JDK动态代理底层原理 CGLIB动态代理 编写代码 三种代理的对比 代理模式使用场景 代理模式 代理模式简介 代理模式属…

测试萌新三天速通python基础(二)列表,字符串,元组,字典,遍历,容器,集合,函数

python基础 字符串下标(索引)切片字符串的替换 replace()字符串拆分 split()字符串的连接 join列表 list列表的增删改查列表的反转 reverse()排序列表嵌套元组 tuple 排序 升序降序交换变量字典 dict查询遍历容器集合函数参数函数的嵌套调⽤函数的返回值模块导⼊的⽅法____name…