深入理解栈与链式队列:从动态数组到链表实现详解

一、队列的基本概念

在计算机科学里,队列是一种极为基础且重要的数据结构,它遵循先进先出(First-In-First-Out,FIFO)的原则。这一原则就如同日常生活中排队等待服务的场景,先到的人会先接受服务,而后到的人则需在队列尾部等待。在计算机系统中,队列被广泛应用于各类场景,比如任务调度、消息传递、缓冲区管理等。

1.1 队列的操作

队列主要有两种基本操作:

  • 入队(Enqueue):也被称作插入操作,它的作用是将一个新元素添加到队列的尾部。
  • 出队(Dequeue):也叫做删除操作,它会移除队列头部的元素。

1.2 队列的特点

  • 顺序性:元素进入队列和离开队列的顺序是固定的,先进入队列的元素会先离开。
  • 受限访问:只能在队列的头部进行出队操作,在队列的尾部进行入队操作,不允许在队列中间进行插入或删除操作。

二、链式队列的实现思路

队列可以通过多种方式来实现,其中链式队列是一种常见的实现方式。链式队列借助链表来存储元素,每个节点包含数据和指向下一个节点的指针。这种实现方式的优点在于可以动态地分配内存,不会受到固定大小的限制。

2.1 链式队列的结构

链式队列主要由两部分构成:

  • 节点(Node):每个节点包含一个数据元素和一个指向下一个节点的指针。
  • 队列(Queue):队列包含两个指针,分别指向队列的头部和尾部。

2.2 链式队列的操作

链式队列的操作和普通队列的操作基本一致,主要包括初始化、入队、出队、获取队列头部和尾部元素、检查队列是否为空、销毁队列以及打印队列等操作。

三、代码实现

3.1 Queue.h 文件

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>

// 链式结构:表示队列 
typedef int QDataType;
typedef struct QListNode
{
    struct QListNode* _next;
    QDataType _data;
}QListNode;

// 队列的结构 
typedef struct Queue
{
    QListNode* _front;
    QListNode* _rear;
    //int size; //队列中有效数据个数
}Queue;

// 初始化队列 
void QueueInit(Queue* q);
// 队尾入队列 
void QueuePush(Queue* q, QDataType data);
// 队头出队列 
void QueuePop(Queue* q);
// 获取队列头部元素 
QDataType QueueFront(Queue* q);
// 获取队列队尾元素 
QDataType QueueBack(Queue* q);
// 获取队列中有效元素个数 
int QueueSize(Queue* q);
// 检测队列是否为空,如果为空返回非零结果,如果非空返回0 
bool QueueEmpty(Queue* q);
// 销毁队列 
void QueueDestroy(Queue* q);
// 打印队列
void QueuePrint(Queue* q);
详细解释
  • QDataType:把 int 重命名为 QDataType,这样做的好处是在后续修改队列存储的数据类型时会更加方便。例如,如果需要存储 char 类型的数据,只需要将 typedef int QDataType; 改为 typedef char QDataType; 即可。
  • QListNode:代表队列中的一个节点,它包含一个指向下一个节点的指针 _next 以及存储的数据 _data。通过这种方式,节点之间可以形成一个链表。
  • Queue:表示队列,包含指向队列头部的指针 _front 和指向队列尾部的指针 _rear。这两个指针可以方便地进行入队和出队操作。
  • 函数声明:对队列的各项操作函数进行了声明,这些函数涵盖了队列的基本操作,为后续的实现提供了接口。

3.2 Queue.c 文件

QueueInit 函数(初始化队列)
// 初始化队列 
void QueueInit(Queue* q) {
    assert(q);
    q->_front = q->_rear = NULL;
    //q->size = 0;
}
  • 功能:对队列进行初始化。
  • 参数q 是指向队列的指针。
  • 详细解释:使用 assert 确保 q 不为空指针,这是为了避免对空指针进行操作而导致程序崩溃。然后将队列的头部和尾部指针都设为 NULL,这表明队列为空。如果队列中有 size 字段,还可以将其初始化为 0。
QueuePush 函数(队尾入队列)
// 队尾入队列
void QueuePush(Queue* q, QDataType data) {
    assert(q);
    QListNode* newnode = (QListNode*)malloc(sizeof(QListNode));
    if (newnode == NULL) {
        perror("malloc fail");
        exit(1);
    }
    newnode->_data = data;
    newnode->_next = NULL;
    if (q->_front == NULL) {
        q->_front = q->_rear = newnode;
    }
    else {
        q->_rear->_next = newnode;
        q->_rear = newnode;
    }
    //q->size++;
}

以下是if (q->_front == NULL) 和else执行的二个方法,也就是为空和不为空的插入(主要判断队列中也没有结点)

  • 功能:在队列尾部添加一个新元素。
  • 参数q 是指向队列的指针,data 是要添加的元素。
  • 详细解释
    • 利用 assert 确保 q 不为空指针。
    • 运用 malloc 为新节点分配内存,若分配失败则输出错误信息并终止程序。这是因为内存分配失败可能会导致后续操作无法正常进行。
    • 对新节点的数据和 _next 指针进行初始化。
    • 若队列为空,就把队列的头部和尾部指针都指向新节点;反之,将新节点添加到队列尾部并更新尾部指针。如果队列中有 size 字段,还需要将其加 1。
