【数据结构与算法分析】使用C语言实现队列的两种(带头结点与不带头结点)链式存储,并且给出一种循环队列的设计思想

这篇具有很好参考价值的文章主要介绍了【数据结构与算法分析】使用C语言实现队列的两种(带头结点与不带头结点)链式存储,并且给出一种循环队列的设计思想。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

前言

  当我们编写程序时,经常需要处理各种数据结构。队列是一种常见的数据结构,它有着广泛的应用场景。队列的基本操作包括入队和出队,应用于模拟等待队列、消息队列、计算机缓存等场合。

  在实际编程中,我们可以用不同的数据结构来实现队列。本文主要介绍了三种不同的队列实现方式,包括带头结点单向队列、不带头结点单向队列和循环队列。这些队列实现方式分别使用了链表、数组等不同的数据结构,在实现细节、时间复杂度和空间利用率等方面具有不同的特点。对于程序员来说,了解不同的队列实现方式,可以更好地选择适合自己应用场景的队列实现方式,提高程序的效率。

队列实现

  队列是一种先进先出(First-In-First-Out, FIFO)的数据结构,它的基本操作包括入队(将元素插入队尾)和出队(将队首元素删除)。

带头结点单向队列

  • 带头结点单向队列是一种使用链表实现的队列,与普通链表不同的是,带头结点单向队列在链表头部添加一个不存储数据的节点,作为链表的头结点,用于方便队列的操作。

  • 在带头结点单向队列中,入队操作是将新元素插入到链表尾部,出队操作是将队首元素的后继节点作为新的队首节点。

  • 由于链表的特性,带头结点单向队列的入队操作是 O ( 1 ) O(1) O(1) 的,而出队操作需要遍历整个链表,因此其时间复杂度为 O ( n ) O(n) O(n),其中 n n n 表示队列的元素个数。

  下面展示一张图表示队列插入的逻辑:
【数据结构与算法分析】使用C语言实现队列的两种(带头结点与不带头结点)链式存储,并且给出一种循环队列的设计思想

  队列节点结构体设计:

// 节点结构体
struct queueNode
{
	// 数据元素
	int elem;
	// 指向下一个元素
	struct queueNode *next;
};

struct queue
{
	// 尾部
	struct queueNode *rear;
	// 头部
	struct queueNode *front;
	// 长度
	int lenth;
};

  代码实现:

/********************** 含头结点 ***************/
// 队列初始化
struct queue*queueInit()
{
	// 申请空间并且初始化
	struct queue*res = (struct queue*)malloc(sizeof(struct queue));
	// 初始化一个虚拟节点
	struct queueNode*node = (struct queueNode*)malloc(sizeof(struct queueNode));
	res->front = node;
	res->rear = node;
	res->lenth = 0;
	return res;
}

// 入队列
void pushQueueNode(struct queue*queue, int elem)
{
	// 新建一个节点
	struct queueNode*node = (struct queueNode*)malloc(sizeof(struct queueNode));
	node->elem = elem;
	node->next = NULL;

	// 添加到尾部
	queue->rear->next = node;
	queue->rear = node;
	queue->lenth++;
}

// 出队列
bool pullQueueNode(struct queue*queue, int&elem)
{
	// 判断队列是否为空
	if (queueIsEmpty(queue))
		return true;

	// 移除队列中的首元素
	struct queueNode*p = queue->front->next;
	elem = p->elem;
	queue->front->next = p->next;
	queue->lenth--;
	// 队列中只有一个元素应该改变队尾指针
	if (queue->rear == p)
		queue->rear = queue->front;
	free(p);
	return false;
}

// 判断队列是否空
bool queueIsEmpty(struct queue*queue)
{
	return queue->lenth == 0;
}

// 打印队列
void outPutQueue(struct queue*queue)
{
	// 判断队列是否为空
	if (queue->lenth == 0)
		return;
	struct queueNode*p = queue->front->next;
	// 遍历队列并打印
	printf("queue:");
	while (p)
	{
		printf("%d ",p->elem);
		p = p->next;
	}
	printf("\n");
}

不带头结点单向队列

  • 与带头结点单向队列相比,不带头结点单向队列不使用头结点,直接从链表的第一个节点开始存储数据。

  • 在不带头结点单向队列中,入队操作是将新元素插入到链表尾部,出队操作是删除链表的头结点,并将头结点的后继节点作为新的队首节点。

  • 由于每次出队操作都需要删除链表的头结点,因此不带头结点单向队列中的实现会导致频繁的内存分配和释放,效率比较低。

  其插入操作的图解为:
【数据结构与算法分析】使用C语言实现队列的两种(带头结点与不带头结点)链式存储,并且给出一种循环队列的设计思想

  队列节点结构体设计:

struct queueNode
{
	// 数据元素
	int elem;
	// 指向下一个元素
	struct queueNode *next;
};

  代码实现:

/********************** 不含头结点 ***************/
// 队列初始化
struct queue*vQueueInit()
{
	// 申请空间并且赋值
	struct queue*res = (struct queue*)malloc(sizeof(struct queue));
	res->lenth = 0;
	res->front = res->rear = NULL;
	return res;
}

// 打印队列
void vOutPutQueue(struct queue*queue)
{
	printf("queue:");
	struct queueNode*p = queue->front;
	// 遍历打印
	while (p)
	{
		printf("%d ",p->elem);
		p = p->next;
	}
	printf("\n");
}

// 入队列
void vPushQueueNode(struct queue*queue, int elem)
{
	// 申请一个节点
	struct queueNode *p = (struct queueNode*)malloc(sizeof(struct queueNode));
	p->elem = elem;
	p->next = NULL;
	// 队列为空时入队列
	if (queue->lenth == 0) 
	{
		queue->front = p;
		queue->rear = p;
	}
	else
	{
		// 先将节点接到队列上
		queue->rear->next = p;
		// 再移动队列尾指针
		queue->rear = p;
	}
	queue->lenth++;
}

// 出队列
bool vPullQueueNode(struct queue*queue, int&elem)
{
	// 判断队列是否为空
	if (vQueueIsEmpty(queue))
		return false;

	// 获取队列首元素 并返回
	struct queueNode*p = queue->front;
	elem = p->elem;
	queue->front = p->next;
	queue->lenth--;

	return true;
}

// 判断队列是否空
bool vQueueIsEmpty(struct queue*queue)
{
	return queue->lenth == 0;
}

  与含头结点队列操作上的区别:主要在于入队列与出队列时需要判断队列是否为空。

循环队列

  • 循环队列是一种使用数组实现的队列,与普通数组不同的是,循环队列的队尾指针和队首指针可以在达到数组的末尾位置时循环到数组的开头位置。

  • 在循环队列中,需要使用两个指针分别指向队首和队尾,或者使用一个指针和队列长度维护队首和队尾位置。

  • 在循环队列中,入队操作是将新元素插入到队尾,如果队列满了,则会返回队列已满的错误;出队操作是将队首元素删除,并将队首指针指向下一个元素。

  • 循环队列的主要优点是空间利用率比较高,可以实现环形存储,而且操作的时间复杂度比带头结点单向队列和不带头结点单向队列都要高效,都是 O ( 1 ) O(1) O(1)

  队列结构体设计:

// 队列的最大长度
#define QUEUEMAXSIZE 10
// 队列结构体
struct queueCirculate
{
	// 存储数据
	int data[QUEUEMAXSIZE];
	// 记录数量
	int count;
	// 记录队列队头
	int front;
	// 记录队列队尾
	int rear;
};

  队列节点结构体设计:

******************** 循环队列 ******************/
// 初始化循环队列
struct queueCirculate*queueCirculateInit(void)
{
	// 申请空间
	struct queueCirculate*res = (struct queueCirculate*)malloc(sizeof(struct queueCirculate));
	// 初始化队列的数量 队头队尾的位置
	res->count = 0;
	res->front = 0;
	res->rear = 0;

	return res;
}

// 入队列
bool pushQueueCirculate(struct queueCirculate*queue,int elem)
{
	// 判断队列是否已满
	if (queue->count == QUEUEMAXSIZE)
		return false;
	// 入队列
	queue->data[queue->rear] = elem;
	// 改变队尾指针的位置 以及 队列的数量
	queue->count++;
	if (queue->count < QUEUEMAXSIZE)
		queue->rear = (queue->rear + 1) % QUEUEMAXSIZE;
	return true;
}

// 出队列
bool pullQueueCirculate(struct queueCirculate*queue, int&elem)
{
	// 判断队列是否为空
	if (queue->count == 0)
		return false;
	// 出队列
	elem = queue->data[queue->front];
	// 移动队头指针 并且 改变队列元素数量
	queue->front = (queue->front + 1) % QUEUEMAXSIZE;
	queue->count--;
	return true;
}

// 队列是否为空
bool queueCirculateIsEmpty(struct queueCirculate*queue)
{
	return queue->count == 0;
}

// 打印队列
void outPutQueueCirculate(struct queueCirculate*queue)
{
	int count = queue->count;
	printf("queue: ");
	for (int i = 0; i < count; ++i)
	{
		int temp = -1;
		pullQueueCirculate(queue, temp);
		printf("%d ", temp);
	}
	printf("\n");
}

总结

  带头结点单向队列、不带头结点单向队列和循环队列各有优缺点,适用于不同的应用场景:

  带头结点单向队列适用于需要实现入队操作频繁、出队操作较少的场景。它可以利用链表的特性,实现入队操作的时间复杂度为 O(1),缺点是出队操作的时间复杂度为 O(n)。

  不带头结点单向队列适用于队列长度较短的需要入队和出队操作都比较频繁的场景。由于不使用头结点,可以节省一些空间,但出队操作需要频繁的内存分配和释放,效率比较低。

  循环队列适用于队列长度较大且入队和出队操作都比较频繁的场景。它的环形存储结构可以充分利用数组的连续存储空间,实现了空间的高效利用。同时,入队和出队操作的时间复杂度都为 O(1),效率比较高。但是,循环队列需要事先定义一个最大长度,如果队列长度超过了最大长度,需要进行扩容操作。此外,由于环形结构的特殊性,实现起来也比较复杂。

  因此,在选择队列实现方式时,需要根据具体的应用场景综合考虑时间复杂度、空间利用率和实现难度等因素,选择最适合自己的队列实现方式。文章来源地址https://www.toymoban.com/news/detail-478720.html

