【数据结构】C语言实现链队列(附完整运行代码)

简介: 【数据结构】C语言实现链队列(附完整运行代码)

一.了解项目功能

在本次项目中我们的目标是实现一个链队列:

链队列使用动态内存分配空间,可以用来存储任意数量的同类型数据.

队列结点(QNode)需要包含两个要素:数据域data,指针域next.

队列结点(QNode)逻辑结构图示如下:

  提供的功能有:

  1.  队列的初始化.
  2.  队列的入队.
  3.  队列的出队.
  4.  队列的长度.
  5.  队列的判空.
  6.  队列取队头元素.
  7.  队列取队尾元素.
  8.  队列的销毁

二.项目功能演示

要编写一个链队列项目,首先要明确我们想要达到的效果是什么样,下面我将用vs2022编译器来为大家演示一下链队列程序运行时的样子:


三.逐步实现项目功能模块及其逻辑详解

通过第二部分对项目功能的介绍,我们已经对链队列的功能有了大致的了解,虽然看似需要实现的功能很多,貌似一时间不知该如何下手,但我们可以分步分模块来分析这个项目的流程,最后再将各部分进行整合,所以大家不用担心,跟着我一步一步分析吧!


!!!注意,该部分的代码只是为了详细介绍某一部分的项目实现逻辑,故可能会删减一些与该部分不相关的代码以便大家理解,需要查看或拷贝完整详细代码的朋友可以移步本文第四部分。


1.实现链队列程序菜单

菜单部分的逻辑比较简单,就是利用C语言printf函数打印出这个菜单界面即可。但要注意菜单的标序要和后续switch...case语句的分支相应,以免导致后续执行语句错乱的问题.基础问题就不过多赘述了,代码如下:

该部分功能实现代码如下:

void QMenu()
{
  printf("**********************************\n");
  printf("******请选择要进行的操作    ******\n");
  printf("******1.链队列入队          ******\n");
  printf("******2.链队列出队          ******\n");
  printf("******3.取队首元素          ******\n");
  printf("******4.取队尾元素          ******\n");
  printf("******5.队列判空            ******\n");
  printf("******6.查询当前队列长      ******\n");
  printf("******7.清空队列            ******\n");
  printf("******0.退出链队列程序      ******\n");
  printf("**********************************\n");
  printf("请选择:>");
 
}

2.实现链程序功能可循环使用

由于我们要实现  的功能可以反复使用的逻辑,且至少在一开始执行一次,因此我们选择do...while的循环语句来实现这一部分的逻辑.

该部分功能实现代码如下:

 int main()
    {
        Que Q;//创建队头尾指针结构体
        QueueInit(&Q);//初始化
 
        int swi = 0;//创建变量swi作为do...while循环的终止条件,以及switch语句的运行条件
        do          //使用do...while实现
        {
 
            QMenu();
            scanf("%d", &swi);
 
            switch (swi)
            {
            case 0:
                // 释放队列内存
                QueueDestroy(&Q);
            
                printf("您已退出程序:>\n");
 
                break;
 
            case 1:
                printf("请输入要入队的数据:>");
                QDatatype pushback_data = 0;
                scanf("%d", &pushback_data);
 
                QueuePush(&Q, pushback_data);
 
                printf("已成功入队:>\n");
                break;
 
            case 2:
                QueuePop(&Q);
                printf("出队成功:>\n");
 
                break;
 
            case 3:
                printf("队首元素为:");
                QDatatype e = QueueFront(&Q);
                printf("%d\n", e);
                break;
 
            case 4:
                printf("队尾元素为:");
                QDatatype n = QueueBack(&Q);
                printf("%d\n", n);
               
                break;
 
            case 5:
                if (!QueueEmpty(&Q))
                {
                    printf("当前队列不为空:>\n");
                }
                else
                {
                    printf("当前队列为空\n");
                }
                break;
 
            case 6:
               printf("当前队列长度为:");
               int size= QueueSize(&Q);
               printf("%d\n", size);
                break;
 
            case 7:
                QueueDestroy(&Q);
                QueueInit(&Q);
                printf("队列已清空:>\n");
                break;
 
            default:
                printf("输入错误,请重新输入\n");
                break;
            }
        } while (swi);
 
        return 0;
    }

3.创建链队列

创建链队列结点的结构体应该包括:存储数据的数据域data,以及存储下一个结点地址的指针域next.

图示如下:

因此我们创建QNode结构体类型时应由一个数据成员类型及一个指向该结构体的结构体指针组成.

然后队列特殊与单链表的点出现了:队列额外需要一个指向队头的队头指针,以及一个指向队尾的队尾指针,以及一个记录队长的整型size.

