【数据结构与算法】第15篇:队列(二):链式队列的实现与应用

一、链式队列的结构

1.1 为什么需要链式队列

顺序队列(循环队列)有容量限制,如果不知道最大元素个数,可能会溢出。链式队列用链表存储,动态分配内存,没有容量限制。

1.2 链式队列的结构

链式队列需要两个指针:

  • front:指向队头节点(出队的一端)

  • rear:指向队尾节点(入队的一端)

text

复制代码
front → [A|next] → [B|next] → [C|next] → NULL
                                           ↑
                                         rear

为了操作方便,我们使用带头结点的方式,让空队列和非空队列的操作统一。

1.3 节点和队列定义

c

复制代码
// 队列节点
typedef struct QueueNode {
    int data;
    struct QueueNode *next;
} QueueNode, *PQueueNode;

// 链式队列
typedef struct {
    PQueueNode front;  // 队头指针(指向头结点)
    PQueueNode rear;   // 队尾指针
    int size;          // 元素个数
} LinkQueue;

二、链式队列的实现

2.1 初始化

创建头结点,front和rear都指向它。

c

复制代码
void initQueue(LinkQueue *queue) {
    queue->front = (PQueueNode)malloc(sizeof(QueueNode));
    if (queue->front == NULL) {
        printf("初始化失败\n");
        exit(1);
    }
    queue->front->next = NULL;
    queue->rear = queue->front;
    queue->size = 0;
}

2.2 判断空队列

c

复制代码
int isEmpty(LinkQueue *queue) {
    return queue->front == queue->rear;
    // 或者 return queue->size == 0;
}

2.3 入队(enqueue)

在队尾插入新节点。

c

复制代码
int enqueue(LinkQueue *queue, int value) {
    PQueueNode newNode = (PQueueNode)malloc(sizeof(QueueNode));
    if (newNode == NULL) {
        printf("内存分配失败\n");
        return -1;
    }
    newNode->data = value;
    newNode->next = NULL;
    
    queue->rear->next = newNode;
    queue->rear = newNode;
    queue->size++;
    return 0;
}

2.4 出队(dequeue)

删除队头节点(头结点后面的第一个节点)。

c

复制代码
int dequeue(LinkQueue *queue, int *value) {
    if (isEmpty(queue)) {
        printf("队列为空\n");
        return -1;
    }
    
    PQueueNode toDelete = queue->front->next;
    *value = toDelete->data;
    
    queue->front->next = toDelete->next;
    
    // 如果删除的是最后一个节点,rear需要指向头结点
    if (queue->rear == toDelete) {
        queue->rear = queue->front;
    }
    
    free(toDelete);
    queue->size--;
    return 0;
}

2.5 获取队头元素

c

复制代码
int getFront(LinkQueue *queue, int *value) {
    if (isEmpty(queue)) {
        printf("队列为空\n");
        return -1;
    }
    *value = queue->front->next->data;
    return 0;
}

2.6 获取队列大小

c

复制代码
int getSize(LinkQueue *queue) {
    return queue->size;
}

2.7 销毁队列

c

复制代码
void destroyQueue(LinkQueue *queue) {
    PQueueNode cur = queue->front;
    while (cur != NULL) {
        PQueueNode temp = cur;
        cur = cur->next;
        free(temp);
    }
    queue->front = NULL;
    queue->rear = NULL;
    queue->size = 0;
}

三、链式队列完整代码

c

复制代码
#include <stdio.h>
#include <stdlib.h>

typedef struct QueueNode {
    int data;
    struct QueueNode *next;
} QueueNode, *PQueueNode;

typedef struct {
    PQueueNode front;
    PQueueNode rear;
    int size;
} LinkQueue;

void initQueue(LinkQueue *queue) {
    queue->front = (PQueueNode)malloc(sizeof(QueueNode));
    if (queue->front == NULL) {
        printf("初始化失败\n");
        exit(1);
    }
    queue->front->next = NULL;
    queue->rear = queue->front;
    queue->size = 0;
}

int isEmpty(LinkQueue *queue) {
    return queue->front == queue->rear;
}

int enqueue(LinkQueue *queue, int value) {
    PQueueNode newNode = (PQueueNode)malloc(sizeof(QueueNode));
    if (newNode == NULL) return -1;
    newNode->data = value;
    newNode->next = NULL;
    
    queue->rear->next = newNode;
    queue->rear = newNode;
    queue->size++;
    return 0;
}

