本期我们来讲解栈和队列的实现,相比之前的变的简洁了,但整体变化不大,大家也可以看看之前写的
目录
栈的结构
我们先来看栈,栈是一种特殊的数据结构,他是后进先出的数据结构,即只能从栈尾入数据和删数据,就像我们将物品放在箱子里一样,最后放进去的物品在最上层,也就可以最先拿到,如果想要拿到最下面的,也就是最开始放进去的,就要把上面的物品全部拿走才行
typedef int STDataType;
typedef struct Stack
{
STDataType* a;
int top;
int capacity;
}ST;
我们实现栈最好是用数组来实现,而不是链表,因为栈只需要进行入栈出栈,如果用链表的话,就是尾插尾删,但是尾删是不好处理的,所以我们使用数组更好
栈的初始化
void STInit(ST* ps) {
assert(ps);
ps->a = (STDataType*)malloc(sizeof(STDataType) * 4);
if (ps->a == NULL) {
perror("malloc fail");
return;
}
ps->capacity = 4;
ps->top = 0; //给0代表栈顶元素的下一个
//ps->top = -1;//给-1代表栈顶元素的位置
}
因为我们的栈是结构体,数据是在数组里,所以我们需要进行断言,初始空间我们选择给4(大家给几个都可以),接着将容量赋值,然后就是top的取值了,-1和0大家都可以选择
栈的销毁
void STDestroy(ST* ps) {
assert(ps);
free(ps->a);
ps->a = NULL;
ps->capacity = 0;
ps->top = 0;
}
销毁我们只需释放数组的空间,然后将这些变量置为0即可
入栈
void STPush(ST* ps, STDataType x) {
assert(ps);
if (ps->top == ps->capacity) {
STDataType* tmp = (STDataType*)realloc(ps->a,sizeof(STDataType)*ps->capacity*2);
if (ps->a == NULL) {
perror("realloc fail");
return;
}
ps->a = tmp;
ps->capacity *= 2;
}
ps->a[ps->top] = x;
ps->top++;
}
入栈我们需要判断栈是否已满,满了的话我们需要扩容,我们扩2倍即可,接着就就是将数据放入栈顶,top++即可
出栈
void STPop(ST* ps) {
assert(ps);
assert(!STEmpty(ps));
ps->top--;
}
出栈我们只需让top--即可,但是不能一直减下去,如果为空的话就不行,所以我们需要判断一下,这里我直接使用了暴力检查,大家也可以用温和一点的
栈内元素个数
int STSize(ST* ps) {
assert(ps);
return ps->top;
}
因为我们选择了top给0,所以这里直接返回,如果top选择了-1,这里返回+1即可
判断栈是否为空
bool STEmpty(ST* ps) {
assert(ps);
return ps->top == 0;
}
因为我们选则了top最初给0,所以top为0就是空,top选择-1的话等于-1就是空
取栈顶元素
STDataType STTop(ST* ps) {
assert(ps);
assert(!STEmpty(ps));
return ps->a[ps->top-1];
}
根据top最初值的不同,这里填写的也不同,另外我们也要断言栈是否为空
我们对栈进行一下测试

这样我们的栈就完成了,是不是非常简单?
接下来我们来看队列
队列的结构
队列是一种先进先出的结构,即队列只能从队尾入数据,队头删数据
那么为了实现队列,我们是应该像栈一样继续使用数据,还是说应该使用链表呢?
我们发现,队列的删除是在头,队列的插入是在尾,所以这里使用链表更好一点,我们选择用单链表即可,我们只需要加一个尾指针
typedef int QDatatype;
typedef struct QueueNode {
struct QueueNode* next;
QDatatype data;
}QNode;
typedef struct Queue {
QNode* head;
QNode* tail;
int size;
}Queue;
到这里可能就有人将链表,栈和队列的结构搞混了,为什么一会是一个结构,一会是两个结构,我们来对比一下就明白了

我们的链表,不管是不是带哨兵位,都需要一个指针即可,指向第一个指针,所以我们看到,这些链表都只需指向头节点即可,那么栈我们可以写成栈的指针吗?可以是可以,但是没必要,如果我们将栈写成指针,那么STInit就需要malloc

