在学习了顺序表和链表之后,为了深入了解其结构,接下来由小编带大家再学习两种数据结构栈和队列,这两种数据结构内部也用到了关于顺序表和链表的相关知识,也能对前面的知识进行回顾,废话不多讲了,Let's go!
1.栈
1.1栈的概念及其结构
栈是一种特殊的线性表,其只允许在固定的一端进行插入和删除操作。我们通常将数据插入和删除的一端称为栈顶,另一端称为栈底。此外,栈中的元素遵循后进先出的原则(Last In First Out)
栈的插入操作叫做进栈/压栈/入栈,入数据是在栈顶
栈的删除操作叫做出栈,出数据也是在栈顶。
进栈和出栈图解:
1.2栈的实现和分类
栈主要分为两类,分别是数组栈和链式栈,但是由于链式栈查询栈顶也就是链表的结尾节点的效率比较低,同时要进栈时还要去多次申请节点,这样会有一定的消耗,效率也比较低,所以小编在这就用顺序表来实现栈。
与顺序表和链表一样,我们首先要先创建3个文件,分别是stack.h(包括栈的定义和一些栈的接口函数的定义),stack.c(包括栈的接口函数的实现),test.c(用于测试接口函数)。
如图:
首先要定义一下栈,代码如下:
// 支持动态增长的栈
typedef int STDataType;
typedef struct Stack
{
STDataType* a; //定义一个数组,用于存储数据
int top; // 用于标定栈顶
int capacity; // 栈的容量
}St;//将struct Stack重定义为St
再定义栈之后,接下来初始化栈。
// 初始化栈
void StackInit(St* ps)
{
assert(ps);
ps->a = NULL;//将栈中的a置空
ps->capacity = 0;//将栈的容量初始化为0
ps->top = 0;//表示top指向栈顶元素的下一个位置
//ps->top=-1; //表示top指向栈顶元素
//以上这两种情况均可以,不过对应的接口函数也应该与其对应
}
入栈:
void StackPush(St* ps, STDataType data)
{
assert(ps);
if (ps->capacity == ps->top)//如果top=0时,top也可以理解为栈内元素个数,如果栈内元素个数与栈
//的容量相等时这时候入栈是需要扩容的,一般情况是扩大为原来的一倍
{
int newcapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;
ps->capacity = newcapacity;
STDataType* tmp = (STDataType*)realloc(ps->a,newcapacity*sizeof(STDataType));
if (tmp == NULL)
{
perror("malloc fail");
return;
}
ps->a = tmp;
tmp = NULL;
}
ps->a[ps->top] = data;//由于top在数组中指向的是栈顶元素的下一个,所以应该先赋值再自增
ps->top++;
}
图示:
出栈:
// 出栈
void StackPop(St* ps)
{
assert(ps);
assert(ps->top > 0);//由于top可以表示为栈内元素的个数,所以如果栈内没有元素的话,肯定是无法出
//栈的,所以这里要设置一个断言,确保栈中至少有一个元素的时候才可以出栈
ps->top--;//由于我们是通过top来访问栈顶的,所以我们只需要将top--就可以做到出栈了,由于原top位
//置的数据随后可能还要重新赋值,所以不用置0/1之类的
}
在讲完入栈和出栈之后,为了验证我们是否入栈或者出栈成功,我们还需要获取栈顶元素来进一步确定,获取栈顶元素:
STDataType StackTop(St* ps)
{
assert(ps);
assert(ps->top>0);//栈如果为空肯定也是无法获取栈顶元素的,这也要设置一个断言
return ps->a[ps->top - 1];//由于top标定的是栈顶元素的下一个位置,所以访问栈顶元素的时候,要用
//top-1来获取栈顶元素
}
获取栈中有效元素的个数:
int StackSize(St* ps)
{
assert(ps);
return ps->top;//top由于标定的是栈顶元素的下一个位置,所以可以理解为栈的有效数据的个数
}
检测栈是否为空:
// 检测栈是否为空,如果为空返回非零结果,如果不为空返回0
int StackEmpty(St* ps)
{
assert(ps);
return ps->top == 0;//栈中有效元素个数top如果等于0表示栈为空返回非0,否则返回0
}
最后当我们使用完一个数据结构之后,我们通常要销毁它,避免该数据结构一直在内存中占空间。
销毁栈:
void StackDestroy(St* ps)
{
assert(ps);
free(ps->a);//释放申请的数组空间
ps->a = NULL;//置空
ps->top = ps->capacity = 0;//将数据容量和有效数据的个数置0
}
最后我们还要通过测试,来判断我们的接口函数是否正确:
void Stacktest01()
{
St st;//创建一个栈
StackInit(&st);//初始化
StackPush(&st, 1);
StackPush(&st, 2);
StackPush(&st, 3);
StackPush(&st, 4);
StackPush(&st, 5);
StackPush(&st, 6);
StackPush(&st, 7);
StackPush(&st, 8);//入栈
if (StackEmpty(&st) != 0)
{
printf("栈为空\n");
}
else
{
printf("栈不为空\n");
}
printf("栈内元素个数为:%d\n", StackSize(&st));
while(!StackEmpty(&st))
{
int tmp = StackTop(&st);
printf("%d ", tmp);
StackPop(&st);//出栈的同时,并打印
}
printf("\n");
StackDestroy(&st);//销毁栈
}
总的结构:
stack.h:
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
// 支持动态增长的栈
typedef int STDataType;
typedef struct Stack
{
STDataType* a;
int top; // 栈顶
int capacity; // 容量
}St;
// 初始化栈
void StackInit(St* ps);
// 入栈
void StackPush(St* ps, STDataType data);
// 出栈
void StackPop(St* ps);
// 获取栈顶元素
STDataType StackTop(St* ps);
// 获取栈中有效元素个数
int StackSize(St* ps);
// 检测栈是否为空,如果为空返回非零结果,如果不为空返回0
int StackEmpty(St* ps);
// 销毁栈
void StackDestroy(St* ps);
stack.c:
#include"Stack.h"
// 初始化栈
void StackInit(St* ps)
{
assert(ps);
ps->a = NULL;
ps->capacity = 0;
ps->top = 0;//表示top指向栈顶元素的下一个位置
//ps->top=-1 表示top指向栈顶元素
}
// 入栈
void StackPush(St* ps, STDataType data)
{
assert(ps);
if (ps->capacity == ps->top)
{
int newcapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;
ps->capacity = newcapacity;
STDataType* tmp = (STDataType*)realloc(ps->a,newcapacity*sizeof(STDataType));
if (tmp == NULL)
{
perror("malloc fail");
return;
}
ps->a = tmp;
tmp = NULL;
}
ps->a[ps->top] = data;
ps->top++;
}
// 出栈
void StackPop(St* ps)
{
assert(ps);
assert(ps->top > 0);
ps->top--;
}
// 获取栈顶元素
STDataType StackTop(St* ps)
{
assert(ps);
assert(ps->top > 0);
return ps->a[ps->top - 1];
}
// 获取栈中有效元素个数
int StackSize(St* ps)
{
assert(ps);
return ps->top;
}
// 检测栈是否为空,如果为空返回非零结果,如果不为空返回0
int StackEmpty(St* ps)
{
assert(ps);
return ps->top == 0;
}
// 销毁栈
void StackDestroy(St* ps)
{
assert(ps);
free(ps->a);
ps->a = NULL;
ps->top = ps->capacity = 0;
}
test.c:
#include"Stack.h"
#include"Queue.h"
#include<stdbool.h>
void Stacktest01()
{
St st;
StackInit(&st);
StackPush(&st, 1);
StackPush(&st, 2);
StackPush(&st, 3);
StackPush(&st, 4);
StackPush(&st, 5);
StackPush(&st, 6);
StackPush(&st, 7);
StackPush(&st, 8);
if (StackEmpty(&st) != 0)
{
printf("栈为空\n");
}
else
{
printf("栈不为空\n");
}
printf("栈内元素个数为:%d\n", StackSize(&st));
while(!StackEmpty(&st))
{
int tmp = StackTop(&st);
printf("%d ", tmp);
StackPop(&st);
}
printf("\n");
StackDestroy(&st);
}
int main()
{
Stacktest01();
return 0;
}
2.队列
2.1队列的概念及结构
队列是一种只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,同时队列具有先进先出的规则(First In First Out)。
此外,入队列:(进行插入操作的一端称为队尾)在队尾插入数据 出队列:(进行删除操作的一端称为对头)在队头删除数据
2.2队列的实现及分类
队列也分为数组队列和链表队列的两种结构,使用链表会更好一点,由于使用数组队列的时候,在出队列时,需要挪动数据,效率比较低,所以小编就实现一下链式队列。
这里和栈一样,也要定义三个文件queue.c(其中包含关于队列接口函数的实现)queue.h(包含队列的定义和接口函数的声明)test.c(接口函数的测试)
首先定义队列,由于我们只对队头和队尾进行操作,所以我们只用定义两个指针来指向队头和队尾即可,同时也要定义节点。
// 链式结构:表示队列
typedef int QDataType;
typedef struct QListNode
{
struct QListNode* next;
QDataType data;
}QNode;
// 队列的结构
typedef struct Queue
{
QNode* begin;//指向队头
QNode* tail;//指向队尾
int size;//队列有效元素个数
}Queue;
队列的初始化:
// 初始化队列
void QueueInit(Queue* q)
{
assert(q);
q->begin = q->tail = NULL;//由于还未入队列,故都要置空
q->size = 0;//有效元素初值为0
}
队尾入队列:
void QueuePush(Queue* q, QDataType data)
{
assert(q);
QNode* tmp = (QNode*)malloc(sizeof(QNode));//向内存中申请一个节点
if (tmp == NULL)//如果malloc申请失败,程序返回,不再向下进行
{
perror("malloc fail");
return;
}
tmp->data = data;//赋值
tmp->next = NULL;//其next指针指空
if (q->begin ==NULL)
{
q->begin = q->tail = tmp;//如果队列为空时,让队尾和队头指针都指向队列的第一个节点
}
else {
q->tail->next = tmp;
q->tail = q->tail->next;//此时队列中不为空,让队尾指针的next指针指向要入队列的节点,同时
//再让队尾指针在指向新入队列的节点,保持队头指针指向不动
}
q->size++;//每入队列一次,队列的有效数据个数加1
}
出队列:
// 队头出队列
void QueuePop(Queue* q)
{
assert(q);
assert(q->begin);//保证队列中有数据,如果没有数据,则无法出队列
QNode* tmp = q->begin;//保存要删除头节点的地址
q->begin = q->begin->next;//让队头指针指向其next指针
if (q->begin == NULL)
{
q->tail = NULL;//如果这时begin指向NULL,表明队列中只有一个元素,所以将这个数据删除之后,
//为了避免野指针的出现,同时也需要将尾指针也指向NULL
}
free(tmp);//释放原头节点
tmp = NULL;
q->size--;//每出队列一次,队列的有效数据个数减1
}
获取队列头部元素:
// 获取队列头部元素
QDataType QueueFront(Queue* q)
{
assert(q);
assert(q->begin);//同样队列不能为NULL,为NULL也无法获取队头元素
return q->begin->data;//直接通过解引用获取数据
}
获取队列尾部元素:
// 获取队列队尾元素
QDataType QueueBack(Queue* q)
{
assert(q);
assert(q->tail);//同样队列不能为NULL,如果队列为空,也是无法获取队尾元素
return q->tail->data;//通过访问尾结点来获取其数据
}
获取队列中的有效元素个数:
// 获取队列中有效元素个数
int QueueSize(Queue* q)
{
assert(q);
return q->size;//size为队列中有效元素的个数
}
检测队列是否为空:
// 检测队列是否为空,如果为空返回非零结果,如果非空返回0
int QueueEmpty(Queue* q)
{
assert(q);
if (q->size == 0)
{
return 1;
}
else
{
return 0;
}
}
在使用完队列之后,避免它占用内存空间,我们同样也要销毁它,队列的销毁:
// 销毁队列
void QueueDestroy(Queue* q)
{
assert(q);
QNode* cur = q->begin;
while (cur)
{
QNode* next = cur->next;
free(cur);
cur = next;
}//由于队列是由一个个节点组成,所以我们要依次释放队列中的每一个节点
q->begin = q->tail = NULL;//将队尾和队头指针都指向空
q->size = 0;//将有效数据个数置0
}
上述我们定义了一些关于队列的接口函数,接下来我们需要测试我们的接口函数:
test.c
void QueueTest01()
{
Queue q;//定义一个队列
QueueInit(&q);//初始化
QueuePush(&q, 1);
QueuePush(&q, 2);
QueuePush(&q, 3);
QueuePush(&q, 4);
QueuePush(&q, 5);
QueuePush(&q, 6);
QueuePush(&q, 7);
QueuePush(&q, 8);//入队列
printf("队列的有效数据为:%d\n", QueueSize(&q));
while(QueueEmpty(&q) == 0)//若队列不为空,就一直出队列
{
int tmp = QueueFront(&q);
QueuePop(&q);
printf("%d ",tmp);//出队列的同时并打印
}
printf("\n");
QueueDestroy(&q);销毁队列
}
int main()
{
//Stacktest01();
QueueTest01();
return 0;
}
队列实现的总结构:
queue.c
#include"Queue.h"
// 初始化队列
void QueueInit(Queue* q)
{
assert(q);
q->begin = q->tail = NULL;
q->size = 0;
}
// 队尾入队列
void QueuePush(Queue* q, QDataType data)
{
assert(q);
QNode* tmp = (QNode*)malloc(sizeof(QNode));
if (tmp == NULL)
{
perror("malloc fail");
return;
}
tmp->data = data;
tmp->next = NULL;
if (q->begin ==NULL)
{
q->begin = q->tail = tmp;
}
else {
q->tail->next = tmp;
q->tail = q->tail->next;
}
q->size++;
}
// 队头出队列
void QueuePop(Queue* q)
{
assert(q);
assert(q->begin );
QNode* tmp = q->begin;
q->begin = q->begin->next;
if (q->begin == NULL)
{
q->tail = NULL;
}
free(tmp);
tmp = NULL;
q->size--;
}
// 获取队列头部元素
QDataType QueueFront(Queue* q)
{
assert(q);
assert(q->begin);
return q->begin->data;
}
// 获取队列队尾元素
QDataType QueueBack(Queue* q)
{
assert(q);
assert(q->tail);
return q->tail->data;
}
// 获取队列中有效元素个数
int QueueSize(Queue* q)
{
assert(q);
return q->size;
}
// 检测队列是否为空,如果为空返回非零结果,如果非空返回0
int QueueEmpty(Queue* q)
{
assert(q);
if (q->size == 0)
{
return 1;
}
else
{
return 0;
}
}
// 销毁队列
void QueueDestroy(Queue* q)
{
assert(q);
QNode* cur = q->begin;
while (cur)
{
QNode* next = cur->next;
free(cur);
cur = next;
}
q->begin = q->tail = NULL;
q->size = 0;
}
queue.h
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
// 链式结构:表示队列
typedef int QDataType;
typedef struct QListNode
{
struct QListNode* next;
QDataType data;
}QNode;
// 队列的结构
typedef struct Queue
{
QNode* begin;
QNode* tail;
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);
// 检测队列是否为空,如果为空返回非零结果,如果非空返回0
int QueueEmpty(Queue* q);
// 销毁队列
void QueueDestroy(Queue* q);
今天关于栈和队列的分享就到此结束了,很感谢你能花费你宝贵的时间来阅读我的博客,希望能够对正在学习数据结构的你有所帮助,最后给博主点个赞吧,拜拜,我们下期再见。