【初阶数据结构篇】队列的实现(赋源码)

简介: 首先队列和栈一样,不能进行遍历和随机访问,必须将队头出数据才能访问下一个,这样遍历求个数是不规范的。


队列


1 代码位置

gitee


2 概念与结构


1.1概念


只允许在⼀端进行插⼊数据操作,在另⼀端进行删除数据操作的特殊线性表,队列具有先进先出FIFO(First In First Out)


⼊队列:进⾏插⼊操作的⼀端称为队尾


出队列:进⾏删除操作的⼀端称为队头



1.2结构


队列也可以数组和链表的结构实现,使⽤链表的结构实现更优⼀些,因为如果使⽤数组的结构,出队列在数组头上出数据,效率会⽐较低。


**数组头删时间复杂度:O(N) ** 数组尾插时间复杂度:O(1)

单链表头删时间复杂度:O(1) 单链表尾插时间复杂度:O(N)


  • 乍一看二者难分伯仲,但如果我们在单链表的结构里再定义一个指向尾结点的指针,那么单链表就可以实现O(1)的尾插时间复杂度,而数组没有什么特别好的办法实现这种转变。


2 队列的实现


Queue.h


#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>
typedef int QDataType;
typedef struct QueueNode//队列节点的结构,即单链表节点的结构
{
  QDataType data;
  struct QueueNode* next;
}QNode;
typedef struct Queue//队列的结构,定义指向队列头尾的指针,以及队列节点的个数
{
  QNode* phead;
  QNode* ptail;
  QDataType size;
}Q;

void QueueInit(Q*);

//入队列,队尾
void QueuePush(Q*, QDataType);

//出队列,队头
void QueuePop(Q*);

//队列判空
bool QueueEmpty(Q*);

//取队头数据
QDataType QueueFront(Q*);

//取队尾数据
QDataType QueueBack(Q*);

//队列有效元素个数
int QueueSize(Q*);

void QueueDestroy(Q*);

test.c


  • 用来测试我们写的函数(函数的调用)
  • 这一部分就是自己写的时候用的测试用例,随便什么都行


最好是写一个方法测试一次,不然找错误的时候会很痛苦😜

#define _CRT_SECURE_NO_WARNINGS 1
#include "Queue.h"
void QueueTest01()
{
  Q q;//定义队列
  QueueInit(&q);
  QueuePush(&q, 1);
  QueuePush(&q, 2);
  QueuePush(&q, 3);
  QueuePush(&q, 4);
  ///
  printf("head:%d\n", QueueFront(&q));
  printf("tail:%d\n", QueueBack(&q));
  printf("size:%d\n", QueueSize(&q));
  QueuePop(&q);
  QueueDestroy(&q);
}

int main()
{
  QueueTest01();
  return 0;
}

Queue.c


函数方法的实现,重点重点!!!


在每一个方法的第一排都使用assert宏来判断形参是否为空(避免使用时传入空指针,后续解引用都会报错)


2.1 队列的初始化和销毁


2.1.1 初始化


前面提到,我们在定义队列时,一个结构体用来定义队列,其中有指向队列头尾的指针(其中还有一个size,用来保存链表长度,后面会讲到为什么),另一个就是队列结点的结构,和单链表一样


#define _CRT_SECURE_NO_WARNINGS 1
#include "Queue.h"
void QueueInit(Q* pq)
{
  assert(pq);
  pq->phead = pq->ptail = NULL;
  pq->size = 0;
}
  • 和单链表一样,初始化只需让指向队列的指针置为空即可,只不过这里多了一个尾指针,同时队列为空,size=0
  • 链表空间都是按需申请,所以入数据都是通过之后的插入方法一个一个实现的

2.1.2 销毁
void QueueDestroy(Q* pq)
{
  assert(pq);
  assert(!QueueEmpty(pq));
  QNode* pcur = pq->phead;
  while (pcur)
  {
    QNode* next = pcur->next;
    free(pcur);
    pcur = next;
  }
  pq->phead = pq->ptail = NULL;
  pq->size = 0;
}

同单链表


2.2 队列插入和删除数据

2.2.1 队尾插入数据(入队列)


同样的我们先写一个申请结点空间的函数


QNode* BuyNode(QDataType x)
{
  QNode* newnode = (QNode*)malloc(sizeof(QNode));
  if (newnode == NULL)
  {
    perror("malloc fail!");
    exit(1);
  }
  newnode->data = x;
  newnode->next = NULL;
  return newnode;
}
  • 根据链表是否为空分两种情况
  • 别忘了size++
void QueuePush(Q* pq, QDataType x)
{
  assert(pq);
  if (pq->phead == NULL)
    pq->phead = pq->ptail = BuyNode(x);
  else
  {
    pq->ptail->next = BuyNode(x);
    pq->ptail = pq->ptail->next;
  }
  pq->size++;
}
2.2.2 队头删除数据(出队列)


删除数据的时候一定要判断队列是否为空!(也可以通过size来判断)

bool QueueEmpty(Q* pq)
{
  assert(pq);
  return pq->phead == NULL && pq->ptail == NULL;
}
  • 根据队列结点个数是否大于一个分两种情况讨论
  • 别忘了size–
