数据结构之队列

Posted 捕获一只小肚皮

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了数据结构之队列相关的知识,希望对你有一定的参考价值。

前言

陆陆续续的,我们已经学完了顺序表,单链表,双链表以及栈.今天,博主更新的内容就是数据结构中的队列.


1. 何为队列


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

允许插入数据操作的一端叫做队尾

允许删除数据操作的一端叫做队头

而删除操作叫做出队,插入操作叫做入队

上面的所有特性,博主用下面一张图给大家演示:

2. 怎样实现队列


既然想要实现队列,我们就需要根据需求抓取供应.

那么我们队列的主要需求是什么呢? 没错,答案就是 入队出队

入队,毫无疑问是尾插操作,用顺序表实现非常方便.

出队,毫无疑问是头删操作,用链表实现非常方便.

也就是说只考虑目前操作的好坏,链表和顺序组持平,那我们再考虑更优可能性吧,嗯~~~.链表比顺序表跟节约空间

所以我们选择用链表实现队列

但是用链表实现的话,尾插操作就比较麻烦了(需要遍历到尾部),怎么解决这个麻烦呢,这里采用再开辟一个结构体,用来包含链表结点,新的结构体中只有头尾指针,代码实现请看下一标题


3. 项目搭建


博主这里用的VS2019,就用它进行演示:

先建立Queue.h,Queue.ctest.c三个文件

Queue.h的作用是写文件引用,函数声明

Queue.c的作用是写文件函数的定义

test.c的作用是测试所写操作是否正确


4. 定义队列


在2标题内容下,已经说明了实现队列最好选择链表链式结构,并单独开一个结构体进行包含,所以下面就是开始这样的实现


typedef int QDataType;

typedef struct QueueNode  //定义队列结点
{
    QDataType data;
    struct QueueNode* next;
}QueueNode;

typedef struct Queue   //定义队列
{
    QueueNode* head;  //队头
    QueueNode* tail;  //队尾
}Queue;

5. 队列的所有操作

对于队列来说,我们用到的操作最多的就是下面几种:

入队(尾插), 出队(头删),取队头元素,取队尾元素,判空,获取队列元素数量,初始化和销毁空间

所以,博主先在Queue.h文件中声明所有方法,后续再详细实现

void QueueInit(Queue* pq);
bool QueueEmpty(Queue* pq);

void QueuePush(Queue* pq, QDataType x);
void QueuePop(Queue* pq);
QDataType QueueFront(Queue* pq);
QDataType QueueBack(Queue* pq);
int QueueSize(Queue* pq);
void QueueDestory(Queue* pq);

5.1 队列之初始化

我们创建一个队列后,队列的头尾指针还是野指针,所以我们将其初始化为NULL.

void QueueInit(Queue* pq)
{
    assert(pq);
    pq->head = pq->tail = NULL;
}

测试是否成功:

成功!!


5.2 队列之判空

判空,即判断队列是否为空,怎么判断呢? 只要头指针没有指向任何空间就说明队列为空

bool QueueEmpty(Queue* pq)
{
    assert(pq);
    
    return pq->head == NULL;
}

5.3 队列之入队

入队,即尾插,在前面的链表和顺序章节中,我们对这个操作还是很熟悉的:

第一步,开辟一个新空间存储数据

第二步,tail->next指向新结点. (只是这里需要注意当链表为空时候)

void QueuePush(Queue* pq, QDataType elem)
{
    assert(pq);
    QueueNode* newnode = (QueueNode*)malloc(sizeof(QueueNode));
    if(newnode == NULL)
    {
        perror("空间申请失败原因:");
        exit(-1);
    }
    newnode->data = elem;
    newnode->next = NULL;
    
    //注意这里队列是否为空
    pq->head == NULL ? (pq->tail = newnode ):(pq->tail->next = newnode,pq->tail = pq->tail->next); 
}

测试:

成功!!!


5.4 队列之出队

很明显,出队即头删,对于头删操作我们也是比较熟悉的:

第一步,先保存下一个结点

第二步,释放头结点

第三步,指向所保存的下一个结点

void QueuePop(Queue* pq)
{
    assert(pq);
    assert(!QueueEmpty(pq)); //如果队列为空,不能删除
    
    QueueNode* next = pq->head->next;//保存下一个结点
    free(pq->head);//释放
    pq->head = next;//指向下一个结点
}

测试是否成功:

成功…???了吗,大家仔细看看上面的代码,想想哪里会出Bug.

