【数据结构之队列】

数据结构学习笔记---006

数据结构之队列

前言:

前篇学习了 数据结构的栈,那么这篇继续学习队列的基础内容。

/知识点汇总/

1、队列的概念和结构

队列 :只允许在一端进行数据操作,在另一端进行删除数据操作的特殊线性表

队列具有先进先出(FIFO:First In First Out)的特点.
入队列操作 :进行插入操作的一端称为队尾。
出队列操作:进行删除操作的一端称为队头。

与栈不同,队列的出队列顺序与出队列顺序是唯一的。

比如入队顺序:1,2,3,4

则出队顺序:1,2,3,4

1.1、如何实现队列?

双向链表、数组、单链表?

排除数组,大量挪动数据

其次,能用单链表解决就暂时不用双链表。所以采用类似于单链表的形式实现。另外,由于队列的特性,一端进队列,另一端出队列,所以额外定义一个结构体类型更加便于操作。
队列的存储结构

c 复制代码
typedef int QDataType;
//定义队列结构体成员和变量
typedef struct QueueNode
{
	QDataType val;
	struct QDataNode* next;
}QNode;

//由于队列的使用,存在多个值,采用结构体的指针,就不用二级指针了。
typedef struct Queue
{
	QNode* phead;
	QNode* ptail;
	int size;      //以空间换时间,解决求队内元素个数的时间复杂度问题
}Queue;

2、队列的实现

2.1、队列的Queue.h

c 复制代码
//队列初始化
void QueueInit(Queue* pq);

//队列销毁
void QueueDestory(Queue* pq);

//队列入队
void QueuePush(Queue* pq, QDataType x);

//队列出队
void QueuePop(Queue* pq);

//取队头
QDataType QueueFront(Queue* pq);

//取队尾
QDataType QueueBack(Queue* pq);

//判空
bool QueueEmpty(Queue* pq);

//取队内元素大小
int QueueSize(Queue* pq);

2.2、队列的Queue.c

队列的操作算是比较简单,理解性掌握即可。只需要注意一下,操作空队列和一个元素的情况的处理即可。

