数据结构:队列(顺序存储和链式存储)

news2024/11/19 5:57:57

文章目录

  • 1. 队列的概念和结构
  • 2. 队列的链式存储实现
    • 2.1 初始化
    • 2.2 判断队列是否为空
    • 2.3 入队列
    • 2.4 出队列
    • 2.5 取队头数据
    • 2.6 取队尾数据
    • 2.7 队列有效数据的个数
    • 2.8 打印队列数据
    • 2.9 销毁
    • 2.10 源代码
  • 3. 队列的顺序存储实现(循环队列)
    • 3.1 初始化
    • 3.2 判断队列是否为空
    • 3.3 判断队列是否为满
    • 3.4 入队列
    • 3.5 出队列
    • 3.6 取队头数据
    • 3.7 取队尾数据
    • 3.8 队列有效数据的个数
    • 3.9 打印队列数据
    • 3.10 销毁
    • 3.11 源代码
  • 4. 队列的顺序存储和链式存储的优缺点

1. 队列的概念和结构

队列(Queue)是一种特殊的线性表,它具有先进先出(FIFO, First In First Out)的特性。在队列中,数据的插入操作被称为入队(Enqueue),数据元素的插入只能在队列的一端进行,这一端通常被称为队尾(Rear);相应地,数据的删除操作被称为出队(Dequeue),数据元素的删除只能在队列的另一端进行,这一端被称为队首(Front)。

在这里插入图片描述

队列的存储结构主要分为两种:链式存储顺序存储。下面来逐一实现

2. 队列的链式存储实现

队列的链式存储结构为单链表,在链式存储队列中,因为将数据入队是从队尾插入所以可能需要从头节点开始遍历到尾节点,时间复杂度较高,为O(n)。但为了降低时间复杂度可以额外开辟一个结构体来存储队列的头指针和尾指针,这样可以有效降低时间复杂度。此外,如果想要知道队列里有效数据的个数,可以往队列结构体里再存储一个size变量记录队列中有效数据的个数

对单链表的实现不了解的可以去看一下我之前的博客:数据结构—单链表

下面我们来定义一下队列的链式存储结构和队列具体要实现哪些功能:

//定义队列结点结构
typedef int QDataType;
typedef struct QueueNode {
	QDataType data;//存储数据
	struct QueueNode* next;//存储指向下一个结点的指针
}QueueNode;

//定义队列结构
typedef struct Queue {
	QueueNode* phead;//头结点指针
	QueueNode* ptail;//尾结点指针
	int size;//队列中有效数据的个数
}Queue;

//初始化
void QueueInit(Queue* pq);
//入队列,队尾
void QueuePush(Queue* pq, QDataType x);
//出队列,队头
void QueuePop(Queue* pq);
//队列判空
bool QueueEmpty(Queue* pq);
//取队头数据
QDataType QueueFront(Queue* pq);
//取队尾数据
QDataType QueueBack(Queue* pq);
//队列有效数据的个数
int Queuesize(Queue* pq);
//销毁队列
void QueueDestory(Queue* pq);
//打印队列数据
void QueuePrint(Queue* pq);

2.1 初始化

思路:将队列这个结构体变量的地址传过来用一级指针来接收,在函数内部将队列的头尾指针指向空和有效数据个数置为0即可,这样就实现了形参改变实参。

//初始化
void QueueInit(Queue* pq)
{
	assert(pq);//pq!=NULL
	pq->phead = pq->ptail = NULL;
	pq->size = 0;
}

2.2 判断队列是否为空

思路:如果队列的头指针或者尾指针指向空,则说明队列为空

//队列判空
bool QueueEmpty(Queue* pq)
{
	assert(pq);//pq!=NULL
	return pq->phead == NULL && pq->ptail == NULL;
}

2.3 入队列

在这里插入图片描述

思路:首先申请一个新节点空间newnode,如果申请失败就打印错误信息,再异常退出,如果申请成功就将数据存储在新节点中,再将新节点的next指针指向空。随后再判断队列是否为空,如果为空就将新节点空间newnode赋给头结点和尾节点的指针,如果不为空,就先将尾节点的next指针指向新节点newnode,再更新尾节点指针(将尾节点指针指向新节点newnode)最后不要忘了将队列中的有效数据个数加一