int dequeue(LinkQueue *queue, int *value) {
    if (isEmpty(queue)) return -1;
    
    PQueueNode toDelete = queue->front->next;
    *value = toDelete->data;
    
    queue->front->next = toDelete->next;
    if (queue->rear == toDelete) {
        queue->rear = queue->front;
    }
    
    free(toDelete);
    queue->size--;
    return 0;
}
复制代码
int getFront(LinkQueue *queue, int *value) {
    if (isEmpty(queue)) return -1;
    *value = queue->front->next->data;
    return 0;
}

int getSize(LinkQueue *queue) {
    return queue->size;
}

void destroyQueue(LinkQueue *queue) {
    PQueueNode cur = queue->front;
    while (cur != NULL) {
        PQueueNode temp = cur;
        cur = cur->next;
        free(temp);
    }
    queue->front = NULL;
    queue->rear = NULL;
    queue->size = 0;
}

void printQueue(LinkQueue *queue) {
    printf("队头 -> ");
    PQueueNode cur = queue->front->next;
    while (cur != NULL) {
        printf("%d ", cur->data);
        cur = cur->next;
    }
    printf("<- 队尾 (size=%d)\n", queue->size);
}

int main() {
    LinkQueue queue;
    initQueue(&queue);
    
    enqueue(&queue, 10);
    enqueue(&queue, 20);
    enqueue(&queue, 30);
    printQueue(&queue);
    
    int val;
    dequeue(&queue, &val);
    printf("出队: %d\n", val);
    printQueue(&queue);
    
    enqueue(&queue, 40);
    enqueue(&queue, 50);
    printQueue(&queue);
    
    destroyQueue(&queue);
    return 0;
}

运行结果:

text

复制代码
队头 -> 10 20 30 <- 队尾 (size=3)
出队: 10
队头 -> 20 30 <- 队尾 (size=2)
队头 -> 20 30 40 50 <- 队尾 (size=4)

四、队列的应用:广度优先搜索(BFS)

4.1 BFS的核心思想

广度优先搜索(Breadth-First Search)是一种图的遍历算法。它的特点是一层一层地向外扩展,先访问离起点近的节点,再访问远的节点。

为什么用队列?

  • BFS需要按照"先进入的节点先扩展"的顺序

  • 队列的FIFO特性完美契合这个需求

4.2 迷宫寻路问题

问题描述:给定一个迷宫,0表示可走,1表示墙,求从起点到终点的最短步数。

算法思路

  1. 起点入队,标记已访问

  2. 当队列不为空时:

    • 取出队头节点

    • 如果这个节点是终点,返回步数

    • 否则,把它上下左右四个方向的邻居中可走且未访问的节点入队,步数+1

为什么BFS能找到最短路径?

因为BFS是一层一层扩展的,第一次到达终点时,走过的步数就是最短的。

4.3 代码实现

c

复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define MAX_SIZE 100

// 坐标结构体
typedef struct {
    int x;
    int y;
    int step;  // 步数
} Point;

// 队列节点(存储Point)
typedef struct QueueNode {
    Point data;
    struct QueueNode *next;
} QueueNode, *PQueueNode;

typedef struct {
    PQueueNode front;
    PQueueNode rear;
    int size;
} LinkQueue;

void initQueue(LinkQueue *q) {
    q->front = (PQueueNode)malloc(sizeof(QueueNode));
    q->front->next = NULL;
    q->rear = q->front;
    q->size = 0;
}

int isEmpty(LinkQueue *q) {
    return q->front == q->rear;
}

void enqueue(LinkQueue *q, Point val) {
    PQueueNode newNode = (PQueueNode)malloc(sizeof(QueueNode));
    newNode->data = val;
    newNode->next = NULL;
    q->rear->next = newNode;
    q->rear = newNode;
    q->size++;
}

int dequeue(LinkQueue *q, Point *val) {
    if (isEmpty(q)) return -1;
    PQueueNode toDelete = q->front->next;
    *val = toDelete->data;
    q->front->next = toDelete->next;
    if (q->rear == toDelete) {
        q->rear = q->front;
    }
    free(toDelete);
    q->size--;
    return 0;
}

void destroyQueue(LinkQueue *q) {
    PQueueNode cur = q->front;
    while (cur != NULL) {
        PQueueNode temp = cur;
        cur = cur->next;
        free(temp);
    }
}

// 四个方向:上、下、左、右
int dx[] = {-1, 1, 0, 0};
int dy[] = {0, 0, -1, 1};

