【数据结构】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();

结语

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


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


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


相关文章
|
14天前
|
安全 C语言
在C语言中,正确使用运算符能提升代码的可读性和效率
在C语言中,运算符的使用需要注意优先级、结合性、自增自减的形式、逻辑运算的短路特性、位运算的类型、条件运算的可读性、类型转换以及使用括号来明确运算顺序。掌握这些注意事项可以帮助编写出更安全和高效的代码。
25 4
|
1天前
|
算法 开发者 计算机视觉
燃爆全场!Python并查集:数据结构界的网红,让你的代码炫酷无比!
在编程的世界里,总有一些数据结构以其独特的魅力和高效的性能脱颖而出,成为众多开发者追捧的“网红”。今天,我们要介绍的这位明星,就是Python中的并查集(Union-Find)——它不仅在解决特定问题上大放异彩,更以其优雅的设计和强大的功能,让你的代码炫酷无比,燃爆全场!
7 0
|
2天前
|
算法 Java 测试技术
数据结构 —— Java自定义代码实现顺序表,包含测试用例以及ArrayList的使用以及相关算法题
文章详细介绍了如何用Java自定义实现一个顺序表类,包括插入、删除、获取数据元素、求数据个数等功能,并对顺序表进行了测试,最后还提及了Java中自带的顺序表实现类ArrayList。
5 0
|
29天前
|
存储 人工智能 C语言
数据结构基础详解(C语言): 栈的括号匹配(实战)与栈的表达式求值&&特殊矩阵的压缩存储
本文首先介绍了栈的应用之一——括号匹配,利用栈的特性实现左右括号的匹配检测。接着详细描述了南京理工大学的一道编程题,要求判断输入字符串中的括号是否正确匹配,并给出了完整的代码示例。此外,还探讨了栈在表达式求值中的应用,包括中缀、后缀和前缀表达式的转换与计算方法。最后,文章介绍了矩阵的压缩存储技术,涵盖对称矩阵、三角矩阵及稀疏矩阵的不同压缩存储策略,提高存储效率。
|
29天前
|
C语言
数据结构基础详解(C语言):图的基本概念_无向图_有向图_子图_生成树_生成森林_完全图
本文介绍了图的基本概念,包括图的定义、无向图与有向图、简单图与多重图等,并解释了顶点度、路径、连通性等相关术语。此外还讨论了子图、生成树、带权图及几种特殊形态的图,如完全图和树等。通过这些概念,读者可以更好地理解图论的基础知识。
|
1月前
|
存储 算法 C语言
C语言手撕实战代码_二叉排序树(二叉搜索树)_构建_删除_插入操作详解
这份二叉排序树习题集涵盖了二叉搜索树(BST)的基本操作,包括构建、查找、删除等核心功能。通过多个具体示例,如构建BST、查找节点所在层数、删除特定节点及查找小于某个关键字的所有节点等,帮助读者深入理解二叉排序树的工作原理与应用技巧。此外,还介绍了如何将一棵二叉树分解为两棵满足特定条件的BST,以及删除所有关键字小于指定值的节点等高级操作。每个题目均配有详细解释与代码实现,便于学习与实践。
|
1月前
|
存储 算法 C语言
C语言手撕实战代码_二叉树_构造二叉树_层序遍历二叉树_二叉树深度的超详细代码实现
这段代码和文本介绍了一系列二叉树相关的问题及其解决方案。其中包括根据前序和中序序列构建二叉树、通过层次遍历序列和中序序列创建二叉树、计算二叉树节点数量、叶子节点数量、度为1的节点数量、二叉树高度、特定节点子树深度、判断两棵树是否相似、将叶子节点链接成双向链表、计算算术表达式的值、判断是否为完全二叉树以及求二叉树的最大宽度等。每道题目均提供了详细的算法思路及相应的C/C++代码实现,帮助读者理解和掌握二叉树的基本操作与应用。
|
1月前
|
存储 算法 C语言
C语言手撕实战代码_循环单链表和循环双链表
本文档详细介绍了用C语言实现循环单链表和循环双链表的相关算法。包括循环单链表的建立、逆转、左移、拆分及合并等操作;以及双链表的建立、遍历、排序和循环双链表的重组。通过具体示例和代码片段,展示了每种算法的实现思路与步骤,帮助读者深入理解并掌握这些数据结构的基本操作方法。
|
1月前
|
算法 C语言 开发者
C语言手撕实战代码_单链表
本文档详细介绍了使用C语言实现单链表的各种基本操作和经典算法。内容涵盖单链表的构建、插入、查找、合并及特殊操作,如头插法和尾插法构建单链表、插入元素、查找倒数第m个节点、合并两个有序链表等。每部分均配有详细的代码示例和注释,帮助读者更好地理解和掌握单链表的编程技巧。此外,还提供了判断子链、查找公共后缀等进阶题目,适合初学者和有一定基础的开发者学习参考。
|
2天前
|
存储 算法 搜索推荐
探索常见数据结构:数组、链表、栈、队列、树和图
探索常见数据结构:数组、链表、栈、队列、树和图
76 64