前言
本篇博客我们来探讨下数据结构中的栈和队列,两种特殊的线性表,我们来看一下它们其中的奥妙吧
💓 个人主页:小张同学zkf
⏩ 文章专栏:数据结构
若有问题 评论区见📝
🎉欢迎大家点赞👍收藏⭐文章
目录
1.栈
1.1栈的概念及结构
“后进先出”的原则
栈有点像弹夹的结构,同一端放入和射出
1.2栈的实现
了解了栈的结构,我们来用代码如何实现栈那?
分为三个文件
stack.h文件里存放栈的结构和函数声明
// 下面是定长的静态栈的结构,实际中一般不实用,所以我们主要实现下面的支持动态增长的栈typedef int STDataType ;#define N 10typedef struct Stack{STDataType _a [ N ];int _top ; // 栈顶} Stack ;// 支持动态增长的栈typedef int STDataType ;typedef struct Stack{STDataType * _a ;int _top ; // 栈顶int _capacity ; // 容量} Stack ;// 初始化栈void StackInit ( Stack * ps );// 入栈void StackPush ( Stack * ps , STDataType data );// 出栈void StackPop ( Stack * ps );// 获取栈顶元素STDataType StackTop ( Stack * ps );// 获取栈中有效元素个数int StackSize ( Stack * ps );// 检测栈是否为空,如果为空返回非零结果,如果不为空返回 0int StackEmpty ( Stack * ps );// 销毁栈void StackDestroy ( Stack * ps );
stack.c文件里是所有函数的定义
初始化函数
这个函数将栈里的所有成员初始化,这里我们要考虑一下这个栈顶top初始值是0好还是-1好
根据图的分析,我们可以得知当top初始值为-1时此刻栈顶top就是最后一个元素,若top初始值为0,此刻栈顶top元素是栈顶数据的下一个位置,所以不同的初始化代表的意义不同,这点我们要注意
// 初始化和销毁
void STInit(ST* pst)
{
assert(pst);
pst->a = NULL;
// top指向栈顶数据的下一个位置
pst->top = 0;
// top指向栈顶数据
//pst->top = -1;
pst->capacity = 0;
}
入栈函数
这个函数就是将数组最后一个空的位置留给新来的元素,所以当我们假设top初始值为0时,此刻的top就是新元素的下标,也是前面元素的个数和,对了在入栈时我们要用top与capacity进行比较,看是否需要扩容,这一步与顺序表当时的步骤是一样的,代码如下
void STPush(ST* pst, STDataType x)
{
assert(pst);
// 扩容
if (pst->top == pst->capacity)
{
int newcapacity = pst->capacity == 0 ? 4 : pst->capacity * 2;
STDataType* tmp = (STDataType*)realloc(pst->a, newcapacity * sizeof(STDataType));
if (tmp == NULL)
{
perror("realloc fail");
return;
}
pst->a = tmp;
pst->capacity = newcapacity;
}
pst->a[pst->top] = x;
pst->top++;
}
出栈函数
这个函数我们只需将栈顶下标减一就行,不要忘了判断栈是否为空
void STPop(ST* pst)
{
assert(pst);
assert(pst->top > 0);
pst->top--;
}
获取栈顶元素的函数
若top为0,返回下标为top-1的元素
STDataType STTop(ST* pst)
{
assert(pst);
assert(pst->top > 0);
return pst->a[pst->top - 1];
}
判空函数
看top是否变回初始化的值
bool STEmpty(ST* pst)
{
assert(pst);
return pst->top == 0;
}
总个数函数
若top为0,返回top
int STSize(ST* pst)
{
assert(pst);
return pst->top;
}
销毁函数
free掉数组,将top与capacity赋予初始值
void STDestroy(ST* pst)
{
assert(pst);
free(pst->a);
pst->a = NULL;
pst->top = pst->capacity = 0;
}
栈的代码如下
stack.h
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h>
#include<assert.h>
typedef int STDataType;
typedef struct Stack
{
STDataType* a;
int top;
int capacity;
}ST;
// 初始化和销毁
void STInit(ST* pst);
void STDestroy(ST* pst);
// 入栈 出栈
void STPush(ST* pst, STDataType x);
void STPop(ST* pst);
// 取栈顶数据
STDataType STTop(ST* pst);
// 判空
bool STEmpty(ST* pst);
// 获取数据个数
int STSize(ST* pst);
stack.c
#include"Stack.h"
// 初始化和销毁
void STInit(ST* pst)
{
assert(pst);
pst->a = NULL;
// top指向栈顶数据的下一个位置
pst->top = 0;
// top指向栈顶数据
//pst->top = -1;
pst->capacity = 0;
}
void STDestroy(ST* pst)
{
assert(pst);
free(pst->a);
pst->a = NULL;
pst->top = pst->capacity = 0;
}
// 入栈 出栈
void STPush(ST* pst, STDataType x)
{
assert(pst);
// 扩容
if (pst->top == pst->capacity)
{
int newcapacity = pst->capacity == 0 ? 4 : pst->capacity * 2;
STDataType* tmp = (STDataType*)realloc(pst->a, newcapacity * sizeof(STDataType));
if (tmp == NULL)
{
perror("realloc fail");
return;
}
pst->a = tmp;
pst->capacity = newcapacity;
}
pst->a[pst->top] = x;
pst->top++;
}
void STPop(ST* pst)
{
assert(pst);
assert(pst->top > 0);
pst->top--;
}
// 取栈顶数据
STDataType STTop(ST* pst)
{
assert(pst);
assert(pst->top > 0);
return pst->a[pst->top - 1];
}
// 判空
bool STEmpty(ST* pst)
{
assert(pst);
return pst->top == 0;
}
// 获取数据个数
int STSize(ST* pst)
{
assert(pst);
return pst->top;
}
text.c
#include<stdio.h>
#include<stdlib.h>
//
//int main()
//{
// // 原地扩容
// // 异地扩容
// int* p1 = (int*)malloc(8);
// printf("%p\n", p1);
//
// int* p2 = (int*)realloc(p1, 80);
// printf("%p\n", p2);
//
// free(p2);
//
//
// int i = 0;
// int ret1 = ++i;
//
// int ret2 = i++;
//
//
//
// return 0;
//}
#include"Stack.h"
//int main()
//{
// ST s;
// STInit(&s);
// STPush(&s, 1);
// STPush(&s, 2);
// STPush(&s, 3);
// STPush(&s, 4);
//
// printf("%d\n", STTop(&s));
// STPop(&s);
// printf("%d\n", STTop(&s));
// STPop(&s);
// STPop(&s);
// STPop(&s);
// STPop(&s);
//
// //printf("%d\n", STTop(&s));
//
// STDestroy(&s);
//
// return 0;
//}
int main()
{
// 入栈:1 2 3 4
// 出栈:4 3 2 1 / 2 4 3 1
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);
}
2.队列
2.1队列的概念及结构
2.2队列的实现
所以我们用链表的方法实现队列
还是三个文件,一个头文件,一个检测文件,一个函数定义的源文件
// 链式结构:表示队列typedef struct QListNode{struct QListNode * _pNext ;QDataType _data ;} QNode ;// 队列的结构typedef struct Queue{QNode * _front ;QNode * _rear ;int size;} Queue ;// 初始化队列void QueueInit ( Queue * q );// 队尾入队列void QueuePush ( Queue * q , QDataType data );// 队头出队列void QueuePop ( Queue * q );// 获取队列头部元素QDataType QueueFront ( Queue * q );// 获取队列队尾元素QDataType QueueBack ( Queue * q );// 获取队列中有效元素个数int QueueSize ( Queue * q );// 检测队列是否为空,如果为空返回非零结果,如果非空返回 0int QueueEmpty ( Queue * q );// 销毁队列void QueueDestroy ( Queue * q );
我们这里可以再定义一个队列的结构用来存放队头指针与队尾指针以及元素个数,方便我们避免函数参数使用过多以及避免函数的参数二级指针的使用
初始化函数
void QueueInit(Queue* pq)
{
assert(pq);
pq->phead = NULL;
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->next = NULL;
newnode->val = x;
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);
/*QNode* next = pq->phead->next;
free(pq->phead);
pq->phead = next;
if (pq->phead == NULL)
pq->ptail = 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);
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;
}
判空函数
若size为0,则此刻队列是空
bool QueueEmpty(Queue* pq)
{
assert(pq);
return pq->size == 0;
}
销毁函数
用一个临时变量进行while循环遍历,遍历完,别忘了将指针置空,数据清为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;
}
以下是队列的所有代码
queue.h
#pragma once
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h>
#include<assert.h>
#include<stdbool.h>typedef int QDataType;
typedef struct QueueNode
{
struct QueueNode* next;
QDataType val;
}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);队尾插入
//void QueuePush(QNode** pphead, QNode** pptail, QDataType x);
队头删除
//void QueuePop(QNode** pphead, QNode** pptail);
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->next = NULL;
newnode->val = x;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);/*QNode* next = pq->phead->next;
free(pq->phead);
pq->phead = next;if (pq->phead == NULL)
pq->ptail = 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);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;
}
test.c
#include"Queue.h"
int main()
{
Queue q;
QueueInit(&q);
QueuePush(&q, 1);
QueuePush(&q, 2);
printf("%d ", QueueFront(&q));
QueuePop(&q);
QueuePush(&q, 3);
QueuePush(&q, 4);
while (!QueueEmpty(&q))
{
printf("%d ", QueueFront(&q));
QueuePop(&q);
}
printf("\n");
return 0;
}
2.3循环队列
结束语
栈和队列这一部分根据前面顺序表和链表的总结还是比较简单的,下片博客我们来探讨一下这部分的有关习题,这部分有点意思哦,那下篇博客见
OK,感谢观看!!!