栈和队列是两种常见的数据结构,都是两种特殊的线性表。本篇文章将对栈和队列进行简要的分析。
1.栈
1.1栈的概念和结构
栈是一种特殊的线性表,与之前学过的顺序表和链表类似,但是栈有自己独有的特点:
栈是一种先进后出(Last In First Out,LIFO)的数据结构,类似于现实生活中的栈。栈有以下主要特点:
- 只能在栈顶进行插入和删除操作。
- 最后插入的元素首先被删除,即最先插入的元素最后被访问。
- 只允许访问栈顶元素,不能访问其他位置的元素。
- 栈的大小是固定的,只能存储有限数量的元素。
用图来表示栈的结构即为:
1.2栈的实现
1.2.1栈的实现的选择
栈一般可以用数组和链表来进行实现。使用数组实现比较方便,如下图:
先进入的数据摆在数组的前面,后进入的数据摆在数组的后面。入栈的时候向后添加数据,需要出栈的时候从右往左出栈(删除或导出数据)即可。
使用链表就不是非常方便,按照外面之前学过的单向链表:
我们可以一个一个数据地添加(入栈),但是如果要出栈的话就显得非常麻烦,可能还要用到双向链表,不如数组简单直接,所以我们在实现栈的时候用的是数组。在后续实现先进先出的队列时链表就显得更加便利了。
1.2.2栈的实现
我们有了实现顺序表和链表的经验,大致知道数据结构有哪些函数,以及大致的实现方法,这对其他数据结构的实现就很有帮助了。
第一步依旧是将代码分为3个文件:stack.h,stack.c,test.c .
首先我们写stack.h(定义实现栈所需要的结构体,头文件的包含以及完成函数的声明):
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h>
#include<assert.h>
typedef int DataType;
typedef struct Stack
{
DataType* p;
int top;
int Capacity;
}ST;
// 初始化和销毁
void STInit(ST* pst);
void STDestroy(ST* pst);
// 入栈 出栈
void STPush(ST* pst, DataType x);
void STPop(ST* pst);
// 取栈顶数据
DataType STTop(ST* pst);
// 判空
bool STEmpty(ST* pst);
// 获取数据个数
int STSize(ST* pst);
//打印函数
void STPrint(ST* pst);
下面我们针对stack.c中需要完成的函数逐一讲解。
(1)初始化函数。
首先要对维护栈的数据进行初始化:指针置为空,int top和int capacity均置为0.
// 初始化函数
void STInit(ST* pst)
{
assert(pst);
pst->p = NULL;
pst->Capacity = 0;
pst->top = 0;
}
(2)销毁函数同理,释放指针并且将指针置为空:
//销毁函数
void STDestroy(ST* pst)
{
assert(pst);
free(pst->p);
pst->p= NULL;
pst->top = pst->Capacity = 0;
}
(3)入栈、出栈
入栈就是数据的插入,数据插入数组非常简单,主要分为两个步骤:1.判断申请的空间是否够用,2.将数据插入数组。(用top代表栈顶数据所在数组元素的下标,每插入一个数据就将top++)
void STPush(ST* pst, DataType x)
{
assert(pst);
//扩容
if (pst->Capacity == pst->top)
{
int NewCapacity = pst->Capacity == 0 ? 4 : 2 * pst->Capacity;
DataType* tmp = (DataType*)realloc(pst->p, sizeof(DataType) * NewCapacity);
pst->Capacity = NewCapacity;
pst->p = tmp;
}
pst->p[pst->top] = x;
pst->top++;
}
出栈比入栈容易很多,不用进行空间的判定,直接将top--即可。
//出栈
void STPop(ST* pst)
{
assert(pst);
pst->top--;
}
(4)取栈顶数据
假设我们现在已经插入了n个数据,top已经++了n次,此时top的值是n,但是栈顶的数据在数组中的下标是n-1,所以我们返回的下标应该是top-1:
// 取栈顶数据
DataType STTop(ST* pst)
{
return pst->p[pst->top-1];
}
(5)获取数据函数和判空函数过于简单不再赘述
(6)打印函数
打印函数通过常规的遍历数组的方式即可。
//打印函数
void STPrint(ST*pst)
{
assert(pst);
while (pst->top)
{
printf("%d ", pst->p[pst->top]);
pst->top--;
}
}
最后简单写一个test.c测试以下效果即可:
#include"Stack.h"
int main()
{
ST st;
STInit(&st);
STPush(&st,1);
STPush(&st, 2);
STPush(&st, 3);
STPush(&st, 4);
STPop(&st);
STPrint(&st);
STDestroy(&st);
return 0;
}
输出结果如下:
2.队列
2.1队列的结构和概念
队列与栈的概念和结构恰好是相反的:
队列是一种线性数据结构,用于存储一系列元素。它遵循先进先出(First In First Out,FIFO)的原则,即最先进入队列的元素最先被取出。队列支持两种基本操作:入队(enqueue)和出队(dequeue)。
- 入队操作:将元素添加到队列的末尾,新元素成为队列中的最后一个元素。
- 出队操作:从队列的头部移除并返回队列中的第一个元素,同时更新队列的头部指针。
队列的结构示意图如下:(它就像一个单向行驶的隧道,先进去的车先出去)
我们的代码最后出来的效果如下:(先进先出,先增添的那一批数据先删除)
2.2队列的实现
队列的实现与栈的实现类似。
2.2.1Queue.h
头文件Queue.h先写由链表定义的队列:
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdio.h>
#include<stdbool.h>
typedef int QDataType;
typedef struct QListNode
{
struct QListNode* next;
QDataType val;
}QNode;
typedef struct Queue
{
QNode* Fir;
QNode* Las;
int size;
}Queue;
// 初始化队列
void QueueInit(Queue* q);
//打印函数
void QueuePrint(Queue* q);
// 队尾入队列
void QueuePush(Queue* q, QDataType data);
// 队头出队列
void QueuePop(Queue* q);
// 获取队列头部元素
QDataType QueueFront(Queue* q);
// 获取队列队尾元素
QDataType QueueBack(Queue* q);
// 获取队列中有效元素个数
int QueueSize(Queue* q);
// 检测队列是否为空,如果为空返回非零结果,如果非空返回0
int QueueEmpty(Queue*q);
// 销毁队列
void QueueDestroy(Queue* q);
注意事项:
队列是由链表构成的。
我们这里创建了两个结构体来修饰队列:
1.QListNode 结构体是用于创造链表的
2.Queue 结构体是用来方便管理队列的(包含前指针,后指针和记录队列大小的数据,两个指针分别指向队首和队尾)。
2.2.2Queue.c
下面就是Queue.c文件代码的实现:
主要的函数已经列在头文件中,.c文件完整代码如下:
#include"queue.h"
struct Queue q;
// 初始化队列
void QueueInit(Queue* q)
{
q->Fir = NULL;
q->Las = NULL;
q->size = 0;
}
//打印函数
void QueuePrint(Queue* q)
{
assert(q);
QNode* Qrecord = q->Fir;
while (q->Las - q->Fir)
{
printf("%d ",q->Fir->val);
q->Fir = q->Fir->next;
}
printf("%d ", q->Fir->val);
q->Fir = Qrecord;
}
// 队尾入队列
void QueuePush(Queue* q, QDataType data)
{
assert(q);
QNode* Newnode = (QNode*)malloc(sizeof(QNode));
Newnode->val = data;
Newnode->next = NULL;
//判断队列是否有元素
if (q->Las == NULL)
{
q->Fir = q->Las = Newnode;
}
else
{
q->Las->next = Newnode;
q->Las = Newnode;
//和下面的代码一个意思
//q->Las->next = Newnode;
//q->Las =q->Las->next;
}
q->size++;
}
// 队头出队列
void QueuePop(Queue* q)
{
assert(q);
assert(q->size!=0);
//一个节点
if (q->size == 1)
{
free(q->Fir);
q->Fir = q->Las = NULL;
}
//多个节点
if(q->size >1)
{
QNode* QTmp = q->Fir;
q->Fir = q->Fir->next;
free(QTmp);
}
q->size--;
}
// 获取队列头部元素
QDataType QueueFront(Queue* q)
{
assert(q);
assert(q->Fir);
return q->Fir->val;
}
// 获取队列队尾元素
QDataType QueueBack(Queue* q)
{
assert(q);
assert(q->Las);
return q->Las->val;
}
// 获取队列中有效元素个数
int QueueSize(Queue* q)
{
assert(q);
return q->size;
}
// 检测队列是否为空,如果为空返回非零结果,如果非空返回0
int QueueEmpty(Queue* q)
{
assert(q);
if (q->size == 0)
{
return true;
}
else
{
return false;
}
}
// 销毁队列
void QueueDestroy(Queue* q)
{
assert(q);
while (q->Las - q->Fir)
{
QNode* tmp = q->Fir;
q->Fir = q->Fir->next;
free(tmp);
tmp = NULL;
}
q->Fir = q->Las = NULL;
q->size = 0;
}
2.2.3test.c
主要代码写好了之后再创建测试文件检测一下,就是开头展示效果的代码:
#include"queue.h"
int main()
{
Queue q;
QueueInit(&q);
QueuePush(&q,1);
QueuePush(&q, 2);
QueuePush(&q, 3);
QueuePush(&q, 4);
QueuePrint(&q);
printf("\n");
QueuePop(&q);
QueuePrint(&q);
printf("\n");
QueuePush(&q, 5);
QueuePush(&q, 6);
QueuePush(&q, 8);
QueuePrint(&q);
printf("\n");
QueuePop(&q);
QueuePop(&q);
QueuePrint(&q);
QueueDestroy(&q);
return 0;
}
步骤:首先是添加1,2,3,4入队列,打印一行;再删除一个元素(因为1是第一个进的所以删除1),打印一行; 再添加5,6,8三个元素,打印一行;再删除两次,打印一行。最后的效果如下:
感谢观看,如有错误请批评指出