当前位置: 首页 > news >正文

3、栈和队列

数据结构笔记:栈与队列

一、栈

(一)栈的基本概念

栈是一种特殊的线性表,仅允许在固定一端(栈顶)进行数据操作,遵循 **“后进先出”(LIFO,Last In First Out)** 的逻辑规则,就像堆叠的盘子 —— 最后放的盘子要先拿,最底下的盘子最后拿。

1. 核心术语

术语

定义

对应操作

栈顶(Top)

允许插入、删除数据的一端

所有操作的核心端点

栈底(Bottom)

与栈顶相对、固定不动的一端

仅作为栈的起始基准

入栈(Push)

将数据插入栈顶

增加数据,栈顶位置上移

出栈(Pop)

将栈顶数据删除

减少数据,栈顶位置下移

取栈顶(Top)

读取栈顶数据但不删除

仅查询,不改变栈结构

栈空

栈中无任何数据(栈顶指向栈底)

无法出栈或取栈顶

栈满

栈中数据达到最大容量

无法入栈

2. 生活中的栈示例
  • 堆叠的盘子:新盘子放最上面(入栈),拿盘子从最上面拿(出栈)。
  • 弹匣:子弹压入时从顶部放入(入栈),射击时从顶部弹出(出栈)。
  • 函数调用:主函数调用子函数时,主函数上下文压栈;子函数执行完,上下文出栈恢复主函数。

(二)栈的存储形式

栈的逻辑结构需通过物理存储实现,主要分为顺序栈(数组存储)和链式栈(链表存储),两种形式各有优劣,需根据场景选择。

1. 顺序栈(数组实现)

连续的数组空间存储栈数据,通过一个 “栈顶指针”(通常是数组下标)标记栈顶位置,结构简单、访问效率高,但需提前确定容量。

(1)结构设计

顺序栈需包含 3 个核心要素:

  • 数据数组(data):存储栈的实际数据。
  • 栈顶指针(top):标记当前栈顶的数组下标(初始值设为-1,表示栈空)。
  • 容量(capacity):数组的最大存储长度(栈的最大容量)。
2)核心操作逻辑

操作

步骤

注意事项

初始化

1. 申请数组空间(大小 = 容量);2. 设top=-1capacity=指定值

若数组申请失败,需释放已分配内存

入栈

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=0rear=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)、取队头

典型应用

函数调用栈、表达式求值、括号匹配

任务调度、消息队列、打印机队列

http://www.dtcms.com/a/338774.html

相关文章:

  • Ansible 配置并行 - 项目管理笔记
  • 零知开源——基于STM32F407VET6与GY-271三轴地磁传感器的高精度电子罗盘设计与实现
  • TensorFlow 面试题及详细答案 120道(11-20)-- 操作与数据处理
  • Auto-CoT:大型语言模型的自动化思维链提示技术
  • OpenCV快速入门(C++版)
  • 常见的 Bash 命令及简单脚本
  • 从 0 到 1 开发校园二手交易系统:飞算 JavaAI 全流程实战
  • 无畏契约手游上线!手机远控模拟器畅玩、抢先注册稀有ID!
  • Windows/Centos 7下搭建Apache服务器
  • MySQL-分库分表(Mycat)
  • 第一章 认识单片机
  • 出现了常规系统错误: Unable to push signed certificate to host 192.168.1.2
  • 从数据表到退磁:Ansys Maxwell中N48磁体磁化指南
  • LINUX 软件编程 -- 线程
  • 决策树的学习(二)
  • MCP(模型上下文协议):是否是 AI 基础设施中缺失的标准?
  • jsPDF 不同屏幕尺寸 生成的pdf不一致,怎么解决
  • Ansible 中的文件包含与导入机制
  • java17学习笔记-Deprecate the Applet API for Removal
  • C语言基础:(十八)C语言内存函数
  • 连接远程服务器上的 jupyter notebook,解放本地电脑
  • 计算机毕设推荐:痴呆症预测可视化系统Hadoop+Spark+Vue技术栈详解
  • 生成式AI的能力边界与职业重构:从“百科实习生“到人机协作增强器
  • 人工智能学派简介
  • 当宠物机器人装上「第六感」:Deepoc 具身智能如何重构宠物机器人照看逻辑
  • Python字符串变量插值深度解析:从基础到高级工程实践
  • 安装DDNS-go
  • 【部署相关】DockerKuberbetes常用命令大全(速查+解释)
  • 便携式科研土壤监测仪:让土壤检测走进 “轻时代”
  • 大数据MapReduce架构:分布式计算的经典范式