// BFS求最短路径
int bfs(int maze[MAX_SIZE][MAX_SIZE], int n, int m, 
        Point start, Point end) {
    int visited[MAX_SIZE][MAX_SIZE] = {0};
    LinkQueue q;
    initQueue(&q);
    
    start.step = 0;
    enqueue(&q, start);
    visited[start.x][start.y] = 1;
    
    while (!isEmpty(&q)) {
        Point cur;
        dequeue(&q, &cur);
        
        // 到达终点
        if (cur.x == end.x && cur.y == end.y) {
            destroyQueue(&q);
            return cur.step;
        }
        
        // 遍历四个方向
        for (int i = 0; i < 4; i++) {
            int nx = cur.x + dx[i];
            int ny = cur.y + dy[i];
            
            // 检查边界和是否可走
            if (nx >= 0 && nx < n && ny >= 0 && ny < m &&
                maze[nx][ny] == 0 && !visited[nx][ny]) {
                visited[nx][ny] = 1;
                Point next = {nx, ny, cur.step + 1};
                enqueue(&q, next);
            }
        }
    }
    
    destroyQueue(&q);
    return -1;  // 不可达
}

int main() {
    // 5x5迷宫,0=路,1=墙
    int maze[MAX_SIZE][MAX_SIZE] = {
        {0, 0, 0, 0, 0},
        {0, 1, 1, 0, 0},
        {0, 0, 0, 1, 0},
        {0, 1, 0, 0, 0},
        {0, 0, 0, 0, 0}
    };
    
    Point start = {0, 0, 0};
    Point end = {4, 4, 0};
    
    int steps = bfs(maze, 5, 5, start, end);
    if (steps != -1) {
        printf("从(%d,%d)到(%d,%d)的最短步数: %d\n", 
               start.x, start.y, end.x, end.y, steps);
    } else {
        printf("无法到达终点\n");
    }
    
    return 0;
}

运行结果:

text

复制代码
从(0,0)到(4,4)的最短步数: 8

五、顺序队列 vs 链式队列

对比项 顺序队列(循环队列) 链式队列
存储方式 数组(连续内存) 链表(非连续)
容量 固定,可能溢出 动态,受内存限制
入队 O(1) O(1)
出队 O(1) O(1)
内存利用率 可能浪费一个单元 每节点多一个指针
适用场景 大小已知 大小未知

六、BFS的其他应用

应用 说明
迷宫最短路径 层序遍历,第一次到达就是最短
单词接龙 从一个单词变换到另一个单词的最少步数
社交网络 找两个人之间的最短关系链
图的连通分量 遍历所有可达节点
拓扑排序 有向无环图的层序遍历

七、小结

这一篇我们实现了链式队列,并用BFS解决了迷宫最短路径问题:

内容 要点
链式队列 用单链表实现,带头结点,front指向头,rear指向尾
入队 在rear后面插入新节点
出队 删除front后面的第一个节点
BFS 利用队列的FIFO特性,一层一层扩展
最短路径 BFS第一次到达终点时的步数就是最短

BFS的核心代码模板

text

复制代码
起点入队,标记已访问
while (队列不为空) {
    取出队头节点
    if (是终点) 返回步数
    遍历所有邻居:
        if (邻居可走且未访问) {
            标记已访问
            邻居入队,步数+1
        }
}

下一篇我们会讲树形结构,从二叉树开始。


八、思考题

  1. 链式队列为什么需要带头结点?不带头结点会有什么问题?

  2. BFS中,为什么第一次到达终点时就是最短路径?DFS能不能保证最短?

  3. 尝试修改BFS代码,让它输出具体的最短路径(不只是步数)。

  4. 如果用栈代替队列实现迷宫寻路,会是什么效果?

欢迎在评论区讨论你的答案。

相关推荐
MY_TEUCK2 小时前
Sealos 平台部署实战指南:结合 Cursor 与版本发布流程
java·人工智能·学习·aigc
三毛的二哥2 小时前
BEV:典型BEV算法总结
人工智能·算法·计算机视觉·3d
2401_873479403 小时前
如何利用IP查询定位识别电商刷单?4个关键指标+工具配置方案
开发语言·tcp/ip·php
我爱cope3 小时前
【从0开始学设计模式-10| 装饰模式】
java·开发语言·设计模式
菜鸟学Python3 小时前
Python生态在悄悄改变:FastAPI全面反超,Django和Flask还行吗?
开发语言·python·django·flask·fastapi
南宫萧幕3 小时前
自控PID+MATLAB仿真+混动P0/P1/P2/P3/P4构型
算法·机器学习·matlab·simulink·控制·pid
浪浪小洋4 小时前
c++ qt课设定制
开发语言·c++
charlie1145141914 小时前
嵌入式C++工程实践第16篇:第四次重构 —— LED模板,从通用GPIO到专用抽象
c语言·开发语言·c++·驱动开发·嵌入式硬件·重构
handler014 小时前
Linux: 基本指令知识点(2)
linux·服务器·c语言·c++·笔记·学习
故事和你914 小时前
洛谷-数据结构1-4-图的基本应用1
开发语言·数据结构·算法·深度优先·动态规划·图论