为了避免设计函数时要传好几个参数的情况,我们将这三个变量额外封装一个结构体,这样只要给函数传结构体变量就可以在函数内部访问这三个变量了.


如果有朋友对队列的两个结构体还搞不太清楚,可以这样理解:Queue结构体中的head指针相当于链表中的头指针,它的使用方式和链表中的头指针完全相同.然后在此基础上,只是多一个tail指针记录尾,多一个整型记录队列长度.

综上,该部分代码如下:

#pragma once
#define _CRT_SECURE_NO_WARNINGS 1
 
#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h>
#include<assert.h>
 
typedef int QDatatype;
 
typedef struct QueueNode//一个是结点结构
{
  struct QueueNode* next;
  QDatatype data;
}QNode;
 
 
typedef struct Queue//一个是队列整体结构
{
  QNode* head;
  QNode* tail;
  int size ;
}Que;

4.链队列的初始化.

回忆我们在链表部分对链表的初始化仅仅是将头指针置为NULL.而到了链队列这里,我们还多出两个需要处理的变量,一个是尾指针tail,一个是链队列长度size.

因此我们封装一个初始化函数将这3个变量一起初始化.

链队列结构初始化部分代码逻辑图示如下:

综上,该部分代码如下:

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

5.链队列的入队.

链队列在入队时思路如下:

  • 开辟新结点
  • 判断队列是否为空队列
  • 为空则将新结点"头插"进队列
  • 不为空则将新结点"尾插"进队列

综上,该部分代码如下:

void QueuePush(Que* pq, QDatatype x) //入队是尾插
{
    //创建新结点
  QNode* newnode = (QNode*)malloc(sizeof(QNode));
  if (newnode == NULL)
  {
    perror("malloc fail");
    return;
  }
    //新结点赋值
  newnode->data = x;
  newnode->next = NULL;
    
    //当队列为空时,入队相当于头插
  if (pq->head == NULL)
  {
    assert(pq->tail == NULL);
        //head为空,tail不为空就出事了,即队头为空但是队尾不为空
    //将head和tail一起指向newnode
    pq->head = pq->tail = newnode;
    
  }
  else//否则就是尾插
  {
        //尾插直接将新结点链接在原来的队尾后面
    pq->tail->next = newnode;
        //然后更新队尾
    pq->tail = newnode;
  }
    
    //新结点插入成功后队列长度要+1
  pq->size++;
 
}

6.链队列的出队.

链队列在出队思路如下:

  • 判断队列是否为空队列
  • 如果是,抛出异常终止程序
  • 如果不是,则判断队列中是否仅剩一个结点
  • 如果只剩一个结点,释放该结点,然后将head和tail置为空
  • 如果不是只剩一个结点,那么使用一个指针记录下当前队头的下一个结点的位置
  • 释放当前队头结点,然后更新队头指针
  • 最后队列长度-1

综上,该部分代码如下:

void QueuePop(Que* pq)//出队是头删,删前先判空
{
  assert(pq);
  assert(!QueueEmpty(pq));//assert为假会终止程序
 
  QNode* cur = pq;
 
  if (pq->head->next == NULL)
  {
    free(pq->head);
    pq->head = pq->tail = NULL;
  }
  else
  {
    QNode* next = pq->head->next;
    free(pq->head);
    pq->head = next;
  }
 
  pq->size--;
 
}

7.链队列的长度.

求链队列的长度,因为我们有设计记录链队列长度的变量size,因此我们直接返回结构体中的size成员的值即可.

int QueueSize(Que* pq)
{
  assert(pq);
 
  return pq->size;
 
}

8.链队列的判空.

判断队列是否为空,我们可以返回(pq->size==0)表达式的值:

  • 如果队列为空,则size=0,则pq->size==0表达式为真,函数返回true.
  • 如果队列不为空,则size不等于0,则pq->size==0表达式为假,函数返回false.

综上,该部分代码如下:

bool QueueEmpty(Que* pq)//判空!为空返回真!
{
  assert(pq);
 
  return pq->size==0;
}

9.链队列取队头元素.

取队头元素思路:

  • 判断队列是否为空
  • 为空抛出异常
  • 不为空返回队头指针指向的头节点的数据域

综上,该部分代码如下:

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

10.链队列取队尾元素.

取队尾元素思路:

  • 判断队列是否为空
  • 为空抛出异常
  • 不为空返回队尾指针指向的尾节点的数据域

综上,该部分代码如下:

QDatatype QueueBack(Que* pq)
{
  assert(pq);
  assert(!QueueEmpty(pq));
 
  return pq->tail->data;
 
}

11.链队列的销毁

链队列的销毁思路:

  • 从头遍历队列的元素,逐一释放链队列中的结点
  • 释放完将队头指针和队尾指针置为NULL,将队列长度size置为0.