void QueuePop(Q* pq)
{
  assert(pq);
  assert(!QueueEmpty(pq));

  //只有一个节点的情况,避免ptail变成野指针
  if (pq->ptail == pq->phead)
  {
    free(pq->phead);
    pq->phead = pq->ptail = NULL;
  }
  else
  {
    QNode* next = pq->phead->next;
    free(pq->phead);
    pq->phead = next;
  }
  pq->size--;
}

2.3 返回队头/队尾数据

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

QDataType QueueBack(Q* pq)
{
  assert(pq);
  assert(!QueueEmpty(pq));
  return pq->ptail->data;
}

2.4 返回队列的有效数据个数


  • size作用
  • 首先队列和栈一样,不能进行遍历和随机访问,必须将队头出数据才能访问下一个,这样遍历求个数是不规范的
  • 其次时间复杂度O(N),程序效率低


所以我们在队列结构里多定义了一个size,很好地解决了这个问题

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

  //不规范且时间复杂度O(n)
  //int size = 0;
  //QNode* pcur = pq->phead;
  //while (pcur)
  //{
  //  size++;
  //  pcur = pcur->next;
  //}
  //return size;


  return pq->size;

}

Queue.c(完整版)

#define _CRT_SECURE_NO_WARNINGS 1
#include "Queue.h"
void QueueInit(Q* pq)
{
  assert(pq);
  pq->phead = pq->ptail = NULL;
  pq->size = 0;
}


QNode* BuyNode(QDataType x)
{
  QNode* newnode = (QNode*)malloc(sizeof(QNode));
  if (newnode == NULL)
  {
    perror("malloc fail!");
    exit(1);
  }
  newnode->data = x;
  newnode->next = NULL;
  return newnode;
}


void QueuePush(Q* pq, QDataType x)
{
  assert(pq);
  if (pq->phead == NULL)
    pq->phead = pq->ptail = BuyNode(x);
  else
  {
    pq->ptail->next = BuyNode(x);
    pq->ptail = pq->ptail->next;
  }
  pq->size++;
}



bool QueueEmpty(Q* pq)
{
  assert(pq);
  return pq->phead == NULL && pq->ptail == NULL;
}
void QueuePop(Q* pq)
{
  assert(pq);
  assert(!QueueEmpty(pq));

  //只有一个节点的情况,避免ptail变成野指针
  if (pq->ptail == pq->phead)
  {
    free(pq->phead);
    pq->phead = pq->ptail = NULL;
  }
  else
  {
    QNode* next = pq->phead->next;
    free(pq->phead);
    pq->phead = next;
  }
  pq->size--;
}



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


QDataType QueueBack(Q* pq)
{
  assert(pq);
  assert(!QueueEmpty(pq));
  return pq->ptail->data;
}


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

  //不规范且时间复杂度O(n)
  //int size = 0;
  //QNode* pcur = pq->phead;
  //while (pcur)
  //{
  //  size++;
  //  pcur = pcur->next;
  //}
  //return size;


  return pq->size;

}



void QueueDestroy(Q* pq)
{
  assert(pq);
  assert(!QueueEmpty(pq));
  QNode* pcur = pq->phead;
  while (pcur)
  {
    QNode* next = pcur->next;
    free(pcur);
    pcur = next;
  }
  pq->phead = pq->ptail = NULL;
  pq->size = 0;
}

对于队列这一种结构的实现,因为和单链表差异也不大,更细节的内容就没有过多赘述,对以单链表的实现方法有什么疑问的话,推荐先去看这一篇哦->单链表的实现方法