到了这里,关于【数据结构与算法分析】使用C语言实现队列的两种(带头结点与不带头结点)链式存储,并且给出一种循环队列的设计思想的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

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

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

相关文章

  • 【算法与数据结构】 C语言实现单链表队列详解

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

    2024年04月11日
    浏览(45)
  • 头歌(C语言)-数据结构与算法-查找-第1关:实现折半查找

    任务描述 相关知识 编程要求 测试说明 任务描述 本关要求通过补全函数 BSL_FindKey 来实现在已排序的顺序表中查找关键码值为 key 的结点并返回该结点的编号。 相关知识 折半查找通常是针对顺序存储的线性表,线性表的结点按关键码从小到大排序,后面称之为折半查找的顺序

    2024年02月10日
    浏览(48)
  • 头歌(C语言)-数据结构与算法-栈的实现-第1关:实现一个顺序存储的栈

    任务描述 相关知识 栈的基本概念 栈结构的定义(C) 顺序栈的操作 编程要求 测试说明 任务描述 本关任务是实现 step1/SeqStack.cpp 中的 SS_IsFull 、 SS_IsEmpty 、 SS_Length 、 SS_Push 和 SS_Pop 五个操作函数,以实现判断栈是否为满、是否为空、求栈元素个数、进栈和出栈等功能。 相关

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

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

    2024年02月06日
    浏览(105)
  • 数据结构与算法细节篇之最短路径问题:Dijkstra和Floyd算法详细描述,java语言实现。

    最短路径的算法有两个, Dijkstra算法 和 Floyd算法 。 Dijkstra算法 解决的是 单源 最短路径问题 。 Floyd算法解决的是 多源 最短路径问题,并且可以处理负权图 。 今天要讲的就是Dijkstra算法。 加: feng--Insist (大写的i),进java交流群讨论互联网+技术。可索要PPT等资料。 其他资料

    2024年02月11日
    浏览(37)
  • 追梦之旅【数据结构篇】——详解小白如何使用C语言实现堆数据结构

        😎博客昵称:博客小梦 😊最喜欢的座右铭:全神贯注的上吧!!! 😊作者简介:一名热爱C/C++,算法等技术、喜爱运动、热爱K歌、敢于追梦的小博主! 😘博主小留言:哈喽! 😄各位CSDN的uu们,我是你的博客好友小梦,希望我的文章可以给您带来一定的帮助,话不

    2023年04月17日
    浏览(32)
  • 数据结构学习记录——图应用实例-拯救007(问题描述、解题思路、伪代码解读、C语言算法实现)

    目录 问题描述  解题思路 伪代码  总体算法 DFS算法 伪代码解读 总体算法 DFS算法 具体实现(C语言) 在老电影“007之生死关头”(Live and Let Die)中有一个情节,007被毒贩抓到一个鳄鱼池中心的小岛上,他用了一种极为大胆的方法逃脱 —— 直接踩着池子里一系列鳄鱼的大脑

    2024年02月05日
    浏览(60)
  • 【数据结构和算法】使用数组的结构实现链表(单向或双向)

    上文我们通过结构体的结构实现了队列 、以及循环队列的实现,我们或许在其他老师的教学中,只学到了用结构体的形式来实现链表、队列、栈等数据结构,本文我想告诉你的是,我们 可以使用数组的结构实现链表、单调栈、单调队列 目录 前言 一、用数组结构的好处 1.数

    2024年01月20日
    浏览(60)
  • C语言数据结构+KMP算法next数组优化计算方法+优化后子串匹配代码实现

    通过我之前那篇KMP算法的讲解,我们可以快速手算KMP算法的next数组,但是之前计算的next数组在一些情况下会有缺陷,比如模式串’aaaab’和主串’aaabaaaab’进行匹配 令模式串指针为j 当第一个元素不匹配时,下一次匹配还是要从模式串的第一个元素与主串匹配,其实我们可以直接写

    2024年02月06日
    浏览(36)
  • 数据结构与算法(七)--使用链表实现栈

    之前我们已经学习了链表的所有操作及其时间复杂度分析,我们可以了解到 对于链表头的相关操作基本都是O(1)的,例如链表头增加、删除元素,查询元素等等 。 那我们其实有一个数据结构其实可以完美利用到这些操作的特点,都是在某一段进行操作,那就是栈 。本章我们

    2024年02月07日
    浏览(39)

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

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

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

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

二维码1

领取红包

二维码2

领红包