前言:前面我们学习了单链表并且模拟了它的实现,今天我们来进一步学习,来学习栈和队列吧!一起加油各位,后面的路只会越来越难走需要我们一步一个脚印!
💖 博主CSDN主页:卫卫卫的个人主页 💞
👉 专栏分类:数据结构 👈
💯代码仓库:卫卫周大胖的学习日记💫
💪关注博主和博主一起学习!一起努力!
什么是栈
栈 :一种特殊的线性表 ,其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端称为栈顶,另一端称为栈底。栈中的数据元素遵守后进先出的原则(如下图所示)。
压栈 :栈的插入操作叫做进栈/压栈/入栈 ,入数据在栈顶。
出栈:栈的删除操作叫做出栈。出数据也在栈顶。
如何实现栈
栈的实现一般可以使用数组或者链表实现,相对而言数组的结构实现更优一些。因为数组在尾上插入数据的代价比较小。所以我们今天通过使用动态数组来模拟实现栈。
栈的基本结构
我们已经确定了通过动态数组来模拟实现栈,那么我们要实现哪些功能呢?
- 栈的初始化
- 栈的销毁
- 入栈
- 出栈
- 获取栈顶元素
- 检测栈是否为空
- 获取栈中有效元素个数
首先我们需要考虑如何记录栈顶元素的位置,我们用top 来记录,但是top 是不是应该是0还是什么数据呢?如果top 指向的是0那么我top是0的时候栈中到底是一个元素还是没有元素,这是十分具有歧义的,所以我们这里用负一来表示top 当有元素进入时就让top加加即可(如下图)。
具体结构如下:
c
typedef struct Stack
{
STDataType* a;
int top; //标识顶部
int capacity;
}ST;
栈的基本功能
c
#pragma once
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
#include<stdbool.h>
typedef int STDataType;
typedef struct Stack
{
STDataType* a;
int top; //标识顶部
int capacity;
}ST;
//栈的初始化
void STInit(ST* pst);
//栈的销毁
void STDestroy(ST* pst);
//栈顶插入删除
//入栈
void STPush(ST* pst, STDataType x);
//出栈
void STPop(ST* pst);
// 获取栈顶元素
STDataType STTop(ST* pst);
// 检测栈是否为空,如果为空返回非零结果,如果不为空返回0
bool STEmpty(ST* pst);
// 获取栈中有效元素个数
int STSize(ST* pst);
栈的初始化
思路导读 :前面我们讲到过用top 来记录栈顶的位置,之所以让它从负一开始就是为了方便我们访问栈顶数据,也是为了防止歧义,如下图所示。我们初始化也只需要将开辟的空间指向空指针,空间容量为0即可。代码实现:
c
void STInit(ST* pst)//栈的初始化
{
assert(pst);
pst->top = -1;//表示top指向栈顶元素的下一个位置
pst->a = NULL;
pst->capacity = 0;
}
栈的销毁
思路导读 :我们只需要找到a 开辟的空间将它释放掉并将他置为空指针即可,然后其它值Top处理为-1,空间容量置为0即可
代码实现:
c
void STDestroy(ST* pst)//栈的销毁
{
assert(pst);
free(pst->a);
pst->a = NULL;
pst->top = -1;
pst->capacity = 0;
}
入栈
思路导读 :我们入栈首先就是得开辟一块空间存放即将要入栈的值,然后将这块空间传给a即可,然后再把栈顶元素赋值,即可完成入栈。(如果这块地方有不懂的友友可以去看我之前的博客有详细讲解动态顺序表)
代码实现:
c
void STPush(ST* pst, STDataType x)//入栈
{
assert(pst);
if (pst->top + 1 == pst->capacity)//判断是否有空间
{
int newcapacity = pst->capacity == 0 ? 4 : pst->capacity * 2;
STDataType* tmp = (STDataType*)realloc(pst->a, sizeof(STDataType) * newcapacity);//防止开辟失败俩个空间都丢失
if (tmp == NULL)
{
perror("realloc fail");
return;
}
pst->a = tmp;
pst->capacity = newcapacity;
}
pst->top++;//top加一
pst->a[pst->top] = x;//栈顶赋值
}
出栈
思路导读 :关于出栈,我们要知道他是先进后出,在前面的图中我们都有详细的讲解,出去一个元素我们让top 往下走一个即可。
代码实现:
c
void STPop(ST* pst)//出栈
{
assert(pst);//判断是不是空
assert(pst->top >= 0);//判断有没有元素
pst->top--;//top减减即可
}
获取栈顶元素
思路导读:首先我们还是得判断传来的数据是否为空,里面是否有元素,然后我们直接访问栈顶元素即可。
c
STDataType STTop(ST* pst)// 获取栈顶元素
{
assert(pst);
assert(pst->top >= 0);
return pst->a[pst->top];
}
函数测试与效果图:
c
void Test2()
{
ST sl;
STInit(&sl);
STPush(&sl, 0);
printf("%d ", STTop(&sl));
STPush(&sl, 9);
printf("%d ", STTop(&sl));
STPush(&sl, 2);
printf("%d ", STTop(&sl));
STPush(&sl, 2);
printf("%d ", STTop(&sl));
printf("上面是入栈后\n");
printf("下面是出栈后栈顶元素\n");
STPop(&sl);
printf("%d ", STTop(&sl));
STPop(&sl);
printf("%d ", STTop(&sl));
STPop(&sl);
printf("%d ", STTop(&sl));
STPop(&sl);
}
检测栈是否为空
思路导读 :首先判断传来的是否有这个结构体,我们只需要判断top 是否是负一如果是说明这个为真则返回0,不是则返回非0的结果
代码实现:
c
bool STEmpty(ST* pst)// 检测栈是否为空,如果为空返回非零结果,如果不为空返回0
{
assert(pst);
return pst->top == -1;//如果是-1则是空返回true,如果不是返回false
}
获取栈中有效元素个数
思路导读 :我们只需要将top加一即为元素个数,因为top指向的栈顶元素。
c
int STSize(ST* pst)// 获取栈中有效元素个数
{
assert(pst);
return pst->top + 1;
}
整体函数测试
c
void Test3()
{
ST sl;
STInit(&sl);
STPush(&sl, 1);
STPush(&sl, 3);
STPush(&sl, 0);
STPush(&sl, 9);
STPush(&sl, 2);
STPush(&sl, 8);
printf("入栈后元素的个数:%d\n", STSize(&sl));
printf("下面是入栈元素\n");
while (!STEmpty(&sl))
{
printf("%d ", STTop(&sl));
STPop(&sl);//出栈
}
STDestroy(&sl);
}
int main()
{
//Test1();
//Test2();
Test3();
return 0;
}
具体效果图 :
到这里关于栈的实现我们都已经讲完了,接下来我们开始队列的学习!
什么是队列
队列 :只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出(我们可以通俗的理解成一头进一头出,先进去的先出去)。
入队列 :进行插入操作的一端称为队尾。
出队列 :进行删除操作的一端称为队头。
具体如下图 :
如何实现队列
由于队列的特殊性,只能一头进一头出,且先进的先出,在实现它的时候我们只需要考虑尾插和头删即可,在尾插和头删上效率较高的应该就是单链表了,但我们应当用一个tail的尾指针来记录,防止尾增的时候再次遍历链表。故我们通过单链表来实现队列。
队列的基本功能与结构
关于下面为什么会在用一个结构体来记录头指针和尾指针,是因为如果我们补定义一个结构体来记录,我们就会在入队列和出队列的时候,需要传一个二级指针来修改队列中的值,为了防止这样的麻烦我们就直接定义一个结构体来记录即可。
c
typedef int QDataType;
typedef struct QueueNode
{
QDataType val;
struct QueueNode* next;
}QNode;
typedef struct Queue//记录头指针和尾指针
{
QNode* phead;
QNode* ptail;
int size;//记录元素个数
}Queue;
// 初始化队列
void QueueInit(Queue* pq);
//销毁队列
void QueueDestory(Queue* pq);
// 队尾入队列
void QueuePush(Queue* pq, QDataType x);
// 队头出队列
void QueuePop(Queue* pq);
// 获取队列头部元素
QDataType QueueFront(Queue* pq);
// 获取队列队尾元素
QDataType QueueBack(Queue* pq);
// 检测队列是否为空,如果为空返回非零结果,如果非空返回0
bool QueueEmpty(Queue* pq);
// 获取队列中有效元素个数
int QueueSize(Queue* pq);
队列的初始化
思路导读 :关于队列的初始化,我们只需要将头指针和尾指针置为空即可,然后将里面的元素置为0。
代码实现:
c
void QueueInit(Queue* pq)//队列的初始化
{
assert(pq);
pq->phead = pq->ptail = NULL;
pq->size = 0;
}
队尾入队列
思路导读 :首先我们得分两种情况:一种是该队列为空第一次入队列,一种是该队列不为空不是第一次入队列,但无论如何都要开辟一块空间存放新开辟的值。当为第一次入队列的时候,我们只需要将头节点和尾节点全部指向这块新开辟的空间即可。当不是第一次入的时候,我们需要找到尾结点将它指向新开辟的这块空间即可完成入队列,且每当进入一个元素我们让size 加加即可知道队列中的元素个数。如下图所示:
代码实现:
c
void QueuePush(Queue* pq, QDataType x)//队尾入队列
{
assert(pq);
QNode* newnode = malloc(sizeof(QNode));
if (newnode == NULL)
{
perror("malloc fail");
return;
}
newnode->val = x;
newnode->next = NULL;
//判断是不是第一次
if (pq->ptail == NULL)
{
pq->phead = pq->ptail = newnode;
}
else
{
pq->ptail->next = newnode;
pq->ptail = newnode;
}
pq->size++;//记录元素
}
队头出队列
思路导读 :首先我们得分为两种情况,一种是队列中只有一个元素的情况,一种是队列中有多个元素的情况。当队列中有多个元素的时候,我们需要一个指针来保留头节点,并找到头节点的下个位置让它成为新的节点,并将原来的头节点释放掉即可完成出队列。但当队列中只有一个元素时候,我们需要将头节点释放的同时也要将尾节点置为空指针,不然就会出现野指针的情况。
代码实现:
c
void QueuePop(Queue* pq)// 队头出队列
{
assert(pq);
assert(pq->phead);
Queue* tmp = pq->phead;//记录头指针,方便释放空间
pq->phead = pq->phead->next;//让头指针指向下一个元素
free(tmp);//释放头指针
if (pq->phead == NULL)//如果队列中只有一个元素,将尾指针也置为空指针
{
pq->ptail = NULL;
}
pq->size--;
}
获取队列头部元素
思路导读 :我们只需要找到头节点指向的val 的值将它返回即可。
代码实现:
c
QDataType QueueFront(Queue* pq)// 获取队列头部元素
{
assert(pq);
assert(pq->phead);
assert(pq->ptail);
return pq->phead->val;
}
获取队列队尾元素
思路导读 :我们在这之前一直记录了一个尾部节点,因此获取队尾元素的时候我们不需要重新在遍历队列,只需要返回尾节点的值即可。
代码实现:
c
QDataType QueueBack(Queue* pq)// 获取队列队尾元素
{
assert(pq);
assert(pq->phead);
assert(pq->ptail);
return pq->ptail->val;
}
检测队列是否为空
思路导读 :我们只需要判断之前存储数据个数的size 是否为空即可。
代码实现:
c
bool QueueEmpty(Queue* pq)// 检测队列是否为空,如果为空返回非零结果,如果非空返回0
{
assert(pq);
return pq->size == 0;
}
获取队列中有效元素个数
思路导读 :我们直接返回记录元素个数的size即可获取队列中有效元素的个数。
c
int QueueSize(Queue* pq)// 获取队列中有效元素个数
{
assert(pq);
return pq->size;
}
销毁队列
思路导读 :我们要把队列中的每一个结点都释放掉,我们需要一个指针dl 记录头指针原本的位置,让头指针往后走遍历队列依次释放经过的位置,具体如下图
代码实现:
c
void QueueDestory(Queue* pq)//销毁队列
{
assert(pq);
Queue* dl = NULL;
while (pq->phead)
{
dl = pq->phead;
pq->phead = pq->phead->next;
free(dl);
}
pq->ptail = NULL;
pq->size = 0;
pq->phead = NULL;
}
整体函数测试
c
void Test2()
{
Queue s;
QueueInit(&s);
QueuePush(&s, 1);
QueuePush(&s, 2);
QueuePush(&s, 3);
QueuePush(&s, 4);
QueuePush(&s, 5);
printf("%d ", QueueFront(&s));
printf("%d ", QueueBack(&s));
printf("%d\n", QueueSize(&s));
QueuePop(&s);
QueuePop(&s);
printf("%d ", QueueFront(&s));
printf("%d\n", QueueSize(&s));
if (!QueueEmpty(&s))
{
QueuePop(&s);
printf("%d ", QueueFront(&s));
printf("%d\n", QueueSize(&s));
}
printf("%d\n", QueueSize(&s));
QueueDestory(&s);
}
int main()
{
//Test1();
Test2();
return 0;
}
运行结果 :
整体代码
c
#pragma once
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
#include<stdbool.h>
typedef int QDataType;
// 队列的结构
typedef struct QueueNode
{
QDataType val;
struct QueueNode* next;
}QNode;
typedef struct Queue//记录头指针和尾指针
{
QNode* phead;
QNode* ptail;
int size;//记录元素个数
}Queue;
// 初始化队列
void QueueInit(Queue* pq);
//销毁队列
void QueueDestory(Queue* pq);
// 队尾入队列
void QueuePush(Queue* pq, QDataType x);
// 队头出队列
void QueuePop(Queue* pq);
// 获取队列头部元素
QDataType QueueFront(Queue* pq);
// 获取队列队尾元素
QDataType QueueBack(Queue* pq);
// 检测队列是否为空,如果为空返回非零结果,如果非空返回0
bool QueueEmpty(Queue* pq);
// 获取队列中有效元素个数
int QueueSize(Queue* pq);
#include"Queen.h"
void QueueInit(Queue* pq)//队列的初始化
{
assert(pq);
pq->phead = pq->ptail = NULL;
pq->size = 0;
}
void QueuePush(Queue* pq, QDataType x)//队尾入队列
{
assert(pq);
QNode* newnode = malloc(sizeof(QNode));//开辟新空间
if (newnode == NULL)
{
perror("malloc fail");
return;
}
//赋值
newnode->val = x;
newnode->next = NULL;
//判断是不是第一次
if (pq->ptail == NULL)
{
pq->phead = pq->ptail = newnode;
}
else
{
pq->ptail->next = newnode;
pq->ptail = newnode;
}
pq->size++;//记录元素
}
void QueuePop(Queue* pq)// 队头出队列
{
assert(pq);
assert(pq->phead);
Queue* tmp = pq->phead;//记录头指针,方便释放空间
pq->phead = pq->phead->next;//让头指针指向下一个元素
free(tmp);//释放头指针
if (pq->phead == NULL)//如果队列中只有一个元素,将尾指针也置为空指针
{
pq->ptail = NULL;
}
pq->size--;
}
QDataType QueueFront(Queue* pq)// 获取队列头部元素
{
assert(pq);
assert(pq->phead);
assert(pq->ptail);
return pq->phead->val;
}
QDataType QueueBack(Queue* pq)// 获取队列队尾元素
{
assert(pq);
assert(pq->phead);
assert(pq->ptail);
return pq->ptail->val;
}
bool QueueEmpty(Queue* pq)// 检测队列是否为空,如果为空返回非零结果,如果非空返回0
{
assert(pq);
return pq->size == 0;
}
int QueueSize(Queue* pq)// 获取队列中有效元素个数
{
assert(pq);
return pq->size;
}
void QueueDestory(Queue* pq)//销毁队列
{
assert(pq);
Queue* dl = NULL;
while (pq->phead)
{
dl = pq->phead;
pq->phead = pq->phead->next;
free(dl);
}
pq->ptail = NULL;
pq->size = 0;
pq->phead = NULL;
}
#define _CRT_SECURE_NO_WARNINGS 1
#include"Queen.h"
void Test1()
{
Queue sl;
QueueInit(&sl);
QueuePush(&sl, 1);
//printf("%d\n", QueueFront(&sl));
QueuePush(&sl, 9);
//printf("%d\n",QueueFront(&sl));
//QueuePop(&sl);
QueuePush(&sl, 2);
QueuePush(&sl, 10);
printf("%d\n", QueueFront(&sl));
printf("%d\n",QueueBack(&sl));
}
void Test2()
{
Queue s;
QueueInit(&s);
QueuePush(&s, 1);
QueuePush(&s, 2);
QueuePush(&s, 3);
QueuePush(&s, 4);
QueuePush(&s, 5);
printf("%d ", QueueFront(&s));
printf("%d ", QueueBack(&s));
printf("%d\n", QueueSize(&s));
QueuePop(&s);
QueuePop(&s);
printf("%d ", QueueFront(&s));
printf("%d\n", QueueSize(&s));
if (!QueueEmpty(&s))
{
QueuePop(&s);
printf("%d ", QueueFront(&s));
printf("%d\n", QueueSize(&s));
}
printf("%d\n", QueueSize(&s));
QueueDestory(&s);
}
int main()
{
//Test1();
Test2();
return 0;
}
结语:今天的内容就到这里吧,谢谢各位的观看,如果有讲的不好的地方也请各位多多指出,作者每一条评论都会读的,谢谢各位。
🫵🫵🫵 祝各位接下来好运连连 💞