初级数据结构(四)——队列

这篇具有很好参考价值的文章主要介绍了初级数据结构(四)——队列。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

   文中代码源文件已上传:数据结构源码

<-上一篇 初级数据结构(三)——栈        |        初级数据结构(五)——树和二叉树的概念 下一篇->

        本篇是属于上一篇的补充篇,因为队列和栈的属性特别类似,很多细节部分可以查看上一篇或者初级据结构的第二篇。

1、队列特性

         之前已知,栈结构特性为 LIFO ,队列则是与之相反的先入先出,后入后出,也称为 FIFO ( Fist In Fist Out )。如下图:

初级数据结构(四)——队列,数据结构与算法,C语言,数据结构,算法,c语言,链表

         因此,队列与栈的区别只在于弹出顺序,其余完全一致。但是,基于队列的特性,如果选用顺序表实现,则需要不断腾挪数据以填充弹出的头部位置,因此这里最好选用链表来实现以减小计算机资源的开销。

2、文件结构

        仍然是三个文件分模块实现:

初级数据结构(四)——队列,数据结构与算法,C语言,数据结构,算法,c语言,链表

        queue.h :用于创建项目的结构体类型以及声明函数;

        queue.c :用于创建队列各种操作功能的函数;

        main.c :仅创建 main 函数,用作测试。

3、队列构建

3.1、代码

        queue.h 中代码如下:

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

#define DATAPRT "%d"
typedef int DATATYPE;

typedef struct QueueNode
{
	DATATYPE data;
	struct QueueNode* next;
}QueueNode;

typedef struct QueueInfo
{
	int size;
	QueueNode* head;
	QueueNode* tail;
}QueueInfo;

//函数定义---------------------------------

//队列初始化
extern void QueueInit(QueueInfo*);
//数据入队
extern void QueuePush(QueueInfo*, DATATYPE);
//数据出队
extern void QueuePop(QueueInfo*);
//销毁队列
extern void QueueDestroy(QueueInfo*);
//获取队列数据
extern DATATYPE QueueGetData(QueueInfo*);
//打印队列数据
extern void QueuePrint(QueueInfo*);

        queue.c 中代码:

#include "queue.h"

//队列初始化
void QueueInit(QueueInfo* info)
{
	//参数有效性验证
	if (!info)
	{
		fprintf(stderr, "Queue Pointer NULL\n");
		return;
	}
	//初始化
	info->size = 0;
	info->head = NULL;
	info->tail = NULL;
}

//数据入队
void QueuePush(QueueInfo* info, DATATYPE data)
{
	//参数有效性验证
	if (!info)
	{
		fprintf(stderr, "Queue Pointer NULL\n");
		return;
	}
	//创建节点
	QueueNode* newNode = (QueueNode*)malloc(sizeof(QueueNode));
	//创建成功验证
	if (!newNode)
	{
		fprintf(stderr, "Malloc Fail\n");
		return;
	}
	//新节点初始化
	newNode->data = data;
	newNode->next = NULL;
	//链接
	if (!info->size)
	{
		info->head = newNode;
		info->tail = newNode;
	}
	else
	{
		info->tail->next = newNode;
		info->tail = newNode;
	}
	info->size++;
}

//数据出队
void QueuePop(QueueInfo* info)
{
	//参数有效性验证
	if (!info)
	{
		fprintf(stderr, "Queue Pointer NULL\n");
		return;
	}
	//空表判断
	if (!info->size)
	{
		fprintf(stderr, "Queue Is Empty\n");
	}
	QueueNode* headNode = info->head->next;
	free(info->head);
	info->head = headNode;
	info->size--;
	if (!info->size)
	{
		info->tail = NULL;
	}
}

//销毁队列
void QueueDestroy(QueueInfo* info)
{
	//参数有效性验证
	if (!info)
	{
		fprintf(stderr, "Queue Pointer NULL\n");
		return;
	}
	//逐级释放节点
	if (info->size > 0)
	{
		while (info->head != info->tail)
		{
			QueueNode* currentNode = info->head;
			info->head = info->head->next;
			free(currentNode);
		}
		free(info->head);
	}
	QueueInit(info);
}

//获取队列数据
DATATYPE QueueGetData(QueueInfo* info)
{
	//参数有效性验证
	assert(info);
	assert(info->head);
	DATATYPE data = info->head->data;
	QueuePop(info);
	return data;
}

//打印队列数据
void QueuePrint(QueueInfo* info)
{
	//参数有效性验证
	if (!info)
	{
		fprintf(stderr, "Queue Pointer NULL\n");
		return;
	}
	//空队列打印NULL
	if (info->size == 0)
	{
		printf("NULL ");
		return;
	}
	printf(DATAPRT" ", QueueGetData(info));
}

        main.c 中的测试用例:

#include "queue.h"

int main()
{
	QueueInfo info;
	QueueInit(NULL);
	QueueInit(&info);
	QueuePush(&info, 1);
	QueuePush(&info, 2);
	QueuePush(&info, 3);
	QueuePush(&info, 4);
	QueuePush(&info, 5);
	QueuePush(&info, 6);
	QueuePrint(&info);
	QueuePrint(&info);
	QueuePrint(&info);
	QueuePrint(&info);
	QueuePrint(&info);
	QueuePrint(&info);
	QueuePrint(&info);
	QueuePush(&info, 1);
	QueuePush(&info, 2);
	QueuePush(&info, 3);
	QueuePush(&info, 4);
	QueuePush(&info, 5);
	QueuePush(&info, 6);
	QueuePop(&info);
	QueuePrint(&info);
	QueuePrint(&info);
	QueuePop(&info);
	QueuePrint(&info);
	QueuePush(&info, 3);
	QueuePrint(&info);
	QueuePrint(&info);
	QueuePrint(&info);
	QueuePrint(&info);
	QueueDestroy(&info);
	return 0;
}

        以上便是全部代码,可自行测试和参考。构建过程中有一些需要注意的点,接下来作阐述。

3.2、构建要点

        1、与常规单链表不同,队列的构建最好以两种不同的结构体进行操作,除了节点信息的结构体之外,需要另外起一个结构体替代单链表的头节点指针。在此结构体内,储存的除了链表头节点指针之外,由于队列的特性,插入数据都是在尾部进行,因此另行创建一个成员变量用于保存尾节点指针。除此之外,为了方便判断队列中的节点个数(常规只判断是否为空队列),最好定义一个成员变量用于储存队列中节点个数。

        2、对于空队列和非空队列的数据入队需要分开讨论。空队列的数据入队需要同时操作头节点及尾节点指针同时指向第一个入队的元素。至于非空队列,需要操作尾节点指针,虽然不再操作头节点指针,但需要另行操作尾节点内部的 next 指针。

        3、数据出队时,如果操作对象时空队列则结束操作,可根据个人喜好自行选择用 assert 警告或者其他方式判断是否为空队列。

        4、销毁队列与单链表的销毁操作一致,直接参考即可。

4、用顺序表构建队列

        虽然不推荐用顺序表,但不代表顺序表不可行。但是在用顺序表构建队列时最好人为地设置阈值以控制什么时候将数组的数据移动至数组前端。

4.1、代码

        文件结构仍与链表实现队列的文件结构一致。代码如下:

        queue.h :

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

#define DATAPRT "%d"

typedef int DATATYPE;

typedef struct Queue
{
	//数据记录
	size_t size;
	int head;
	int tail;
	size_t capacity;
	//数据
	DATATYPE data[0];
}Queue;

//函数声明------------------------------------------
//初始化创建队列
extern Queue* QueueCreate(void);
//数据入队
extern void QueuePush(Queue*, DATATYPE);
//数据出队
extern void QueuePop(Queue*);
//销毁队列
extern void QueueDestroy(Queue**);
//获取队列数据
extern DATATYPE QueueGetData(Queue*);
//打印队列数据
extern void QueuePrint(Queue*);

        queue.c :

#include "queue.h"

//初始化创建队列
Queue* QueueCreate(void)
{
	//创建队列
	Queue* queue = (Queue*)malloc(sizeof(Queue) + 4 * sizeof(DATATYPE));
	//队列创建结果检查
	if (!queue)
	{
		fprintf(stderr, "Malloc Fail");
		return;
	}
	//队列数据初始化
	queue->size = 0;
	queue->head = -1;
	queue->tail = -1;
	queue->capacity = 4;
	return queue;
}

//数据入队
void QueuePush(Queue* queue, DATATYPE data)
{
	//参数有效性检查
	if (!queue)
	{
		fprintf(stderr, "Queue Address NULL\n");
		return;
	}
	queue->tail++;
	if (queue->tail + 1 >= queue->capacity)
	{
		//队列扩容
		size_t tempSize = (sizeof(Queue) + sizeof(DATATYPE) * queue->capacity * 2);
		Queue* tempQueue = (Queue*)realloc(queue, tempSize);
		//队列扩容结果检查
		if (!tempQueue)
		{
			fprintf(stderr, "Realloc Fail\n");
			return;
		}
		queue = tempQueue;
		queue->capacity *= 2;
	}
	//数据入队
	queue->data[queue->tail] = data;
	//空队列判断
	if (0 == queue->size)
	{
		queue->head = queue->tail;
	}
	queue->size++;
}