//入队列,队尾
void QueuePush(Queue* pq, QDataType x)
{
	assert(pq);//pq!=NULL
	//申请新节点
	QueueNode* newnode = (QueueNode*)malloc(sizeof(QueueNode));
	if (newnode == NULL)
	{
		//申请失败
		perror("malloc fail!");//打印提升消息
		exit(1);//异常退出
	}
	newnode->data = x;
	newnode->next = NULL;
	//申请成功,判断队列是否为空
	if (QueueEmpty(pq))
	{
		pq->phead = pq->ptail = newnode;
	}
	else
	{
		pq->ptail->next = newnode;
		pq->ptail = newnode;
	}
	pq->size++;
}

2.4 出队列

思路:首先队列不能为空,队列为空直接报错。如果队列不为空,则分以下两种情况:

第一种情况:如果队列只有一个节点,那么头尾指针都指向这个节点,所以直接销毁这个节点的空间后再将头尾指针指向空(否则头尾指针会变成野指针)

在这里插入图片描述

第二种情况:如果队列的节点个数大于1的话,因为出队列是从队头出,所以可以先将头指针赋给del,再将头指针指向头指针指向的下一个节点的指针(pq->phead = pq->phead->next),再将del指向的空间销毁并且将del指向空。同理,也可以将头指针的next指针用新指针存储起来,再销毁头指针的空间并且将头指针指向新指针,两种方法都可以,以下用的是第一种。
请添加图片描述
注意因为是出队列,最后不要忘了将队列中的有效数据个数size减一

//出队列,队头
void QueuePop(Queue* pq)
{
	assert(pq);//pq!=NULL
	assert(!QueueEmpty(pq));//队列不能为空
	if (pq->phead==pq->ptail)
	{
		free(pq->phead);
		pq->phead = pq->ptail = NULL;
	}
	else
	{
		QueueNode* del = pq->phead;
		pq->phead = pq->phead->next;
		free(del);
		del = NULL;
	}
	pq->size--;
}

2.5 取队头数据

思路:因为是取队头数据,所以队列不能为空,直接将队头数据返回即可。

//取队头数据
QDataType QueueFront(Queue* pq)
{
	assert(pq);//pq!=NULL
	assert(!QueueEmpty(pq));//队列不能为空
	return pq->phead->data;
}

2.6 取队尾数据

思路:因为是取队尾数据,所以队列不能为空,直接将队尾数据返回即可。

//取队尾数据
QDataType QueueBack(Queue* pq)
{
	assert(pq);//pq!=NULL
	assert(!QueueEmpty(pq));//队列不能为空
	return pq->ptail->data;
}

2.7 队列有效数据的个数

思路:直接将队列中有效数据的个数size返回即可。

//队列有效数据的个数
int Queuesize(Queue* pq)
{
	assert(pq);//pq!=NULL
	return pq->size;
}

2.8 打印队列数据

思路:首先队列不能为空,只需每次打印队头数据,再出队列即可,当队列为空时就停止循环,所以循环条件为队列不为空:!QueueEmpty(pq)

//打印队列数据
void QueuePrint(Queue* pq)
{
	assert(pq);//pq!=NULL
	assert(!QueueEmpty(pq));//队列不能为空
	while (!QueueEmpty(pq))
	{
		printf("%d ", QueueFront(pq));
		QueuePop(pq);
	}
	printf("\n");
}

2.9 销毁

思路:首先队列不能为空,定义一个节点指针pcur指向队列的头结点,只需将头指针赋给pcur即可(pcur=pq->phead),然后从头节点开始遍历队列,再定义一个指针指向释放节点的下一个节点(next),每次释放完当前节点pcur后就让pcur指向下一个节点next,直到全部节点都释放完,最后再将队列的头指针phead和尾指针ptail指向空,还有队列中的有效数据个数size置为0即可

//销毁队列
void QueueDestory(Queue* pq)
{
	assert(pq);//pq!=NULL
	assert(!QueueEmpty(pq));//队列不能为空
	QueueNode* pcur = pq->phead;
	while (pcur)
	{
		QueueNode* next = pcur->next;
		free(pcur);
		pcur = next;
	}
	pq->phead = pq->ptail = NULL;
	pq->size = 0;
}

