DS线性表之队列的讲解和实现(5)

文章目录

  • 前言
  • 一、队列的概念及结构
  • 二、队列的实现
    • 队列节点和队列
    • 初始化
    • 销毁
    • 判断是否为空
    • 入队列
    • 出队列
    • 获取队头队尾数据
    • 获取队列元素个数
  • 三、实际使用效果
  • 总结


前言

  队列实现源代码
  队列是我们遇到的第二个实用数据结构,栈和队列地位等同


一、队列的概念及结构

  队列是指只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表。队列具有先进先出 FIFO(First In First Out) 这一点跟栈的先进后出是相反的

队列的核心操作有两个:

  • 入队列:即尾插,进行插入操作的一端称为队尾
  • 出队列:即头删,进行删除操作的一端称为对头

在这里插入图片描述

队列可以考虑顺序表和链表来实现,但是因为队列是一种一端插入一端删除的数据结构,所以最好的实现方式是头删尾插效率高的方式

  1. 顺序表实现:尾插和尾删效率较高,但是头插和头删效率低,因此不太适合
  2. 单链表实现:头删尾插效率较高,链表头删,链表尾插
  3. 双向链表实现:头插头删尾插尾删效率都较高,可以实现队列

照这么看,似乎双向链表是最完美的答案

但是实际上,我们是采用第二种单链表来实现队列,你可能会差异,单链表要遍历一遍才能到达尾节点,而双向链表可以在常量操作内访问到尾节点,为什么我们还会采用单链表来实现队列呢?

答案是我们定义一个队列节点结构体的前提下,在定义一个队列的结构体,里面放着两个队列节点指针front、tail,这样双向链表这里就显得过于复杂,且多开辟了一个头节点,浪费了一点空间,也是不太适合的一种体现

二、队列的实现

  好,掌握了以上知识后,我们来立刻实现!

队列节点和队列

就像前文说的,我们定义两个结构体来实现队列
大致有点像下图:
在这里插入图片描述

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

typedef struct Queue
{
    QNode* head;
    QNode* tail;

	// 根据需要甚至还可以定义一个大小变量
	// int size;
}Queue;

初始化

首先我们肯定要先验空,确保我们确确实实实例化了一个队列结构体q,此时q内部的两个指针,我们需要先置空

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

    pq->head = pq->tail = NULL;
}

销毁

动态开辟的内存,需要手动释放,且由于是链表,所以需要一个一个循环释放
因此思路就是通过一个指针变量cur,一直销毁直到指向为空
在这里插入图片描述

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

    QNode* cur = pq->head;
    while (cur != NULL){
        QNode* next = cur->next;
        free(cur);
        cur = next;
    }
    pq->head = pq->tail = NULL; // 头尾节点指针所指向的内容已被回收,因此不置空将成为野指针
}

判断是否为空

我们看初始化函数就知道,当队列为空的时候,头节点指针为空指针(其实尾节点指针也是~),我们可以通过这个来判断

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

    return pq->head == NULL;
}

入队列

入队就是尾插
在这里插入图片描述
但是我们要分两种情况,一种是队列有数据,另外一种是队列没有数据的时候,这时候头尾节点指针都指向这个新开辟的节点;如果队列已经有数据了,这时候尾节点指针发生变化

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

    QNode* newNode = (QNode*)malloc(sizeof(QNode));
    if (newNode == NULL){
        printf("malloc fail\n");
        exit(-1);
    }

    newNode->data = val;
    newNode->next = NULL;

    if (pq->tail == NULL){ // 如果无数据,用pq->head来判断也行
        pq->head = pq->tail = newNode;
    }
    else {
        pq->tail->next = newNode;
        pq->tail = newNode; // 此时newNode就是新的尾节点
    }
}

出队列

出队就是头删
在这里插入图片描述

void QueuePop(Queue* pq)
{
    assert(pq);
    assert(pq->head);

    if (pq->head->next == NULL) // 只有一个节点的时候
    {
        free(pq->head);
        pq->head = pq->tail = NULL;
    }
    else { // 当有多个节点的时候
        QNode* next = pq->head->next;
        free(pq->head);
        pq->head = next;
    }
}

获取队头队尾数据

对于队列,我们是有权限来访问队头队尾两个元素,就像栈也有权限访问栈顶元素一样(但是栈底不行!)

QDataType QueueFront(Queue* pq)
{
    assert(pq);
    assert(pq->head);

    return pq->head->data;
}

QDataType QueueBack(Queue* pq)
{
    assert(pq);
    assert(pq->head);

    return pq->tail->data;
}

获取队列元素个数

其实你的队列如果有size变量的话,那这里应该蛮简单,只是我这里提供的没有,那么就是经典的遍历套路

int QueueSize(Queue* pq)
{
	assert(pq);
	
	int size = 0;
	QNode* cur = pq->head;
	while (cur != NULL)
	{
		size++;
		cur = cur->next;
	}
	
	return size;
}

三、实际使用效果

// 各接口一览
void QueueInit(Queue* pq);
void QueueDestroy(Queue* pq);

void QueuePush(Queue* pq, QDataType val);
void QueuePop(Queue* pq);

QDataType QueueFront(Queue* pq);
QDataType QueueBack(Queue* pq);
int QueueSize(Queue* pq);

bool isQueueEmpty(Queue* pq);

在这里插入图片描述


总结

  栈和队列,将会贯穿我们后面的学习,所以,请对它们有深刻的认识

上一篇:树莓派应用--AI项目实战篇来啦-12.OpenCV摄像头云台物体追踪


下一篇:Anaroute - 理论学习(一)-三、Routing frame work