//数据出队
void QueuePop(Queue* queue)
{
	//参数有效性检查
	if (!queue)
	{
		fprintf(stderr, "Queue Address NULL\n");
		return;
	}
	//空队列检查
	if (!queue->size)
	{
		fprintf(stderr, "Queue Is Empty\n");
		return;
	}
	queue->head++;
	queue->size--;
	//弹出后空队列检查
	if (!queue->size)
	{
		queue->head = -1;
		queue->tail = -1;
	}
	//空间回收
	if ((queue->size < queue->capacity / 2) && queue->capacity > 4)
	{
		//数据迁移
		for (int i = queue->head, j = 0; i <= queue->tail; i++, j++)
		{
			queue->data[j] = queue->data[i];
		}
		//空间收缩
		size_t tempSize = (sizeof(Queue) + sizeof(DATATYPE) * queue->capacity / 2);
		Queue* tempQueue = (Queue*)realloc(queue, tempSize);
		//队列空间收缩结果检查
		if (!tempQueue)
		{
			fprintf(stderr, "Realloc Fail");
			return;
		}
		//记录迁移
		tempQueue->capacity = queue->capacity / 2;
		tempQueue->head = 0;
		tempQueue->tail = queue->size - 1;
		queue = tempQueue;
	}
}

//销毁队列
void QueueDestroy(Queue** queue)
{
	//参数有效性检查
	if (!(*queue) || !queue)
	{
		fprintf(stderr, "Queue Address NULL\n");
		return;
	}
	free(*queue);
	*queue = NULL;
}

//获取队列数据
DATATYPE QueueGetData(Queue* queue)
{
	//参数有效性检查
	assert(queue);
	//空队列检查检查
	assert(queue->size);
	DATATYPE data = queue->data[queue->head];
	QueuePop(queue);
	return data;
}


//打印队列数据
void QueuePrint(Queue* queue)
{
	//参数有效性检查
	assert(queue);
	//空队列检查检查
	if (!queue->size)
	{
		printf("NULL ");
		return;
	}
	printf(DATAPRT" ", QueueGetData(queue));
}

        main.c 的测试用例:

#include "queue.h"

int main()
{
	Queue* queue = QueueCreate();
	QueuePush(NULL, 1);
	QueuePush(queue, 1);
	QueuePush(queue, 2);
	QueuePush(queue, 3);
	QueuePush(queue, 4);
	QueuePush(queue, 5);
	QueuePush(queue, 6);
	QueuePush(queue, 7);
	QueuePush(queue, 8);
	QueuePush(queue, 9);
	QueuePop(NULL);
	QueuePrint(queue);
	QueuePrint(queue);
	QueuePush(queue, 70);
	QueuePush(queue, 72);
	QueuePrint(queue);
	QueuePrint(queue);
	QueuePrint(queue);
	QueuePop(queue);
	QueuePop(queue);
	QueuePop(queue);
	QueuePop(queue);
	QueuePrint(queue);
	QueuePrint(queue);
	QueuePrint(queue);
	QueuePrint(queue);
	QueuePush(queue, 1);
	QueuePush(queue, 2);
	QueuePush(queue, 3);
	QueuePush(queue, 4);
	QueuePush(queue, 5);
	QueuePush(queue, 6);
	QueuePush(queue, 7);
	QueuePush(queue, 8);
	QueuePush(queue, 9);
	QueuePrint(queue);
	QueuePrint(queue);
	QueueDestroy(&queue);
	QueuePrint(queue);

	return 0;
}

4.2、构建要点

        这里采用的是柔性数组顺序表,当然也可以用结构体包含数组指针的顺序表构建。

        1、对于空队列的初始化,头数据的下标与尾数据的下标应该设置为 -1 ,这点与栈的顶部数据下标设置完全一致。

        2、通过头部元素的数组下标及尾部元素的数组下标来进行队列有效部分内容的控制,出队则头部数组下标 +1 ,入队则数组尾部下标 +1。

        3、数据出队必须进行空队列检查。此外,出队之后对队列中剩余有效元素进行判断,最佳方案是,如果剩余数据元素个数小于开辟空间的一半,则从头部至尾部元素依次腾挪到数组 0 下标位置开始的空间。腾挪完毕后再对过大的空间进行回收。

        4、此外,数据腾挪的时候使用 realloc 有个坑。由于 realloc 的原地扩容和异地扩容是不可控的,如果 realloc 是原地扩容,对临时队列的操作也同样会影响到真实的队列。如上述代码的出队函数,如果用 realloc 创建 tempQueue ,有可能 tempQueue 与 queue 指向同一块空间,所以切不可先对空间进行回收后才进行数据腾挪。文章来源地址https://www.toymoban.com/news/detail-764305.html