2.2.1、队列的初始化
c 复制代码
//队列初始化
void QueueInit(Queue* pq)
{
	assert(pq);
	pq->phead = pq->ptail = NULL;
	pq->size = 0;
}
2.2.2、队列销毁
c 复制代码
//队列销毁
void QueueDestory(Queue* pq)
{
	assert(pq);

	QNode* cur = pq->phead;
	while (cur)
	{
		QNode* next =cur->next;
		free(cur);
		cur = next;
	}
	pq->phead = pq->ptail = NULL;
	pq->size = 0;
}
2.2.3、队列入队、出队操作
c 复制代码
//队列入队
void QueuePush(Queue* pq, QDataType x)
{
	assert(pq);
	QNode* newnode = (QNode*)malloc(sizeof(QNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		return;
	}
	newnode->next = NULL;
	newnode->val = x;
	if (pq->ptail == NULL)
	{
		pq->ptail = pq->phead = newnode;
	}
	else
	{
		pq->ptail->next = newnode;
		pq->ptail = newnode;
	}
	pq->size++;
}

//队列出队
void QueuePop(Queue* pq)
{
	assert(pq);
	//判空
	assert(pq->phead);
	//一个节点的情况
	QNode* del = pq->phead;
	pq->phead = pq->phead->next;
	free(del);
	del = NULL;
	if (pq->phead == NULL)
	{
		pq->ptail = NULL;
	}
	pq->size--;
}
2.2.4、队列取队头、队尾操作
c 复制代码
//取队头
QDataType QueueFront(Queue* pq)
{
	assert(pq);
	//判空
	assert(pq->phead);
	return pq->phead->val;
}

//取队尾
QDataType QueueBack(Queue* pq)
{
	assert(pq);
	//判空
	assert(pq->phead);
	return pq->ptail->val;
}
2.2.5、队列的判空
c 复制代码
//判空
bool QueueEmpty(Queue* pq)
{
	assert(pq);
	//判空
	return pq->phead == NULL;
}
2.2.5、取队内元素大小
c 复制代码
//取队内元素大小
int QueueSize(Queue* pq)
{
	assert(pq);
	//判空
	//assert(pq->phead);
	return pq->size;
}

2.3、队列的main.c

c 复制代码
#include "Queue.h"

//测试1:
void TestQ1()
{
	Queue q;
	QueueInit(&q);//初始化
	QueuePush(&q, 1);//入队
	QueuePush(&q, 2);//入队
	QueuePush(&q, 3);//入队
	QueuePush(&q, 4);//入队
	QueuePush(&q, 5);//入队
	while (!QueueEmpty(&q))
	{
		printf("%d ", QueueFront(&q));
		QueuePop(&q);
	}
	QueueDestory(&q);//销毁
}
//测试2:
void TestQ2()
{
	Queue q;
	QueueInit(&q);//初始化
	QueuePush(&q, 1);//入队
	QueuePush(&q, 2);//入队
	QueuePush(&q, 3);//入队
	printf("%d ", QueueFront(&q));
	QueuePop(&q);
	QueuePush(&q, 4);//入队
	printf("%d ", QueueFront(&q));
	QueuePop(&q);
	QueuePush(&q, 5);//入队
	while (!QueueEmpty(&q))
	{
		printf("%d ", QueueFront(&q));
		QueuePop(&q);
	}
	QueueDestory(&q);//销毁
}
int main()
{
	//TestQ1();
	TestQ2();
	return 0;
}

小结

队列的元素入队出队顺序是固定的,属于一对一的关系

3、队列巩固练习

3.1、练习题1:用队列实现栈

练习题1:用队列实现栈 -- 实现两个队列实现先进后出栈的push、top、pop、empty
思路 :将第一个队列出队,到第二个队列入队,然后反复颠倒,直到按栈的方式完成所有操作。
核心:一个队列存储数据,另一个队列在出队列时,倒数据。

c 复制代码
#include "Queue.h"

typedef struct
{
	Queue q1;
	Queue q2;
}MyStack;

MyStack* myStackCreat()
{
	MyStack* pst = (MyStack*)malloc(sizeof(MyStack));
	QueueInit(&pst->q1);//注意这里的传参 ,栈地址
	QueueInit(&pst->q2);
	return pst;
}

void myStackPush(MyStack* obj, int x)
{
	if (!QueueEmpty(&obj->q1))//谁不为空,就向那个队列入队数据
	{
		QueuePush(&obj->q1, x);
	}
	else
	{
		QueuePush(&obj->q2, x);
	}
}

int myStackPop(MyStack* obj)
{
	Queue* emptyq = &obj->q1;
	Queue* noemptyq = &obj->q2;
	if (!QueueEmpty(&obj->q1))
	{
		emptyq = &obj->q2;
		noemptyq = &obj->q1;
	}
	//将非空队列前N-1个导入空队列,最后一个就是要的栈顶元素。
	while (QueueSize(noemptyq) > 1)
	{
		QueuePush(emptyq, QueueFront(noemptyq));
		QueuePop(noemptyq);
	}
	//直到栈顶元素就是最后一个元素
	int top = QueueFront(noemptyq);
	QueuePop(noemptyq);
	return top;
}

int myStackTop(MyStack* obj)
{
	if (!QueueEmpty(&obj->q1))
	{
		return QueueBack(&obj->q1);
	}
	else
	{
		return  QueueBack(&obj->q2);
	}
}

bool msStackEmpty(MyStack* obj)
{
	return QueueEmpty(&obj->q1) && QueueEmpty(&obj->q2);
}

MyStack* myStackFree(MyStack* obj)
{
	QueueDestory(&obj->q1);
	QueueDestory(&obj->q2);
	free(obj);
	obj = NULL;
	return NULL;
}
int main()
{
	MyStack* pst = myStackCreat();
	myStackPush(pst, 1);
	myStackPush(pst, 2);
	myStackPush(pst, 3);
	myStackPush(pst, 4);
	myStackPush(pst, 5);

	while (!msStackEmpty(pst))
	{
		printf("%d ", myStackTop(pst));
		myStackPop(pst); 
	}
	myStackFree(pst);
	return 0;
}

3.2、练习题2:用栈实现队列

练习题2:用栈实现队列 -- 实现两个栈实现先进先出队列的push、top、pop、empty
思路:两个栈,入数据都是入左边栈,出数据都是出右边栈,然后,右边不为空,则右边先出完,左边再倒数据。总之,右边栈没数据,左边栈再到数据。

c 复制代码
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <stdbool.h>

typedef int STDatatype;
//定义数组栈结构体成员及变量
typedef struct Stack
{
	STDatatype* a;
	int top;           //标识栈顶位置
	int capacity;
}ST;


//栈初始化
void STInit(ST* pst)
{
	assert(pst);
	pst->a = NULL;
	pst->capacity = 0;
	//注意栈顶给-1还是0,因为要明确top是栈顶元素还是栈顶元素的下一个元素。而且此时栈内只有一个元素时表示top=0,那么top无法区分top指向栈顶元素,还是指向栈顶元素的下一个位置,所以给-1还是0,写法不同
	//pst->top = -1; //表示top指向栈顶元素的位置
	pst->top = 0;  //表示top指向栈顶元素的下一个元素位置。
	//主要区别,先++top还是先赋值的区别,根据自己合理即可
}

//栈销毁
void STDestory(ST* pst)
{
	assert(pst);
	free(pst->a);
	pst->a = NULL;
	pst->capacity = pst->top = 0;
}

//入栈
void STPush(ST* pst, STDatatype x)
{
	assert(pst);
	//检查容量,扩容
	if (pst->top == pst->capacity)
	{
		int newcapacity = pst->capacity == 0 ? 4 : pst->capacity * 2;
		STDatatype* tmp = (STDatatype*)realloc(pst->a, sizeof(STDatatype) * newcapacity);
		if (tmp == NULL)
		{
			perror("realloc fail");
			return;
		}
		pst->a = tmp;
		pst->capacity = newcapacity;
	}
	//入栈
	pst->a[pst->top] = x;
	pst->top++;
}

//出栈
void STPop(ST* pst)
{
	assert(pst);
	//判空
	assert(pst->top > 0);
	pst->top--;
}

//获取栈顶元素
STDatatype STTop(ST* pst)
{
	assert(pst);
	//判空
	assert(pst->top > 0);
	return pst->a[pst->top - 1];//注意此时top表示栈顶元素的下一个,所以需要减1
}

//栈空判定
bool STEmpty(ST* pst)
{
	assert(pst);
	//直接以判断返回即可
	return pst->top == 0;
}

//获取栈的大小
int STSize(ST* pst)
{
	assert(pst);
	return pst->top;
}

typedef struct
{
	ST pushst;
	ST popst;
}MyQueue;

MyQueue* myQueueCreate()
{
	MyQueue* obj = (MyQueue*)malloc(sizeof(MyQueue));
	//初始化栈
	STInit(&obj->pushst);//注意传地址与二级指针的传参应用
	STInit(&obj->popst);//类型与接口类型相同,所以这里传地址
	return obj;
}

void myQueuePush(MyQueue* obj,int x)
{
	//一个栈入栈,一个栈入出栈的元素再出栈
	//所以这里栈只管入栈元素就行
	STPush(&obj->pushst, x);
}

int myQueuePeek(MyQueue* obj);

int myQueuePop(MyQueue* obj)
{
	int front = myQueuePeek(obj);//因为peek只返回栈顶元素,不会删除操作,所以直接利用返回值进行popQueue操作
	STPop(&obj->popst);//取得栈顶元素就出元素(即删除),没有元素就返回的空
	return front;
}

int myQueuePeek(MyQueue* obj)//返回对头的数据,不删除元素
{
	//写法1:
	//if (!STEmpty(&obj->popst))//如果popst不为空,那么直接出数据
	//{
	//	return STTop(&obj->popst);
	//}
	//else//popst不为空,那么导数据
	//{
	//	//倒数据
	//	while (!STEmpty(&obj->pushst))//当push不为空,就导数据
	//	{
	//		STPush(&obj->popst, STTop(&obj->pushst));
	//		STPop(&obj->pushst);
	//	}
	//	//否则,pushst为空就出popst
	//	return STTop(&obj->popst);
	//}

	//写法二:
	if (STEmpty(&obj->popst))//如果popst为空,那么直接导数据
	{
		//倒数据
		while (!STEmpty(&obj->pushst))//当push不为空,就导数据
		{
			STPush(&obj->popst, STTop(&obj->pushst));//将pushst的栈顶元素取出,入popst的栈。
			STPop(&obj->pushst);//然后删除pushst元素
		}
	}
	//否则,pushst为空就出popst
	return STTop(&obj->popst);//反正出数据都是出的popst
}

bool myQueueEmpty(MyQueue* obj)
{
	//两边栈都为空,队列才为空
	return STEmpty(&obj->pushst) && STEmpty(&obj->popst);
}

void myQueueFree(MyQueue* obj)
{
	//两个栈都销毁
	STDestory(&obj->pushst);
	STDestory(&obj->popst);
	free(obj);
	obj = NULL;
}

int main()
{
	MyQueue* pst = myQueueCreate();
	myQueuePush(pst, 1);
	myQueuePush(pst, 2);
	myQueuePush(pst, 3);
	myQueuePush(pst, 4);
	myQueuePush(pst, 5);

	while (!myQueueEmpty(pst))
	{
		printf("%d ", myQueuePeek(pst));
		myQueuePop(pst);
	}
	myQueueFree(pst);
	return 0;
}

3.3、练习题3:设计循环队列

练习题3:设计循环队列 -- 满足队列的先进先出的原则,其次对尾与队头被连接后形成一个循环。

注意的是,循环队列满了,就不能再入队了,删除同样需要判断是否为空了。

主要考察队头队尾的关系运算。

怎么判空?

队头等于队尾为空

怎么判满?

1.增加一个size,front = back & & size=0为空,size!=0,不为空

2.对开一个位置(解决假溢出,或者说无法区分空和满的问题),始终保持一个位置空置,当k为队列长度(数据个数),k+1为空间个数,此时(back+1)%(k+1) == front就是满
总结 :front = back就是空 back的下一个 = front就是满
思路1 :以开辟多一个空间的数组实现,front指向头,back指向队尾的下一个(不建议指向队尾,不好操作),关键在于取模运算
思路2:链表实现,front==back就是空,back->next == front就是满,但是相比数组较为复杂(唯独就是队尾不好取,单链表无法回溯,要么增加一个指针,或者双向链表)

c 复制代码
#include "Queue.h"

typedef struct
{
	int* a;    //数组
	int front; //头
	int back;  //尾
	int k;     //队内元素个数或队列大小
}MyCircularQueue;

MyCircularQueue* myCircularQueueCreate(int k)
{
	MyCircularQueue* obj = (MyCircularQueue*)malloc(sizeof(MyCircularQueue));
	//循环队列的初始化
	obj->a = (int*)malloc(sizeof(int) * (k + 1));//开辟元素个数加1个空间
	obj->front = 0;
	obj->back = 0;
	obj->k = k;
	return obj;
}

bool myCircularQueueIsFull(MyCircularQueue* obj);

bool myCircularQueueEnQueue(MyCircularQueue* obj, int value)
{
	if (myCircularQueueIsFull(obj))
	{
		return false;
	}
	obj->a[obj->back] = value;
	obj->back++;
	//循环处理
	obj->back %= (obj->k + 1);
	//obj->k++;//队列的大小是固定的,不是size
	return true;
}

bool myCircularQueueIsEmpty(MyCircularQueue* obj);

bool myCircularQueueDeQueue(MyCircularQueue* obj)
{
	if (myCircularQueueIsEmpty(obj))
	{
		return false;
	}
	++obj->front;
	//循环处理
	obj->front %= (obj->k + 1);
	return true;
}

bool myCircularQueueIsEmpty(MyCircularQueue* obj)
{
	return obj->front == obj->back;//队头等于队尾就为空,要注意的是此时的队尾是指向多开的空间地址
}

bool myCircularQueueIsFull(MyCircularQueue* obj)
{
	return (obj->back + 1) % (obj->k + 1) == obj->front;//队尾的下一个==front就是满
}

int myCircluarQueueFront(MyCircularQueue* obj)//取队头
{
	if (myCircularQueueIsEmpty(obj))
		return -1;
	return obj->a[obj->front];
}

//写法1:
//int myCircularQueueRear(MyCircularQueue* obj)//取队尾
//{
//	//return obj->a[obj->back - 1];//直接取出这种属于,情况没有考虑到位,会涉及到越界的问题
//	//两种处理方法:单独判断一下即可
//	if (obj->back == 0)
//		return obj->a[obj->k];//back被循环到头的位置,back==0,那么取队尾就是返回的k
//	else
//		return obj->a[obj->back - 1];//否则,其它情况就是满足返回back-1的位置
//}

//写法二:
int myCircularQueueRear(MyCircularQueue* obj)//取队尾
{
	if (myCircularQueueIsEmpty(obj))
		return -1;
	return obj->a[(obj->back - 1 + obj->k + 1) % (obj->k + 1)];
//back == 0 (0-1+4+1)%(4+1) = 4    处理back == 0 准备的  处理back>0准备的
	//满足上述条件,简化得出:
	//return obj->a[(obj->back + obj->k) % (obj->k + 1)];
}
//扩展:
//如何计算循环队列的数据个数:正常情况下,back-front
//满足特殊情况,考虑循环情况,back-front为负数,那么就是:(back+(k+1)-front)%(k+1)

void myCirCularQueueFree(MyCircularQueue* obj)
{
	free(obj->a);
	obj->a = NULL;
	free(obj);
	obj = NULL;
}

int main()
{
	MyCircularQueue* pst = myCircularQueueCreate(5);
	myCircularQueueEnQueue(pst, 1);
	myCircularQueueEnQueue(pst, 2);
	myCircularQueueEnQueue(pst, 3);
	myCircularQueueEnQueue(pst, 4);
	myCircularQueueEnQueue(pst, 5);

	if (!myCircularQueueIsEmpty(pst))
	{
		printf("%d ", myCircluarQueueFront(pst));
		printf("%d ", myCircularQueueRear(pst));
	}
	myCircularQueueDeQueue(pst);//头删
	if (!myCircularQueueIsEmpty(pst))
	{
		printf("%d ", myCircluarQueueFront(pst));
		printf("%d ", myCircularQueueRear(pst));
	}

	myCirCularQueueFree(pst);
	return 0;
}
相关推荐
JSU_曾是此间年少11 分钟前
数据结构——线性表与链表
数据结构·c++·算法
sjsjs1118 分钟前
【数据结构-合法括号字符串】【hard】【拼多多面试题】力扣32. 最长有效括号
数据结构·leetcode
密码小丑1 小时前
11月4日(内网横向移动(一))
笔记
朱一头zcy1 小时前
C语言复习第9章 字符串/字符/内存函数
c语言
此生只爱蛋1 小时前
【手撕排序2】快速排序
c语言·c++·算法·排序算法
blammmp1 小时前
Java:数据结构-枚举
java·开发语言·数据结构
何曾参静谧1 小时前
「C/C++」C/C++ 指针篇 之 指针运算
c语言·开发语言·c++
鸭鸭梨吖2 小时前
产品经理笔记
笔记·产品经理
昂子的博客2 小时前
基础数据结构——队列(链表实现)
数据结构
齐 飞2 小时前
MongoDB笔记01-概念与安装
前端·数据库·笔记·后端·mongodb