QueueEmpty 函数(检测队列是否为空)
// 检测队列是否为空,如果为空返回非零结果,如果非空返回0 
bool QueueEmpty(Queue* q) {
    assert(q);
    return q->_front == NULL;
}
  • 功能:检查队列是否为空。
  • 参数q 是指向队列的指针。
  • 详细解释:使用 assert 确保 q 不为空指针,若队列头部指针为 NULL 则表明队列为空,返回 true;反之返回 false
QueuePop 函数(队头出队列)
// 队头出队列 
void QueuePop(Queue* q) {
    assert(!QueueEmpty(q));
    if (q->_front == q->_rear)
    {
        free(q->_front);
        q->_front = q->_rear = NULL;
    }
    else {
        QListNode* next = q->_front->_next;
        free(q->_front);
        q->_front = next;
    }
    //q->size--;
}

以下是if (q->_front == q->_rear) 和else执行的二个方法,也就是检查是否为一个结点,如果是就把队头置为空最后队头和队尾置为NULL,如果不是就把队头的下一个指的指针保存到新的临时结点中,最后重新赋给队头。

 

  • 功能:移除队列头部的元素。
  • 参数q 是指向队列的指针。
  • 详细解释
    • 利用 assert 确保队列不为空。
    • 若队列中只有一个元素,释放该元素并将队列的头部和尾部指针都设为 NULL
    • 若队列中有多个元素,释放头部元素并更新头部指针。如果队列中有 size 字段,还需要将其减 1。
QueueFront 函数(获取队列头部元素)
// 获取队列头部元素 
QDataType QueueFront(Queue* q) {
    assert(!QueueEmpty(q));
    return q->_front->_data;
}
  • 功能:获取队列头部的元素。
  • 参数q 是指向队列的指针。
  • 详细解释:使用 assert 确保队列不为空,然后返回队列头部元素的数据。
QueueBack 函数(获取队列队尾元素)
// 获取队列队尾元素 
QDataType QueueBack(Queue* q) {
    assert(!QueueEmpty(q));
    return q->_rear->_data;
}
  • 功能:获取队列尾部的元素。
  • 参数q 是指向队列的指针。
  • 详细解释:使用 assert 确保队列不为空,然后返回队列尾部元素的数据。
QueueSize 函数(获取队列中有效元素个数)
// 获取队列中有效元素个数 
int QueueSize(Queue* q) {
    assert(q);
    QListNode* pcur = q->_front;
    int size = 0;
    while (pcur) {
        size++;
        pcur = pcur->_next;
    }
    return size;
    //return q->size;
}
  • 功能:获取队列中有效元素的个数。
  • 参数q 是指向队列的指针。
  • 详细解释
    • 运用 assert 确保 q 不为空指针。
    • 通过遍历队列的方式统计元素个数。如果队列中有 size 字段,也可以直接返回该字段的值。
QueueDestroy 函数(销毁队列)
// 销毁队列 
void QueueDestroy(Queue* q) {
    assert(q);
    QListNode* pcur = q->_front;
    while (pcur)
    {
        QListNode* next = pcur->_next;
        free(pcur);
        pcur = next;
    }
    q->_front = q->_rear = NULL;
    //pq->size = 0;
}
  • 功能:销毁队列,释放队列中所有节点的内存。
  • 参数q 是指向队列的指针。
  • 详细解释
    • 利用 assert 确保 q 不为空指针。
    • 遍历队列,释放每个节点的内存。
    • 最后把队列的头部和尾部指针都设为 NULL。如果队列中有 size 字段,还需要将其设为 0。
QueuePrint 函数(打印队列)
//打印队列
void QueuePrint(Queue * q) {
    assert(q);
    QListNode* pcur = q->_front;
    while (pcur) {
        printf("%d ", pcur->_data);
        pcur = pcur->_next;
    }
    printf("\n");
}
  • 功能:打印队列中的所有元素。
  • 参数q 是指向队列的指针。
  • 详细解释
    • 运用 assert 确保 q 不为空指针。
    • 遍历队列,打印每个节点的数据。

3.3 test.c 文件

#include"Queue.h"
void test01() {
    Queue plist;
    QueueInit(&plist);
    QueuePush(&plist, 1);
    QueuePush(&plist, 2);
    QueuePush(&plist, 3);
    // 调用打印函数
    QueuePrint(&plist);
    //QueuePop(&q);
    int front = QueueFront(&plist);
    int rear = QueueBack(&plist);
    printf("front:%d\n", front);
    printf("rear:%d\n", rear);
    printf("size:%d\n", QueueSize(&plist));
    QueueDestroy(&plist);
}
int main() {
    test01();
    return 0;
}
详细解释
  • test01 函数
    • 对队列进行初始化。
    • 向队列中添加三个元素。
    • 打印队列中的元素。
    • 获取队列的头部和尾部元素并打印。
    • 获取队列的元素个数并打印。
    • 销毁队列。
  • main 函数:调用 test01 函数进行测试。

