数据结构-队列及其链表方式实现

目录

1.队列的概念

二.队列的实现

1.基本结构

2.常见接口

1.QueueInit函数-对队列结构进行初始化

2.QueuePush函数-队尾入队列

3.QueuePop函数-队头出数据

4.QueueBack函数-获取队尾元素

5.QueueSize函数-获取队列有效元素个数

6.QueueEmpty函数-判断队列是否为空

7.QueueDestroy函数-队列的销毁


1.队列的概念

队列:只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出FIFO(First In First Out)。
入队列:进行插入操作的一端称为 队尾
出队列:进行删除操作的一端称为 队头
数据结构-队列及其链表方式实现_第1张图片

 

二.队列的实现

队列也可以用顺序表链表的结构实现,但是由于需要进行一端插入一端删除的操作,如果使用数组的结构,在顺序表头上出数据,需要移动n-1个数据,时间复杂度为O(n),效率会比较低。因此使用链表的结构实现更优一些。我们知道,链表对头部进行操作时间复杂度为O(1),对尾部操作则需要进行‘找尾’操作遍历链表,时间复杂度为O(n)。那么如何在队列实现中提高效率呢?下面我们介绍队列实现的结构。

1.基本结构

typedef int QDataType;

// 链式结构:表示队列 
typedef struct QListNode
{
	struct QListNode* _next;
	QDataType _data;
}QNode;

// 队列的结构 
typedef struct Queue
{
	QNode* _front;	//头指针
	QNode* _rear;	//尾指针
}Queue;

其中STDataType为重定义数据类型,这样定义有利于我们通过简便的修改,达到使栈存放不同类型数据的目的。

QNode为链表结点的基础结构,变量_data用于存放数据,而_next为指向下一个结点的指针。

Queue用于对队列进行管理,其头指针_front指向链表头结点,尾指针_rear指向链表尾结点。利用此结构存储头尾结点,即解决了前文尾部操作效率的问题,使其时间复杂度变为O(1)。

2.常见接口

// 初始化队列 
void QueueInit(Queue* q);
// 队尾入队列 
void QueuePush(Queue* q, QDataType data);
// 队头出队列 
void QueuePop(Queue* q);
// 获取队列头部元素 
QDataType QueueFront(Queue* q);
// 获取队列队尾元素 
QDataType QueueBack(Queue* q);
// 获取队列中有效元素个数 
int QueueSize(Queue* q);
// 检测队列是否为空,如果为空返回非零结果,如果非空返回0 
int QueueEmpty(Queue* q);
// 销毁队列 
void QueueDestroy(Queue* q);

1.QueueInit函数-对队列结构进行初始化

这里需要我们先创建一个队列结构变量,再调用QueueInit函数传入参数对其进行初始化。同理这里也可以实现为QueueCreate函数,即在函数内利用动态内存开辟完成对队列空间指针的创建,并在进行初始化后返回该指针,这里不多赘述,此处介绍前者。
void QueueInit(Queue* q)
{
	assert(q);

	q->_front = NULL;
	q->_rear = NULL;
}

先进行断言防止传入指针为空,然后初始化其内部变量指针为NULL。

2.QueuePush函数-队尾入队列

void QueuePush(Queue* q, QDataType data)
{
	assert(q);

	QNode* newNode = (QNode*)malloc(sizeof(QNode));
	if (newNode == NULL)
	{
		printf("malloc error\n");
		exit(-1);
	}
	newNode->_data = data;
	newNode->_next = NULL;

	if (q->_front == NULL)
	{
		q->_front = newNode;
		q->_rear = newNode;
	}
	else
	{
		q->_rear->_next = newNode;
		q->_rear = newNode;
	}
}

要插入新数据,则要通过动态内存分配创建新结点指针newnode用于存放数据,当然还需要对开辟是否成功进行判断,若失败,则打印错误信息。若成功,则先初始化新结点,然后进行尾插。此处尾插和链表插入操作相同,不进行赘述。只需注意若队列为空,插入时将newnode赋值给_front和_rear,若队列不为空,则只需将新结点插入尾部,并更新尾部指针_rear。

3.QueuePop函数-队头出数据

void QueuePop(Queue* q)
{
	assert(q);
	assert(q->_front && q->_rear);

	QNode* next = q->_front->_next;
	free(q->_front);
	q->_front = next;
	if (q->_front == NULL)
		q->_rear = NULL;
}

此处需要防止队列为空,故进行断言。然后进行头删操作即可,这也在链表里介绍过。只需额外注意若队列只有一个数据进行头删,则需要将_rear置为空。下面图解说明这一点:

数据结构-队列及其链表方式实现_第2张图片

如图,若队列大于1个元素,则释放p1结点,_front指向下一个元素即可。

数据结构-队列及其链表方式实现_第3张图片

 然而按照上面的逻辑,当队列只有一个元素时,我们发现_rear指针还指向已被释放的空间,此时该指针便成了野指针,因此需要我们进行置空操作。

4.QueueBack函数-获取队尾元素

QDataType QueueBack(Queue* q)
{
	assert(q);
	assert(q->_front && q->_rear);

	return q->_rear->_data;
}

断言后返回队尾数据即可。

5.QueueSize函数-获取队列有效元素个数

int QueueSize(Queue* q)
{
	assert(q);
	QNode* cur = q->_front;
	int count = 0;
	while (cur != NULL)
	{
		count++;
		cur = cur->_next;
	}
	return count;
}

 由于链表内存空间的不连续,想要获取队列元素个数,只能对链表进行遍历。以cur为变量,头结点为初值开始遍历结点,直到cur为NULL时跳出循环,此处用变量count计数,遍历完成后return count。

6.QueueEmpty函数-判断队列是否为空

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

	return q->_front == NULL;
}

条件表达式_front==NULL,若为真,则说明队列为空,返回bool类型值True。否则为假,队列不为空,返回False。

7.QueueDestroy函数-队列的销毁

void QueueDestroy(Queue* q)
{
	assert(q);

	QNode* cur = q->_front;
	while (cur != NULL)
	{
		QNode* next = cur->_next;
		free(cur);
		cur = next;
	}
	q->_front = NULL;
	q->_rear = NULL;
}

遍历链表对结点进行依次释放,释放完成后将Queue内变量置为初始值即可。

队列介绍到此结束,感谢阅读!

你可能感兴趣的:(c++,开发语言,数据结构,c语言,链表)