此文主要栈与队列的介绍以及使用c的具体实现,同时介绍了循环队列的具体实现,以及其实现的难点,并对其进行了绘图示意,希望可以帮助大家,如有错误,还望大家不吝赐教。
一、栈(Stack)
1、什么是栈?
栈作为一种数据结构,是一种只能在一端进行插入和删除操作的特殊线性表。<br>举例来说,就像是一串糖葫芦,只能从尖端插入山楂球(数据),也只能从尖端咬出完整的山楂球。这也就是栈的后入先出(LIFO),last in first out。
2、栈的实现
结构体
借用数组实现栈。
- 使用typedef,方便后续修改栈中内容的数据类型;
- top指向栈顶,其初始值即可设为0,也可设为-1,看具体的实现方式,本文中采取前者,top指向的也是栈顶的下一位;
- capacity表示此时栈的容量,增加栈的存在便于当容量不够时,增加容量。
typedef int STDataType;
typedef struct Stack
{
STDataType* a;
int top; // 栈顶
int capacity; // 容量
}Stack;
void StackInit(Stack* ps)
{
assert(ps);
ps->capacity = ps->top = 0;
ps->a = NULL; // 指针需置空,否则在检查容量时使用,会出现访问野指针的情况
Checkcapacity(ps);
}
插入 Push
- 设置capacity就是为了扩容,所以在插入数据时,首先就要检查栈容量;
- 数据插入后,top再++,保持指向栈顶下一位的状态。
void Checkcapacity(Stack* ps)
{
assert(ps);
// 容量不够时,翻倍增加
if (ps->capacity == ps->top)
{
int newcapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;
STDataType* tmp = (STDataType*)realloc(ps->a, sizeof(STDataType) * newcapacity);
if (tmp == NULL)
{
perror("malloc failed");
exit(-1);
}
ps->a = tmp;
ps->capacity = newcapacity;
}
}
void StackPush(Stack* ps, STDataType data)
{
assert(ps);
Checkcapacity(ps);
ps->a[ps->top++] = data;
}
删除Pop
数组实现,只需将top下移即可。
void StackPop(Stack* ps)
{
assert(ps);
// 有数据才可删
assert(ps->top);
ps->top--;
}
访问栈顶数据
由于top指向的是下一个插入数据时的位置,因此访问栈顶时,需要 -1。
STDataType StackTop(Stack* ps)
{
assert(ps);
assert(ps->top);
return ps->a[ps->top - 1];
}
其余基本操作
// 数组现有数据数量,即为top值
int StackSize(Stack* ps)
{
assert(ps);
return ps->top;
}
// 检查栈是否为空
int StackEmpty(Stack* ps)
{
assert(ps);
return ps->top == 0;
}
// 销毁栈
void StackDestroy(Stack* ps)
{
free(ps->a);
ps->capacity = ps->top = 0;
}
二、队列(Queue)
1、什么是队列?
队列亦是一种特殊的线性表,特殊之处在于它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作,和栈一样,队列是一种操作受限制的线性表。 这个数据结构的名称其实很好的表示的它的性质,就像一条队列,在食堂排队,先排的人先打饭。也就是先进先出(FIFO),First in First out。
2、队列的实现
结构体
考虑到栈采用的数组实现,对于有异曲同工之妙的队列,此文采取链表实现。
- 分别定义结点的数据结构与队列的数据结构;
- 链表实现相比数组实现的优势:如果使用数组的结构,出队列在数组头上出数据,会出现大量的数据挪动,效率会比较低。
typedef int QDataType;
// 链式结构:表示队列
typedef struct QListNode
{
QDataType data;
struct QListNode* next;
}QNode;
// 队列的结构
typedef struct Queue
{
QNode* front;
QNode* rear;
}Queue;
void QueueInit(Queue* q)
{
assert(q);
q->front = q->rear = NULL;
}
插入Push
新申请节点空点,随后将其链接到队列中即可。
void QueuePush(Queue* q, QDataType data)
{
assert(q);
QNode* tmp = (QNode*)malloc(sizeof(QNode));
if (!tmp)
{
perror("malloc failed");
exit(-1);
}
tmp->data = data;
tmp->next = NULL;
// 没有元素
if (q->front == NULL)
{
q->front = q->rear = tmp;
}
else
{
q->rear->next = tmp;
q->rear = tmp;
}
}
删除Pop
将表尾结点取消链接,随后释放。
void QueuePop(Queue* q)
{
assert(q);
// 只有一个元素
if (q->front == q->rear)
{
free(q->front);
q->front = q->rear = NULL;
}
else
{
QNode* tmp = q->front;
q->front = tmp->next;
free(tmp);
}
}
其余基本操作
// 访问队头数据
QDataType QueueFront(Queue* q)
{
assert(q);
return q->front->data;
}
// 访问队尾数据
QDataType QueueBack(Queue* q)
{
assert(q);
return q->rear->data;
}
// 队列数据数量
int QueueSize(Queue* q)
{
assert(q);
QNode* cur = q->front;
int size = 0;
while (cur != q->rear)
{
cur = cur->next;
size++;
}
return size;
}
// 队列判空
int QueueEmpty(Queue* q)
{
assert(q);
return q->front == NULL;
}
// 销毁队列
void QueueDestroy(Queue* q)
{
assert(q);
while (q->front != NULL)
{
QueuePop(q);
}
}
三、循环队列
此题在力扣有相关OJ题————>设计循环队列。
实现难点
- 若按寻常队列,front指向队头,rear指向队尾,则会出现一个问题:队列空和满的状态时,都有
front == rear
,(图画的很烂,大家凑合看看,哈哈哈哈哈)如下: front指向队头,rear指向队尾的下一处,则队列满时与队列空时都指向同一位置; - 为了解决此问题,多申请一个结点,则判空条件为
front == rear
,判满条件为rear + 1 == front
。如下:
循环队列的实现
结构体
// 利用数组实现循环队列
typedef struct {
int* data;
int head;
int rear;
int sz;
} MyCircularQueue;
创建循环队列
MyCircularQueue* myCircularQueueCreate(int k) {
MyCircularQueue* obj = (MyCircularQueue*)malloc(sizeof(MyCircularQueue));
// 多申请一个空间,便于判断队列是空还是满
obj->data = (int*)malloc(sizeof(int) * (k + 1));
obj->sz = k + 1;
obj->head = obj->rear = 0;
return obj;
}
入队Push
此处,判断队列是否已满,即用rear
与front
比较,但由于rear++时有可能会大于队列的容量,故此时需要模运算(%),得到不大于容量的rear值,同时也就达到了循环效果;如下:
bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) {
int tmp = (obj->rear + 1) % obj->sz;
// 队列已满
if(tmp == obj->head)
{
return false;
}
obj->data[obj->rear] = value;
obj->rear = tmp;
return true;
}
删除Pop
同理,当front到达循环队列末尾时,需要注意采用模运算
bool myCircularQueueDeQueue(MyCircularQueue* obj) {
// 队列为空
if(obj->head == obj->rear)
{
return false;
}
obj->head = (obj->head + 1) % obj->sz;
return true;
}
其余基本操作,访问队头队尾等;
int myCircularQueueFront(MyCircularQueue* obj) {
if(obj->head == obj->rear)
{
return -1;
}
return obj->data[obj->head];
}
int myCircularQueueRear(MyCircularQueue* obj) {
if(obj->head == obj->rear)
{
return -1;
}
int tmp = (obj->rear - 1 + obj->sz) % obj->sz;
return obj->data[tmp];
}
bool myCircularQueueIsEmpty(MyCircularQueue* obj) {
return obj->head == obj->rear;
}
bool myCircularQueueIsFull(MyCircularQueue* obj) {
int tmp = (obj->rear + 1) % obj->sz;
return tmp == obj->head;
}
void myCircularQueueFree(MyCircularQueue* obj) {
free(obj->data);
obj->data = NULL;
free(obj);
obj = NULL;
}