C语言超详细讲解队列的实现及代码

目录
  • 前言
    • 队列的概念
    • 队列的结构
    • 队列的应用场景
  • 队列的实现
    • 创建队列结构
    • 队列初始化
    • 队列销毁
    • 入队列
    • 出队列
    • 队列判空
    • 获取队列元素个数
    • 获取队列头部元素
    • 获取队列尾部元素
  • 总代码
    • Queue.h 文件
    • Queue.c 文件
    • Test.c 文件

前言

队列的概念

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

队列和前文所学的栈还是有一定区别的,队列明确指出先进先出。假如说一个队列的入队顺序为A B C D,那么出队顺序一定为A B C D,因为无论你是在A进去再出来,然后B进去再出来接着CD进去再出来或者类似的,都不会影响它最终的出队顺序A B C D。这点和栈还是有区别的,毕竟栈是后进先出。

队列的结构

队列的应用场景

队列:

  • 公平排队
  • 广度优先遍历 ……

栈:

  • 解决括号匹配
  • 逆波兰表达式求解
  • 递归改非递归 ……

队列的实现

  • 在实现之前,首先得考虑用哪种结构好,是用数组结构还是链式结构呢?上文的栈我们使用的是数组结构,难道队列也要用吗?
  • 其实不然。应该使用链式结构。前文栈删除数据不需要挪动数据,使用数组结构即可满足需求,而队列在删除数据时需要把后面的数据挪到前面,使用链式结构非常容易实现,只需改变节点指向即可,而数组结构想要实现挪动数据则非常麻烦。综上,使用链式结构是最优的。此外,单链表即可满足需求,不需要使用其余较为复杂的链式结构。

创建队列结构

思路:

这里要定义两个结构体,除了要定义1个链式结构来记录各个节点外,还要定义一个结构来记录队头和队尾。以此方便后续的队尾入数据,队头出数据。

Queue.h 文件:

//创建队列结构
typedef int QDataType; //方便后续更改存储数据类型,本文以int为例
 //创建队列节点
typedef struct QueueNode
{
	QDataType data; //存储数据
	struct QueueNode* next; //记录下一个节点
}QNode;
 //保存队头和队尾
typedef struct Queue
{
	QNode* head; //头指针
	QNode* tail; //尾指针
}Queue;

队列初始化

思路:

队列可以为空,但是管理头指针和尾指针的结构体不能为空,所以一开始就要断言。其次,在插入数据前,队列肯定是空的,所以直接把头指针和尾指针置空即可。

Queue.h 文件:

//初始化队列
void QueueInit(Queue* pq);

Queue.c 文件:

//初始化队列
void QueueInit(Queue* pq)
{
	assert(pq);
	pq->head = pq->tail = NULL;
}

队列销毁

思路:

销毁队列就是把队列的每个数据都销毁掉,那么需要遍历链表进行挨个销毁free。首先定义一个cur指针为pq->head,用来保存第一个数据,遍历cur,如果不为空,就free。最后把tail和head置空即可。

Queue.h 文件:

//销毁队列
void QueueDestory(Queue* pq);

Queue.c 文件:

//销毁队列
void QueueDestory(Queue* pq)
{
	assert(pq);
	QNode* cur = pq->head;
	while (cur)
	{
		QNode* next = cur->next;
		free(cur);
		cur = next;
	}
	pq->head = pq->tail = NULL;
}

入队列

思路:

入队列其实很简单,只需要尾插即可,首先要新创建一个节点来保存新插入的数据。但是在尾插之前要考虑如果一开始队列没有数据,为空,那么只需要把head和tail节点指向新节点newnode节点即可。相反的,如果一开始就有数据,那么只需正常尾插把tail的next指向新节点newnode,再把newnode赋给tail即可。

Queue.h 文件:

//入队列
void QueuePush(Queue* pq, QDataType x);

Queue.c 文件:

//入队列
void QueuePush(Queue* pq, QDataType x)
{
	assert(pq);
	//创建一个新节点保存数据
	QNode* newnode = (QNode*)malloc(sizeof(QNode));
	//暴力检测newnode,因为malloc的都要检测
	assert(newnode);
	newnode->next = NULL;
	newnode->data = x;
	//如果一开始没有数据,为空的情况
	if (pq->tail == NULL)
	{
		assert(pq->head == NULL);
		pq->head = pq->tail = newnode;
	}
	else
	{
		pq->tail->next = newnode;
		pq->tail = newnode;
	}
}

出队列

思路:

特殊情况:

这里在删除数据时,首先要考虑特殊情况,当删到只剩一个数据时,再删一次,此时数据是没了,不过head为空了,而tail变成野指针了,为了避免此现象的产生,单独讨论并置空head和tail即可。

一般情况:

此时只需要定义一个next指针保存head的下一个节点,将head移动到next即可,并把旧的head置空。