综上,该部分代码如下:

void QueueDestroy(Que* pq)
{
  assert(pq);
 
  QNode* cur = pq->head;
  while (cur)
  {
    QNode* next = cur->next;
    free(cur);
    cur = next;
  }
  pq->head = pq->tail = NULL;
  pq->size = 0;
 
}

四.项目完整代码

我们将程序运行的代码分别在三个工程文件中编辑,完整代码如下:

test.c文件

#include"Queue.h"
  //因为链队列头指针是必须的
  //而在队尾插入则尾指针同样也是必须的
  //因此我们不妨设置两个指针,一个记录队头,一个记录队尾
  //为方便起见,多个数据我们把头指针和尾指针再封装一个结构体
 
  //单链表不设置尾指针的原因是它不能解决尾删问题
 
    int main()
    {
        Que Q;//初始化
        QueueInit(&Q);
 
        int swi = 0;//创建变量swi作为do...while循环的终止条件,以及switch语句的运行条件
        do          //使用do...while实现
        {
 
            QMenu();
            scanf("%d", &swi);
 
            switch (swi)
            {
            case 0:
                // 释放队列内存
                QueueDestroy(&Q);
            
                printf("您已退出程序:>\n");
 
                break;
 
            case 1:
                printf("请输入要入队的数据:>");
                QDatatype pushback_data = 0;
                scanf("%d", &pushback_data);
 
                QueuePush(&Q, pushback_data);
 
                printf("已成功入队:>\n");
                break;
 
            case 2:
                QueuePop(&Q);
                printf("出队成功:>\n");
 
                break;
 
            case 3:
                printf("队首元素为:");
                QDatatype e = QueueFront(&Q);
                printf("%d\n", e);
                break;
 
            case 4:
                printf("队尾元素为:");
                QDatatype n = QueueBack(&Q);
                printf("%d\n", n);
               
                break;
 
            case 5:
                if (!QueueEmpty(&Q))
                {
                    printf("当前队列不为空:>\n");
                }
                else
                {
                    printf("当前队列为空\n");
                }
                break;
 
            case 6:
               printf("当前队列长度为:");
               int size= QueueSize(&Q);
               printf("%d\n", size);
                break;
 
            case 7:
                QueueDestroy(&Q);
                QueueInit(&Q);
                printf("队列已清空:>\n");
                break;
 
            default:
                printf("输入错误,请重新输入\n");
                break;
            }
        } while (swi);
 
        return 0;
    }

Queue.c 文件

#include"Queue.h"
 
void QueueInit(Que* pq)
{
  assert(pq);
 
  pq->head = pq->tail = NULL;
  pq->size = 0;
 
}
void QueueDestroy(Que* pq)
{
  assert(pq);
 
  QNode* cur = pq->head;
  while (cur)
  {
    QNode* next = cur->next;
    free(cur);
    cur = next;
  }
  pq->head = pq->tail = NULL;
  pq->size = 0;
 
}
 
void QueuePush(Que* pq, QDatatype x) //入队是尾插
{
  QNode* newnode = (QNode*)malloc(sizeof(QNode));
  if (newnode == NULL)
  {
    perror("malloc fail");
    return;
  }
  newnode->data = x;
  newnode->next = NULL;
 
  if (pq->head == NULL)//头插
  {
    assert(pq->tail == NULL);//head为空,tail不为空就出事了,队头为空但是队尾不为空
    
    pq->head = pq->tail = newnode;
    
  }
  else//尾插
  {
    pq->tail->next = newnode;
    pq->tail = newnode;
  }
 
  pq->size++;
 
}
 
 
void QueuePop(Que* pq)//出队是头删
{
  assert(pq);
  assert(pq->head!=NULL);//***
 
  QNode* cur = pq;
 
  if (pq->head->next == NULL)
  {
    free(pq->head);
    pq->head = pq->tail = NULL;
  }
  else
  {
    QNode* next = pq->head->next;
    free(pq->head);
    pq->head = next;
  }
 
  pq->size--;
 
}
 
int QueueSize(Que* pq)
{
  assert(pq);
 
  return pq->size;
 
}
 
bool QueueEmpty(Que* pq)//判空!为空返回真!
{
  assert(pq);
 
  return pq->size==0;
}
 
QDatatype QueueFront(Que* pq)
{
  assert(pq);
  assert(!QueueEmpty(pq));
 
  return pq->head->data;
 
}
QDatatype QueueBack(Que* pq)
{
  assert(pq);
  assert(!QueueEmpty(pq));
 
  return pq->tail->data;
 
}
 
