一. 前言
各位读者朋友们大家好,上期我们讲完了单链表的结构以及代码实现,这期我们一起来学习栈和队列并且用C语言来实现这两个数据结构。首先我们先来学习栈。
二. 栈
1. 栈的概念及结构
1.1 栈的概念
栈是一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端
称为栈顶,另一端称为栈底。栈中的数据元素遵守后进先出LIFO(Last In First Out)的原则。
- 压栈:将数据插入栈的操作叫做压栈,插入的元素在栈顶。
- 出栈:栈的删除操作叫做出栈。出数据也在栈顶。
1.2 栈的结构
这里我们选择用顺序表的结构来实现栈。
typedef int STDadaType;
typedef struct Stack
{
STDadaType* arr;
int top;//指向栈顶元素的下一个位置
int capacity;
}ST;
2. 栈的实现
栈要实现以下的接口:
- 1.栈的初始化和销毁
- 2.入栈和出栈
- 3.取栈顶元素
- 4.判空
- 5.栈的元素个数
下面我们来一一实现这些接口。
2.1 栈的初始化和销毁
2.1.1 栈的初始化
void STInit(ST* pst)
{
assert(pst);
pst->arr = NULL;
pst->top = 0;
pst->capacity = 0;
}
栈的初始化的实现和顺序表的初始化是一样的。这里我们将top初始化为0,top指向栈顶的下一个位置,top也就等价于顺序表中的size。
2.1.2 栈的销毁
void STDestroy(ST* pst)
{
assert(pst);
free(pst->arr);
pst->arr = NULL;
pst->capacity = pst->top = 0;
}
arr的空间是动态申请开辟的,在不使用后要将它释放掉,并将它置为空指针来规避野指针。
2.2 入栈和出栈
2.2.1 入栈
入栈就是压栈,将数据插入到栈顶,因为我们是使用的顺序表结构,因此入栈也就相当于在顺序表的末尾插入元素。我们在初始化栈的时候将top初始化为0,那么也就意味着top是指向栈顶元素的后一个位置。因此,入栈就是在top位置插入数据。
void STPush(ST* pst, STDadaType x)
{
assert(pst);
//扩容
if (pst->top == pst->capacity)
{
int newcapacity = pst->capacity == 0 ? 4 : pst->capacity * 2;
STDadaType* tmp = (STDadaType*)realloc(pst->arr, sizeof(STDadaType) * newcapacity);
if (tmp == NULL)
{
perror("rrealloc fail");
return;
}
pst->arr = tmp;
pst->capacity = newcapacity;
}
pst->arr[pst->top++] = x;
}
在入栈的时候我们也要考虑空间的问题,当top和capacity相等时,也就是栈满或栈为空的情况下要扩容。在插入数据后,记得top++。
2.2.2 出栈
出栈就是将栈顶的元素删除,也就相当于顺序表的尾删,只需要将top-1即可。
void STPop(ST* pst)
{
assert(pst);
assert(pst->top > 0);
pst->top--;
}
2.3 取栈顶元素
因为我们的top初始化为0,所以栈顶元素是在top-1的位置。
STDadaType STTop(ST* pst)
{
assert(pst);
assert(pst->top > 0);
return pst->arr[pst->top - 1];
}
2.4 判空
栈的元素为零即为空。
bool STEmpty(ST* pst)
{
assert(pst);
return pst->top == 0;
}
2.5 栈的元素个数
int STSize(ST* pst)
{
assert(pst);
return pst->top;
}
3. 栈的元素打印
因为栈中的元素是后进先出的,因此在打印的时候,我们要将栈顶的元素删除后再打印下一个元素。
int main()
{
ST s;
STInit(&s);
STPush(&s, 1);
STPush(&s, 2);
STPush(&s, 3);
STPush(&s, 4);
while (!STEmpty(&s))
{
printf("%d ", STTop(&s));
STPop(&s);
}
STDestroy(&s);
return 0;
}
但是注意,出栈的顺序不一定是最后进的先出,也可以边进边出。
int main()
{
ST s;
STInit(&s);
STPush(&s, 1);
STPush(&s, 2);
printf("%d ", STTop(&s));
STPop(&s);
STPush(&s, 3);
STPush(&s, 4);
while (!STEmpty(&s))
{
printf("%d ", STTop(&s));
STPop(&s);
}
STDestroy(&s);
return 0;
}
这样,我们在将2入栈之后接着出栈了。
以上,我们就讲完了栈这一数据结构,接下来我们讲队列这个数据结构。
三. 队列
1. 队列的概念及结构
1.1 队列的概念
队列:只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出FIFO(First In First Out) 。
- 入队列:进行插入操作的一端称为队尾
- 出队列:进行删除操作的一端称为队头
1.2 队列的结构
这里我们选择使用链表结构实现队列。
typedef int QDataType;
typedef struct QueueNode
{
QDataType val;
struct QueueNode* next;
}QNode;
因为我们在后续接口中要使用队列的头指针和尾指针,我们创建一个结构体来存储头指针和尾指针,以及队列中有效元素的个数,这样也规避了使用二级指针。
typedef struct Queue
{
QNode* phead;
QNode* ptail;
int size;
}Queue;
2. 队列的实现
队列要实现以下的接口:
- 队列的初始化和销毁
- 出队列和入队列
- 获取队列头部和尾部数据
- 判空
- 获取队列有效元素个数
2.1 队列的初始化和销毁
2.1.1 队列的初始化
void QueueInit(Queue* pq)
{
assert(pq);
pq->phead = NULL;
pq->ptail = NULL;
pq->size = 0;
}
2.1.2 队列的销毁
void QueueDestroy(Queue* pq)
{
assert(pq);
QNode* pcur = pq->phead;
while (pcur)
{
QNode* next = pcur->next;
free(pcur);
pcur = next;
}
pq->phead = pq->ptail = NULL;
pq->size = 0;
}
这里的next指针记录下一个节点,避免释放后找不到下一个节点。
2.2 出队列和入队列
2.2.1 入队列
当队列为空时,我们在队列中插入数据,头指针和尾指针都指向新插入的节点,当不为空时,就相当于链表尾插节点。
void QueuePush(Queue* pq, QDataType x)
{
assert(pq);
QNode* newnode = (QNode*)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++;
}
因为只有在入队列的时候才会创建节点,因此创建节点的功能就在入队列的函数中实现。
2.2.2 出队列
因为队列是先进先出的数据结构,因此出队列就相当于是链表的头删。在这里我们要考虑一下队列中只有一个结点的情况。
在我们将这个节点释放后,头指针指向了空,但是尾指针还是指向原来的地址,当我们再次使用尾指针时,尾指针就成了野指针,因此我们在这种情况下也要将尾指针置空。
void QueuePop(Queue* pq)
{
assert(pq);
assert(pq->size != 0);//队列不为空
if (pq->phead->next == NULL)//只有一个节点
{
free(pq->phead);
pq->phead = pq->ptail = NULL;
}
else
{
QNode* next = pq->phead->next;
free(pq->phead);
pq->phead = next;
}
pq->size--;
}
3. 获取队列头部和尾部数据
3.1 获取队列头部元素
QDataType QueueFront(Queue* pq)
{
assert(pq);
assert(pq->phead);
return pq->phead->val;
}
3.2 获取队列尾部元素
QDataType QueueBack(Queue* pq)
{
assert(pq);
assert(pq->ptail);
return pq->ptail->val;
}
4. 获取队列有效元素个数
int QueueSize(Queue* pq)
{
assert(pq);
return pq->size;
}
5. 判空
bool QueueEmpty(Queue* pq)
{
assert(pq);
return pq->size == 0;
}
四. 整体代码呈现
1. 栈
Stack.h
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h>
#include<assert.h>
typedef int STDadaType;
typedef struct Stack
{
STDadaType* arr;
int top;//指向栈顶元素的下一个位置
int capacity;
}ST;
//初始化和销毁
void STInit(ST* pst);
void STDestroy(ST* pst);
//入栈和出栈
void STPush(ST* pst, STDadaType x);
void STPop(ST* pst);
//取栈顶元素
STDadaType STTop(ST* pst);
//判空
bool STEmpty(ST* pst);
//元素个数
int STSize(ST* pst);
Stack.c
#include"Stack.h"
//初始化和销毁
void STInit(ST* pst)
{
assert(pst);
pst->arr = NULL;
pst->top = 0;
pst->capacity = 0;
}
void STDestroy(ST* pst)
{
assert(pst);
free(pst->arr);
pst->arr = NULL;
pst->capacity = pst->top = 0;
}
//入栈和出栈
void STPush(ST* pst, STDadaType x)
{
assert(pst);
//扩容
if (pst->top == pst->capacity)
{
int newcapacity = pst->capacity == 0 ? 4 : pst->capacity * 2;
STDadaType* tmp = (STDadaType*)realloc(pst->arr, sizeof(STDadaType) * newcapacity);
if (tmp == NULL)
{
perror("rrealloc fail");
return;
}
pst->arr = tmp;
pst->capacity = newcapacity;
}
pst->arr[pst->top++] = x;
}
void STPop(ST* pst)
{
assert(pst);
assert(pst->top > 0);
pst->top--;
}
//取栈顶元素
STDadaType STTop(ST* pst)
{
assert(pst);
assert(pst->top > 0);
return pst->arr[pst->top - 1];
}
//判空
bool STEmpty(ST* pst)
{
assert(pst);
return pst->top == 0;
}
//元素个数
int STSize(ST* pst)
{
assert(pst);
return pst->top;
}
2. 队列
Queue.h
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h>
#include<assert.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 QueueDestroy(Queue* pq);
//入队列
void QueuePush(Queue* pq, QDataType x);
//出队列
void QueuePop(Queue* pq);
//获取队列头部数据
QDataType QueueFront(Queue* pq);
//获取队列尾部数据
QDataType QueueBack(Queue* pq);
//获取队列有效元素个数
int QueueSize(Queue* pq);
//判空
bool QueueEmpty(Queue* pq);
Queue.c
#include"Queue.h"
//初始化
void QueueInit(Queue* pq)
{
assert(pq);
pq->phead = NULL;
pq->ptail = NULL;
pq->size = 0;
}
//销毁
void QueueDestroy(Queue* pq)
{
assert(pq);
QNode* pcur = pq->phead;
while (pcur)
{
QNode* next = pcur->next;
free(pcur);
pcur = next;
}
pq->phead = pq->ptail = NULL;
pq->size = 0;
}
//入队列
void QueuePush(Queue* pq, QDataType x)
{
assert(pq);
QNode* newnode = (QNode*)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->size != 0);//队列不为空
if (pq->phead->next == NULL)//只有一个节点
{
free(pq->phead);
pq->phead = pq->ptail = NULL;
}
else
{
QNode* next = pq->phead->next;
free(pq->phead);
pq->phead = next;
}
pq->size--;
}
//获取队列头部数据
QDataType QueueFront(Queue* pq)
{
assert(pq);
assert(pq->phead);
return pq->phead->val;
}
//获取队列尾部数据
QDataType QueueBack(Queue* pq)
{
assert(pq);
assert(pq->ptail);
return pq->ptail->val;
}
//获取队列有效元素个数
int QueueSize(Queue* pq)
{
assert(pq);
return pq->size;
}
//判空
bool QueueEmpty(Queue* pq)
{
assert(pq);
return pq->size == 0;
}
五. 结语
以上我们就讲完了栈和队列这两个数据结构,希望对大家有帮助,欢迎大家批评指正!