首先我们要来讲的一种数据结构叫做【栈】,相信你一定不陌生,在很多场合都听说过,以及在代码出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去判断无需考虑栈顶指针Topst->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");elseprintf("当前栈不为空\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");elseprintf("当前栈不为空\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");elseprintf("队列不为空\n");printf("队列大小为:%d\n", QueueSize(&q));QueueDestroy(&q);
}int main(void)
{test();return 0;
}
链接
链接
链接
对于【栈与队列】,可以看出,并没有我们之前实现的顺序表和链表那么复杂,也正是因为如此,它在显示生活中就广泛地被使用到,我们一起来聊聊和这个