2.10 源代码

Queue.h头文件

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>
//定义队列结点结构
typedef int QDataType;
typedef struct QueueNode {
	QDataType data;//存储数据
	struct QueueNode* next;//存储指向下一个结点的指针
}QueueNode;

//定义队列结构
typedef struct Queue {
	QueueNode* phead;//头结点指针
	QueueNode* ptail;//尾结点指针
	int size;//队列中有效数据的个数
}Queue;

//初始化
void QueueInit(Queue* pq);
//入队列,队尾
void QueuePush(Queue* pq, QDataType x);
//出队列,队头
void QueuePop(Queue* pq);
//队列判空
bool QueueEmpty(Queue* pq);
//取队头数据
QDataType QueueFront(Queue* pq);
//取队尾数据
QDataType QueueBack(Queue* pq);
//队列有效数据的个数
int Queuesize(Queue* pq);
//销毁队列
void QueueDestory(Queue* pq);
//打印队列数据
void QueuePrint(Queue* pq);

Queue.c源文件

#include "Queue.h"
//初始化
void QueueInit(Queue* pq)
{
	assert(pq);//pq!=NULL
	pq->phead = pq->ptail = NULL;
	pq->size = 0;
}
//队列判空
bool QueueEmpty(Queue* pq)
{
	assert(pq);//pq!=NULL
	return pq->phead == NULL && pq->ptail == NULL;
}
//入队列,队尾
void QueuePush(Queue* pq, QDataType x)
{
	assert(pq);//pq!=NULL
	//申请新节点
	QueueNode* newnode = (QueueNode*)malloc(sizeof(QueueNode));
	if (newnode == NULL)
	{
		//申请失败
		perror("malloc fail!");//打印提升消息
		exit(1);//异常退出
	}
	newnode->data = x;
	newnode->next = NULL;
	//申请成功,判断队列是否为空
	if (QueueEmpty(pq))
	{
		pq->phead = pq->ptail = newnode;
	}
	else
	{
		pq->ptail->next = newnode;
		pq->ptail = newnode;
	}
	pq->size++;
}
//出队列,队头
void QueuePop(Queue* pq)
{
	assert(pq);//pq!=NULL
	assert(!QueueEmpty(pq));//队列不能为空
	if (pq->phead==pq->ptail)
	{
		free(pq->phead);
		pq->phead = pq->ptail = NULL;
	}
	else
	{
		QueueNode* del = pq->phead;
		pq->phead = pq->phead->next;
		free(del);
		del = NULL;
	}
	pq->size--;
}
//取队头数据
QDataType QueueFront(Queue* pq)
{
	assert(pq);//pq!=NULL
	assert(!QueueEmpty(pq));//队列不能为空
	return pq->phead->data;
}
//取队尾数据
QDataType QueueBack(Queue* pq)
{
	assert(pq);//pq!=NULL
	assert(!QueueEmpty(pq));//队列不能为空
	return pq->ptail->data;
}
//队列有效数据的个数
int Queuesize(Queue* pq)
{
	assert(pq);//pq!=NULL
	return pq->size;
}
//销毁队列
void QueueDestory(Queue* pq)
{
	assert(pq);//pq!=NULL
	assert(!QueueEmpty(pq));//队列不能为空
	QueueNode* pcur = pq->phead;
	while (pcur)
	{
		QueueNode* next = pcur->next;
		free(pcur);
		pcur = next;
	}
	pq->phead = pq->ptail = NULL;
	pq->size = 0;
}
//打印队列数据
void QueuePrint(Queue* pq)
{
	assert(pq);//pq!=NULL
	assert(!QueueEmpty(pq));//队列不能为空
	while (!QueueEmpty(pq))
	{
		printf("%d ", QueueFront(pq));
		QueuePop(pq);
	}
	printf("\n");
}

3. 队列的顺序存储实现(循环队列)

对顺序表实现不熟悉的可以看一下我之前的博客:数据结构—顺序表

队列的顺序存储结构通常是由一个静态数组和一个记录队列头元素位置的变量front以及一个记录队列尾元素的下一个位置的变量rear组成,因为数组的空间大小在创建时就已经确立了,所以顺序存储结构还存储了队列的最大容量maxsize