到了这里,关于初级数据结构(四)——队列的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 初级数据结构(二)——链表

     文中代码源文件已上传:数据结构源码 -上一篇 初级数据结构(一)——顺序表        |        初级数据结构(三)——栈 下一篇-         与顺序表数据连续存放不同,链表中每个数据是分开存放的,而且存放的位置尤其零散,毫无规则可言。对于零散的数据而

    2024年02月05日
    浏览(49)
  • 初级数据结构(四)——队列

       文中代码源文件已上传:数据结构源码 -上一篇 初级数据结构(三)——栈        |        初级数据结构(五)——树和二叉树的概念 下一篇-         本篇是属于上一篇的补充篇,因为队列和栈的属性特别类似,很多细节部分可以查看上一篇或者初级据结构的第

    2024年02月04日
    浏览(36)
  • 【算法与数据结构】 C语言实现单链表队列详解

    前面我们学习了队列的顺序表的实现,本节将用单链表实现队列。 队列也可以数组和链表的结构实现, 使用链表的结构实现更优一些,因为如果使用数组的结构,出队列在数组头上出数据,效率会比较低 。下面我们先复习一下队列的基本概念: 队列:只允许在一端进行插入

    2024年04月11日
    浏览(60)
  • 头歌(C语言)-数据结构与算法-队列-第1关:实现一个顺序存储的队列

    任务描述 相关知识 顺序存储的队列 顺序队列的主要操作 编程要求 测试说明 任务描述 本关任务:实现 step1/SeqQueue.cpp 中的 SQ_IsEmpty 、 SQ_IsFull 、 SQ_Length 、 SQ_In 和 SQ_Out 五个操作函数,以实现判断队列是否为空、是否为满、求队列长度、队列元素入队和出队等功能。 相关知

    2024年02月06日
    浏览(162)
  • 【C/C++数据结构与算法】C语言栈与队列

    目录 一、栈 二、队列 三、循环队列 特性: 顺序存储,后进先出 优点是可随机访问,尾部增删效率高 缺点是可能会浪费空间,不知道头部增删 特性: 链式存储,先进先出 优点是无空间浪费,头部增删效率高 缺点是不能随机访问,尾部增删效率低 特性: 顺序存储,先进先

    2024年02月09日
    浏览(62)
  • C数据结构与算法——队列 应用(C语言纯享版 迷宫)

    实验任务 (1) 掌握顺序循环队列及其C语言的表示; (2) 掌握入队、出队等基本算法的实现; (3) 掌握顺序循环队列的基本应用(求解迷宫通路)。 实验内容 使用C语言实现顺序循环队列的类型定义与算法函数; 编写main()函数并根据需要修改、补充相关的类型定义与函数,以实

    2024年02月15日
    浏览(56)
  • 数据结构01-线性结构-链表栈队列-队列篇

    本系列为C++数据结构系列,会介绍 线性结构,简单树,特殊树,简单图等。本文为线性结构部分。 线性结构 【 3 】链表:单链表、双向链表、循环链表 【 3 】栈 【 3 】队列 队列(Queue)与栈一样,是一种线性存储结构,它具有如下特点: (1)队列中的数据元素遵循“先进

    2024年02月16日
    浏览(42)
  • 数据结构,队列,顺序表队列,链表队列

            队列是一种常见的数据结构,它具有先进先出(First-In-First-Out,FIFO)的特性,类似于排队等候的场景。以下是队列的要点: 1. 定义:队列是一种线性数据结构,由一系列元素组成,可以进行插入和删除操作。插入操作(称为入队)只能在队列的末尾进行,删除操

    2024年02月11日
    浏览(41)
  • 数据结构——线性数据结构(数组,链表,栈,队列)

    数组(Array) 是一种很常见的数据结构。它由相同类型的元素(element)组成,并且是使用一块连续的内存来存储。 我们直接可以利用元素的索引(index)可以计算出该元素对应的存储地址。 数组的特点是: 提供随机访问 并且容量有限。 2.1. 链表简介 链表(LinkedList) 虽然是

    2024年02月11日
    浏览(44)
  • 【数据结构和算法初阶(C语言)】复杂链表(随机指针,随机链表的复制)题目详解+链表顺序表结尾

    目录  1.随机链表的复制 1.2题目描述  1.3题目分析 1.4解题: 2.顺序表和链表对比 2.1cpu高速缓存利用率 3.结语 一个长度为  n  的链表,每个节点包含一个额外增加的随机指针  random   该指针可以指向链表中的任何节点或空节点。        构造这个链表的  深拷贝 。 深拷贝

    2024年03月10日
    浏览(89)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包