数据结构-队列(链队列与循环队列)

目录

队列的概念及结构

概念

结构

链队列​

初始化QueueInit

销毁QueueDestroy

入队QueuePush

出队QueuePop

队头元素QueueFront​

队尾元素QueueBack

队列长度QueueSize

判断是否为空队列QueueEmpty 

循环队列

概念及结构

循环队列的基本操作​

初始化CQueueInit

销毁CQueueDestroy

入队CQueuePush

​出队CQueuePop

队头元素CQueueFront与队尾元素CQueueBack​

判空CQueueEmpty与判满CQueueFull​

队列长度CQueueSize​

代码

链队列

循环队列


队列的概念及结构

概念

   队列也是一种特殊的线性表,其操作受到限制。只允许在队列的一端进行插入数据操作,而在队列的另一端进行删除数据操作。

   在队列中,进行插入数据操作的一端叫做队尾,其操作叫做入队列或者进队列;而进行删除数据操作的一端叫做队头或者队首,其操作叫做出队列或者离队列。

   如上图所示,n1,n2,n3,n4,n5依次从队尾入队,队头元素是n1,队尾元素是n5;而出队只能从另一端队头出,次序依次为n1,n2,n3,n4,n5。这和我们日常生活中的排队相似,最早排队的也是最早离队的,所以队列的特性可以概括为先进先出(First In First Out,FIFO)。

结构

   队列的实现可以选择顺序表与链表,但因为队列是在两端进行操作,如果选择顺序表来实现,在进行出队操作的时候需要挪动数据,效率会比较低。所以队列的实现一般都是选择单链表,在单链表的头部进行出队,单链表的尾部进行入队;为了避免在入队的时候需要找尾结点,我们可以事先保存单链表尾结点的地址来提高效率。


链队列

初始化QueueInit

   初始化队列,将队列的头指针与尾指针置空,也就是创建一个空队列。

销毁QueueDestroy

   销毁的操作就是先将非出队的元素释放,再将头指针与尾指针置空。

入队QueuePush

   如果入队时,队列为空需要将头指针与尾指针指向第一个元素;而其他情况就是将尾指针的next改为newNode的地址,再将尾指针指向newNode。

出队QueuePop

   当出队时,队列中只剩下一个元素需要先释放最后一个元素,再将头指针与尾指针置空;其他情况就是先保存队头元素下一个元素的地址,先将头指针指向的队头元素释放,然后将头指针改为事先保存好的元素地址。

队头元素QueueFront

队尾元素QueueBack

队列长度QueueSize

   求队列的长度,就是将单链表遍历一遍。

判断是否为空队列QueueEmpty 

   如果队列为空返回true,不为空返回false。


循环队列

概念及结构

   在顺序表实现的队列中,为了避免出队操作需要挪动数据造成的效率低下;我们也可以采用两个下标:一个head记录队头元素的下标,另一个tail记录队尾元素的下一个位置下标。入队时:队列未满,将入队元素赋给tail位置,然后tail下标加一;出队时:队列不空,head下标直接加一,不需要挪动数据。

   但这个顺序表实现的队列有一个缺点,一旦队列满了,我们就不能插入下一个元素,即使队列前面仍有空间;而循环队列的出现就是来解决这个问题的。

   循环队列是一种线性数据结构,其操作表现基于 FIFO(先进先出)原则并且队尾被连接在队头之后以形成一个循环。它也被称为“环形缓冲器”。使用循环队列,我们能继续使用前面的空间去存储新的值;而且head与tail的下标我们能用除法取余(%)来计算(capacity为开辟元素空间的个数)。

  • 入队:tail=(tail+1)%capacity
  • 出队:head=(head+1)%capacity
  • 队列长度:Size=(capacity+tail-head)%capacity

除了使用mod运算,我们也可以直接判断当head或者tail等于capacity时直接将head或者tail赋值为零;而tail的前一个prev等于-1时,直接将prev赋值为capacity-1即可。

   那么我们该如何判断循环队列的空与满呢?显然循环队列空与满的条件都是head==tail,为了区分循环队列的空与满,有两种常用的处理方式:

  • 一:多开辟一个空间,当head在tail的下一个位置则表示队列已满。队列满的条件:(tail+1)%capacity==head;队列空的条件:head==tail。
  • 二:增加一个记录队列长度的数据成员size,当size==0时说明队列为空,当size==capacity说明队列已满。

循环队列的基本操作