假设一开始我们将front和rear两个变量置为0,每次将元素入队后rear都会加一,那么当rear=maxsize时就可以判断队列为满吗?显然我们忽略了一个重要的东西:front一定为0吗?,如果我们一开始将几个元素入队(入队在队尾入),然后再出队(出队是在队头出)那么front就会++,不会再指向0,然后再往队列里面插入元素,当rear=maxsize时如果还有元素要插入的话就会造成数组越界(从下标为0开始,最大下标为maxsize-1),但是front(front不为0)之前还有位置是空的还可以再插入数据,此时怎么将数据插入到下标为0的位置呢?

我们可以用pq->rear = (pq->rear + 1) % pq->maxsize这个式子,当rear==maxsize-1时执行这条语句后rear就会变成0,也就是说将数组最大下标为maxsize-1的位置插入数据后,下一个要插入数据的位置就变成了0,这样就完美解决了数组越界问题,队列也变成了循环队列。同理,每次出队时front都会加一,当从数组最大下标位置(maxsize-1)出队后front就会回到0这个位置,可以推出式子为:pq->front = (pq->front + 1) % pq->maxsize

在这里插入图片描述

如上图,那么又来了一个问题:当rear=front时(初始化都为0)可以判断队列为空,但是当队列为满时rear也刚好等于front,那该怎么解决这个问题呢?以下给出三种解决方案:

第一种:人为浪费一个空间,在申请数组空间大小时再多申请一个空间,然后当(pq->rear + 1)%(pq->maxsize + 1)等于pq->front时队列为满

第二种:在队列结构体里再存储一个记录队列有效数据个数的变量size,当size=maxsize时说明队列为满

第三种:在对列结构体里再存储一个变量msg,初始化为0,每次出队都将msg设置为0,每次入队都将msg设置为1。因为只有出队才能让队列为空,只有入队才能让队列为满。当pq->rear等于pq->front同时msg等于1时队列为满,当pq->rear等于pq->front同时msg等于0时队列为空

接下来介绍第一种方法,第一种方法示意图如下:
在这里插入图片描述

首先,定义队列的顺序存储结构体和具体要实现的功能:

//定义队列结构
typedef int QDataType;
typedef struct Queue {
	QDataType* data;//存储有效数据的数组
	int front;//队列头指针
	int rear;//队列尾指针(指向尾元素的下一个位置)
	int maxsize;//队列最大容量
}Queue;
//初始化
Queue* QueueInit(int sum);
//队列判空
bool QueueEmpty(Queue* pq);
//判断队列是否为满
bool QueueFull(Queue* pq);
//入队列,队尾
void QueuePush(Queue* pq, QDataType x);
//出队列,队头
void QueuePop(Queue* pq);
//取队头数据
QDataType QueueFront(Queue* pq);
//取队尾数据
QDataType QueueBack(Queue* pq);
//队列有效数据的个数
int Queuesize(Queue* pq);
//销毁队列
void QueueDestory(Queue* pq);
//打印队列数据
void QueuePrint(Queue* pq);

3.1 初始化

思路:首先创建一个队列结构体空间,最后再返回这个队列结构体指针,开辟大小为sum+1的数组空间,申请失败就打印错误信息并退出,申请成功就将数组空间赋给pq->data,并且将front和rear置为0,再将sum赋给maxsize。

//初始化
Queue* QueueInit(int sum)
{
	Queue* pq = (Queue*)malloc(sizeof(Queue));//创建一个队列空间
	QDataType* tmp = (QDataType*)malloc((sum + 1) * sizeof(QDataType));
	//申请sum+1个空间
	if (tmp == NULL)
	{
		//申请失败
		perror("malloc fail!");
		exit(1);
	}
	//申请成功
	pq->data = tmp;
	pq->front = pq->rear = 0;
	pq->maxsize = sum;
	return pq;
}

3.2 判断队列是否为空

思路:当pq->rear等于pq->front时为空

//队列判空
bool QueueEmpty(Queue* pq)
{
	assert(pq);//pq!=NULL
	return pq->front == pq->rear;
}

3.3 判断队列是否为满