void QMenu()
{
  printf("**********************************\n");
  printf("******请选择要进行的操作    ******\n");
  printf("******1.链队列入队          ******\n");
  printf("******2.链队列出队          ******\n");
  printf("******3.取队首元素          ******\n");
  printf("******4.取队尾元素          ******\n");
  printf("******5.队列判空            ******\n");
  printf("******6.查询当前队列长      ******\n");
  printf("******7.清空队列            ******\n");
  printf("******0.退出链队列程序      ******\n");
  printf("**********************************\n");
  printf("请选择:>");
 
}

Queue.h文件

#pragma once
#define _CRT_SECURE_NO_WARNINGS 1
 
#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h>
#include<assert.h>
 
typedef int QDatatype;
 
typedef struct QueueNode//一个是结点结构
{
  struct QueueNode* next;
  QDatatype data;
}QNode;
 
 
typedef struct Queue//一个是队列整体结构
{
  QNode* head;
  QNode* tail;
  int size ;
}Que;
 
void QueueInit(Que* pq);
void QueueDestroy(Que* pq);
 
void QueuePush(Que* pq,QDatatype x);
void QueuePop(Que* pq);
 
int QueueSize(Que* pq);
 
bool QueueEmpty(Que* pq);
 
QDatatype QueueFront(Que* pq);
QDatatype QueueBack(Que* pq);
 
void QMenu();

结语

希望这篇链队列的实现详解能对大家有所帮助,欢迎大佬们留言或私信与我交流.


学海漫浩浩,我亦苦作舟!关注我,大家一起学习,一起进步!


数据结构栈与队列篇思维导图:


相关文章
|
21天前
|
算法 数据处理 C语言
C语言中的位运算技巧,涵盖基本概念、应用场景、实用技巧及示例代码,并讨论了位运算的性能优势及其与其他数据结构和算法的结合
本文深入解析了C语言中的位运算技巧,涵盖基本概念、应用场景、实用技巧及示例代码,并讨论了位运算的性能优势及其与其他数据结构和算法的结合,旨在帮助读者掌握这一高效的数据处理方法。
31 1
|
29天前
|
存储 安全 数据管理
C语言之考勤模拟系统平台(千行代码)
C语言之考勤模拟系统平台(千行代码)
51 4
|
29天前
|
存储 算法 搜索推荐
【趣学C语言和数据结构100例】91-95
本文涵盖多个经典算法问题的C语言实现,包括堆排序、归并排序、从长整型变量中提取偶数位数、工人信息排序及无向图是否为树的判断。通过这些问题,读者可以深入了解排序算法、数据处理方法和图论基础知识,提升编程能力和算法理解。
45 4
|
20天前
|
存储 算法 程序员
C 语言递归算法:以简洁代码驾驭复杂逻辑
C语言递归算法简介:通过简洁的代码实现复杂的逻辑处理,递归函数自我调用解决分层问题,高效而优雅。适用于树形结构遍历、数学计算等领域。
|
22天前
|
存储 缓存 算法
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式,强调了合理选择数据结构的重要性,并通过案例分析展示了其在实际项目中的应用,旨在帮助读者提升编程能力。
43 5
|
27天前
|
存储 安全 物联网
C语言物联网开发之设备安全与代码可靠性隐患
物联网设备的C语言代码安全与可靠性至关重要。一是防范代码安全漏洞,包括缓冲区溢出和代码注入风险,通过使用安全函数和严格输入验证来预防。二是提高代码跨平台兼容性,利用`stdint.h`定义统一的数据类型,并通过硬件接口抽象与适配减少平台间的差异,确保程序稳定运行。
|
21天前
|
并行计算 算法 测试技术
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面,旨在通过综合策略提升程序性能,满足实际需求。
49 1
|
1月前
|
C语言
【数据结构】栈和队列(c语言实现)(附源码)
本文介绍了栈和队列两种数据结构。栈是一种只能在一端进行插入和删除操作的线性表,遵循“先进后出”原则;队列则在一端插入、另一端删除,遵循“先进先出”原则。文章详细讲解了栈和队列的结构定义、方法声明及实现,并提供了完整的代码示例。栈和队列在实际应用中非常广泛,如二叉树的层序遍历和快速排序的非递归实现等。
174 9
|
1月前
|
存储 算法
非递归实现后序遍历时,如何避免栈溢出?
后序遍历的递归实现和非递归实现各有优缺点,在实际应用中需要根据具体的问题需求、二叉树的特点以及性能和空间的限制等因素来选择合适的实现方式。
32 1
|
1月前
|
存储 算法 Java
数据结构的栈
栈作为一种简单而高效的数据结构,在计算机科学和软件开发中有着广泛的应用。通过合理地使用栈,可以有效地解决许多与数据存储和操作相关的问题。