3、栈和队列
数据结构笔记:栈与队列
一、栈
(一)栈的基本概念
栈是一种特殊的线性表,仅允许在固定一端(栈顶)进行数据操作,遵循 **“后进先出”(LIFO,Last In First Out)** 的逻辑规则,就像堆叠的盘子 —— 最后放的盘子要先拿,最底下的盘子最后拿。
1. 核心术语
术语 | 定义 | 对应操作 |
栈顶(Top) | 允许插入、删除数据的一端 | 所有操作的核心端点 |
栈底(Bottom) | 与栈顶相对、固定不动的一端 | 仅作为栈的起始基准 |
入栈(Push) | 将数据插入栈顶 | 增加数据,栈顶位置上移 |
出栈(Pop) | 将栈顶数据删除 | 减少数据,栈顶位置下移 |
取栈顶(Top) | 读取栈顶数据但不删除 | 仅查询,不改变栈结构 |
栈空 | 栈中无任何数据(栈顶指向栈底) | 无法出栈或取栈顶 |
栈满 | 栈中数据达到最大容量 | 无法入栈 |
2. 生活中的栈示例
- 堆叠的盘子:新盘子放最上面(入栈),拿盘子从最上面拿(出栈)。
- 弹匣:子弹压入时从顶部放入(入栈),射击时从顶部弹出(出栈)。
- 函数调用:主函数调用子函数时,主函数上下文压栈;子函数执行完,上下文出栈恢复主函数。
(二)栈的存储形式
栈的逻辑结构需通过物理存储实现,主要分为顺序栈(数组存储)和链式栈(链表存储),两种形式各有优劣,需根据场景选择。
1. 顺序栈(数组实现)
用连续的数组空间存储栈数据,通过一个 “栈顶指针”(通常是数组下标)标记栈顶位置,结构简单、访问效率高,但需提前确定容量。
(1)结构设计
顺序栈需包含 3 个核心要素:
- 数据数组(data):存储栈的实际数据。
- 栈顶指针(top):标记当前栈顶的数组下标(初始值设为-1,表示栈空)。
- 容量(capacity):数组的最大存储长度(栈的最大容量)。
(2)核心操作逻辑
操作 | 步骤 | 注意事项 |
初始化 | 1. 申请数组空间(大小 = 容量);2. 设top=-1、capacity=指定值 | 若数组申请失败,需释放已分配内存 |
入栈 | 1. 先判断栈是否满(top == capacity-1);2. 栈顶指针top+1;3. 数据存入data[top] | 栈满时无法入栈,需提示 “溢出” |
出栈 | 1. 先判断栈是否空(top == -1);2. 清空data[top](可选,避免脏数据);3. 栈顶指针top-1 | 栈空时无法出栈,需提示 “下溢” |
取栈顶 | 1. 判断栈是否空;2. 返回data[top] | 仅读取,不改变top值 |
遍历 | 从top开始,向下遍历到0,依次打印data[i] | 遵循 “从栈顶到栈底” 的顺序 |
(3)示例代码(C 语言)
// 1. 头文件(sequential_stack.h) #ifndef __SEQUENTIAL_STACK_H #define __SEQUENTIAL_STACK_H #include <stdio.h> #include <stdbool.h> #include <stdlib.h> #include <string.h> // 支持两种数据类型(可通过宏切换) #define STU_DATA 0 // 学生数据类型 #define INT_DATA 1 // 整数数据类型 #define USE_DATA INT_DATA // 当前使用的类型:整数 // 学生结构体 typedef struct Student { int id; // 学号 char name[20]; // 姓名 int age; // 年龄 } Student; // 整数结构体 typedef struct IntData { int value; // 整数值 } IntData; // 选择数据类型 #if USE_DATA == STU_DATA typedef Student DataType; #elif USE_DATA == INT_DATA typedef IntData DataType; #endif // 顺序栈管理结构体 typedef struct SequentialStack { DataType* data; // 数据数组指针 int top; // 栈顶指针 int capacity; // 栈容量 } SeqStack, *SeqStackPtr; // 函数声明 SeqStackPtr SeqStack_Init(int capacity); // 初始化栈 void SeqStack_Destroy(SeqStackPtr stack); // 销毁栈 bool SeqStack_IsEmpty(SeqStackPtr stack); // 判断栈空 bool SeqStack_IsFull(SeqStackPtr stack); // 判断栈满 int SeqStack_Push(SeqStackPtr stack, DataType data); // 入栈 int SeqStack_Pop(SeqStackPtr stack); // 出栈 DataType SeqStack_GetTop(SeqStackPtr stack); // 取栈顶 void SeqStack_Print(SeqStackPtr stack); // 打印栈 #endif // 2. 实现文件(sequential_stack.c) #include "sequential_stack.h" // 初始化栈 SeqStackPtr SeqStack_Init(int capacity) { if (capacity <= 0) return NULL; // 申请栈管理结构体 SeqStackPtr stack = (SeqStackPtr)malloc(sizeof(SeqStack)); if (!stack) return NULL; // 申请数据数组 stack->data = (DataType*)malloc(sizeof(DataType) * capacity); if (!stack->data) { free(stack); return NULL; } stack->top = -1; // 初始栈空 stack->capacity = capacity; return stack; } // 销毁栈 void SeqStack_Destroy(SeqStackPtr stack) { if (stack) { if (stack->data) free(stack->data); // 先释放数据数组 free(stack); // 再释放管理结构体 } } // 判断栈空 bool SeqStack_IsEmpty(SeqStackPtr stack) { return stack->top == -1; } // 判断栈满 bool SeqStack_IsFull(SeqStackPtr stack) { return stack->top == stack->capacity - 1; } // 入栈(成功返回0,失败返回-1) int SeqStack_Push(SeqStackPtr stack, DataType data) { if (SeqStack_IsFull(stack)) return -1; stack->top++; // 栈顶上移 stack->data[stack->top] = data; // 存入数据 return 0; } // 出栈(成功返回0,失败返回-1) int SeqStack_Pop(SeqStackPtr stack) { if (SeqStack_IsEmpty(stack)) return -1; memset(&stack->data[stack->top], 0, sizeof(DataType)); // 清空数据(可选) stack->top--; // 栈顶下移 return 0; } // 取栈顶(栈空返回默认值) DataType SeqStack_GetTop(SeqStackPtr stack) { DataType defaultData = {0}; return SeqStack_IsEmpty(stack) ? defaultData : stack->data[stack->top]; } // 打印栈(从栈顶到栈底) void SeqStack_Print(SeqStackPtr stack) { if (SeqStack_IsEmpty(stack)) { printf("栈为空!\n"); return; } printf("顺序栈(栈顶→栈底):\n"); for (int i = stack->top; i >= 0; i--) { #if USE_DATA == INT_DATA printf("第%d个元素:%d\n", i + 1, stack->data[i].value); #elif USE_DATA == STU_DATA printf("第%d个元素:学号=%d,姓名=%s,年龄=%d\n", i + 1, stack->data[i].id, stack->data[i].name, stack->data[i].age); #endif } } // 3. 测试文件(main.c) #include "sequential_stack.h" int main() { // 初始化容量为5的栈 SeqStackPtr stack = SeqStack_Init(5); if (!stack) { printf("栈初始化失败!\n"); return -1; } // 入栈3个整数 IntData data1 = {10}, data2 = {20}, data3 = {30}; SeqStack_Push(stack, data1); SeqStack_Push(stack, data2); SeqStack_Push(stack, data3); SeqStack_Print(stack); // 输出:30 → 20 → 10 // 取栈顶 IntData top = SeqStack_GetTop(stack); printf("栈顶元素:%d\n", top.value); // 输出:30 // 出栈 SeqStack_Pop(stack); SeqStack_Print(stack); // 输出:20 → 10 // 销毁栈 SeqStack_Destroy(stack); return 0; } |
2. 链式栈(链表实现)
用不连续的链表节点存储栈数据,栈顶对应链表的 “头节点后第一个节点”,通过指针串联节点,无需提前确定容量,动态扩容。
(1)结构设计
链式栈采用单向循环链表(避免尾节点指针为NULL的判断,简化操作),核心要素:
- 节点(Node):每个节点包含 “数据域”(存储数据)和 “指针域”(指向后一个节点)。
- 栈顶指针(topPtr):指向链表的 “头节点”(头节点不存数据,仅用于简化操作)。
- 节点计数(count):记录栈中有效数据节点的数量(可选,用于快速判断栈空 / 栈大小)。
(2)核心操作逻辑
链式栈的操作基于 “头插法”(入栈)和 “头删法”(出栈),因为头节点位置固定,操作效率最高。
操作 | 步骤 | 注意事项 |
初始化 | 1. 申请管理结构体;2. 申请头节点,头节点nextPtr指向自身;3. 设count=0 | 头节点不存数据,仅用于串联链表 |
入栈(头插法) | 1. 申请新数据节点,存入数据;2. 新节点nextPtr指向头节点的nextPtr;3. 头节点nextPtr指向新节点;4. count+1 | 无需判断栈满(链表可动态扩容) |
出栈(头删法) | 1. 判断栈是否空(头节点nextPtr指向自身);2. 记录要删除的节点(头节点nextPtr);3. 头节点nextPtr指向删除节点的nextPtr;4. 释放删除节点;5. count-1 | 栈空时无法出栈 |
取栈顶 | 1. 判断栈是否空;2. 返回头节点nextPtr的数据域 | 仅读取,不改变链表结构 |
遍历 | 从 “头节点nextPtr” 开始,循环遍历到 “头节点”,依次打印节点数据 | 遍历顺序即 “栈顶→栈底” |
(3)示例代码(C 语言)
// 1. 头文件(linked_stack.h) #ifndef __LINKED_STACK_H #define __LINKED_STACK_H #include <stdio.h> #include <stdbool.h> #include <stdlib.h> #include <string.h> // 支持两种数据类型 #define STU_DATA 0 #define INT_DATA 1 #define USE_DATA INT_DATA // 学生/整数结构体(同顺序栈,省略重复代码) typedef struct Student { int id; char name[20]; int age; } Student; typedef struct IntData { int value; } IntData; // 选择数据类型 #if USE_DATA == STU_DATA typedef Student DataType; #elif USE_DATA == INT_DATA typedef IntData DataType; #endif // 链表节点 typedef struct Node { DataType data; // 数据域 struct Node* next; // 指针域(指向后一个节点) } Node, *NodePtr; // 链式栈管理结构体 typedef struct LinkedStack { NodePtr top; // 指向头节点(栈顶基准) int count; // 数据节点数量 } LinkStack, *LinkStackPtr; // 函数声明 LinkStackPtr LinkStack_Init(); // 初始化栈 void LinkStack_Destroy(LinkStackPtr stack); // 销毁栈 NodePtr LinkStack_CreateNode(DataType data); // 创建数据节点 bool LinkStack_IsEmpty(LinkStackPtr stack); // 判断栈空 void LinkStack_Push(LinkStackPtr stack, NodePtr newNode); // 入栈 int LinkStack_Pop(LinkStackPtr stack); // 出栈 DataType LinkStack_GetTop(LinkStackPtr stack); // 取栈顶 void LinkStack_Print(LinkStackPtr stack); // 打印栈 #endif // 2. 实现文件(linked_stack.c) #include "linked_stack.h" // 初始化栈(创建头节点) LinkStackPtr LinkStack_Init() { LinkStackPtr stack = (LinkStackPtr)malloc(sizeof(LinkStack)); if (!stack) return NULL; // 创建头节点 NodePtr head = (NodePtr)malloc(sizeof(Node)); if (!head) { free(stack); return NULL; } head->next = head; // 头节点自循环(初始无数据节点) stack->top = head; stack->count = 0; return stack; } // 销毁栈(释放所有节点和管理结构体) void LinkStack_Destroy(LinkStackPtr stack) { if (!stack) return; // 销毁所有数据节点 NodePtr curr = stack->top->next; // 从第一个数据节点开始 while (curr != stack->top) { // 循环到 head 结束 NodePtr temp = curr; curr = curr->next; free(temp); } free(stack->top); // 释放头节点 free(stack); // 释放管理结构体 } // 创建数据节点(成功返回节点指针,失败返回NULL) NodePtr LinkStack_CreateNode(DataType data) { NodePtr node = (NodePtr)malloc(sizeof(Node)); if (!node) return NULL; node->data = data; node->next = node; // 初始自循环(插入时再调整) return node; } // 判断栈空(头节点next指向自身即空) bool LinkStack_IsEmpty(LinkStackPtr stack) { return stack->top->next == stack->top; } // 入栈(头插法) void LinkStack_Push(LinkStackPtr stack, NodePtr newNode) { if (!stack || !newNode) return; newNode->next = stack->top->next; // 新节点指向原第一个数据节点 stack->top->next = newNode; // 头节点指向新节点(新节点成为栈顶) stack->count++; } // 出栈(成功返回0,失败返回-1) int LinkStack_Pop(LinkStackPtr stack) { if (LinkStack_IsEmpty(stack)) return -1; NodePtr delNode = stack->top->next; // 要删除的节点(栈顶) stack->top->next = delNode->next; // 头节点跳过删除节点 free(delNode); // 释放节点内存 stack->count--; return 0; } // 取栈顶(栈空返回默认值) DataType LinkStack_GetTop(LinkStackPtr stack) { DataType defaultData = {0}; return LinkStack_IsEmpty(stack) ? defaultData : stack->top->next->data; } // 打印栈(从栈顶到栈底) void LinkStack_Print(LinkStackPtr stack) { if (LinkStack_IsEmpty(stack)) { printf("栈为空!\n"); return; } printf("链式栈(栈顶→栈底):\n"); NodePtr curr = stack->top->next; // 从第一个数据节点开始 int index = 1; while (curr != stack->top) { #if USE_DATA == INT_DATA printf("第%d个元素:%d\n", index, curr->data.value); #elif USE_DATA == STU_DATA printf("第%d个元素:学号=%d,姓名=%s,年龄=%d\n", index, curr->data.id, curr->data.name, curr->data.age); #endif curr = curr->next; index++; } } // 3. 测试文件(main.c) #include "linked_stack.h" int main() { // 初始化链式栈 LinkStackPtr stack = LinkStack_Init(); if (!stack) { printf("栈初始化失败!\n"); return -1; } // 入栈3个整数 IntData d1={10}, d2={20}, d3={30}; NodePtr n1=LinkStack_CreateNode(d1), n2=LinkStack_CreateNode(d2), n3=LinkStack_CreateNode(d3); LinkStack_Push(stack, n1); LinkStack_Push(stack, n2); LinkStack_Push(stack, n3); LinkStack_Print(stack); // 输出:30 → 20 → 10 // 取栈顶 IntData top = LinkStack_GetTop(stack); printf("栈顶元素:%d\n", top.value); // 输出:30 // 出栈 LinkStack_Pop(stack); LinkStack_Print(stack); // 输出:20 → 10 // 销毁栈 LinkStack_Destroy(stack); return 0; } |
(三)顺序栈与链式栈对比
对比维度 | 顺序栈 | 链式栈 |
存储方式 | 连续数组 | 不连续链表节点 |
容量限制 | 固定(需提前确定),满栈后无法入栈 | 动态(无固定容量,仅受内存限制) |
操作效率 | 入栈、出栈均为 O (1)(数组下标直接访问) | 入栈、出栈均为 O (1)(指针操作) |
内存利用率 | 可能浪费(若容量未用完),无碎片 | 无浪费(按需分配节点),有少量指针开销 |
适用场景 | 数据量固定、操作频繁的场景(如函数调用栈) | 数据量不确定、需动态扩容的场景(如动态任务队列) |
二、队列
(一)队列的基本概念
队列是另一种特殊的线性表,允许在一端(队尾)插入数据、在另一端(队头)删除数据,遵循 **“先进先出”(FIFO,First In First Out)** 的逻辑规则,就像排队买票 —— 先排队的人先买票,后排队的人后买票。
1. 核心术语
术语 | 定义 | 对应操作 |
队头(Front) | 允许删除数据的一端 | 出队操作的端点 |
队尾(Rear) | 允许插入数据的一端 | 入队操作的端点 |
入队(EnQueue) | 将数据插入队尾 | 增加数据,队尾位置后移 |
出队(DeQueue) | 将队头数据删除 | 减少数据,队头位置后移 |
取队头(Front) | 读取队头数据但不删除 | 仅查询,不改变队列结构 |
队空 | 队列中无任何数据(队头 = 队尾) | 无法出队或取队头 |
队满 | 队列中数据达到最大容量 | 无法入队 |
2. 生活中的队列示例
- 排队买票:新加入的人站队尾(入队),最前面的人买完票离开(出队)。
- 打印机任务:先提交的打印任务先执行(出队),后提交的任务排队(入队)。
- 消息队列:服务器先处理早收到的消息(队头),后收到的消息存在队尾。
(二)队列的存储形式
队列的物理存储同样分为顺序队列(数组实现,通常为循环队列)和链式队列(链表实现),其中顺序队列采用 “循环” 结构是为了避免数组空间浪费。
1. 顺序队列(循环队列,数组实现)
用连续数组存储队列数据,通过 “队头指针”(front)和 “队尾指针”(rear)标记队列范围,采用 “循环” 逻辑(指针超过数组长度后回到起点),解决普通顺序队列 “假满” 问题(队尾到数组末尾但队头有空闲空间)。
(1)关键设计:如何判断空 / 满
循环队列需牺牲一个数组空间(不存储数据),通过以下规则判断空 / 满:
- 队空:front == rear(队头和队尾指针指向同一位置)。
- 队满:(rear + 1) % capacity == front(队尾指针后移一位后与队头指针重合)。
(2)结构设计
循环队列核心要素:
- 数据数组(data):存储队列数据。
- 队头指针(front):标记队头元素的下标(初始为 0)。
- 队尾指针(rear):标记队尾元素的下一个空闲位置(初始为 0)。
- 容量(capacity):数组长度(实际最大存储数据量 = 容量 - 1,因牺牲一个空间)。
(3)核心操作逻辑
操作 | 步骤 | 注意事项 |
初始化 | 1. 申请数组空间(大小 = 容量);2. 设front=0、rear=0 | 容量需大于 1(否则无法区分空 / 满) |
入队 | 1. 判断队列是否满;2. 数据存入data[rear];3. rear=(rear+1)%capacity(循环后移) | 利用取余实现 “循环”,避免指针溢出 |
出队 | 1. 判断队列是否空;2. 清空data[front](可选);3. front=(front+1)%capacity(循环后移) | 队头指针后移后,原队头数据视为无效 |
取队头 | 1. 判断队列是否空;2. 返回data[front] | 仅读取,不改变front值 |
遍历 | 从front开始,循环(rear - front + capacity)%capacity次,依次打印data[i] | 遍历次数 = 队列中数据个数 |
(4)示例代码(C 语言)
// 1. 头文件(circular_queue.h) #ifndef __CIRCULAR_QUEUE_H #define __CIRCULAR_QUEUE_H #include <stdio.h> #include <stdbool.h> #include <stdlib.h> #include <string.h> // 支持两种数据类型 #define STU_DATA 0 #define INT_DATA 1 #define USE_DATA INT_DATA // 学生/整数结构体(同栈,省略重复代码) typedef struct Student { int id; char name[20]; int age; } Student; typedef struct IntData { int value; } IntData; // 选择数据类型 #if USE_DATA == STU_DATA typedef Student DataType; #elif USE_DATA == INT_DATA typedef IntData DataType; #endif // 循环队列管理结构体 typedef struct CircularQueue { DataType* data; // 数据数组 int front; // 队头指针 int rear; // 队尾指针 int capacity; // 数组容量(实际存储量=capacity-1) } CircQueue, *CircQueuePtr; // 函数声明 CircQueuePtr CircQueue_Init(int capacity); // 初始化队列 void CircQueue_Destroy(CircQueuePtr queue); // 销毁队列 bool CircQueue_IsEmpty(CircQueuePtr queue); // 判断队空 bool CircQueue_IsFull(CircQueuePtr queue); // 判断队满 int CircQueue_EnQueue(CircQueuePtr queue, DataType data); // 入队 int CircQueue_DeQueue(CircQueuePtr queue); // 出队 DataType CircQueue_GetFront(CircQueuePtr queue); // 取队头 int CircQueue_GetSize(CircQueuePtr queue); // 获取队列大小 void CircQueue_Print(CircQueuePtr queue); // 打印队列 #endif // 2. 实现文件(circular_queue.c) #include "circular_queue.h" // 初始化队列(容量需≥2,否则无法区分空/满) CircQueuePtr CircQueue_Init(int capacity) { if (capacity < 2) return NULL; CircQueuePtr queue = (CircQueuePtr)malloc(sizeof(CircQueue)); if (!queue) return NULL; queue->data = (DataType*)malloc(sizeof(DataType) * capacity); if (!queue->data) { free(queue); return NULL; } queue->front = 0; queue->rear = 0; queue->capacity = capacity; return queue; } // 销毁队列 void CircQueue_Destroy(CircQueuePtr queue) { if (queue) { if (queue->data) free(queue->data); free(queue); } } // 判断队空 bool CircQueue_IsEmpty(CircQueuePtr queue) { return queue->front == queue->rear; } // 判断队满 bool CircQueue_IsFull(CircQueuePtr queue) { return (queue->rear + 1) % queue->capacity == queue->front; } // 获取队列大小(数据个数) int CircQueue_GetSize(CircQueuePtr queue) { return (queue->rear - queue->front + queue->capacity) % queue->capacity; } // 入队(成功返回0,失败返回-1) int CircQueue_EnQueue(CircQueuePtr queue, DataType data) { if (CircQueue_IsFull(queue)) return -1; queue->data[queue->rear] = data; queue->rear = (queue->rear + 1) % queue->capacity; // 循环后移 return 0; } // 出队(成功返回0,失败返回-1) int CircQueue_DeQueue(CircQueuePtr queue) { if (CircQueue_IsEmpty(queue)) return -1; memset(&queue->data[queue->front], 0, sizeof(DataType)); // 清空数据(可选) queue->front = (queue->front + 1) % queue->capacity; // 循环后移 return 0; } // 取队头(队空返回默认值) DataType CircQueue_GetFront(CircQueuePtr queue) { DataType defaultData = {0}; return CircQueue_IsEmpty(queue) ? defaultData : queue->data[queue->front]; } // 打印队列(从队头到队尾) void CircQueue_Print(CircQueuePtr queue) { if (CircQueue_IsEmpty(queue)) { printf("队列为空!\n"); return; } printf("循环队列(队头→队尾):\n"); int size = CircQueue_GetSize(queue); for (int i = 0; i < size; i++) { int index = (queue->front + i) % queue->capacity; // 循环计算下标 #if USE_DATA == INT_DATA printf("第%d个元素:%d\n", i + 1, queue->data[index].value); #elif USE_DATA == STU_DATA printf("第%d个元素:学号=%d,姓名=%s,年龄=%d\n", i + 1, queue->data[index].id, queue->data[index].name, queue->data[index].age); #endif } } // 3. 测试文件(main.c) #include "circular_queue.h" int main() { // 初始化容量为5的循环队列(实际存储4个数据) CircQueuePtr queue = CircQueue_Init(5); if (!queue) { printf("队列初始化失败!\n"); return -1; } // 入队3个整数 IntData d1={10}, d2={20}, d3={30}; CircQueue_EnQueue(queue, d1); CircQueue_EnQueue(queue, d2); CircQueue_EnQueue(queue, d3); CircQueue_Print(queue); // 输出:10 → 20 → 30 // 取队头 IntData front = CircQueue_GetFront(queue); printf("队头元素:%d\n", front.value); // 输出:10 // 出队 CircQueue_DeQueue(queue); CircQueue_Print(queue); // 输出:20 → 30 // 入队第4个数据(测试循环) IntData d4={40}; CircQueue_EnQueue(queue, d4); CircQueue_Print(queue); // 输出:20 → 30 → 40 // 销毁队列 CircQueue_Destroy(queue); return 0; } |
2. 链式队列(链表实现)
用单向循环链表存储队列数据,队头对应 “头节点后第一个节点”,队尾对应 “最后一个数据节点”,通过队头指针(frontPtr)和队尾指针(rearPtr)快速定位,无需牺牲空间,动态扩容。
(1)结构设计
链式队列核心要素:
- 节点(Node):包含 “数据域” 和 “指针域”(指向后一个节点)。
- 头节点(Head):不存数据,仅用于简化队头操作(队头指针指向头节点)。
- 队头指针(frontPtr):指向头节点,通过frontPtr->next访问队头数据节点。
- 队尾指针(rearPtr):直接指向最后一个数据节点,方便入队操作。
- 节点计数(count):记录数据节点数量(可选,用于快速获取队列大小)。
(2)核心操作逻辑
链式队列的操作基于 “尾插法”(入队)和 “头删法”(出队),确保入队和出队效率均为 O (1)。
操作 | 步骤 | 注意事项 |
初始化 | 1. 申请管理结构体;2. 申请头节点,头节点next指向自身;3. 设frontPtr=rearPtr=头节点、count=0 | 初始无数据节点,队头和队尾指针均指向头节点 |
入队(尾插法) | 1. 申请新数据节点,存入数据;2. 新节点next指向头节点;3. 原队尾节点next指向新节点;4. rearPtr指向新节点;5. count+1 | 队尾指针直接更新,无需遍历链表 |
出队(头删法) | 1. 判断队列是否空;2. 记录要删除的节点(frontPtr->next);3. 头节点next指向删除节点的next;4. 若删除节点是最后一个数据节点,需将rearPtr重置为头节点;5. 释放删除节点;6. count-1 | 需处理 “删除后队列空” 的情况,避免rearPtr指向无效节点 |
取队头 | 1. 判断队列是否空;2. 返回frontPtr->next的数据域 | 仅读取,不改变链表结构 |
遍历 | 从frontPtr->next开始,循环到rearPtr,依次打印节点数据 | 遍历顺序即 “队头→队尾” |
(3)示例代码(C 语言)
// 1. 头文件(linked_queue.h) #ifndef __LINKED_QUEUE_H #define __LINKED_QUEUE_H #include <stdio.h> #include <stdbool.h> #include <stdlib.h> #include <string.h> // 支持两种数据类型 #define STU_DATA 0 #define INT_DATA 1 #define USE_DATA INT_DATA // 学生/整数结构体(同栈,省略重复代码) typedef struct Student { int id; char name[20]; int age; } Student; typedef struct IntData { int value; } IntData; // 选择数据类型 #if USE_DATA == STU_DATA typedef Student DataType; #elif USE_DATA == INT_DATA typedef IntData DataType; #endif // 链表节点 typedef struct Node { DataType data; // 数据域 struct Node* next; // 指针域 } Node, *NodePtr; // 链式队列管理结构体 typedef struct LinkedQueue { NodePtr front; // 指向头节点(队头基准) NodePtr rear; // 指向队尾数据节点 int count; // 数据节点数量 } LinkQueue, *LinkQueuePtr; // 函数声明 LinkQueuePtr LinkQueue_Init(); // 初始化队列 void LinkQueue_Destroy(LinkQueuePtr queue); // 销毁队列 NodePtr LinkQueue_CreateNode(DataType data); // 创建数据节点 bool LinkQueue_IsEmpty(LinkQueuePtr queue); // 判断队空 void LinkQueue_EnQueue(LinkQueuePtr queue, NodePtr newNode); // 入队 int LinkQueue_DeQueue(LinkQueuePtr queue); // 出队 DataType LinkQueue_GetFront(LinkQueuePtr queue); // 取队头 int LinkQueue_GetSize(LinkQueuePtr queue); // 获取队列大小 void LinkQueue_Print(LinkQueuePtr queue); // 打印队列 #endif // 2. 实现文件(linked_queue.c) #include "linked_queue.h" // 初始化队列(创建头节点) LinkQueuePtr LinkQueue_Init() { LinkQueuePtr queue = (LinkQueuePtr)malloc(sizeof(LinkQueue)); if (!queue) return NULL; // 创建头节点 NodePtr head = (NodePtr)malloc(sizeof(Node)); if (!head) { free(queue); return NULL; } head->next = head; // 头节点自循环 queue->front = head; queue->rear = head; // 初始队尾=头节点(无数据) queue->count = 0; return queue; } // 销毁队列 void LinkQueue_Destroy(LinkQueuePtr queue) { if (!queue) return; // 销毁所有数据节点 NodePtr curr = queue->front->next; // 从第一个数据节点开始 while (curr != queue->front) { // 循环到 head 结束 NodePtr temp = curr; curr = curr->next; free(temp); } free(queue->front); // 释放头节点 free(queue); // 释放管理结构体 } // 创建数据节点 NodePtr LinkQueue_CreateNode(DataType data) { NodePtr node = (NodePtr)malloc(sizeof(Node)); if (!node) return NULL; node->data = data; node->next = node; // 初始自循环 return node; } // 判断队空 bool LinkQueue_IsEmpty(LinkQueuePtr queue) { return queue->front->next == queue->front; } // 获取队列大小 int LinkQueue_GetSize(LinkQueuePtr queue) { return queue->count; } // 入队(尾插法) void LinkQueue_EnQueue(LinkQueuePtr queue, NodePtr newNode) { if (!queue || !newNode) return; newNode->next = queue->front; // 新节点指向头节点(维持循环) queue->rear->next = newNode; // 原队尾节点指向新节点 queue->rear = newNode; // 队尾指针更新为新节点 queue->count++; } // 出队(成功返回0,失败返回-1) int LinkQueue_DeQueue(LinkQueuePtr queue) { if (LinkQueue_IsEmpty(queue)) return -1; NodePtr delNode = queue->front->next; // 要删除的队头节点 queue->front->next = delNode->next; // 头节点跳过删除节点 // 若删除的是最后一个数据节点,重置队尾指针 if (delNode == queue->rear) { queue->rear = queue->front; } free(delNode); queue->count--; return 0; } // 取队头 DataType LinkQueue_GetFront(LinkQueuePtr queue) { DataType defaultData = {0}; return LinkQueue_IsEmpty(queue) ? defaultData : queue->front->next->data; } // 打印队列(从队头到队尾) void LinkQueue_Print(LinkQueuePtr queue) { if (LinkQueue_IsEmpty(queue)) { printf("队列为空!\n"); return; } printf("链式队列(队头→队尾):\n"); NodePtr curr = queue->front->next; // 从第一个数据节点开始 int index = 1; while (curr != queue->front) { #if USE_DATA == INT_DATA printf("第%d个元素:%d\n", index, curr->data.value); #elif USE_DATA == STU_DATA printf("第%d个元素:学号=%d,姓名=%s,年龄=%d\n", index, curr->data.id, curr->data.name, curr->data.age); #endif curr = curr->next; index++; } } // 3. 测试文件(main.c) #include "main.c) #include "linked_queue.h" int main() { // 初始化链式队列 LinkQueuePtr queue = LinkQueue_Init(); if (!queue) { printf("队列初始化失败!\n"); return -1; } // 入队3个整数 IntData d1={10}, d2={20}, d3={30}; NodePtr n1=LinkQueue_CreateNode(d1), n2=LinkQueue_CreateNode(d2), n3=LinkQueue_CreateNode(d3); LinkQueue_EnQueue(queue, n1); LinkQueue_EnQueue(queue, n2); LinkQueue_EnQueue(queue, n3); LinkQueue_Print(queue); // 输出:10 → 20 → 30 // 取队头 IntData front = LinkQueue_GetFront(queue); printf("队头元素:%d\n", front.value); // 输出:10 // 出队 LinkQueue_DeQueue(queue); LinkQueue_Print(queue); // 输出:20 → 30 // 入队第4个数据 IntData d4={40}; NodePtr n4=LinkQueue_CreateNode(d4); LinkQueue_EnQueue(queue, n4); LinkQueue_Print(queue); // 输出:20 → 30 → 40 // 销毁队列 LinkQueue_Destroy(queue); return 0; } |
(三)顺序队列与链式队列对比
对比维度 | 顺序队列(循环) | 链式队列 |
存储方式 | 连续数组(牺牲 1 个空间) | 不连续链表节点 |
容量限制 | 固定(需提前确定) | 动态(无固定容量) |
操作效率 | 入队、出队均为 O (1) | 入队、出队均为 O (1) |
内存利用率 | 可能浪费 1 个空间,无碎片 | 无浪费(按需分配),有少量指针开销 |
适用场景 | 数据量固定、对内存连续性要求高的场景(如嵌入式设备) | 数据量不确定、需灵活扩容的场景(如服务器消息队列) |
三、栈与队列的核心区别
对比维度 | 栈 | 队列 |
操作端 | 仅栈顶一端 | 队头(删除)、队尾(插入)两端 |
逻辑规则 | 后进先出(LIFO) | 先进先出(FIFO) |
核心操作 | 入栈(Push)、出栈(Pop)、取栈顶 | 入队(EnQueue)、出队(DeQueue)、取队头 |
典型应用 | 函数调用栈、表达式求值、括号匹配 | 任务调度、消息队列、打印机队列 |