Queue.h 文件:

//出队列
void QueuePop(Queue* pq);

Queue.c 文件:

//出队列
void QueuePop(Queue* pq)
{
	assert(pq);
	assert(pq->head && pq->tail); //tail和head均不能为空
	//特殊:当删到head=tail的位置时
	if (pq->head->next == NULL)
	{
		free(pq->head);
		pq->head = pq->tail = NULL;
	}
	//一般情况
	else
	{
		//保存head的下一个节点
		QNode* next = pq->head->next;
		free(pq->head);
		pq->head = next;
	}
}

队列判空

思路:

如果head为空或者tail为空都是判空的条件,直接返回即可。

Queue.h 文件:

//判空
bool QueueEmpty(Queue* pq);

Queue.c 文件:

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

获取队列元素个数

思路:

求元素个数其实不难,只需要定义一个cur指针为第一个数据pq->head,定义变量size来记录个数。依次遍历cur,不为空,size就++。这种遍历的思想不复杂,但时间复杂度达到O(N),不是太好,想要O(1)的话可以直接在当初定义结构体时多定义一个size变量,专门用来记录有效元素个数,每次入队列size++,出队列size--。这样实现是比较好的,不过为了封装成一个独立模块,还是采用遍历的方式。如下:

Queue.h 文件:

//获取有效元素个数
size_t QueueSize(Queue* pq);

Queue.c 文件:

//获取有效元素个数
size_t QueueSize(Queue* pq)
{
	assert(pq);
	QNode* cur = pq->head;
	size_t size = 0;
	while (cur)
	{
		size++;
		cur = cur->next;
	}
	return size;
}

获取队列头部元素

思路:

首先要断言头部不能为空,如果头部都为空了,那还怎么能获得头部元素,其次直接返回头部head的数据即可。

Queue.h 文件:

//获取队头元素
QDataType QueueFront(Queue* pq);

Queue.c 文件:

//获取队头元素
QDataType QueueFront(Queue* pq)
{
	assert(pq);
	assert(pq->head); //头部不能为空
	return pq->head->data;
}

获取队列尾部元素

思路:

有了获取队头元素的经验,队尾就更简单了,把head换位tail即可,结构与上文一样。

Queue.h 文件:

//获取队尾元素
QDataType QueueBack(Queue* pq);

Queue.c 文件:

//获取队尾元素
QDataType QueueBack(Queue* pq)
{
	assert(pq);
	assert(pq->tail); //尾部不能为空
	return pq->tail->data;
}

总代码

Queue.h 文件

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

//创建队列结构
typedef int QDataType; //方便后续更改存储数据类型,本文以int为例
 //创建队列节点
typedef struct QueueNode
{
	QDataType data; //存储数据
	struct QueueNode* next; //记录下一个节点
}QNode;
 //保存队头和队尾
typedef struct Queue
{
	QNode* head; //头指针
	QNode* tail; //尾指针
}Queue;

//初始化队列
void QueueInit(Queue* pq);

//销毁队列
void QueueDestory(Queue* pq);

//入队列
void QueuePush(Queue* pq, QDataType x);

//出队列
void QueuePop(Queue* pq);

//判空
bool QueueEmpty(Queue* pq);

//获取有效元素个数
size_t QueueSize(Queue* pq);

//获取队头元素
QDataType QueueFront(Queue* pq);

//获取队尾元素
QDataType QueueBack(Queue* pq);

Queue.c 文件

#define _CRT_SECURE_NO_WARNINGS 1
#include"Queue.h"

//初始化队列
void QueueInit(Queue* pq)
{
	assert(pq);
	pq->head = pq->tail = NULL;
}

//销毁队列
void QueueDestory(Queue* pq)
{
	assert(pq);
	QNode* cur = pq->head;
	while (cur)
	{
		QNode* next = cur->next;
		free(cur);
		cur = next;
	}
	pq->head = pq->tail = NULL;
}

//入队列
void QueuePush(Queue* pq, QDataType x)
{
	assert(pq);
	//创建一个新节点保存数据
	QNode* newnode = (QNode*)malloc(sizeof(QNode));
	//暴力检测newnode,因为malloc的都要检测
	assert(newnode);
	newnode->next = NULL;
	newnode->data = x;
	//如果一开始没有数据,为空的情况
	if (pq->tail == NULL)
	{
		assert(pq->head == NULL);
		pq->head = pq->tail = newnode;
	}
	else
	{
		pq->tail->next = newnode;
		pq->tail = newnode;
	}
}

//出队列
void QueuePop(Queue* pq)
{
	assert(pq);
	assert(pq->head && pq->tail); //tail和head均不能为空
	//特殊:当删到head=tail的位置时
	if (pq->head->next == NULL)
	{
		free(pq->head);
		pq->head = pq->tail = NULL;
	}
	//一般情况
	else
	{
		//保存head的下一个节点
		QNode* next = pq->head->next;
		free(pq->head);
		pq->head = next;
	}
}

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

