【数据结构与算法】第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. 如果用栈代替队列实现迷宫寻路,会是什么效果?

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

相关推荐
丝斯20112 小时前
AI学习笔记整理(78)——Python学习7
人工智能·笔记·学习
Leventure_轩先生2 小时前
[RL]强化学习指导搭建IC2E核反应堆
开发语言·php
唯_ww2 小时前
COMSOL学习笔记(一)曲线图数据导出及画图
笔记·学习
迷途之人不知返2 小时前
初次学习模板
c++
算法鑫探2 小时前
C语言密码验证:3次机会解锁
c语言·数据结构·算法·新人首发
我没想到原来他们都是一堆坏人2 小时前
SQL 表结构定义(索引与约束)学习笔记一——索引、外键与级联约束
笔记·sql·学习
程序猿追2 小时前
HarmonyOS 6.0 实战:用 Native C++ NDK 开发一款本地计步器应用
c++·华为·harmonyos
zzginfo2 小时前
var、let、const、无申明 四种变量在赋值前,使用的情况
开发语言·前端·javascript
穿条秋裤到处跑2 小时前
每日一道leetcode(2026.03.30):判断通过操作能否让字符串相等 II
算法·leetcode