【数据结构】C语言实现队列(链式队列)

C语言实现队列(链式队列)

  • 一、队列
  • 二、链式队列的接口定义
  • 三、链式队列的实现
    • 3.1 初始化Init
    • 3.2 销毁Destroy
    • 3.3 队列判空empty
    • 3.4 入队push-EnQueue
    • 3.5 出队pop-DeQueue
    • 3.6 队首front
    • 3.7 队尾back
  • 源码
  • 总结


一、队列

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

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

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


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

链式队列:链表的头当做队头,链表的尾当做队尾,这样在尾部插入与在头部删除比较简单。


我们使用无头、单向、非循环链表来实现链式队列,因为队列需要频繁的队头和队尾操作,所以我们定义头指针和尾指针。

因为我们使用的是无头链表实现,所以插入时需要对空链表单独处理,删除时,需要对只有一个元素的情况单独处理。

二、链式队列的接口定义

结点定义:因为使用单链表实现队列,所以队列结点与单链表结点相同。

typedef int QDataType;
typedef struct QueueNode
{
	struct QueueNode* next;
	QDataType data;
}QNode;

由于频繁对队头、队尾进行操作,所以有head和tail两个指针:

typedef struct Queue
{
	QNode* phead;
	QNode* ptail;
	int size;
}Queue;

我们使用size变量记录队列中的元素个数,这样可以直接返回该值,但在插入和删除时要维护该size的值。如果不加size成员,就需要遍历链表,累计size:

int QueueSize(Queue* pq)
{
	assert(pq);

	return pq->size;
}

三、链式队列的实现

3.1 初始化Init

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

	pq->phead = NULL;
	pq->ptail = NULL;
	pq->size = 0;
}

3.2 销毁Destroy

void QueueDestroy(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;
}

3.3 队列判空empty

可以通过头尾指针都为空来判断队列是否为空,也可以通过队列的元素size是否等于0来判断队列是否为空。

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

	/*return pq->phead == NULL
		&& pq->ptail == NULL;*/
	return pq->size == 0;
}

3.4 入队push-EnQueue

队尾入队,也就是对链表的尾插。

单链表进行尾插时需要找到尾结点,由于这里我们使用ptail尾指针就是指向尾结点,因此找尾这一步省去了。

找到尾结点后需要插入到尾结点后,值得说的是,由于插入时单链表可能为空,因此需要对为空的情况单独处理:

void QueuePush(Queue* pq, QDataType x)
{
	assert(pq);

	// 待插入结点
	QNode* newnode = (QNode*)malloc(sizeof(QNode));
	if (newnode == NULL)
	{
		perror("malloc fail\n");
		return;
	}
	newnode->data = x;
	newnode->next = NULL;

	if (pq->ptail == NULL)
	{
		// 1.链表为空
		assert(pq->phead == NULL);

		pq->phead = pq->ptail = newnode;
	}
	else
	{
		// 2.链表不为空直接插入尾结点后
		pq->ptail->next = newnode;
		pq->ptail = newnode;
	}

	pq->size++;
}

3.5 出队pop-DeQueue

队首出队,也就是链表的头删。

删除时需要判空处理,队列中至少有一个元素才能进行删除,我们可以使用断言实现。

对于没有尾指针的单链表头删非常简单,只需要将头指针指向头结点下一个结点。

但是我们使用单链表实现队列时,附加了尾指针,这时链表的头删还需要考虑尾指针,直白的说就是在删除最后一个元素时,还要修改尾指针的指向。

void QueuePop(Queue* pq)
{
	assert(pq);
	assert(!QueueEmpty(pq));

	if (pq->phead->next == NULL)
	{
		// 删除最后一个元素时修改尾指针指向
		free(pq->phead);
		pq->phead = pq->ptail = NULL;
	}
	else
	{
		// 正常头删
		QNode* next = pq->phead->next;
		free(pq->phead);
		pq->phead = next;
	}

	pq->size--;
}

3.6 队首front

头指针phead指向头结点,便找到链表第一个元素,也就是队首元素。

QDataType QueueFront(Queue* pq)
{
	assert(pq);
	assert(!QueueEmpty(pq));

	return pq->phead->data;
}

3.7 队尾back

尾指针指向尾结点,便找到链表最后一个元素,也就是队尾元素。

QDataType QueueBack(Queue* pq)
{
	assert(pq);
	assert(!QueueEmpty(pq));

	return pq->ptail->data;
}

源码

Gitee-Queue

总结

链式队列的实现本质上是对单链表进行操作。

入队和出队分别对应单链表的尾插和头删。因为是无头单向非循环的单链表,因此尾插时需要对空链表单独处理。又因为我们需要维护尾指针,因此头删时,删除最后一个元素时还需要对尾指针单独处理。这些在上面代码中都有所体现。

你可能感兴趣的:(C语言数据结构,数据结构,c语言,队列)