栈与队列入门:定义、操作及完整 C 语言实现教程
一、用生活场景读懂栈和队列
你是否经历过这些场景?
1. 奶茶店排队点单:先到的人先下单,后到的人只能排后面(队列);
2. 收拾碗柜时叠盘子:后叠上去的盘子,要先拿下来才能用到下面的(栈)。
这两个日常场景,恰好对应了数据结构中两种基础且核心的结构 —— 队列(Queue)和栈(Stack)。它们都是 “线性结构”(数据元素排成一条直线),但因数据操作规则不同,适用场景天差地别。今天就带大家从 “是什么→怎么用→用在哪”,彻底搞懂这对 “孪生兄弟”。
二、队列和栈的差异
1.定义与核心规则
(1)队列(Queue):先进先出(FIFO)
队列就像一条单向通道,数据从 “队尾” 进入(入队,enqueue),从 “队头” 离开(出队,dequeue),中间元素无法直接操作。比如电影院检票,只有前一个人检票完成,下一个人才能进入。
核心操作:入队、出队、判断队空、取队头元素、取队尾元素、取元素个数
(2)栈(Stack):后进先出(LILO)
栈类似一个垂直的容器,数据从 “栈顶” 进入(压栈,push),也只能从 “栈顶” 离开(弹栈,pop),栈底元素最后才能访问。比如叠放的书本,只能先拿最上面的一本。
核心操作:入栈、出栈、查看栈顶、判断栈空、取栈元素个数
2.可视化对比
| 特性 | 队列 | 栈 |
| 操作规则 | 先进先出 | 后进先出 |
| 插入/删除操作端 | 队尾插入、队头出队列 | 仅栈顶插入/删除 |
| 访问限制 | 只能访问队头/队尾元素 | 仅能访问栈顶元素 |
三、队列和栈的实现
1.队列实现
(1)结构和接口声明(可以根据代码内容尝试自行实现)
#define _CRT_SECURE_NO_WARNINGS
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <assert.h>typedef int QDataType;
typedef struct QNode
{QDataType val;struct QNode* next;
}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);
//队列判空
bool QueueEmpty(Queue* pq);
//队列有效元素个数
int QueueSize(Queue* pq);(2)接口实现
#define _CRT_SECURE_NO_WARNINGS
#include "Queue.h"//初始化
void QueueInit(Queue* pq)
{assert(pq);pq->phead = 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("QueuePush::malloc");return;}newnode->val = x;if (pq->phead == 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);//如果只有一个节点if (pq->phead == pq->ptail){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;
}//队列判空
bool QueueEmpty(Queue* pq)
{assert(pq);return pq->size == 0;
}//队列有效元素个数
int QueueSize(Queue* pq)
{assert(pq);return pq->size;
}2.栈实现
(1)结构和接口声明(可以根据代码内容尝试自行实现)
#define _CRT_SECURE_NO_WARNINGS#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);
(2)接口实现
#define _CRT_SECURE_NO_WARNINGS#include "Stack.h"// 初始化和销毁
void STInit(ST* pst)
{assert(pst);pst->a = NULL;
// pst->top = -1; // 指向栈顶元素,为-1时表示无数据-》放数据时要先++pst->top = 0; // 指向栈顶元素的下一个元素,相当于size了(有效数据个数)-》放数据时要后++pst->capacity = 0;
}void STDestroy(ST* pst)
{assert(pst);free(pst->a);pst->a = NULL;pst->top = 0; pst->capacity = 0;
}// 入栈 出栈
void STPush(ST* pst, STDataType x)
{assert(pst);// 扩容if (pst->top == pst->capacity){int newcapacity = pst->capacity == 0 ? 4 : 2 * pst->capacity; //要判断是不是第一次扩容STDataType* tmp = realloc(pst->a, newcapacity * sizeof(STDataType)); //如果realloc给的指针为空相当于mallocif (tmp == NULL){perror("STPush::realloc");return;}pst->a = tmp;pst->capacity = newcapacity;}pst->a[pst->top] = x;pst->top++;
}void STPop(ST* pst)
{assert(pst);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;
}