目录
相关文章
|
7月前
|
前端开发 Java
java实现队列数据结构代码详解
本文详细解析了Java中队列数据结构的实现,包括队列的基本概念、应用场景及代码实现。队列是一种遵循“先进先出”原则的线性结构,支持在队尾插入和队头删除操作。文章介绍了顺序队列与链式队列,并重点分析了循环队列的实现方式以解决溢出问题。通过具体代码示例(如`enqueue`入队和`dequeue`出队),展示了队列的操作逻辑,帮助读者深入理解其工作机制。
246 1
|
5月前
|
存储 安全 Java
Java 集合面试题从数据结构到 HashMap 源码剖析详解及长尾考点梳理
本文深入解析Java集合框架,涵盖基础概念、常见集合类型及HashMap的底层数据结构与源码实现。从Collection、Map到Iterator接口,逐一剖析其特性与应用场景。重点解读HashMap在JDK1.7与1.8中的数据结构演变,包括数组+链表+红黑树优化,以及put方法和扩容机制的实现细节。结合订单管理与用户权限管理等实际案例,展示集合框架的应用价值,助你全面掌握相关知识,轻松应对面试与开发需求。
295 3
232.用栈实现队列,225. 用队列实现栈
在232题中,通过两个栈(`stIn`和`stOut`)模拟队列的先入先出(FIFO)行为。`push`操作将元素压入`stIn`,`pop`和`peek`操作则通过将`stIn`的元素转移到`stOut`来实现队列的顺序访问。 225题则是利用单个队列(`que`)模拟栈的后入先出(LIFO)特性。通过多次调整队列头部元素的位置,确保弹出顺序符合栈的要求。`top`操作直接返回队列尾部元素,`empty`判断队列是否为空。 两题均仅使用基础数据结构操作,展示了栈与队列之间的转换逻辑。
|
10月前
|
存储 C语言 C++
【C++数据结构——栈与队列】顺序栈的基本运算(头歌实践教学平台习题)【合集】
本关任务:编写一个程序实现顺序栈的基本运算。开始你的任务吧,祝你成功!​ 相关知识 初始化栈 销毁栈 判断栈是否为空 进栈 出栈 取栈顶元素 1.初始化栈 概念:初始化栈是为栈的使用做准备,包括分配内存空间(如果是动态分配)和设置栈的初始状态。栈有顺序栈和链式栈两种常见形式。对于顺序栈,通常需要定义一个数组来存储栈元素,并设置一个变量来记录栈顶位置;对于链式栈,需要定义节点结构,包含数据域和指针域,同时初始化栈顶指针。 示例(顺序栈): 以下是一个简单的顺序栈初始化示例,假设用C语言实现,栈中存储
500 77
|
8月前
|
存储 自然语言处理 数据库
【数据结构进阶】AVL树深度剖析 + 实现(附源码)
在深入探讨了AVL树的原理和实现后,我们不难发现,这种数据结构不仅优雅地解决了传统二叉搜索树可能面临的性能退化问题,还通过其独特的平衡机制,确保了在任何情况下都能提供稳定且高效的查找、插入和删除操作。
676 19
|
8月前
|
数据库 C++
【数据结构进阶】红黑树超详解 + 实现(附源码)
本文深入探讨了红黑树的实现原理与特性。红黑树是一种自平衡二叉搜索树,通过节点着色(红/黑)和特定规则,确保树的高度接近平衡,从而实现高效的插入、删除和查找操作。相比AVL树,红黑树允许一定程度的不平衡,减少了旋转调整次数,提升了动态操作性能。文章详细解析了红黑树的性质、插入时的平衡调整(变色与旋转)、查找逻辑以及合法性检查,并提供了完整的C++代码实现。红黑树在操作系统和数据库中广泛应用,其设计兼顾效率与复杂性的平衡。
1596 3
|
9月前
|
算法 调度 C++
STL——栈和队列和优先队列
通过以上对栈、队列和优先队列的详细解释和示例,希望能帮助读者更好地理解和应用这些重要的数据结构。
229 11
|
9月前
|
DataX
☀☀☀☀☀☀☀有关栈和队列应用的oj题讲解☼☼☼☼☼☼☼
### 简介 本文介绍了三种数据结构的实现方法:用两个队列实现栈、用两个栈实现队列以及设计循环队列。具体思路如下: 1. **用两个队列实现栈**: - 插入元素时,选择非空队列进行插入。 - 移除栈顶元素时,将非空队列中的元素依次转移到另一个队列,直到只剩下一个元素,然后弹出该元素。 - 判空条件为两个队列均为空。 2. **用两个栈实现队列**: - 插入元素时,选择非空栈进行插入。 - 移除队首元素时,将非空栈中的元素依次转移到另一个栈,再将这些元素重新放回原栈以保持顺序。 - 判空条件为两个栈均为空。
|
10月前
|
存储 C++ 索引
【C++数据结构——栈与队列】环形队列的基本运算(头歌实践教学平台习题)【合集】
【数据结构——栈与队列】环形队列的基本运算(头歌实践教学平台习题)【合集】初始化队列、销毁队列、判断队列是否为空、进队列、出队列等。本关任务:编写一个程序实现环形队列的基本运算。(6)出队列序列:yzopq2*(5)依次进队列元素:opq2*(6)出队列序列:bcdef。(2)依次进队列元素:abc。(5)依次进队列元素:def。(2)依次进队列元素:xyz。开始你的任务吧,祝你成功!(4)出队一个元素a。(4)出队一个元素x。
411 13
【C++数据结构——栈与队列】环形队列的基本运算(头歌实践教学平台习题)【合集】
|
10月前
|
存储 C语言 C++
【C++数据结构——栈与队列】链栈的基本运算(头歌实践教学平台习题)【合集】
本关任务:编写一个程序实现链栈的基本运算。开始你的任务吧,祝你成功!​ 相关知识 初始化栈 销毁栈 判断栈是否为空 进栈 出栈 取栈顶元素 初始化栈 概念:初始化栈是为栈的使用做准备,包括分配内存空间(如果是动态分配)和设置栈的初始状态。栈有顺序栈和链式栈两种常见形式。对于顺序栈,通常需要定义一个数组来存储栈元素,并设置一个变量来记录栈顶位置;对于链式栈,需要定义节点结构,包含数据域和指针域,同时初始化栈顶指针。 示例(顺序栈): 以下是一个简单的顺序栈初始化示例,假设用C语言实现,栈中存储整数,最大
218 9

热门文章

最新文章