首先我们要来讲的一种数据结构叫做【栈】,相信你一定不陌生,在很多场合都听说过,以及在代码出Bug时都会有【栈溢出】这样一种错误发生,但那种栈是内存中的栈,和我们数据结构中的栈可不一样。接下来我们一起来看看这种数据结构
所以我选择使用顺序栈来实现,因为其实现起来比较简单、快捷;当前链式栈也是可以的
typedef int STDataType;
typedef struct Stack {
STDataType* a;
int top; //栈顶指针
int capacity; //容量
}ST;
接下去是对于顺序栈的接口算法实现
void InitStack(ST* st)
assert(st); //警惕随意操作,传入空指针
st->a = (STDataType*)malloc(sizeof(STDataType) * 4);
if (st->a == NULL)
{
perror("fail mallic");
exit(-1);
}
st->top = 0; //初始化为0表示指向当前栈顶元素的后一元素
st->capacity = 4;
void DestroyStack(ST* st)
{
assert(st);
free(st->a);
st->a = NULL;
st->top = st->capacity = 0;
}
st->a[st->top] = x; //top指向栈顶元素的后一元素,因此直接入栈即可
//栈满扩容逻辑
if (st->top == st->capacity)
{
//初始化时已经malloc开辟过空间了,因此无需考虑容量为空的情况
STDataType* tmp = (STDataType*)realloc(st->a, st->capacity * 2 * sizeof(STDataType));
if (tmp == NULL)
{
perror("fail realloc");
exit(-1);
}
st->a = tmp;
st->capacity *= 2;
//顺序表扩容逻辑
void SLCheckCapacity(SL* ps)
{
//扩容
/* 酒店的案例
* 原地扩容:发现与上一次开辟空间旁有足够的位置
* 异地扩容:没有足够的位置,重新找一块地方,将原来开辟空间中的内容自动拷贝过来放在新空间中
* 然后释放原来的空间
*/
if (ps->size == ps->capacity)
{
int newCapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
SLDataType* tmp = (SLDataType*)realloc(ps->a, newCapacity * sizeof(SLDataType*));
//判断是否开辟空间成功【失败会返回空指针null pointer】
if (tmp == NULL)
{
perror("realloc fail\n");
exit(-1); //结束掉程序【程序异常结束】
}
//扩容成功
ps->a = tmp;
ps->capacity = newCapacity;
}
}
if(top == 0)
return;
assert(!StackEmpty(st)); //用Empty去判断无需考虑栈顶指针Top
return st->a[st->top - 1];
bool StackEmpty(ST* st)
{
return (st->top == 0); //看初始化时的栈顶指针top
}
int StackSize(ST* st)
{
return st->top;
}
其实讲到这个接口的算法实现就有小伙伴提出疑问了,栈顶指针的逻辑我是搞懂了,但是就这样一个求解栈元素个数的功能,为什么不直接在使用的时候获取呢?而是要单独封装为一个函数?这其实就是你缺乏工程经验了
现在你应该知道为什么要将这个求解Size的功能以及Empty()单独封装为一个函数了吧
实现完了这个栈的接口算法,接下来我们来测试一波~
stack.h
#pragma once
#include
#include
#include
typedef int STDataType;
typedef struct Stack {
STDataType* a;
int top; //栈顶指针
int capacity; //容量
}ST;
/*初始化栈*/
void InitStack(ST* st);
/*销毁栈*/
void DestroyStack(ST* st);
/*入栈*/
void PushStack(ST* st, STDataType x);
/*出栈*/
void PopStack(ST* st);
/*返回栈顶元素*/
STDataType StackTop(ST* st);
/*判空*/
bool StackEmpty(ST* st);
/*栈的元素个数*/
int StackSize(ST* st);
stack.cpp
#define _CRT_SECURE_NO_WARNINGS 1
//顺序表尾插尾删快、头插头删慢(需要移动数据)
//链表头插头删快、尾插尾删慢(需要考虑头结点是否改变)
//--->因此栈选择使用数组的形式实现,不用链式栈
#include "stack.h"
/*初始化栈*/
void InitStack(ST* st)
{
assert(st); //警惕随意操作,传入空指针
st->a = (STDataType*)malloc(sizeof(STDataType) * 4);
if (st->a == NULL)
{
perror("fail mallic");
exit(-1);
}
st->top = 0; //初始化为0表示指向当前栈顶元素的后一元素
st->capacity = 4;
}
/*销毁栈*/
void DestroyStack(ST* st)
{
assert(st);
free(st->a);
st->a = NULL;
st->top = st->capacity = 0;
}
/*入栈*/
void PushStack(ST* st, STDataType x)
{
//栈满扩容逻辑
if (st->top == st->capacity)
{
//初始化时已经malloc开辟过空间了,因此无需考虑容量为空的情况
STDataType* tmp = (STDataType*)realloc(st->a, st->capacity * 2 * sizeof(STDataType));
if (tmp == NULL)
{
perror("fail realloc");
exit(-1);
}
st->a = tmp;
st->capacity *= 2;
}
st->a[st->top] = x; //top指向栈顶元素的后一元素,因此直接入栈即可
st->top++; //然后栈顶指针后移,为下一次入栈做准备
}
/*出栈*/
void PopStack(ST* st)
{
assert(st);
//assert(st->top > 0);
assert(!StackEmpty(st)); //用Empty去判断无需考虑栈顶指针Top
st->top--;
}
/*返回栈顶元素*/
STDataType StackTop(ST* st)
{
return st->a[st->top - 1];
}
/*判空*/
bool StackEmpty(ST* st)
{
return (st->top == 0); //看初始化时的栈顶指针top
}
/*栈的元素个数*/
int StackSize(ST* st)
{
return st->top;
}
test.cpp
#define _CRT_SECURE_NO_WARNINGS 1
#include "stack.h"
void StackTest1()
{
ST st;
InitStack(&st);
PushStack(&st, 1);
PushStack(&st, 2);
PushStack(&st, 3);
PushStack(&st, 4);
PushStack(&st, 5);
bool ret = StackEmpty(&st);
if (ret)
printf("当前栈为空\n");
else
printf("当前栈不为空\n");
printf("栈中元素个数为:%d\n", StackSize(&st));
STDataType top = StackTop(&st);
PopStack(&st);
printf("当前栈顶元素为%d\n", top);
top = StackTop(&st);
PopStack(&st);
printf("当前栈顶元素为%d\n", top);
top = StackTop(&st);
PopStack(&st);
printf("当前栈顶元素为%d\n", top);
top = StackTop(&st);
PopStack(&st);
printf("当前栈顶元素为%d\n", top);
top = StackTop(&st);
PopStack(&st);
printf("当前栈顶元素为%d\n", top);
top = StackTop(&st);
PopStack(&st);
ret = StackEmpty(&st);
if (ret)
printf("当前栈为空\n");
else
printf("当前栈不为空\n");
DestroyStack(&st);
}
int main(void)
{
StackTest1();
return 0;
}
说完栈之后,我们就该来说说队列了,对于队列,我是使用链表实现的
所以我选择使用链式队来实现,因为其实现起来比顺序队来的高效、可靠;
typedef int QDataType;
typedef struct QueueNode {
QDataType data;
struct QueueNode* next;
}QNode;
typedef struct Queue {
QNode* front;
QNode* rear;
size_t sz;
}Qu;
接下去是对于链式队的接口算法实现
void QueueInit(Qu* q)
{
q->front = NULL;
q->rear = NULL;
q->sz = 0;
}
void SLIstDestroy(SLTNode** pphead)
{
SLTNode* cur = *pphead;
while (cur)
{
SLTNode* nextNode = cur->next;
free(cur);
cur = nextNode;
}
*pphead = NULL;
}
QNode* cur = q->front;
while (cur)
{
QNode* del = cur;
cur = cur->next;
free(del);
//del = NULL; 无需再将del置为空,因为其为局部变量不会被访问到
}
/*创建结点初始化*/
QNode* newNode = (QNode*)malloc(sizeof(QNode));
if (newNode == NULL)
{
perror("fail malloc");
exit(-1);
}
newNode->data = x;
newNode->next = NULL;
/*尾插*/
if (q->rear == NULL)
{ //队列为空
q->front = q->rear = newNode;
}
else
{
q->rear->next = newNode;
q->rear = newNode;
}
//有多个结点
QNode* del = q->front;
q->front = q->front->next;
free(del);
//只有一个结点
if (q->front == q->rear)
{
free(q->front);
q->front = q->rear = NULL;
}
QDataType QueueFront(Qu* q)
{
assert(q);
assert(!QueueEmpty(q));
return q->front->data;
}
QDataType QueueBack(Qu* q)
{
assert(q);
assert(!QueueEmpty(q));
return q->rear->data;
}
bool QueueEmpty(Qu* q)
{
assert(q);
return q->front == NULL && q->rear == NULL;
}
size_t QueueSize(Qu* q)
{
return q->sz;
}
Push
sz++;
Pop
sz--;
接下来我们继续来测试一波~
queue.h
#pragma once
#include
#include
#include
typedef int QDataType;
typedef struct QueueNode {
QDataType data;
struct QueueNode* next;
}QNode;
typedef struct Queue {
QNode* front;
QNode* rear;
size_t sz;
}Qu;
/*初始化队列*/
void QueueInit(Qu* q);
/*销毁队列*/
void QueueDestroy(Qu* q);
/*入队*/
void QueuePush(Qu* q, QDataType x);
/*出队*/
void QueuePop(Qu* q);
/*获取队头*/
QDataType QueueFront(Qu* q);
/*获取队尾*/
QDataType QueueBack(Qu* q);
/*判空*/
bool QueueEmpty(Qu* q);
/*求解队列大小*/
size_t QueueSize(Qu* q);
queue.cpp
#define _CRT_SECURE_NO_WARNINGS 1
#include "queue.h"
/*初始化队列*/
void QueueInit(Qu* q)
{
q->front = NULL;
q->rear = NULL;
q->sz = 0;
}
/*销毁队列*/
void QueueDestroy(Qu* q)
{
assert(q);
QNode* cur = q->front;
while (cur)
{
QNode* del = cur;
cur = cur->next;
free(del);
//del = NULL; 无需再将del置为空,因为其为局部变量不会被访问到
}
q->front = q->rear = NULL; //头尾指针要置空
}
/*入队*/
void QueuePush(Qu* q, QDataType x)
{
assert(q);
/*创建结点初始化*/
QNode* newNode = (QNode*)malloc(sizeof(QNode));
if (newNode == NULL)
{
perror("fail malloc");
exit(-1);
}
newNode->data = x;
newNode->next = NULL;
/*尾插*/
if (q->rear == NULL)
{ //队列为空
q->front = q->rear = newNode;
}
else
{
q->rear->next = newNode;
q->rear = newNode;
}
q->sz++; //结点个数 + 1
}
/*出队*/
void QueuePop(Qu* q)
{
assert(q);
assert(!QueueEmpty(q));
//1.只有一个结点
if (q->front == q->rear)
{
free(q->front);
q->front = q->rear = NULL;
}
//2.有多个结点
else
{
QNode* del = q->front;
q->front = q->front->next;
free(del);
}
q->sz--;
}
/*获取队头*/
QDataType QueueFront(Qu* q)
{
assert(q);
assert(!QueueEmpty(q));
return q->front->data;
}
/*获取队尾*/
QDataType QueueBack(Qu* q)
{
assert(q);
assert(!QueueEmpty(q));
return q->rear->data;
}
/*判空*/
bool QueueEmpty(Qu* q)
{
assert(q);
return q->front == NULL && q->rear == NULL;
}
/*求解队列大小*/
size_t QueueSize(Qu* q)
{
return q->sz;
}
test.cpp
#define _CRT_SECURE_NO_WARNINGS 1
//顺序队 —— 需要在队头出入数据很麻烦
//--->链队
/*
* 1.单
* 2.哨兵位 可要可不要
* 3.非循环
*/
/* 若指针指向一块空间释放了,需不需要置空?
* 若是别人能访问到,就需要置空,否则访问到的这个指针就会变成野指针;
* 若是访问不到没关系
*
* --》头指针和尾指针需要置空
*/
#include "queue.h"
void test()
{
Queue q;
QueueInit(&q);
QueuePush(&q, 1);
QueuePush(&q, 2);
QueuePush(&q, 3);
QueuePush(&q, 4);
if (QueueEmpty(&q))
printf("队列为空\n");
else
printf("队列不为空\n");
printf("队列大小为:%d\n", QueueSize(&q));
while (!QueueEmpty(&q))
{
printf("队头元素为:%d\n",QueueFront(&q));
QueuePop(&q);
}
if (QueueEmpty(&q))
printf("队列为空\n");
else
printf("队列不为空\n");
printf("队列大小为:%d\n", QueueSize(&q));
QueueDestroy(&q);
}
int main(void)
{
test();
return 0;
}
链接
链接
链接
对于【栈与队列】,可以看出,并没有我们之前实现的顺序表和链表那么复杂,也正是因为如此,它在显示生活中就广泛地被使用到,我们一起来聊聊和这个