思路:当(pq->rear + 1) % (pq->maxsize + 1) 等于 pq->front时为满

//判断队列是否为满
bool QueueFull(Queue* pq)
{
	assert(pq);//pq!=NULL
	return (pq->rear + 1) % (pq->maxsize + 1) == pq->front;
}

3.4 入队列

思路:既然要入队列,所以队列不能为满,要使用assert断言。每次往数组下标为rear的位置插入数据后,都不要忘了将rear先+1后取模,防止数组越界

//入队列,队尾
void QueuePush(Queue* pq, QDataType x)
{
	assert(pq);//pq!=NULL
	assert(!QueueFull(pq));//队列不为满
	pq->data[pq->rear] = x;
	pq->rear = (pq->rear + 1) % (pq->maxsize + 1);//因为申请了maxsize+1个空间所以被取余的是pq->maxsize+1
}

3.5 出队列

思路:既然要出队列,所以队列不能为空,要使用assert断言。每次将数组下标为front的数据出队列后,为了防止数组越界,所以最后不要忘了将front先+1后取模

//出队列,队头
void QueuePop(Queue* pq)
{
	assert(pq);//pq!=NULL
	assert(!QueueEmpty(pq));//队列不为空
	pq->front = (pq->front + 1) % (pq->maxsize + 1);
}

3.6 取队头数据

思路:直接将队头数据返回即可

//取队头数据
QDataType QueueFront(Queue* pq)
{
	assert(pq);//pq!=NULL
	assert(!QueueEmpty(pq));//队列不为空
	return pq->data[pq->front];
}

3.7 取队尾数据

思路先定义一个变量prev等于rear-1(因为rear-1刚好是队尾数据的下标)。因为rear可能指向0,再减一就变成-1了,所以还得先判断rear是否等于0。如果等于0的话,那么队尾数据的下标刚好等于maxsize,所以将maxsize赋给prev,最后将队尾数据返回即可

//取队尾数据
QDataType QueueBack(Queue* pq)
{
	assert(pq);//pq!=NULL
	assert(!QueueEmpty(pq));//队列不为空
	int prev = pq->rear - 1;
	if (pq->rear == 0)
	{
		prev = pq->maxsize;
	}
	return pq->data[prev];
}

3.8 队列有效数据的个数

思路直接套用公式(rear-front+maxsize)%maxsize即可需要注意的是当队列为满时rear=front,和队列为空的情况一样返回的是0,所以这里还需要特判一下队列为不为满,为满则返回maxsize

//队列有效数据的个数
int Queuesize(Queue* pq)
{
	assert(pq);//pq!=NULL
	if (QueueFull(pq))
	{
		return pq->maxsize;
	}
	return (pq->rear - pq->front + pq->maxsize) % pq->maxsize;
}

3.9 打印队列数据

思路:打印完队头数据就将队头出队,一直到队列为空即可,所以循环条件为:!QueueEmpty(pq)

//打印队列数据
void QueuePrint(Queue* pq)
{
	assert(pq);//pq!=NULL
	while (!QueueEmpty(pq))
	{
		printf("%d ", QueueFront(pq));
		QueuePop(pq);
	}
	printf("\n");
}

3.10 销毁

思路:如果pq->data不为空则销毁数组空间并且将front,rear,maxsize置为0即可,如果pq->data为空指针(指向的数组为空)则直接将front,rear,maxsize置为0即可

//销毁队列
void QueueDestory(Queue* pq)
{
	assert(pq);//pq!=NULL
	if (pq->data)
	{
		free(pq->data);//销毁空间
	}
	pq->front = pq->rear = pq->maxsize = 0;
}

3.11 源代码

Queue.h头文件

#pragma once//只包含一次头文件,防止重复调用
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>
//定义队列结构
typedef int QDataType;
typedef struct Queue {
	QDataType* data;//存储有效数据的数组
	int front;//队列头指针
	int rear;//队列尾指针(指向尾元素的下一个位置)
	int maxsize;//队列最大容量
}Queue;
//初始化
Queue* QueueInit(int sum);
//队列判空
bool QueueEmpty(Queue* pq);
//判断队列是否为满
bool QueueFull(Queue* pq);
//入队列,队尾
void QueuePush(Queue* pq, QDataType x);
//出队列,队头
void QueuePop(Queue* pq);
//取队头数据
QDataType QueueFront(Queue* pq);
//取队尾数据
QDataType QueueBack(Queue* pq);
//队列有效数据的个数
int Queuesize(Queue* pq);
//销毁队列
void QueueDestory(Queue* pq);
//打印队列数据
void QueuePrint(Queue* pq);