四、应用场景

  1. 括号匹配:遍历字符串,左括号入栈,右括号与栈顶匹配。

  2. 函数调用栈:系统栈用于保存函数调用现场。

  3. 逆波兰表达式:用栈实现表达式求值。


五、总结

通过上述代码,我们实现了一个基于链式结构的队列,并且对队列的各项操作函数进行了详细的实现和测试。在实际应用中,队列可以用来解决很多问题,例如任务调度、消息处理等。掌握队列的基本操作和实现原理,对于学习数据结构和算法是非常有帮助的。

希望本文能帮助你更好地理解链式队列的实现和应用。如果你有任何疑问或者建议,欢迎在评论区留言。

附上代码Queue.h

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>

// 链式结构:表示队列 
typedef int QDataType;
typedef struct QListNode
{
	struct QListNode* _next;
	QDataType _data;
}QListNode;

// 队列的结构 
typedef struct Queue
{
	QListNode* _front;
	QListNode* _rear;
	//int size; //队列中有效数据个数
}Queue;

// 初始化队列 
void QueueInit(Queue* q);
// 队尾入队列 
void QueuePush(Queue* q, QDataType data);
// 队头出队列 
void QueuePop(Queue* q);
// 获取队列头部元素 
QDataType QueueFront(Queue* q);
// 获取队列队尾元素 
QDataType QueueBack(Queue* q);
// 获取队列中有效元素个数 
int QueueSize(Queue* q);
// 检测队列是否为空,如果为空返回非零结果,如果非空返回0 
bool QueueEmpty(Queue* q);
// 销毁队列 
void QueueDestroy(Queue* q);
// 打印队列
void QueuePrint(Queue* q);

代码:Queue.c

#include"Queue.h"

// 初始化队列 
void QueueInit(Queue* q) {
	assert(q);
	q->_front = q->_rear = NULL;
	//q->size = 0;
}
// 队尾入队列
void QueuePush(Queue* q, QDataType data) {
	assert(q);
	QListNode* newnode = (QListNode*)malloc(sizeof(QListNode));
	if (newnode == NULL) {
		perror("malloc fail");
		exit(1);
	}
	newnode->_data = data;
	newnode->_next = NULL;
	if (q->_front == NULL) {
		q->_front = q->_rear = newnode;
	}
	else {
		q->_rear->_next = newnode;
		q->_rear = newnode;
	}
	//q->size++;
}
// 检测队列是否为空,如果为空返回非零结果,如果非空返回0 
bool QueueEmpty(Queue* q) {
	assert(q);
	return q->_front == NULL;
}

// 队头出队列 
void QueuePop(Queue* q) {
	assert(!QueueEmpty(q));
	if (q->_front == q->_rear)
	{
		free(q->_front);
		q->_front = q->_rear = NULL;
	}
	else {
		QListNode* next = q->_front->_next;
		free(q->_front);
		q->_front = next;
	}
	//q->size--;
}
// 获取队列头部元素 
QDataType QueueFront(Queue* q) {
	assert(!QueueEmpty(q));
	return q->_front->_data;
}
// 获取队列队尾元素 
QDataType QueueBack(Queue* q) {
	assert(!QueueEmpty(q));
	return q->_rear->_data;
}
// 获取队列中有效元素个数 
int QueueSize(Queue* q) {
	assert(q);
	QListNode* pcur = q->_front;
	int size = 0;
	while (pcur) {

		size++;
		pcur = pcur->_next;
	}
	return size;
	//return q->size;
}

// 销毁队列 
void QueueDestroy(Queue* q) {
	assert(q);
	QListNode* pcur = q->_front;
	while (pcur)
	{
		QListNode* next = pcur->_next;
		free(pcur);
		pcur = next;
	}
	q->_front = q->_rear = NULL;
	//pq->size = 0;
}
//打印队列
void QueuePrint(Queue * q) {
	assert(q);
	QListNode* pcur = q->_front;
	while (pcur) {
		printf("%d ", pcur->_data);
		pcur = pcur->_next;
	}
	printf("\n");
}

代码:test.c

#include"Queue.h"
void test01() {
    Queue plist;
    QueueInit(&plist);
    QueuePush(&plist, 1);
    QueuePush(&plist, 2);
    QueuePush(&plist, 3);
    // 调用打印函数
    QueuePrint(&plist);
    //QueuePop(&q);
    int front = QueueFront(&plist);
    int rear = QueueBack(&plist);
    printf("front:%d\n", front);
    printf("rear:%d\n", rear);
    printf("size:%d\n", QueueSize(&plist));
    QueueDestroy(&plist);

}
int main() {
    test01();
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

南玖·

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值