初始化CQueueInit

   先申请队列所需要的数据成员,再申请maxSize个数的空间;因为我们采用的是第一种处理方式所以记得要多开一个空间。

销毁CQueueDestroy

   必须先释放数组的空间,否则数组的空间找不到会出现内存泄漏与越界访问。

入队CQueuePush

   入队成功返回true否则返回false;先将x赋值给tail位置,再将tail下标更新;可以选择tail=(tail+1)%capacity,也可以选择直接判断。

出队CQueuePop

   出队成功返回true否则返回false,将head下标更新;可以选择head=(head+1)%capacity,也可以选择head++,然后判断head是否符合数组下标范围,如果超过将head赋值为零。

队头元素CQueueFront与队尾元素CQueueBack

判空CQueueEmpty与判满CQueueFull

队列长度CQueueSize


代码

链队列

// 头文件
typedef int QueueDataType; // 存储的数据类型

typedef struct QueueNode
{
	struct QueueNode* next; // 指向下一个结点
	QueueDataType data; // 存储数据
}QueueNode; 

typedef struct Queue
{
	QueueNode* head; // 头指针
	QueueNode* tail; // 尾指针
}Queue;

void QueueInit(Queue* pq);
void QueueDestroy(Queue* pq);

void QueuePush(Queue* pq, QueueDataType x);
void QueuePop(Queue* pq);

QueueDataType QueueFront(Queue* pq);
QueueDataType QueueBack(Queue* pq);

size_t QueueSize(Queue* pq);
bool QueueEmpty(Queue* pq);

// 源文件
void QueueInit(Queue* pq) // 初始化
{
	assert(pq);
	pq->head = pq->tail = NULL; // 将头指针与尾指针指向空
}

void QueueDestroy(Queue* pq) // 销毁
{
	assert(pq);
	while (!QueueEmpty(pq)) // 先将非出队的元素释放
	{
		QueuePop(pq);
	}

	/*QueueNode* curr = pq->head;
	while (curr)
	{
		QueueNode* next = curr->next;
		free(curr);
		curr = next;
	}*/

	pq->head = pq->tail = NULL; // 再将头指针与尾指针置空
}

void QueuePush(Queue* pq, QueueDataType x) // 入队
{
	assert(pq);

	QueueNode* newNode = (QueueNode*)malloc(sizeof(QueueNode));
	if (newNode == NULL)
	{
		perror("malloc fail");
		return;
	}
	newNode->data = x;
	newNode->next = NULL;

	if (pq->head==NULL&&pq->tail==NULL)
	{
		pq->head = pq->tail = newNode;
	}
	else
	{
		pq->tail->next = newNode;
		pq->tail = newNode;
	}
}

void QueuePop(Queue* pq) // 出队
{
	assert(pq);
	assert(!QueueEmpty(pq)); // 队列为空不需要出队

	if (pq->head == pq->tail) // pq->head->next==NULL
	{
		free(pq->head);
		pq->head = pq->tail = NULL;
	}
	else
	{
		QueueNode* next = pq->head->next;
		free(pq->head);
		pq->head = next;
	}
}

QueueDataType QueueFront(Queue* pq) // 获取队头元素
{
	assert(pq);
	assert(!QueueEmpty(pq));
	return pq->head->data;
}

QueueDataType QueueBack(Queue* pq) // 获取队尾元素
{
	assert(pq);
	assert(!QueueEmpty(pq));
	return pq->tail->data;
}

size_t QueueSize(Queue* pq) // 求队列的长度
{
	QueueNode* curr = pq->head;
	size_t size = 0;
	while (curr)
	{
		size++;
		curr = curr->next;
	}

	return size;
}

bool QueueEmpty(Queue* pq) // 判断队列是否为空
{
	return pq->head==NULL&&pq->tail==NULL;
}

循环队列

// 头文件
typedef int CQueueDataType; // 数据的类型

typedef struct CQueue
{
	CQueueDataType* a; // 开辟空间的首地址
	int head; // 队头元素的下标
	int tail; // 队尾元素下一个位置的下标
	int capacity; // 开辟空间的总个数
}CQueue;

// 初始化
CQueue* CQueueInit(int maxSize);
// 销毁
void CQueueDestroy(CQueue* pcq);
// 入队
bool CQueuePush(CQueue* pcq, CQueueDataType x);
// 出队
bool CQueuePop(CQueue* pcq);
// 队头元素
CQueueDataType CQueueFront(CQueue* pcq);
// 队尾元素
CQueueDataType CQueueBack(CQueue* pcq);
// 判空
bool CQueueEmpty(CQueue* pcq);
// 判满
bool CQueueFull(CQueue* pcq);
// 求队列中元素的个数
size_t CQueueSize(CQueue* pcq);

