本篇博客主要为介绍栈和队列到底是什么?以及如何用c语言实现一个栈和一个队列。
1.栈
栈是一种线性的数据结构,栈最大的特点就是“后入先出”。栈插入数据时叫作压栈,删除数据的时候叫作出栈,无论是出栈还是压栈都是从栈顶这个地方出和入的。用文字来概括栈不是那么的直观,相对于文字图更加直观一些。
如图描述了栈的结构和压栈和出栈。
1.1 如何创建一个栈
根据上面的叙述大致了解了栈的基本结构,下面就使用c语言创建一个栈。首先创建Stack.c文件(栈的功能文件),Stack.h(头文件),test.c(测试文件)。这里我们选择创建数组的方式来实现一个栈。
1.1.1 创建结构体和方法函数的声明
stack.h代码
//首先包含所用的头文件
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>
//实现一个动态增长的栈
typedef int STDataType;//栈内不可能只存整形数据,方便以后修改数据的类型
struct Stack
{
STDataType* a;//指向数组的指针
int size;//数组中最后一个元素的下一个元素的下标也就是栈顶
int capacity;//能存多少数据容量
};
typedef struct Stack 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);
1.1.2 栈的初始化和销毁
想要初始化栈就是要修改栈中的内容所以需要传结构体的指针。这里唯一需要注意的是size的值,如果size为零那么它就是最后一个元素的下一个位置的下标。下面的图片叙述了两种情况,这里写的代码基于第一种写法。
// 初始化栈
void StackInit(ST* ps)
{
assert(ps);//断言一下防止传空指针
ps->a = NULL;//将指针置为空
ps->size = 0;//代表size是最后一个元素下一个元素的下标
ps->capacity = 0;//能存多少数据容量
}
void StackDestroy(ST* ps)
{
assert(ps);
free(ps->a);//释放a指向的空间
ps->capacity = ps->size = 0;
}
1.1.3 入栈和出栈
入栈就是插入数据,出栈的话就是删除数据。
// 入栈
void StackPush(ST* ps, STDataType data)
{
//先判断栈中的空间是否足够,当size=capacity时说明栈的空间不足
int newcapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;//三目运算符如果capacity是零的话就给一个初始值4
if (ps->capacity == ps->size)
{
STDataType* tmp = (STDataType*)realloc(ps->a, sizeof(STDataType) * newcapacity);//为栈创建空间
//判断空间是否创建失败
if (tmp==NULL)
{
perror("realloc::fail");
return;
}
//创建成功
ps->a = tmp;
}
//插入数数据,然后size++
ps->a[ps->size] = data;
ps->size++;
ps->capacity = newcapacity;
}
// 出栈
void StackPop(ST* ps)
{
assert(ps);
assert(ps->size > 0);//防止size为负
//删除数据只需要size--即可
ps->size--;
}
1.1.4 获取栈顶元素 获取栈中有效元素个数 栈是否为空
需要注意的是这里的获取栈中有效数据,可以看图理解。
// 获取栈顶元素
STDataType StackTop(ST* ps)
{
assert(ps);
assert(ps->size > 0);//防止栈为空
return ps->a[ps->size - 1];//size-1即为栈顶元素
}
// 获取栈中有效元素个数
int StackSize(ST* ps)
{
assert(ps);
return ps->size;
}
// 栈是否为空
bool StackEmpty(ST* ps)
{
assert(ps);
return ps->size == 0;//若size为零即为空若size不为零那就是不为空
}
打印栈中的数据
因为栈的特点,在打印栈中数据的时候我们需要先获取栈顶的元素然后删除栈顶的元素直到栈为空。
#include"Stack.h"
int main()
{
ST s;
StackInit(&s);
StackPush(&s, 1);
StackPush(&s, 2);
StackPush(&s, 3);
StackPush(&s, 4);
while (!StackEmpty(&s))
{
printf("%d ",StackTop(&s));
StackPop(&s);
}
StackDestroy(&s);
return 0;
}
2.队列
队列是一种,一头用于删除数据,一头用来插入数据的一种特殊的线性数据结构。与栈不同的是队列是“先入先出”的。队尾用来插入数据,队头删除数据。
2.1如何创建一个队列
与栈一样先创建文件Queue.c Queue.h test.c
2.1.1创建结构体和方法函数声明
创建一个队列和创建栈是不同的,这里创建栈使用的是单链表的方法。这里我们需要创建两个结构体,一个结构体是队列中的节点,另一个结构体中有两个指针用来维护队列的队头和队尾。
Queue.h
//首先包含所用的头文件
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>
//创建一个动态增长的队列
typedef int QDataType;//队列内不可能只存整形数据,方便以后修改数据的类型
typedef struct QueueNode
{
struct QueueNode* next;//指向下一个节点的指针
QDataType data;//节点中存储的数据
}QNode;
typedef struct Queue
{
QNode* head;//指向队头的指针
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);
2.1.2 初始化队列
初始化队列时只需要队Queue这个结构体中的数据进行初始化,因为这个结构体是用来维护这个队列的。
// 初始化队列
void QueueInit(Queue* q)
{
assert(q);
q->head = NULL;
q->tail = NULL;
q->size = 0;
}
2.1.3 队尾入队列和队头出队列
入队列时,首先要创建一个QNode节点,并判断head tail 指针是否为空如果为空就直接将创建的节点的指针直接赋值给head 和tail。如果head 不为空那么就执行尾插。出队头进行出队列需要先找到下一个记录下一个节点的指针next,然后free掉head,再让head=next。
// 队尾入队列
void QueuePush(Queue* q, QDataType data)
{
assert(q);
//创建节点
QNode* newnode = (QNode*)malloc(sizeof(QNode));
//判断newnode是否为空
if (newnode == NULL)
{
perror("malloc:fial");
return;
}
//首先判断head=和tail是否为空
if (q->head == NULL)
{
q->head = q->tail = newnode;
}
q->tail->next = newnode;
q->tail = newnode;
q->tail->data = data;
q->size++;
}
// 队头出队列
void QueuePop(Queue* q)
{
assert(q);
assert(q->head);
QNode* next = q->head->next;
free(q->head);
q->head = next;
q->size--;
}
2.1.4 获取队列头部元素 ,获取队列队尾元素,获取队列中有效元素个数,检测队列是否为空
这几个函数都很简单就不一 一介绍了。
// 获取队列头部元素
QDataType QueueFront(Queue* q)
{
assert(q);
assert(q->head);
return q->head->data;
}
// 获取队列队尾元素
QDataType QueueBack(Queue* q)
{
assert(q);
assert(q->tail);
return q->tail->data;
}
// 获取队列中有效元素个数
int QueueSize(Queue* q)
{
assert(q);
return q->size;
}
// 检测队列是否为空,如果为空返回true不为空返回false
bool QueueEmpty(Queue* q)
{
assert(q);
return q->size == 0;
}
2.1.5 销毁队列
销毁队列时,因为我们创建的是节点所以我们要一个一个的free掉所有的节点,首先我们要记录第一个节点的下一个节点next,free掉head再将head=next。
// 销毁队列
void QueueDestroy(Queue* q)
{
assert(q);
while (!QueueEmpty(q))
{
QNode* next = q->head->next;
free(q->head);
q->head = next;
}
q->head = NULL;
q->tail = NULL;
q->size = 0;
}
2.1.6 打印队列中的数据
打印队列中的数据与栈的类似就不过多赘述了。
#include"Queue.h"
int main()
{
Queue st;
QueueInit(&st);
QueuePush(&st, 1);
QueuePush(&st, 2);
QueuePush(&st, 3);
QueuePush(&st, 4);
while (!QueueEmpty(&st))
{
printf("%d ",QueueFront(&st));
QueuePop(&st);
}
QueueDestroy(&st);
return 0;
}
以上就是有关栈和队列的概念以及如何实现,本次的分析就到这里。希望大佬们多多指点,希望大家点赞 评论 收藏 感谢感谢。