队列的实现及相关操作

队列

队列的概念

队列,是一种特殊的线性表,只允许在一端进行插入数据操作,在另一端进行删除,进行插入操作的一端称为队尾出队列:进行删除操作的一端称为队头。队列的数据遵守先进后出的原则

队列就和我们日常生活中的排队一样,讲究先来后到

队列的实现

队列可以用数组或链表的结构实现。不过使用链表的结构实现更优一些,因为如果使用数组的结构,出队列在数组头上出数据,效率会比较低。

这里我们使用单链表即可,带不带头无所谓

队列的定义
typedef int QDataType;

//定义队列链表的结点
typedef struct QListNode
{
	struct QListNode* next;
	QDataType data;
}QNode;

//定义队列
typedef struct Queue
{
	QNode* head;
	QNode* tail;
	int size;
}Queue;

主体结构是struct Queue,这是我们定义的队列,由指向头尾结点的两个指针和队列长度三个要素组成

//在单链表中是没有这个结构体的。在这里存在的意义是队列需要头尾两个指针,所以就定义了这个结构体。单链表中只要指向头结点的指针即可

指向头尾结点的指针,分别用于实现头删和尾插

这里size存在的意义是:以空间换时间。如果完全依靠QueueSize来获得队列长度的话,必须要遍历队列。而我们现在定义一个size变量,当push或pop时改变。最后在得到队列长度时只需返回size即可,不再需要遍历

然后队列存储数据的主体形式是单链表,所以又定义了结点struct QListNode

队列的初始化
void QueueInit(Queue* qq)
{
	assert(qq);

	qq->head = NULL;
	qq->tail = NULL;
	qq->size=0;
}
队尾入队列
void QueuePush(Queue* qq, QDataType val)
{
	assert(qq);

	//先新建结点
	QNode* newnode = (QNode*)malloc(sizeof(QNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}
	newnode->data = val;
	newnode->next = NULL;

	//队尾入队列
	if (qq->head == NULL)
	{
		qq->head = qq->tail = newnode;
	}
	else
	{
		qq->tail->next = newnode;
		qq->tail = newnode;
	}

	qq->size++;
}

这里先要新建结点,再将其从队尾入队列

这里没必要像单链表那样还定义了一个BuyNode函数。整个队列只有这里需要新建结点,所以没必要再额外写个函数了

这里尾插(后续不用二级指针也同理),为什么不用传二级指针呢?因为我们将头指针放在了一个结构体中,然后将结构体地址传了过去。

队头出队列
void QueuePop(Queue* qq)
{
	assert(qq);
	//队列不能为空
	assert(!QueueEmpty(qq));

	if (qq->head->next == NULL)
	{
		free(qq->head);
		qq->head = qq->tail = NULL;
	}
	else
	{
		//保存队列的第二个节点的地址
		QNode* headnext = qq->head->next;

		free(qq->head);
		qq->head = headnext;
	}

	qq->size--;
}

这里要关注:head为空时,tail是否为空;或者tail为空时,head是否为空。如果不是,那说明代码出问题了。这一问题体现在代码中是这一句qq->head = qq->tail = NULL;当队列中最后一个结点被删除时,我们不能只将head置空,tail也需要置空

获取队头数据
QDataType QueueFront(Queue* qq)
{
	assert(qq);
	//队列不能为空
	assert(!QueueEmpty(qq));

	return qq->head->data;
}
获取队尾数据
QDataType QueueBack(Queue* qq)
{
	assert(qq);
	//队列不能为空
	assert(!QueueEmpty(qq));

	return qq->tail->data;
}
判断队列是否为空
bool QueueEmpty(Queue* qq)
{
	assert(qq);

	return qq->head == NULL && qq->tail == NULL;
}
返回队列有效长度
int QueueSize(Queue* qq)
{
	assert(qq);

	return qq->size;
}
销毁队列
void QueueDestroy(Queue* qq)
{
	assert(qq);

	QNode* cur = qq->head;
	while (cur)
	{
		QNode* curnext = cur->next;
		free(cur);
		cur = curnext;
	}
	
	qq->head = qq->tail = NULL;
	qq->size = 0;
}

链表的销毁要通过遍历找到每一个结点,再销毁

再次说一下对malloc的空间置空的问题。如果被释放的空间别人可以访问到则需要置空,如果别人访问不到了,置不置空就无所谓了

所以队列的头尾指针一定要置空,但中间的就无所谓了

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值