没错,当这样出队到最后只剩下一个结点时候,tail将会是一个野指针,如下图:

就会发现,当剩下最后一个时候,tail还是指向了原来位置,形成一个野指针

修改代码:

void QueuePop(Queue* pq)
{
    assert(pq);
    assert(!QueueEmpty(pq)); //如果队列为空,不能删除
    
    QueueNode* next = NULL;
    
    pq->head->next == NULL?
    (free(pq->head),pq->head = pq->tail = NULL):
    (next = pq->head->next, free(pq->head),pq->head = next);//条件表达式
}

现在测试,才是真的成功


5.5 队列之获取队首

这个比较简单,直接返回即可

QDataType QueueFront(Queue* pq)
{
    assert(pq);
    assert(!QueueEmpty(pq)); //不能为空
    
    return pa->head->data;
}

5.6 队列之获取队尾

同理,直接返回

QDataType QueueBack(Queue* pq)
{
    assert(pq);
    assert(!QueueEmpty(pq)); //不能为空
    
    return pa->tail->data;
}

测试是否成功:

成功!!


5.7 队列之返回队列元素数量

这个直接开一个变量num,然后遍历队列,进行计数

int QueueSize(Queue* pq)
{
    assert(pq);
    int num = 0;
    QueueNode* cur = pq->head;
    while(cur)
    {
        num++;
        cur = cur->next;
    }
    return num;
}

5.8 队列之销毁空间

挨个销毁每个空间

void QueueDestory(Queue* pq)
{
	assert(pq);
	QueueNode* cur = pq->head;
	while (cur)
	{
		QueueNode* next = cur->next;
		free(cur);
		cur = next;
	}
	pq->head = pq->tail = NULL;
}

测试是否成功:

成功!!!


总代码

Queue.h头文件

#pragma once
#include<assert.h>
#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h>

typedef int QDataType;

typedef struct QueueNode  //定义队列结点
{
    QDataType data;
    struct QueueNode* next;
}QueueNode;

typedef struct Queue   //定义队列
{
    QueueNode* head;  //队头
    QueueNode* tail;  //队尾
}Queue;


void QueueInit(Queue* pq);

void QueueDestory(Queue* pq);

void QueuePush(Queue* pq, QDataType elem);

void QueuePop(Queue* pq);

int QueueSize(Queue* pq);

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

bool QueueEmpty(Queue* pq);

Queue.c源文件

#include "Queue.h"
void QueueInit(Queue* pq)
{
	assert(pq);

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

void QueuePush(Queue* pq, QDataType elem)
{
	assert(pq);
	QueueNode* newnode = (QueueNode*)malloc(sizeof(QueueNode));
	if (newnode == NULL)
	{
		perror("空间申请失败原因:");
		exit(-1);
	}
	newnode->data = elem;
	newnode->next = NULL;

	//注意这里队列是否为空
	pq->head == NULL ? 
	(pq->tail = pq->head = newnode) : (pq->tail->next = newnode, pq->tail = pq->tail->next);
}

void QueuePop(Queue* pq)
{
	assert(pq);
	assert(!QueueEmpty(pq)); //如果队列为空,不能删除

	QueueNode* next = NULL;

	pq->head->next == NULL ?
		(free(pq->head), pq->head = pq->tail = NULL) :
		(next = pq->head->next, free(pq->head), pq->head = next);//条件表达式
}


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

	int num = 0;
	QueueNode* cur = pq->head;
	while (cur)
	{
		num++;
		cur = cur->next;
	}
	return num;
}

QDataType QueueFront(Queue* pq)
{
	assert(pq);
	assert(!QueueEmpty(pq));
	return pq->head->data;
}

QDataType QueueBack(Queue* pq)
{
	assert(pq);
	assert(!QueueEmpty(pq));
	return pq->tail->data;
}
bool QueueEmpty(Queue* pq)
{
	assert(pq);
	return pq->head == NULL;
}

void QueueDestory(Queue* pq)
{
	assert(pq);
	QueueNode* cur = pq->head;
	while (cur)
	{
		QueueNode* next = cur->next;
		free(cur);
		cur = next;
	}
	pq->head = pq->tail = NULL;
}

以上是关于数据结构之队列的主要内容,如果未能解决你的问题,请参考以下文章

数据结构之链式队列的代码实现及有趣应用

数据结构之链式队列的代码实现及有趣应用

perl中的队列

数据结构之栈和队列

数据结构之队列c代码实现

[数据结构]之队列