C语言数据结构(二)—— 受限线性表 【栈(Stack)、队列(Queue)】

news2024/11/24 20:47:13

在数据结构逻辑层次上细分,线性表可分为一般线性表和受限线性表。一般线性表也就是我们通常所说的“线性表”,可以自由的删除或添加结点。受限线性表主要包括栈和队列,受限表示对结点的操作受限制。

一般线性表详解,请参考文章:C语言数据结构(一)—— 数据结构理论、线性表【动态数组、链表(企业版单向链表)】

1栈(Stack)

1.1栈的基本概念

  • 概念:

首先它是一个线性表,也就是说,栈元素具有线性关系,即前驱后继关系。只不过它是一种特殊的线性表而已。定义中说是在线性表的表尾进行插入和删除操作,这里表尾是指栈顶,而不是栈底。

  • 特性

它的特殊之处在于限制了这个线性表的插入和删除的位置,它始终只在栈顶进行。这也就使得:栈底是固定的,最先进栈的只能在栈底。符合先进后出的数据结构。

  • 操作

  • 栈的插入操作,叫做进栈,也成压栈。类似子弹入弹夹(如下图所示)

  • 栈的删除操作,叫做出栈,也有的叫做弾栈退栈。如同弹夹中的子弹出夹(如下图所示)

遍历:不重复不遗漏查看每个元素,并且执行过后不会更改元素
遍历算法属于非质变算法
栈能否遍历?不能

1.2栈的顺序存储

  • 基本概念

栈的顺序存储结构简称顺序栈,它是运算受限制的顺序表。顺序栈的存储结构是:利用一组地址连续的的存储单元依次存放自栈底到栈顶的数据元素,同时附设指针top只是栈顶元素在顺序表中的位置。

  • 设计与实现

因为栈是一种特殊的线性表,所以栈的顺序存储可以通过顺序线性表来实现。数组首地址端做栈底,尾地址端做栈顶,方便数据插入和删除。

对外接口设计:
初始化
入栈
出栈
返回栈顶
返回元素个数
判断是否为空
销毁栈
#define _CRT_SECURE_NO_WARNINGS
#include<stdlib.h>
#include<stdio.h>
#include<string.h>
#define MAX 1024

struct SStack {
    //栈中的数组
    void * data[MAX];
    //栈的大小
    int m_Size;
};

typedef void * SeqStack;

//初始化栈
SeqStack init_SeqStack() {
    struct SStack * myStack = malloc(sizeof(struct SStack));
    if (myStack == NULL) {
        return NULL;
    }
    memset(myStack->data, 0, sizeof(void *) * MAX);
    myStack->m_Size = 0;
    return myStack;
}

//入栈
void push_SeqStack(SeqStack stack, void * data) {
    //本质  数组的尾插
    if (stack == NULL) {
        return;
    }
    if (data == NULL) {
        return;
    }
    //还原栈结构体
    struct SStack * myStack = stack;
    //判断栈是否满
    if (myStack->m_Size == MAX) {
        return;
    }
    //数组进行尾插
    myStack->data[myStack->m_Size] = data;
    //更新栈大小
    myStack->m_Size++;
}

//出栈
void pop_SeqStack(SeqStack stack) {
    //本质 数组的尾删除
    if (stack == NULL) {
        return;
    }
    //还原栈结构体
    struct SStack * myStack = stack;
    if (myStack->m_Size == 0) {
        return;
    }

    myStack->data[myStack->m_Size - 1] = NULL;
    myStack->m_Size--;
}

//返回栈顶
void * top_SeqStack(SeqStack stack) {
    //本质 返回数组的最后一个元素
    if (stack == NULL) {
        return NULL;
    }
    //还原栈结构体
    struct SStack * myStack = stack;
    if (myStack->m_Size == 0) {
        return NULL;
    }
    return myStack->data[myStack->m_Size - 1];
}

//返回栈大小
int size_SeqStack(SeqStack stack) {
    if (stack == NULL) {
        return -1;
    }
    //还原栈结构体
    struct SStack * myStack = stack;
    return myStack->m_Size;
}

//判断栈是否为空
int isEmpty_SeqStack(SeqStack stack) {
    if (stack == NULL) {
        return -1; //传入空指针 返回真 栈也是空
    }
    //还原栈结构体
    struct SStack * myStack = stack;
    if (myStack->m_Size == 0) {
        return 1; //1 代表真 栈确实为空
    }
    return 0;// 0假 不为空
}

//销毁
void destroy_SeqStack(SeqStack stack) {
    if (stack == NULL) {
        return;
    }
    free(stack);
    stack = NULL;
}


