【数据结构】队列

一、队列

1、队列的概念及结构

队列:是只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出(First In First Out)的特点。

入队列:进行插入操作的一端称为队尾。

出队列:进行删除操作的一端称为队头。

如图所示:

1.2队列的应用:

1.任务调度:多个任务按照顺序排队等待执行。

2.广度优先搜索(BFS):在图或树的遍历中,按层次遍历节点。

3.缓存管理:缓存中的数据按照访问顺序排列,最先进入的数据最先被替换。

4.算法设计:某些算法的设计与队列密切相关,如哈夫曼编码、循环队列等。

1.3队列存在的优缺点:

优点:

1、先进先出(FIFO):队列保持元素的插入顺序,对线插入的元素最先被处理。

2、简单高效:队列的基本操作包括入队和出队,它们的时间复杂度都是O(1),无论队列的大小如何,操作的时间复杂度都是固定的。

3、应用广泛:队列在很多算法和实际应用场景中都有着广泛的应用,如任务调度、缓存管理、广度优先搜索。

缺点:

1、随机访问困难:队列只允许在队头删除元素,在队尾插入元素,不支持随机访问。如果需要在队列的其他位置插入或删除数据,运行效率较低。

2、队列大小的限制:使用数组实现的队列在创建时需要指定最大容量,因此队列中的元素有限。如果队列已满,则无法插入新的数据。

3、存储空间浪费:如果队列中实际的元素个数远小于最大容量,那么可能造成存储空间的浪费,因为队列的空间是固定的。

1.4队列的基本结构

本文队列采用链式存储进行讲解,而非数组存储。

1.4.1链式存储的原因

基于队列先进先出的特点,队列先进的数据从队尾进来,接着从队头出去,链式存储利用指针将一个一个的元素连接起来。插入数据,则用指针在队尾链接数据;删除数据,则释放掉队头元素。

相较于链式存储,数组存储在队尾插入数据简单(时间复杂度为O(1)),但由于删除数据需要在队头进行,还需把后面的数据通通往前移一位,由此以来,时间复杂度就将变为O(N),效率变低,所以不推荐使用。

1.4.2存储结构
typedef int QDatatype;   //暂定为整数类型
typedef struct QueueNode
{
    QDatatype val;      //存放数据
    struct QueueNode* next; //存放链
}QNode;

但仅仅这些还不够,我们还要标记队列的队头和队尾,以方便插入和删除。这样一来,我们队列插入和删除操作的时间复杂度就变为了O(1)。

typedef struct Queue
{
    QNode* head; //标记队头指针,用于删除数据
    QNode* tail; //标记队尾指针,用于插入数据
    int size;    //标记队列中有效元素个数
}Queue; 

ps:这是一种重要的算法思维(以空间换时间),提高算法效率。

1.5队列的基本操作

  • QueInit(&pq);   初始化一个空队列
  • QuePush(&pq, x);   往队列新插入一个数据,并将其放在队尾
  • QuePop(&pq);   删除队列中队头的元素,并释放
  • QueFront(&pq);   获取处在队列队头的元素数据
  • QueBack(&pq);   获取处在队列队尾的元素数据
  • QueEmpty(&pq);   判断队列是否为空
  • QueSize(&pq);   返回获取队列中元素的个数
  • QueDestroy(&pq);   销毁队列,并释放pq所占用的存储空间

2、队列的实现算法

1.初始化

void QueInit(Queue* pq)
{
    pq->head = NULL; 
    pq->tail = NULL; //将队列的头尾指针置为空
    pq->size = 0;    //队列长度初始化为零
}

2.入队列

void QuePush(Queue* pq, QDatatype x)
{
    //判空
    assert(pq);

    QNode* newnode = (QNode*)malloc(sizeof(QNode));
    //检验是否创建成功
    if(newnode == NULL)
    {
        perror("malloc fail");
        return;
    }
    newnode->val = x;  //将x值赋给newnode
    newnode->next = NULL;
    //无哨兵位,则需要检验队列中是否为空
    //若不为空
    if(pq->tail == NULL)
    {
        pq->tail->next = newnode;//将元素放在队尾
        pq->tail = newnode;      //将队尾变作新插入的元素
    }
    //若为空
    else
    {
        pq->head = pq->tail = newnode;
    }
    pq->size++;
}

3.出队列

void QuePop(Queue* pq)
{
    assert(pq);
    //当队列中没有节点时,就会强制返回
    assert(pq->head != NULL);
    
    QNode* cur = pq->head;
    
    //这里需要分情况讨论,若只有一个节点,tail和head会指向同一个节点
    //若有多个节点,tail和head则会分别只想不同的节点,所以需要分开讨论

    //若只有一个节点
    if(pq->tail->next == NULL)
    {
        free(pq->tail);
        pq->tail = pq->head = NULL;
    }
    //若有多个节点
    else
    {
        QNode* next = cur->next;//next指向cur的下一个节点,以方便下次删除找到队头
        free(pq->head);
        cur = next;
    }
    pq->size--;
}