Queue.c源文件

#include "Queue.h"
//初始化
Queue* QueueInit(int sum)
{
	Queue* pq = (Queue*)malloc(sizeof(Queue));//创建一个队列空间
	QDataType* tmp = (QDataType*)malloc((sum + 1) * sizeof(QDataType));
	//申请sum+1个空间
	if (tmp == NULL)
	{
		//申请失败
		perror("malloc fail!");
		exit(1);
	}
	//申请成功
	pq->data = tmp;
	pq->front = pq->rear = 0;
	pq->maxsize = sum;
	return pq;
}
//队列判空
bool QueueEmpty(Queue* pq)
{
	assert(pq);//pq!=NULL
	return pq->front == pq->rear;
}
//判断队列是否为满
bool QueueFull(Queue* pq)
{
	assert(pq);//pq!=NULL
	return (pq->rear + 1) % (pq->maxsize + 1) == pq->front;
}
//入队列,队尾
void QueuePush(Queue* pq, QDataType x)
{
	assert(pq);//pq!=NULL
	assert(!QueueFull(pq));//队列不为满
	pq->data[pq->rear] = x;
	pq->rear = (pq->rear + 1) % (pq->maxsize + 1);//因为申请了maxsize+1个空间所以被取余的是pq->maxsize+1
}
//出队列,队头
void QueuePop(Queue* pq)
{
	assert(pq);//pq!=NULL
	assert(!QueueEmpty(pq));//队列不为空
	pq->front = (pq->front + 1) % (pq->maxsize + 1);
}
//取队头数据
QDataType QueueFront(Queue* pq)
{
	assert(pq);//pq!=NULL
	assert(!QueueEmpty(pq));//队列不为空
	return pq->data[pq->front];
}
//取队尾数据
QDataType QueueBack(Queue* pq)
{
	assert(pq);//pq!=NULL
	assert(!QueueEmpty(pq));//队列不为空
	int prev = pq->rear - 1;
	if (pq->rear == 0)
	{
		prev = pq->maxsize;
	}
	return pq->data[prev];
}
//队列有效数据的个数
int Queuesize(Queue* pq)
{
	assert(pq);//pq!=NULL
	if (QueueFull(pq))
	{
		return pq->maxsize;
	}
	return (pq->rear - pq->front + pq->maxsize) % pq->maxsize;
}
//打印队列数据
void QueuePrint(Queue* pq)
{
	assert(pq);//pq!=NULL
	while (!QueueEmpty(pq))
	{
		printf("%d ", QueueFront(pq));
		QueuePop(pq);
	}
	printf("\n");
}
//销毁队列
void QueueDestory(Queue* pq)
{
	assert(pq);//pq!=NULL
	if (pq->data)
	{
		free(pq->data);//销毁空间
	}
	pq->front = pq->rear = pq->maxsize = 0;
}

4. 队列的顺序存储和链式存储的优缺点

一. 顺序存储队列

优点:

空间利用率高:在静态分配的内存空间中,顺序存储队列的存储密度大,空间利用率高。
访问效率高:由于数据是连续存储的,队列的访问(如查看队首或队尾元素)操作效率高,时间复杂度为O(1)

缺点:

假溢出问题:在顺序存储队列中,如果队列满了(即队尾指针达到了数组的上界),但实际可能队首还有空间未被利用,这会导致队列出现“假溢出”现象。解决假溢出的方法之一是采用循环队列,但这会增加实现的复杂度。
动态扩容成本高:如果采用动态数组实现顺序队列,当队列容量不足时,需要进行扩容操作,这通常涉及到大量数据的移动,成本较高。
空间限制:顺序存储队列的大小受限于静态分配的内存大小,或者动态扩容的阈值。

二. 链式存储队列

优点:

无假溢出问题:链式存储队列通过指针连接节点,理论上队列的大小只受限于系统内存的大小,不存在假溢出问题。
动态扩容方便:在链式存储队列中,当需要增加新的元素时,只需动态地分配新的节点即可,无需进行大量数据的移动。
空间利用率灵活:每个节点可以只存储必要的数据和指针,空间利用率较为灵活。

缺点:

空间利用率相对较低:由于每个节点除了存储数据外,还需要存储指针,因此相比于顺序存储,链式存储的空间利用率较低。
访问效率低:在链式存储队列中,访问某个特定位置的元素可能需要从头节点开始遍历,时间复杂度较高,为O(n)。但为了降低时间复杂度可以额外开辟一个结构体来存储队列的头指针和尾指针,这样可以有效降低时间复杂度,变为O(1),但同时也提高了内存开销。
内存管理开销:动态分配和释放节点需要额外的内存管理开销。

对以上内容有不同看法的欢迎来讨论,希望对大家的学习有帮助,多多支持哦!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1955320.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

五、获取树形结构数据递归写法

1、mysql库表字段 学习&#xff1a;构建表结构时的规范&#xff0c;字段类型的选择 CREATE TABLE pms_category (cat_id bigint(20) NOT NULL AUTO_INCREMENT COMMENT 分类id,name char(50) DEFAULT NULL COMMENT 分类名称,parent_cid bigint(20) DEFAULT NULL COMMENT 父分类…

2024年必知:9大项目成本管理系统推荐

本文将分享9大优质项目成本管理系统&#xff1a;PingCode、Worktile、中望软件、用友、智慧工地云平台、SAP ERP、Microsoft Project、Wrike、Zoho Projects。 在项目管理领域&#xff0c;控制成本往往是最挑战的一环&#xff0c;特别是在预算和资源受限的情况下。选择合适的项…

从0开始搭建vue + flask 旅游景点数据分析系统(二):搭建基础框架

这一期目标是把系统的布局给搭建起来&#xff0c;采用一个非常简单的后端管理风格&#xff0c;可以参考官方的页面 https://element.eleme.cn/#/zh-CN/component/container 下面我们开始搭建&#xff0c;首先&#xff0c;安装一下vue-router&#xff0c;element-ui npm insta…

【前端 12】js事件绑定

JavaScript 事件绑定 在Web开发中&#xff0c;事件绑定是实现用户与网页交互的重要机制。JavaScript 提供了多种方式来绑定和处理事件&#xff0c;使得开发者能够灵活地控制网页的行为。本文将详细介绍JavaScript中事件绑定的两种主要方式&#xff0c;并通过实例演示如何应用这…

查找(find)磁盘分区 压缩

一&#xff1a;查找的应用 find 查找位置 选项 参数 按用户查找 属主是root find /root -user root 按文件类型查找 文件类型为普通文件的 find /root -type f 文件类型为目录的 find /root -type d fin…

机器学习算法——常规算法,在同的业务场景也需要使用不同的算法(一)

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;开发者-曼亿点 &#x1f468;‍&#x1f4bb; hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍&#x1f4bb; 本文由 曼亿点 原创 &#x1f468;‍&#x1f4bb; 收录于专栏&#xff1a…

渗透测试——prime1靶场实战演练{常用工具}端口转发

文章目录 概要信息搜集 概要 靶机地址&#xff1a;https://www.vulnhub.com/entry/prime-1,358 信息搜集 nmap 扫网段存活ip及端口 找到除了网关外的ip&#xff0c;开放了80端口&#xff0c;登上去看看 是一个网站&#xff0c;直接上科技扫一扫目录 python dirsearch.py -u …

YUV/NV12、ARGB8888图像同比例缩放,不拉伸处理

1&#xff09;图像处理入门级程序设计&#xff0c;分享给将要学习或者正在学习图像开发的同学。 2&#xff09;内容属于原创&#xff0c;若转载&#xff0c;请说明出处。 3&#xff09;提供相关问题有偿答疑和支持。 需求&#xff1a;基于SigmaStar平台SSC375&#xff0c;实…

深入源码:解析SpotBugs (2) 检测运行流程