struct Person {
    char name[64];
    int age;
};

void test() {

    //创建栈
    SeqStack myStack = init_SeqStack();

    //创建数据
    struct Person p1 = { "赵云", 18 };
    struct Person p2 = { "张飞", 19 };
    struct Person p3 = { "关羽", 20 };
    struct Person p4 = { "刘备", 19 };
    struct Person p5 = { "诸葛亮", 12 };
    struct Person p6 = { "黄忠", 17 };

    //入栈
    push_SeqStack(myStack, &p1);
    push_SeqStack(myStack, &p2);
    push_SeqStack(myStack, &p3);
    push_SeqStack(myStack, &p4);
    push_SeqStack(myStack, &p5);
    push_SeqStack(myStack, &p6);

    printf("栈的大小为:%d\n", size_SeqStack(myStack));

    //栈不为空 开始查看栈顶 并出栈
    while (isEmpty_SeqStack(myStack) == 0) {
        struct Person * pTop = top_SeqStack(myStack);
        printf("栈顶元素-姓名:%s  年龄:%d \n", pTop->name, pTop->age);

        //出栈
        pop_SeqStack(myStack);
    }

    printf("栈的大小为:%d\n", size_SeqStack(myStack));

}

//程序入口
int main() {

    test();

    system("pause"); // 按任意键暂停  阻塞功能

    return EXIT_SUCCESS; //返回 正常退出值  0

}

1.3栈的链式存储

  • 基本概念

栈的链式存储结构简称链栈。

思考如下问题
栈只是栈顶来做插入和删除操作,栈顶放在链表的头部还是尾部呢?
由于单链表有头指针,而栈顶指针也是必须的,那干嘛不让他俩合二为一呢,所以比较好的办法就是把栈顶放在单链表的头部。另外都已经有了栈顶在头部了,单链表中比较常用的头结点也就失去了意义,通常对于链栈来说,是不需要头结点的。
  • 设计与实现

链栈是一种特殊的线性表,链栈可以通过链式线性表来实现头节点端做栈顶比较方便

对外接口设计:
初始化
入栈
出栈
返回栈顶
返回元素个数
判断是否为空
销毁栈
#define _CRT_SECURE_NO_WARNINGS
#include<stdlib.h>
#include<stdio.h>
#include<string.h>

//节点结构体
struct LinkNode{
    //只维护指针域
    struct LinkNode * next;
};

//链表结构体
struct LStack {
    struct LinkNode pHeader;//头节点
    int m_Size;//栈大小
};

typedef void * LinkStack;

//初始化
LinkStack init_LinkStack() {
    struct LStack * mystack = malloc(sizeof(struct LStack));
    if (mystack == NULL) {
        return NULL;
    }

    mystack->pHeader.next = NULL;
    mystack->m_Size = 0;

    return mystack;
}

//入栈
void push_LinkStack(LinkStack stack, void * data) {
    //本质 头插
    if (stack == NULL) {
        return;
    }
    if (data == NULL) {
        return;
    }
    struct LStack * mystack = stack;
    //取出用户的前4个字节
    struct LinkNode * myNode = data;
    //建立节点之间的关系
    myNode->next = mystack->pHeader.next;
    mystack->pHeader.next = myNode;
    //更新栈的大小
    mystack->m_Size++;
}

//出栈
void pop_LinkStack(LinkStack stack) {
    //本质 头删
    if (stack == NULL) {
        return;
    }
    struct LStack * mystack = stack;
    if (mystack->m_Size == 0) {
        return;
    }
    //记录指向第一个节点的指针
    struct LinkNode * pFirst = mystack->pHeader.next;
    //更新节点指向
    mystack->pHeader.next = pFirst->next;
    //更新栈大小
    mystack->m_Size--;
}

//返回栈顶
void * top_LinkStack(LinkStack stack) {
    if (stack == NULL) {
        return NULL;
    }
    struct LStack * mystack = stack;
    if (mystack->m_Size == 0) {
        return NULL;
    }
    return mystack->pHeader.next;
}

//返回元素个数
int size_LinkStack(LinkStack stack) {
    if (stack == NULL) {
        return -1;
    }
    struct LStack * mystack = stack;
    return mystack->m_Size;
}

//判断是否为空
int isEmpty_LinkStack(LinkStack stack) {
    if (stack == NULL) {
        return -1;
    }
    struct LStack * mystack = stack;
    if (mystack->m_Size == 0) {
        return 1; //为空 返回真
    }
    return 0;
}

//销毁栈
void destroy_LinkStack(LinkStack stack) {
    if (stack == NULL) {
        return;
    }
    free(stack);
    stack = NULL; 
}