//获取有效元素个数
size_t QueueSize(Queue* pq)
{
	assert(pq);
	QNode* cur = pq->head;
	size_t size = 0;
	while (cur)
	{
		size++;
		cur = cur->next;
	}
	return size;
}

//获取队头元素
QDataType QueueFront(Queue* pq)
{
	assert(pq);
	assert(pq->head); //头部不能为空
	return pq->head->data;
}

//获取队尾元素
QDataType QueueBack(Queue* pq)
{
	assert(pq);
	assert(pq->tail); //尾部不能为空
	return pq->tail->data;
}

Test.c 文件

#define _CRT_SECURE_NO_WARNINGS 1
#include"Queue.h"
void TestQueue()
{
	Queue q;
	QueueInit(&q);
	//插入数据
	QueuePush(&q, 1);
	QueuePush(&q, 2);
	QueuePush(&q, 3);
	QueuePush(&q, 4);
	//打印
	while (!QueueEmpty(&q))
	{
		printf("%d ", QueueFront(&q));
		QueuePop(&q);
	}
	printf("\n");
}
int main()
{
	TestQueue();
	return 0;
}

到此这篇关于C语言超详细讲解队列的实现及代码的文章就介绍到这了,更多相关C语言 队列的实现内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • C语言数据结构进阶之栈和队列的实现

    目录 栈的实现: 一.栈的概念和性质 二.栈的实现思路 三.栈的相关变量内存布局图 四.栈的初始化和销毁 五.栈的接口实现: 1.入栈 2.出栈 3.获取栈顶的数据 4.获取栈的元素个数 5.判断栈是否为空 队列的实现: 一.队列的概念和性质 二.队列的实现思路 三.队列相关变量的内存布局图 四.队列的初始化和销毁 五.队列的接口实现: 1. 入数据 2.出数据 3.取队头数据 4.取队尾数据 5.获取队列元素个数 6.判断队列是否为空 总结 栈的实现: 一.栈的概念和性质 栈(stack)又名

  • C语言数据结构链表队列的实现

    C语言数据结构链表队列的实现 1.写在前面 队列是一种和栈相反的,遵循先进先出原则的线性表. 本代码是严蔚敏教授的数据结构书上面的伪代码的C语言实现代码. 分解代码没有包含在内的代码如下: #include <stdio.h> #include <stdlib.h> #define OK 1 #define ERROR 0 typedef int QElemtype; typedef int status; 2.代码分解 2.1对队列和节点的结构定义 typedef struct

  • C语言 队列的实现全解析

    目录 队列的实现 基本概念 创建结构体 初始化结构体 销毁队列结构体 入队 出队 判断队列是否为空 访问对头的值 访问队尾的值 返回队列的长度 Queue.h Queue.c Test.c “ 江天一色无纤尘,皎皎空中孤月轮. ” 队列的实现 基本概念 队列:只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出 FIFO(First In First Out) 入队列:进行插入操作的一端称为队尾 出队列:进行删除操作的一端称为队头 队列也可以数组和链表的结构实现,

  • C语言超详细讲解队列的实现及代码

    目录 前言 队列的概念 队列的结构 队列的应用场景 队列的实现 创建队列结构 队列初始化 队列销毁 入队列 出队列 队列判空 获取队列元素个数 获取队列头部元素 获取队列尾部元素 总代码 Queue.h 文件 Queue.c 文件 Test.c 文件 前言 队列的概念 队列:只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出FIFO(First In First Out) 入队列:进行插入操作的一端称为队尾 出队列:进行删除操作的一端称为队头 队列和前文所学的栈

  • C语言超详细讲解栈的实现及代码

    目录 前言 栈的概念 栈的结构 栈的实现 创建栈结构 初始化栈 销毁栈 入栈 出栈 获取栈顶元素 获取栈中有效元素个数 检测栈是否为空 总代码 Stack.h 文件 Stack.c 文件 Test.c 文件 前言 栈的概念 栈:一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作.进行数据插入和删除操作的一端称为栈顶,另一端称为栈底.栈中的数据元素遵守后进先出LIFO(Last In First Out)的原则.有点类似于手枪弹夹,后压进去的子弹总是最先打出,除非枪坏了. 压栈:栈的插入

  • C语言超详细讲解栈与队列实现实例

    目录 1.思考-1 2.栈基本操作的实现 2.1 初始化栈 2.2 入栈 2.3 出栈 2.4 获取栈顶数据 2.5 获取栈中有效元素个数 2.6 判断栈是否为空 2.7 销毁栈 3.测试 3.1测试 3.2测试结果 4.思考-2 5.队列的基本操作实现 5.1 初始化队列 5.2 队尾入队列 5.3 队头出队列 5.4 队列中有效元素的个数 5.5 判断队列是否为空 5.6 获取队头数据 5.7 获取队尾的数据 5.8 销毁队列 6.测试 6.1测试 6.2 测试结果 1.思考-1 为什么栈用

  • C语言 超详细讲解链接器

    目录 1 什么是链接器 2 声明与定义 3 命名冲突 3.1 命名冲突 3.2 static修饰符 4 形参.实参.返回值 5 检查外部类型 6 头文件 1 什么是链接器 典型的链接器把由编译器或汇编器生成的若干个目标模块,整合成一个被称为载入模块或可执行文件的实体–该实体能够被操作系统直接执行. 链接器通常把目标模块看成是由一组外部对象组成的.每个外部对象代表着机器内存中的某个部分,并通过一个外部名称来识别.因此,==程序中的每个函数和每个外部变量,如果没有被声明为static,就都是一个外部

  • C语言 超详细讲解库函数

    目录 1 返回整数的getchar函数 2 更新顺序文件 3 缓冲输出与内存分配 4 库函数 练习 1 返回整数的getchar函数 代码: #include<stdio.h> int main() { char c; while((c = getchar())!=EOF)//getchar函数的返回值为整型 putchar(c); return 0; } 上述代码有三种可能: 某些合法的输入字符在被"截断"后使得c的取值与EOF相同,程序将在复制的中途停止. c根本不可能

  • C语言 超详细讲解算法的时间复杂度和空间复杂度

    目录 1.前言 1.1 什么是数据结构? 1.2 什么是算法? 2.算法效率 2.1 如何衡量一个算法的好坏 2.2 算法的复杂度 2.3 复杂度在校招中的考察 3.时间复杂度 3.1 时间复杂度的概念 3.2 大O的渐进表示法 3.3 常见时间复杂度计算举例 4.空间复杂度 5. 常见复杂度对比 1.前言 1.1 什么是数据结构? 数据结构(Data Structure)是计算机存储.组织数据的方式,指相互之间存在一种或多种特定关系的数据元素的集合. 1.2 什么是算法? 算法(Algorit

  • C语言超详细讲解字符串相乘

    目录 前言 一. 分析思路 二.使用步骤 1.代码如下 2.memset函数 三.总结 前言 我们已经知道,正常的两位整形数据通过*相乘,C语言中int为4字节,32bit(字节),其机器码第一位为符号位,余下31位表示数字,表示范围:-2^31(-2147483648)~2^31-1(2147483647),但超过了这个范围我们该如何做呢? 提示:将数字以字符串的形式进行操作 一. 分析思路 示例: 我们把每一个数都看成是一个字符串,每一个元素为十进制数字所对应的字 符,由于是后面的元素先进行

  • C语言超详细讲解排序算法上篇

    目录 1.直接插入排序 2.希尔排序(缩小增量排序) 3.直接选择排序 4.堆排序 进入正式内容之前,我们先了解下初阶常见的排序分类 :我们今天讲前四个! 1.直接插入排序 基本思想:当插入第i(i>=1)个元素时,前面的array[0],array[1],…,array[i-1]已经排好序,此时用array[i]的排 序码与array[i-1],array[i-2],…的排序码顺序进行比较,找到插入位置即将array[i]插入,原来位置上的元素顺序后移! 直接插入排序的特性总结: 1. 元素集

  • C语言超详细讲解轮转数组

    目录 题目描述 实例 解题思路 1. 先整体逆转 2.逆转子数组[0, k - 1] 3.逆转子数组[k, numsSize - 1] 易错点 代码 题目描述 给你一个数组,将数组中的元素向右轮转 k 个位置,其中 k 是非负数.OJ链接 实例 1.实例1 输入: nums = [1,2,3,4,5,6,7], k = 3输出: [5,6,7,1,2,3,4]解释:向右轮转 1 步: [7,1,2,3,4,5,6]向右轮转 2 步: [6,7,1,2,3,4,5]向右轮转 3 步: [5,6,7

  • C语言超详细讲解数据结构中双向带头循环链表

    目录 一.概念 二.必备工作 2.1.创建双向链表结构 2.2.初始化链表 2.3.动态申请节点 2.4.打印链表 2.5.销毁链表 三.主要功能 3.1.在pos节点前插入数据 尾插 头插 3.2.删除pos处节点数据 尾删 头删 3.3.查找数据 四.总代码 List.h 文件 List.c 文件 Test.c 文件 五.拓展 一.概念 前文我们已经学习了单向链表,并通过oj题目深入了解了带头节点的链表以及带环链表,来画张图总体回顾下: 在我们学习的链表中,其实总共有8种,都是单双向和带不带

随机推荐