1. 架构概述 SpotBugs的架构设计主要围绕以下几个核心组件展开&#xff1a; 分析引擎&#xff1a;这是SpotBugs的核心&#xff0c;负责读取Java字节码&#xff08;.class文件&#xff09;&#xff0c;并应用预定义的规则集来检测潜在的代码问题。规则集&#xff1a;一组预定义…

『 Linux 』线程控制

文章目录 线程库线程的创建线程库中的线程ID线程等待及线程退出C11 中的线程库线程库的线程与轻量型进程 线程库 在Linux内核中没有实际的线程概念,只有轻量级进程的概念,即使用task_struct内核数据结构模仿线程; 所以本质上在Linux内核中无法直接调用系统调用接口创建线程,只能…

人工智能学习①

LLM背景知识介绍 大语言模型 (LLM) 背景 用于理解和生成人类语言&#xff0c;能够处理诸如文本分类、问答、翻译和对话等多种自然语言任务。 语言模型 (Language Model, LM) &#xff1a;给定一个短语&#xff08;一个词组或者一句话&#xff09;语言模型可以生成&#xff0…

机器学习数学基础(1)--线性回归与逻辑回归

声明&#xff1a;本文章是根据网上资料&#xff0c;加上自己整理和理解而成&#xff0c;仅为记录自己学习的点点滴滴。可能有错误&#xff0c;欢迎大家指正。 1 线性回归和逻辑回归与机器学习的关系 线性回归属于机器学习 – 监督学习 – 回归 – 线性回归&#xff0c; 逻辑…

Apache DolphinScheduler Worker Task执行原理解析

大家好&#xff0c;我是蔡顺峰&#xff0c;是白鲸开源的高级数据工程师&#xff0c;同时也是Apache DolphinScheduler社区的committer和PMC member。今天我要分享的主题是《Worker Task执行原理》。 整个分享会分为三个章节&#xff1a; Apache DolphinScheduler的介绍Apache …

数据结构——二叉树定义

一、二叉树概念 二叉树是一种树形数据结构&#xff0c;其中每个节点最多有两个子节点&#xff0c;通常称为左子节点和右子节点。每个子节点本身又可以是一个二叉树。二叉树在计算机科学中有着广泛的应用&#xff0c;例如在搜索算法、排序算法等领域 二叉树(Binary Tree)是n(n…

告别繁琐,2024年PDF合并神器搜罗

有时候我们下载得到的PDF文件可能是被拆分成多份文档&#xff0c;这样对于我们查看文件就会造成一定的困扰。这时候如果把他们合并为一份文件就能方便很多。这次我就介绍几款pdf合并工具来解决这个问题吧。 第一款EIDTOR 福昕PDF 链接&#xff1a;https://editor.foxitsoftwar…

C++ STL 容器之deque

deque与vector同属C STL容器&#xff0c;二者有些相似。deque 采用动态数组来管理元素&#xff0c;提供随机存取&#xff0c;它与vector 几乎一摸一样的接口。不同的是&#xff1a;deque的动态数组头尾都开放&#xff0c;能在头尾两端进行快速安插和散出。下面是deque与vector的…

android前台服务

关于作者&#xff1a;CSDN内容合伙人、技术专家&#xff0c; 从零开始做日活千万级APP。 专注于分享各领域原创系列文章 &#xff0c;擅长java后端、移动开发、商业变现、人工智能等&#xff0c;希望大家多多支持。 未经允许不得转载 目录 一、导读二、使用2.1 添加权限2.2 新建…

nginx 版本升级

Nginx 的版本最开始使用的是 Nginx-1.18.0 &#xff0c; 由于服务升级&#xff0c;需要将 Nginx 的版本升级到 Nginx-1.19.7 &#xff0c;要求 Nginx 不能中断提供服务。 为了应对上述的需求&#xff0c;提供两种解决方案&#xff1a; 方案1&#xff1a; make upgrade 完成升…

(二十四)进阶算法

文章目录 &#xff08;一&#xff09;埃氏筛法1. 原理2. 代码3. 特点 &#xff08;二&#xff09;欧拉筛法1. 原理2. 代码3. 特点 &#xff08;三&#xff09;分解质因数1. 原理2. 代码 &#xff08;四&#xff09;斐波那契数列1. 递推式2. 代码(1) 方法1(2) 方法2 经过12天的“…