struct Person {
    struct LinkNode node;
    char name[64];
    int age;
};

void test() {
    //创建栈
    LinkStack myStack = init_LinkStack();

    //创建数据
    struct Person p1 = { NULL,"赵云", 18 };
    struct Person p2 = { NULL,"张飞", 19 };
    struct Person p3 = { NULL,"关羽", 20 };
    struct Person p4 = { NULL,"刘备", 19 };
    struct Person p5 = { NULL,"诸葛亮", 12 };
    struct Person p6 = { NULL,"黄忠", 17 };

    //入栈
    push_LinkStack(myStack, &p1);
    push_LinkStack(myStack, &p2);
    push_LinkStack(myStack, &p3);
    push_LinkStack(myStack, &p4);
    push_LinkStack(myStack, &p5);
    push_LinkStack(myStack, &p6);

    printf("栈的大小为:%d\n", size_LinkStack(myStack));

    //栈不为空 开始查看栈顶 并出栈
    while (isEmpty_LinkStack(myStack) == 0) {
        struct Person * pTop = top_LinkStack(myStack);
        printf("栈顶元素-姓名:%s  年龄:%d \n", pTop->name, pTop->age);

        //出栈
        pop_LinkStack(myStack);
    }

    printf("栈的大小为:%d\n", size_LinkStack(myStack));
}

//程序入口
int main() {

    test();

    system("pause"); // 按任意键暂停  阻塞功能

    return EXIT_SUCCESS; //返回 正常退出值  0

}

1.4 栈的应用(案例)

1.4.1 就近匹配

几乎所有的编译器都具有检测括号是否匹配的能力,那么如何实现编译器中的符号成对检测?如下字符串:

5+5*(6)+9/3*1)-(1+3(

算法思路
从第一个字符开始扫描
当遇见普通字符时忽略,
当遇见左括号时压入栈中
当遇见右括号时从栈中弹出栈顶符号,并进行匹配
匹配成功:继续读入下一个字符
匹配失败:立即停止,并报错
结束:
成功: 所有字符扫描完毕,且栈为空
失败:匹配失败或所有字符扫描完毕但栈非空

总结

  • 当需要检测成对出现但又互不相邻的事物时可以使用栈“后进先出”的特性;

  • 栈非常适合于需要“就近匹配”的场合;

1.4.2 中缀表达式和后缀表达式

  • 后缀表达式(由波兰科学家在20世纪50年代提出)

  • 将运算符放在数字后面 ===》 符合计算机运算

  • 我们习惯的数学表达式叫做中缀表达式===》符合人类思考习惯

  • 实例

  • 5 +4 => 5 4 +

  • 1 +2 * 3 => 1 2 3 * +

  • 8 +(3 – 1 ) * 5 => 8 3 1 – 5 * +

  • 中缀转后缀算法:

遍历中缀表达式中的数字和符号:

  • 对于数字:直接输出

  • 对于符号:

  • 左括号:进栈

  • 运算符号:与栈顶符号进行优先级比较

  • 若栈顶符号优先级低:此符号进栈

(默认栈顶若是左括号,左括号优先级最低)

  • 若栈顶符号优先级不低:将栈顶符号弹出并输出,之后进栈

  • 右括号:将栈顶符号弹出并输出,直到匹配左括号,将左括号和右括号同时舍弃

遍历结束:将栈中的所有符号弹出并输出

  • 动手练习

将我们喜欢的读的中缀表达式转换成计算机喜欢的后缀表达式

中缀表达式: 8 + ( 3 – 1 ) * 5

后缀表达式: 8 3 1 – 5 * +

1.4.3 基于后缀表达式计算

  • 思考

计算机是如何基于后缀表达式计算的?

例如:8 3 1 –5 * +

  • 计算规则

遍历后缀表达式中的数字和符号

  • 对于数字:进栈

  • 对于符号:

  • 从栈中弹出右操作数

  • 从栈中弹出左操作数

  • 根据符号进行运算

  • 将运算结果压入栈中

遍历结束:栈中的唯一数字为计算结果

2队列(Queue)

2.1队列基本概念

队列是一种特殊的受限制的线性表。

队列(queue)是只允许在一端进行插入操作,而在另一端进行删除操作的线性表

队列是一种先进先出的t(First In First Out)的线性表,简称FIFO。允许插入的一端为队尾,允许删除的一端为队头。队列不允许在中间部位进行操作!假设队列是q=(a1,a2,……,an),那么a1就是队头元素,而an是队尾元素。这样我们就可以删除时,总是从a1开始,而插入时,总是在队列最后。这也比较符合我们通常生活中的习惯,排在第一个的优先出列,最后来的当然排在队伍最后。如下图:

2.3队列的顺序存储

  • 基本概念

队列也是一种特殊的线性表;可以用线性表顺序存储来模拟队列

对外接口设计:
初始化队列 init
入队 push
出队 pop
返回队头 front
返回队尾 back
返回队列大小 size
判断是否为空 isEmpty
销毁队列 destroy

头文件 dynamicArray.h:

#pragma  once 
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
#include<stdlib.h>

//动态数据结构体
struct dynamicArray
{
    void ** pAddr; // 维护开辟到堆区真实数组的指针

    int m_Capacity; //数组容量

    int m_Size; //数组大小
};

//初始化数组 参数代表 初始化的容量
struct dynamicArray * init_dynamicArray(int capacity);

//插入元素
void insert_dynamicArray(struct dynamicArray * arr, int pos, void * data);

//遍历数组
void foreach_dynamicArray(struct dynamicArray * arr, void(*myPrint)(void *));

//删除数组
void removeByPos_dynamicArray(struct dynamicArray * arr, int pos);

//按照值 来删除数组中数据
void removeByValue_dynamicArray(struct dynamicArray * arr, void * data, int(*myCompare)(void *, void *));

//销毁数组
void destroy_dynamicArray(struct dynamicArray * arr);

头文件dynamicArray.h :

#pragma  once
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include "dynamicArray.h"


#define  MAX 1024

typedef void * seqQueue;

//初始化队列
seqQueue init_SeqQueue();
//入队
void push_SeqQueue( seqQueue queue , void * data);
//出队
void pop_SeqQueue(seqQueue queue);
//返回队头元素
void * front_SeqQueue(seqQueue queue);

//返回队尾元素
void * back_SeqQueue(seqQueue queue);

//队列大小
int size_SeqQueue(seqQueue queue);

//判断是否为空
int isEmpty_SeqQueue(seqQueue queue);

//销毁队列
void destroy_SeqQueue(seqQueue queue);

源文件 dynamicArray.c :

#include "dynamicArray.h"


//初始化数组 参数代表 初始化的容量
struct dynamicArray * init_dynamicArray(int capacity)
{
    struct dynamicArray * array = malloc(sizeof(struct dynamicArray));

    if (array == NULL)
    {
        return NULL;
    }

    //给数组属性初始化
    array->m_Capacity = capacity;
    array->m_Size = 0;
    array->pAddr = malloc(sizeof(void *)* capacity);

    if (array->pAddr == NULL)
    {
        return NULL;
    }

    return array;
}


//插入元素
void insert_dynamicArray(struct dynamicArray * arr, int pos, void * data)
{
    if (arr == NULL)
    {
        return;
    }
    if (data == NULL)
    {
        return;
    }

    if (pos < 0 || pos > arr->m_Size)
    {
        //无效的位置  进行尾插
        pos = arr->m_Size;
    }

    //判断是否有空间进行插入,如果没有空间了,那么动态扩展
    if (arr->m_Size >= arr->m_Capacity)
    {
        //1、计算申请空间大小
        int newCapacity = arr->m_Capacity * 2;

        //2、创建新空间
        void ** newSpace = malloc(sizeof (void *)* newCapacity);

        //3、 将原有数据拷贝到新空间下
        memcpy(newSpace, arr->pAddr, sizeof(void*)* arr->m_Capacity);

        //4、 释放原有空间
        free(arr->pAddr);

        //5、 更新指针的指向
        arr->pAddr = newSpace;

        //6、更新新数组容量
        arr->m_Capacity = newCapacity;
    }


    //插入数据

    for (int i = arr->m_Size - 1; i >= pos; i--)
    {
        //数据后移
        arr->pAddr[i + 1] = arr->pAddr[i];
    }
    //将新数据放入到指定位置中
    arr->pAddr[pos] = data;


    //更新数组大小
    arr->m_Size++;
}

//遍历数组
void foreach_dynamicArray(struct dynamicArray * arr, void(*myPrint)(void *))
{
    if (arr == NULL)
    {
        return;
    }

    if (myPrint == NULL)
    {
        return;
    }

    for (int i = 0; i < arr->m_Size; i++)
    {
        myPrint(arr->pAddr[i]);
    }
}


//删除数组
void removeByPos_dynamicArray(struct dynamicArray * arr, int pos)
{
    if (arr == NULL)
    {
        return;
    }

    //无效位置 就直接return
    if (pos < 0 || pos >arr->m_Size - 1)
    {
        return;
    }

    //移动数据
    for (int i = pos; i < arr->m_Size - 1; i++)
    {
        arr->pAddr[i] = arr->pAddr[i + 1];
    }

    //更新大小
    arr->m_Size--;

}

//按照值 来删除数组中数据
void removeByValue_dynamicArray(struct dynamicArray * arr, void * data, int(*myCompare)(void *, void *))
{
    if (arr == NULL)
    {
        return;
    }
    if (data == NULL)
    {
        return;
    }

    for (int i = 0; i < arr->m_Size; i++)
    {
        if (myCompare(arr->pAddr[i], data))
        {
            //如果对比成功了,那么要删除i下标的元素
            removeByPos_dynamicArray(arr, i);
            break;
        }
    }


}

//销毁数组
void destroy_dynamicArray(struct dynamicArray * arr)
{

    if (arr == NULL)
    {
        return;
    }

    if (arr->pAddr != NULL)
    {
        free(arr->pAddr);
        arr->pAddr = NULL;
    }


    free(arr);
    arr = NULL;

}

源文件 seqQueue.c :

#include "seqQueue.h"

//初始化队列
seqQueue init_SeqQueue()
{
    struct dynamicArray * array = init_dynamicArray(MAX);

    return array;
}
//入队
void push_SeqQueue(seqQueue queue, void * data)
{
    //等价于 尾插
    if (queue == NULL)
    {
        return;
    }
    if (data == NULL)
    {
        return;
    }

    struct dynamicArray * array = queue;

    if (array->m_Size >= MAX)
    {
        return;
    }

    insert_dynamicArray(array, array->m_Size, data);
}
//出队
void pop_SeqQueue(seqQueue queue)
{
    //等价于  头删除
    if (queue == NULL)
    {
        return;
    }

    struct dynamicArray * array = queue;

    if (array->m_Size <= 0)
    {
        return;
    }
    removeByPos_dynamicArray(array, 0);
}
//返回队头元素
void * front_SeqQueue(seqQueue queue)
{
    if (queue == NULL)
    {
        return NULL;
    }

    struct dynamicArray * array = queue;

    return array->pAddr[0];

}

//返回队尾元素
void * back_SeqQueue(seqQueue queue)
{
    if (queue == NULL)
    {
        return NULL;
    }

    struct dynamicArray * array = queue;
    
    return array->pAddr[array->m_Size - 1];

}

//队列大小
int size_SeqQueue(seqQueue queue)
{
    if (queue == NULL)
    {
        return -1;
    }

    struct dynamicArray * array = queue;

    return array->m_Size;

}

//判断是否为空
int isEmpty_SeqQueue(seqQueue queue)
{
    if (queue == NULL)
    {
        return -1;
    }

    struct dynamicArray * array = queue;
    if (array->m_Size == 0)
    {
        return 1;
    }

    return 0;
}

//销毁队列
void destroy_SeqQueue(seqQueue queue)
{

    if (queue == NULL)
    {
        return ;
    }

    destroy_dynamicArray(queue);
    queue = NULL;
}

源文件test.c :

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include "seqQueue.h"

struct Person
{
    char name[64];
    int age;
};

void test01()
{
    //初始化队列
    seqQueue myQueue = init_SeqQueue();

    //准备数据
    struct Person p1 = { "aaa", 10 };
    struct Person p2 = { "bbb", 20 };
    struct Person p3 = { "ccc", 30 };
    struct Person p4 = { "ddd", 40 };
    struct Person p5 = { "eee", 50 };

    //入队
    push_SeqQueue(myQueue, &p1);
    push_SeqQueue(myQueue, &p2);
    push_SeqQueue(myQueue, &p3);
    push_SeqQueue(myQueue, &p4);
    push_SeqQueue(myQueue, &p5);

    printf("队列大小为:%d\n", size_SeqQueue(myQueue));

    while (isEmpty_SeqQueue(myQueue) == 0) 
    {
        //队头元素
        struct Person * pFront =  front_SeqQueue(myQueue);
        printf("队头元素姓名:%s 年龄:%d \n", pFront->name, pFront->age);

        //队尾元素
        struct Person * pBack = back_SeqQueue(myQueue);
        printf("队尾元素姓名:%s 年龄:%d \n", pBack->name, pBack->age);

        //出队
        pop_SeqQueue(myQueue);
    }

    printf("队列大小为:%d\n", size_SeqQueue(myQueue));

    //销毁队列
    destroy_SeqQueue(myQueue);
    myQueue = NULL;
}

int main(){

    test01();

    system("pause");
    return EXIT_SUCCESS;
}

2.4队列的链式存储

  • 基本概念

队列也是一种特殊的线性表;可以用线性表链式存储来模拟队列的链式存储

头文件 linkQueue.h :

#pragma  once
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
#include<stdlib.h>

//节点结构体
struct QueueNode
{
    struct QueueNode * next;

};

//链表的结构体 --- 队列
struct LQueue
{
    struct QueueNode pHeader; //头节点
    int m_Size; //队列的大小
    struct QueueNode * pTail; //记录尾节点的指针
};

typedef void * LinkQueue;

//初始化队列
LinkQueue init_LinkQueue();
//入队
void push_LinkQueue(LinkQueue queue, void * data);
//出队
void pop_LinkQueue(LinkQueue queue);
//返回队头
void * front_LinkQueue(LinkQueue queue);
//返回队尾
void * back_LinkQueue(LinkQueue queue);
//返回队列大小
int size_LinkQueue(LinkQueue queue);
//判断队列是否为空
int isEmpty_LinkQueue(LinkQueue queue);
//销毁队列
void destroy_LinkQueue(LinkQueue queue);

源文件 linkQueue.c :

#include "linkQueue.h"


//初始化队列
LinkQueue init_LinkQueue()
{
    struct LQueue * myQueue = malloc(sizeof(struct LQueue));
    if (myQueue == NULL)
    {
        return NULL;
    }

    myQueue->m_Size = 0;
    myQueue->pHeader.next = NULL;
    myQueue->pTail = &myQueue->pHeader; //尾节点开始指向的就是头节点
    return myQueue;
}
//入队
void push_LinkQueue(LinkQueue queue, void * data)
{
    //等价于 尾插
    if (queue == NULL)
    {
        return;
    }
    if (data == NULL)
    {
        return;
    }

    struct LQueue * myQueue = queue;

    struct QueueNode * myNode = data; 


    //更改指针指向
    myQueue->pTail->next = myNode;
    myNode->next = NULL;
    //更新尾节点
    myQueue->pTail = myNode;

    //更新队列大小
    myQueue->m_Size++;

}
//出队
void pop_LinkQueue(LinkQueue queue)
{
    //等价于 头删 

    if (queue == NULL)
    {
        return;
    }
    struct LQueue * myQueue = queue;

    if (myQueue->m_Size == 0)
    {
        return;
    }

    if (myQueue->m_Size == 1)
    {
        myQueue->pHeader.next = NULL;
        myQueue->pTail = &myQueue->pHeader; //维护尾节点指针
        myQueue->m_Size = 0;
        return;
    }

    //记录第一个节点
    struct QueueNode * pFirst = myQueue->pHeader.next;

    myQueue->pHeader.next = pFirst->next;

    //更新队列大小
    myQueue->m_Size--;

}
//返回队头
void * front_LinkQueue(LinkQueue queue)
{
    if (queue == NULL)
    {
        return NULL;
    }
    struct LQueue * myQueue = queue;

    return myQueue->pHeader.next;

}
//返回队尾
void * back_LinkQueue(LinkQueue queue)
{
    if (queue == NULL)
    {
        return NULL;
    }
    struct LQueue * myQueue = queue;

    return myQueue->pTail;

}
//返回队列大小
int size_LinkQueue(LinkQueue queue)
{
    if (queue == NULL)
    {
        return -1;
    }
    struct LQueue * myQueue = queue;

    return myQueue->m_Size;

}
//判断队列是否为空
int isEmpty_LinkQueue(LinkQueue queue)
{
    if (queue == NULL)
    {
        return -1;
    }
    struct LQueue * myQueue = queue;

    if (myQueue->m_Size == 0)
    {
        return 1;
    }

    return 0;

}
//销毁队列
void destroy_LinkQueue(LinkQueue queue)
{
    if (queue == NULL)
    {
        return;
    }
    free(queue);
    queue = NULL;
}

源文件 test.c :

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include "linkQueue.h"

struct Person
{
    void * node;
    char name[64];
    int age;
};

void test01()
{
    //初始化队列
    LinkQueue myQueue = init_LinkQueue();

    //准备数据
    struct Person p1 = { NULL, "aaa", 10 };
    struct Person p2 = { NULL, "bbb", 20 };
    struct Person p3 = { NULL, "ccc", 30 };
    struct Person p4 = { NULL, "ddd", 40 };
    struct Person p5 = { NULL, "eee", 50 };

    //入队
    push_LinkQueue(myQueue, &p1);
    push_LinkQueue(myQueue, &p2);
    push_LinkQueue(myQueue, &p3);
    push_LinkQueue(myQueue, &p4);
    push_LinkQueue(myQueue, &p5);

    printf("队列大小为:%d\n", size_LinkQueue(myQueue));

    while (isEmpty_LinkQueue(myQueue) == 0)
    {
        //队头元素
        struct Person * pFront = front_LinkQueue(myQueue);
        printf("链式存储 队头元素姓名:%s 年龄:%d \n", pFront->name, pFront->age);

        //队尾元素
        struct Person * pBack = back_LinkQueue(myQueue);
        printf("链式存储 队尾元素姓名:%s 年龄:%d \n", pBack->name, pBack->age);

        //出队
        pop_LinkQueue(myQueue);
    }

    printf("队列大小为:%d\n", size_LinkQueue(myQueue));

    //销毁队列
    destroy_LinkQueue(myQueue);
    myQueue = NULL;
}


int main(){

    test01();

    system("pause");
    return EXIT_SUCCESS;
}

一般线性表详解,请参考文章:C语言数据结构(一)—— 数据结构理论、线性表【动态数组、链表(企业版单向链表)】

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/373793.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

数据结构基础之栈和队列

目录​​​​​​​ 前言 1、栈 2、队列 2.1、实现队列 2.2、循环队列 前言 上一篇中我们介绍了数据结构基础中的《动态数组》&#xff0c;本篇我们继续来学习两种基本的数据结构——栈和队列。 1、栈 特点&#xff1a;栈也是一种线性结构&#xff0c;相比数组&#xff…

(汇总记录)电机控制算法

1.S曲线应用电机加减速 电机控制 | S曲线加减速 - Tuple - 博客园 (cnblogs.com) 如要将S型曲线应用到电机的加减速控制上&#xff0c;需要将方程在X、Y坐标系进行平移&#xff0c;同时对曲线进行拉升变化&#xff1a;即 Y A B / ( 1 exp( -ax b ) ) &#xff0c;则根据该…

Pandas怎么添加数据列删除列

Pandas怎么添加数据列 1、直接赋值 # 1、直接赋值df.loc[:, "最高气温"] df["最高气温"].str.replace("℃", "").astype("int32")df.loc[:, "最低气温"] df["最低气温"].str.replace("℃"…

Java异常架构与异常关键字

Java异常简介 Java异常是Java提供的一种识别及响应错误的一致性机制。 Java异常机制可以使程序中异常处理代码和正常业务代码分离&#xff0c;保证程序代码更加优雅&#xff0c;并提高程序健壮性。在有效使用异常的情况下&#xff0c;异常能清晰的回答what, where, why这3个问…

【编程入门】N种编程语言做个应用市场(appstore)

背景 前面已输出多个系列&#xff1a; 《十余种编程语言做个计算器》 《十余种编程语言写2048小游戏》 《17种编程语言10种排序算法》 《十余种编程语言写博客系统》 《十余种编程语言写云笔记》 《N种编程语言做个记事本》 本系列做了个应用市场&#xff0c;支持下载安装安卓…

Bootstrap系列之导航

Bootstrap导航 可以在 ul 元素上添加 .nav类&#xff0c;在每个 li 选项上添加 .nav-item 类&#xff0c;在每个链接上添加 .nav-link 类: 基本的导航 <div class"container mt-3"><h2>导航</h2><p>简单的水平导航:</p><ul class&…

基于yolov5与改进VGGNet的车辆多标签实时识别算法

摘 要 为了能快速、有效地识别视频中的车辆信息&#xff0c;文中结合YOLOv3算法和CNN算法的优点&#xff0c;设计了一种能实时识别车辆多标签信息的算法。首先&#xff0c;利用具有较高识别速度和准确率的YOLOv3实现对视频流中车辆的实时监测和定位。在获得车辆的位置信息后…

《亚马逊逆向工作法》读书笔记

文章目录书籍信息构件&#xff1a;领导力准则与机制亚马逊领导力准则机制&#xff1a;强化领导力准则年度计划&#xff1a;OP1与OP2S-Team目标亚马逊的薪酬制度&#xff1a;强化长期思维招聘&#xff1a;亚马逊独特的抬杆者流程抬杆者招聘流程组织&#xff1a;独立单线程领导模…

Redis-Java代码使用示例

在我之前的项目中&#xff0c;使用Redis是我们团队自己封装了一个Redis操作类&#xff0c;但是这只是在Spring提供的RedisTemplate上做了一层封装而已&#xff0c;当时使用不是很熟练&#xff0c;都是一边在网上查资料&#xff0c;一边使用&#xff1b;这篇文章会介绍两种使用方…

分布式一致性算法——Paxos 和 Raft 算法

写在前面 本文隶属于专栏《100个问题搞定大数据理论体系》&#xff0c;该专栏为笔者原创&#xff0c;引用请注明来源&#xff0c;不足和错误之处请在评论区帮忙指出&#xff0c;谢谢&#xff01; 本专栏目录结构和参考文献请见100个问题搞定大数据理论体系 I. 简介 介绍Paxos…

局域网实现PC、Pad、Android互联

文章目录局域网实现PC、Pad、Android互联一、网络邻居1、 Windows 配置1.1 开启共享功能1.2 设置用户1.3 共享文件夹2、 Pad 连接二、 FTP & HTTP1、 电脑配置1.1 HTTP 服务1.2 FTP 服务2、 连接3、 电脑连接 FTP三、 其他方式局域网实现PC、Pad、Android互联 在我们使用多…

【micropython】SPI触摸屏开发

背景&#xff1a;最近买了几块ESP32模块&#xff0c;看了下mircopython支持还不错&#xff0c;所以买了个SPI触摸屏试试水&#xff0c;记录一下使用过程。硬件相关&#xff1a;SPI触摸屏使用2.4寸屏幕&#xff0c;常见淘宝均可买到&#xff0c;驱动为ILI9341&#xff0c;具体参…

windows服务器实用(2)——搭建本地文档管理(gitbit的部署)

windows服务器实用——部署gitbit 在日常的项目管理中&#xff0c;无论是文档还是代码&#xff0c;一般都是存在本地。但是本地的文件存在一定的不确定性&#xff0c;尤其是当文档经常改动的时候&#xff0c;如果要找回之前改动的文件是很困难的。如果每次的改动都存在本地&am…

数据结构与算法之链表

目录单链表概念单链表操作循环链表概念循环链表操作双向循环链表概念双向循环链表操作单链表 概念 单链表也叫单向链表&#xff0c;是链表中最简单的一种形式&#xff0c;它的每个节点包含两个域&#xff0c;一个信息域&#xff08;元素域&#xff09;和一个链接域。这个链接…

微信投票-课后程序(JAVA基础案例教程-黑马程序员编著-第七章-课后作业)

【实验7-5】 微信投票 【任务介绍】 1.任务描述 如今微信聊天已经普及到几乎每一个人&#xff0c;在聊天中&#xff0c;经常会有人需要帮忙在某个APP中投票。本案例要求编写一个模拟微信投票的程序&#xff0c;通过在控制台输入指令&#xff0c;实现添加候选人、查看当前投票…

【C语言刷题】找单身狗、模拟实现atoi

目录 一、找单身狗 1.暴力循环法 2.分组异或法 二、模拟实现atoi 1.atoi函数的功能 2.模拟实现atoi 一、找单身狗 题目描述&#xff1a;给定一个数组中只有两个数字是出现一次&#xff0c;其他所有数字都出现了两次。 编写一个函数找出这两个只出现一次的数字。 比如&…

【Maven】(三)Maven仓库概念及私服安装与使用 附:Nexus安装包下载地址

文章目录1.前言2.Maven的仓库2.1.仓库类型3.私服Nexus3.1.Nexus的安装与配置3.1.1.使用安装包安装3.1.2.使用Docker安装3.2.Nexus配置3.2.1.仓库配置在这里插入图片描述4.私服的使用4.1.修改Maven配置4.2.从私服中下载构件4.3.推送构件到私服5.小结1.前言 本系列文章记录了 Ma…

超级困惑:单品牌好还是多品牌好?

超级困惑&#xff1a;单品牌好还是多品牌好&#xff1f; 相当于&#xff1a;买一套房好还是多套房好&#xff1f; 品牌是增加被消费者选择的优势 同一公司多品牌名之间&#xff0c;要区分明显 趣讲大白话&#xff1a;品牌要花大笔银子滴 【安志强趣讲信息科技87期】 **********…

?? JavaScript 双问号(空值合并运算符)

?? JavaScript 双问号&#xff08;空值合并运算符) 一、简述 在网上浏览 JavaScript 代码时或者学习其他代码时&#xff0c;可能会发现有的表达式用了两个问号&#xff08;??&#xff09;如下所示&#xff1a; let username; console.log(username ?? "Guest"…

kafka-console-ui v1.0.6发布

前言kafka-console-ui 是一款web版的kafka管理平台&#xff0c;从第一次发布到现在已经两年了&#xff0c;断断续续也更新了7个版本了&#xff08;v1.0.0~v1.0.6&#xff09;。一些常用的功能也陆续完善了不少&#xff0c;相对最新的kafka版本&#xff0c;某些功能上还是有所欠…