//源文件
// 初始化
CQueue* CQueueInit(int maxSize)
{
	assert(maxSize > 0);

	CQueue* ret = (CQueue*)malloc(sizeof(CQueue));
	if (!ret)
	{
		perror("malloc fail");
		return NULL;
	}

	ret->a = (CQueueDataType*)malloc(sizeof(CQueueDataType) * (maxSize + 1));
	if (!ret->a)
	{
		perror("malloc fail");
		free(ret);
		return NULL;
	}

	ret->head = ret->tail = 0;
	ret->capacity = maxSize + 1;

	return ret;
}

// 销毁
void CQueueDestroy(CQueue* pcq)
{
	assert(pcq);
	free(pcq->a);
	pcq->a = NULL;
	free(pcq);
	pcq = NULL;
}

// 入队
bool CQueuePush(CQueue* pcq, CQueueDataType x)
{
	assert(pcq);

	if(CQueueFull(pcq))
	{
		return false;
	}
	pcq->a[pcq->tail]=x;

	/*pcq->tail = (pcq->tail + 1) % pcq->capacity;*/

	pcq->tail++;
	if (pcq->tail == pcq->capacity)
	{
		pcq->tail = 0;
	}

	return true;
}

// 出队
bool CQueuePop(CQueue* pcq)
{
	assert(pcq);

	if (CQueueEmpty(pcq))
	{
		return false;
	}

	pcq->head = (pcq->head + 1) % pcq->capacity;
	/*pcq->head++;
	if (pcq->head == pcq->capacity)
	{
		pcq->head = 0;
	}*/

	return true;
}

// 队头元素
CQueueDataType CQueueFront(CQueue* pcq)
{
	assert(pcq);
	assert(!CQueueEmpty(pcq));
	return pcq->a[pcq->head];
}

// 队尾元素
CQueueDataType CQueueBack(CQueue* pcq)
{
	assert(pcq);
	assert(!CQueueEmpty(pcq));

	int prev = (pcq->tail - 1 + pcq->capacity) % pcq->capacity;

	/*int prev = pcq->tail-1;

	if (prev == -1)
	{
		prev = pcq->capacity - 1;
	}*/

	return pcq->a[prev];
}

// 判空
bool CQueueEmpty(CQueue* pcq)
{
	assert(pcq);

	return pcq->head == pcq->tail;
}

// 判满
bool CQueueFull(CQueue* pcq)
{
	assert(pcq);

	int next = (pcq->tail + 1) % pcq->capacity;

	/*int next = pcq->tail + 1;
	if (next == pcq->capacity)
	{
		next = 0;
	}*/

	return next == pcq->head;
}

// 求队列中元素的个数
size_t CQueueSize(CQueue* pcq)
{
	assert(pcq);

	return(pcq->tail + pcq->capacity - pcq->head) % pcq->capacity;
}

  • 8
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 10
    评论
循环队列是一种特殊的队列,其特点是队列的元素在物理结构上是环形排列的。educoder实训教学平台提供了循环队列的基本操作,包括初始化、入队、出队和判空等。 循环队列的初始化操作是创建一个空队列,并设置队列的头指针和尾指针都为0。入队操作是往队尾插入一个元素,插入后尾指针向后移动一位,并将元素放入新的队尾位置。出队操作是将队首元素删除,删除后头指针向后移动一位。判空操作是通过比较队列的头指针和尾指针是否相等来确定队列是否为空。 链队列是使用链表实现的队列,其特点是可以动态地分配内存空间,不会造成内存溢出的问题。educoder实训教学平台也提供了链队列的基本操作,包括初始化、入队、出队和判空等。 链队列的初始化操作是创建一个空队列,并设置头指针和尾指针都指向空节点。入队操作是在链队列的尾部插入一个新节点,尾指针向后移动一位,并将新节点的数据放入新的尾节点。出队操作是删除链队列的头节点,头指针向后移动一位。判空操作是通过判断链队列的头指针和尾指针是否指向同一节点来确定队列是否为空。 通过educoder实训教学平台提供的循环队列链队列的基本操作,学习者可以掌握数据结构队列的实现方式和基本操作的原理,进而应用于解决一些实际问题。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值