而需要malloc,这里就会需要传二级指针,非常麻烦,现在我们将结构体创建到了外部,已经有了结构体,所以不需要malloc,而且已经有了结构体,只需要结构体指针就可以了,数据结构是非常灵活的,写成指针的话,我们可以采用malloc传二级指针,也可以采用返回值的方式,不过相比我们写的结构来说,会比较麻烦一点
回过头来继续看我们的队列结构,队列也是一个一个的节点,所以我们定义了QNode,接着我们将QNode包装起来变为Queue,这样我们的队列就完成了,Queue里有两个指针,一个指向队列的头,一个指向队列的尾,最后我们可以加一个size,也是有好处的
队列的初始化
void QueueInit(Queue* pq) {
assert(pq);
pq->head = pq->tail = NULL;
pq->size = 0;
}
我们传的是结构体的指针,所以是绝对不会为空,所以我们要进行断言,后续同理,就不再啰嗦
队列的销毁
void QueueDestroy(Queue* pq) {
assert(pq);
QNode* cur = pq->head;
while (cur) {
QNode* next = cur->next;
free(cur);
cur = next;
}
pq->head = pq->tail = NULL;
pq->size = 0;
}
队列的销毁和链表的销毁很像,我们一个一个释放即可
入队列
void QueuePush(Queue* pq, QDatatype x) {
QNode* newnode =(QNode*) malloc(sizeof(QNode));
if (newnode == NULL) {
perror("malloc fail");
return;
}
newnode->data = x;
newnode->next = NULL;
if (pq->head == NULL) {
assert(pq->tail==NULL);
pq->head = pq->tail = newnode;
}
else {
pq->tail->next = newnode;
pq->tail = newnode;
}
pq->size++;
}
入队列就是尾插,我们创建新节点,新节点初始化后,连接到队列的尾部,再更新尾即可,但是基于我们队列最开始时head和tail都可能为空,所以我们要进行判断,如果队列是空,我们将新节点赋给头尾即可,我们在判断队列是否为空时,判断头或者尾有一个为空即可,两个都判断也可以,我这里就是两个都判断了,先判断了头,如果头为空,但是尾不是空,那就说明出问题了
出队列
void QueuePop(Queue* pq) {
assert(pq);
assert(pq->head!=NULL);
/*Queue* next = pq->head->next;
free(pq->head);
pq->head = next;
if (pq->head == NULL) {
pq->tail = NULL;
}*/
if (pq->head->next == NULL) {
free(pq->head);
pq->head = pq->tail = NULL;
}
else {
Queue* next = pq->head->next;
free(pq->head);
pq->head = next;
}
pq->size--;
}
出队列就是头删,我们保存头节点的下一个然后进行删除即可,但是如果是最后一个节点的话,tail就会变为野指针,所以我们需要单独判断,这里有两种写法,大家选一种喜欢的即可
返回队列的元素个数
void QueueSize(Queue* pq) {
assert(pq);
return pq->size;
}
因为我们最开始定义了size,所以这里非常简单,如果没有定义的话,只能遍历去数
判断队列是否为空
bool QueueEmpty(Queue* pq) {
assert(pq);
return pq->size == 0;
}
同样的,这里也可以使用size来判断,或者对队列的头和尾均为空来判断也是可以的
取队头元素
QDatatype QueueFront(Queue* pq) {
assert(pq);
assert(!QueueEmpty(pq));
return pq->head->data;
}
我们要对队列进行判空,不为空我们才能取数据
取队尾元素
QDatatype QueueBack(Queue* pq) {
assert(pq);
assert(!QueueEmpty(pq));
return pq->tail->data;
}
与取队头元素同理
接着我们对队列进行测试