出队列需要注意的几点

(1)当队列中有元素时才可以进行删除操作,所以要进行判空(assert(pq->head != NULL))

(2)当队列中只有一个节点时,如果只把tail释放掉是不对的,因为tail和head指向同一个节点,释放掉tail,head就变成了野指针,导致严重错误

(3)当队列中有多个节点,head和tail指向不同的节点,才可以正常删除数据

只有一个节点的情况:

有多个节点的情况:

4.获取队头数据

QDatatype QueFront(Queue* pq)
{
	assert(pq);
	assert(pq->head != NULL);

	return pq->head->val;//直接返回队头节点的数据值
}

5.获取队尾数据

QDatatype QueBack(Queue* pq)
{
	assert(pq);
	assert(pq->head != NULL);

	return pq->tail->val; //获取队尾数据值
}

6.判空操作

bool QueEmpty(Queue* pq)
{
	assert(pq);
    
    //判断size是否为空来返回,若为空,则返回true;若非空,则返回false
	return pq->size == 0;
}

7.获取队列节点个数

int QueSize(Queue* pq)
{
	assert(pq);
    //在此不需要循环队列,直接返回,因为size标记了队列中的节点个数
	return pq->size;
}

不需要循环队列,因此时间复杂度为O(1)如果遍历队列链表,时间复杂度为O(N),相比之下,相当于优化了结构的性能

8.销毁队列

void QueDestroy(Queue* pq)
{
	assert(pq);

	QNode* cur = pq->head;
	while (cur)
	{
		QNode* Next = cur->next; //Next用于迭代队头
		free(cur);
		cur = Next;
	}

	pq->head = pq->tail = NULL;//指针都置为空
	pq->size = 0;
}

3、源码呈现

3.1Queue.h

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

typedef int QDatatype;
typedef struct QueueNode
{
	QDatatype val;
	struct QueueNode* next;

}QNode;

typedef struct Queue
{
	QNode* head;
	QNode* tail;
	int size;
}Queue;

//初始化
void QueInit(Queue* pq);
//销毁
void QueDestroy(Queue* pq);

void QuePush(Queue* pq, QDatatype x);
void QuePop(Queue* pq);
bool QueEmpty(Queue* pq);
int QueSize(Queue* pq);

QDatatype QueFront(Queue* pq);
QDatatype QueBack(Queue* pq);

3.2Queue.c

#include"Queue.h"

void QueInit(Queue* pq)
{
	assert(pq);

	pq->head = NULL;
	pq->tail = NULL;
	pq->size = 0;
}

void QueDestroy(Queue* pq)
{
	assert(pq);

	QNode* cur = pq->head;
	while (cur)
	{
		QNode* Next = cur->next;
		free(cur);
		cur = Next;
	}

	pq->head = pq->tail = NULL;
	pq->size = 0;
}

//入队列
void QuePush(Queue* pq, QDatatype x)
{
	assert(pq);

	QNode* newnode = (QNode*)malloc(sizeof(QNode));

	if (newnode == NULL)
	{
		perror("malloc fail");
		return;
	}
	newnode->val = x;
	newnode->next = NULL;

	//不带哨兵位,需要进行检验
	//如果pq->tail不为空
	if (pq->tail)
	{
		pq->tail->next = newnode;
		pq->tail = newnode;
	}
	//如果为空
	else
	{
		pq->head = pq->tail = newnode;
	}
	pq->size++;
}


//出队列

void QuePop(Queue* pq)
{
	assert(pq);
	//零个节点
	assert(pq->head != NULL);

	//一个节点
	if (pq->head->next == NULL)
	{
		free(pq->head);
		pq->head = pq->tail = NULL;
	}
	//多个节点
	else
	{
		QNode* next = pq->head->next;
		free(pq->head);
		pq->head = next;

	}
	pq->size--;
}

QDatatype QueFront(Queue* pq)
{
	assert(pq);
	assert(pq->head != NULL);

	return pq->head->val;
}

QDatatype QueBack(Queue* pq)
{
	assert(pq);
	assert(pq->head != NULL);
	return pq->tail->val;
}

int QueSize(Queue* pq)
{
	assert(pq);
	return pq->size;
}

bool QueEmpty(Queue* pq)
{
	assert(pq);

	return pq->size == 0;
}

3.3Test.c

#include"Queue.h"

int main()
{
	Queue q;
	
	QueInit(&q);
	QuePush(&q, 1);
	QuePush(&q, 2);
	QuePush(&q, 3);
	QuePush(&q, 4);

	//printf("%d ", QueFront(&q));

	while (!QueEmpty(&q))
	{
		printf("%d ", QueFront(&q));
		QuePop(&q);
	}
	QueDestroy(&q);
	return 0;
}

码字不易,来个一键三连呗~~

评论 15
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值