在了解过较为基础的顺序表和链表之后,今天梳理栈和队列,栈和队列的原理是相似的,栈是类似一个弹夹的实现,队列类似于水管的实现。
栈:后进先出 last in first out
队列:先进先出 first in first out
栈 接口实现
实现链表大抵有两种方式,数组实现和单链表实现。
我们先来分析一下单链表实现,由于栈的实现只需要一个口子,用来出入数据,单链表的头尾都可以实现这个过程,但相较而言头插较易实现,所以在单链表实现时用头节点作为栈顶。
数组我们相当熟悉了,数组的实现就是在末尾插入数据,这点就和栈十分相似,并且数组的删除数据更加简单,所以我们用数组来创建栈。
//stack.h
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h>
#include<assert.h>
typedef int STDataType;
typedef struct Stack//此结构体是一个类数组的结构,a指向数组的起始位置,top来表示下表结合a可以找到数组中的任意的数,capacity用来存放数组大小
{
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);//获取栈顶元素
STDataType STTop(ST* ps);//获取栈顶元素
int STSize(ST* ps);//栈大小
bool STEmpty(ST* ps);//探空
(以上是栈常用的接口,这些接口是前辈们在使用时总结出最常用的接口)
//stack.c
#include"Stack.h"
void STInit(ST* ps)
{
assert(ps);
ps->a = NULL;
ps->top = 0;//top指向栈顶元素的下一个,在插入一个第一个元素后,top++(同43行)。top也可以一直指向栈顶元素,则top需要初始化为-1,这样在输入第一个数时top++来到0
ps->capacity = 0;
}
void STDestroy(ST* ps)
{
assert(ps);
free(ps->a);
ps->a = NULL;
ps->top = ps->capacity = 0;
}
//栈顶
void STPush(ST* ps, STDataType x)
{
//首先断言ps不能为空指针,否则会压栈失败
assert(ps);
if (ps->top == ps->capacity)//满了 扩容
{
int newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;//如果没有容量初始化容量,容量不足则扩容
STDataType* tmp = (STDataType*)realloc(ps->a, newcapacity * sizeof(STDataType));//tmp刚开始为空,在功能上等同于malloc,所有可以直接使用realloc
if (tmp == NULL)
{
perror("realloc fail");
return;
}
ps->a = tmp;
ps->capacity = newcapacity;
}
ps->a[ps->top] = x;//同上先把数据放入top下表的位置,让后让top++,让top始终指向栈顶的下一个(也就是新数据插入时的位置)
ps->top++;
}
void STPop(ST* ps)//出栈
{
assert(ps);
assert(!STEmpty(ps));
ps->top--;
}
STDataType STTop(ST* ps)//获取栈顶元素
{
assert(ps);
assert(!STEmpty(ps));
return ps->a[ps->top - 1];
}
int STSize(ST* ps)
{
assert(ps);
return ps->top;
}
bool STEmpty(ST* ps)//判空,判断栈是否为空
{
assert(ps);
return ps->top == 0;
}
在栈的实现中,top的初始化是十分有讲究的
top=0时,插入一个数据top向后移一次,这样的话top始终指向栈顶元素的下一个
top=-1时,top始终指向栈顶元素
队列 接口实现
链表队列:尾插入队,头插出队,单项不循环(头节点可带可不带),在分割链表等肯能产生空链表的情况下必循设立哨兵位
数组队列:好入队,难出队,因为数组只能通过一个口来出入数据
//Queue.h
#pragma once
#pragma once
#include<stdlib.h>
#include<stdbool.h>
#include<assert.h>
typedef int QDataType;
typedef struct QueueNode
{
int val;
struct QueueNode* next;
}QNode;
入队列
//void QueuePush(QNode** pphead, QNode** pptail);
//
出队列
//void QueuePop(QNode** pphead, QNode** pptail);
typedef struct Queue//和栈的不同之处,第2个结构体存放收尾指针
{
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);
bool QueueEmpty(Queue* pq);
int QueueSize(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* cur = pq->phead;
while (cur)
{
QNode* next = cur->next;
free(cur);
cur = 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)
{
pq->ptail->next = newnode;
pq->ptail = newnode;
}
else
{
pq->phead = pq->ptail = newnode;
}
pq->size++;
}
// 出队列
void QueuePop(Queue* pq)
{
assert(pq);
// 0个节点
// 温柔检查
//if (pq->phead == NULL)
// return;
// 暴力检查
assert(pq->phead != NULL);
// 一个节点
// 多个节点
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 != NULL);
return pq->phead->val;
}
QDataType QueueBack(Queue* pq)
{
assert(pq);
// 暴力检查
assert(pq->ptail != NULL);
return pq->ptail->val;
}
bool QueueEmpty(Queue* pq)
{
assert(pq);
return pq->size == 0;
}
int QueueSize(Queue* pq)
{
assert(pq);
return pq->size;
}