以上皆为本期全部内容,希望大家可以有所收获,最后附上全部代码
全部代码
//stack.h
#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* ps);
void STDestroy(ST* ps);
void STPush(ST* ps, STDataType x);
void STPop(ST* ps);
int STSize(ST* ps);
bool STEmpty(ST* ps);
STDataType STTop(ST* ps);
//stack.c
#include"stack.h"
void STInit(ST* ps) {
assert(ps);
ps->a = (STDataType*)malloc(sizeof(STDataType) * 4);
if (ps->a == NULL) {
perror("malloc fail");
return;
}
ps->capacity = 4;
ps->top = 0; //给0代表栈顶元素的下一个
//ps->top = -1;//给-1代表栈顶元素的位置
}
void STDestroy(ST* ps) {
assert(ps);
free(ps->a);
ps->a = NULL;
ps->capacity = 0;
ps->top = 0;
}
void STPush(ST* ps, STDataType x) {
assert(ps);
if (ps->top == ps->capacity) {
STDataType* tmp = (STDataType*)realloc(ps->a,sizeof(STDataType)*ps->capacity*2);
if (ps->a == NULL) {
perror("realloc fail");
return;
}
ps->a = tmp;
ps->capacity *= 2;
}
ps->a[ps->top] = x;
ps->top++;
}
void STPop(ST* ps) {
assert(ps);
assert(!STEmpty(ps));
ps->top--;
}
int STSize(ST* ps) {
assert(ps);
return ps->top;
}
bool STEmpty(ST* ps) {
assert(ps);
return ps->top == 0;
}
STDataType STTop(ST* ps) {
assert(ps);
assert(!STEmpty(ps));
return ps->a[ps->top-1];
}
//queue.h
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
#include<stdbool.h>
typedef int QDatatype;
typedef struct QueueNode {
struct QueueNode* next;
QDatatype data;
}QNode;
typedef struct Queue {
QNode* head;
QNode* tail;
int size;
}Queue;
void QueueInit(Queue* pq);
void QueueDestroy(Queue* pq);
void QueuePush(Queue* pq,QDatatype x);
void QueuePop(Queue* pq);
void QueueSize(Queue* pq);
bool QueueEmpty(Queue* pq);
QDatatype QueueFront(Queue* pq);
QDatatype QueueBack(Queue* pq);
//queue.c
#include"queue.h"
void QueueInit(Queue* pq) {
assert(pq);
pq->head = pq->tail = NULL;
pq->size = 0;
}
void QueueDestroy(Queue* pq) {
assert(pq);
QNode* cur = pq->head;
while (cur) {
QNode* next = cur->next;
free(cur);
cur = next;
}
pq->head = pq->tail = NULL;
pq->size = 0;
}
void QueuePush(Queue* pq, QDatatype x) {
QNode* newnode =(QNode*) malloc(sizeof(QNode));
if (newnode == NULL) {
perror("malloc fail");
return;
}
newnode->data = x;
newnode->next = NULL;
if (pq->head == NULL) {
assert(pq->tail==NULL);
pq->head = pq->tail = newnode;
}
else {
pq->tail->next = newnode;
pq->tail = newnode;
}
pq->size++;
}
void QueuePop(Queue* pq) {
assert(pq);
assert(pq->head!=NULL);
/*Queue* next = pq->head->next;
free(pq->head);
pq->head = next;
if (pq->head == NULL) {
pq->tail = NULL;
}*/
if (pq->head->next == NULL) {
free(pq->head);
pq->head = pq->tail = NULL;
}
else {
Queue* next = pq->head->next;
free(pq->head);
pq->head = next;
}
pq->size--;
}
void QueueSize(Queue* pq) {
assert(pq);
return pq->size;
}
bool QueueEmpty(Queue* pq) {
assert(pq);
return pq->size == 0;
}
QDatatype QueueFront(Queue* pq) {
assert(pq);
assert(!QueueEmpty(pq));
return pq->head->data;
}
QDatatype QueueBack(Queue* pq) {
assert(pq);
assert(!QueueEmpty(pq));
return pq->tail->data;
}
如有错误,还请指正
本文详细介绍了如何使用C语言实现栈和队列的基本操作,包括初始化、销毁、入栈、出栈、入队列、出队列等,同时讨论了栈和队列的数据结构特点以及选择数组或链表实现的原因。
1352

被折叠的 条评论
为什么被折叠?



