C语言数据结构之栈和队列的实现及应用

 更新时间:2022年08月18日 09:26:50   作者:蒋灵瑜的笔记本  
栈和队列是一种数据结构,只规定了性质,并没有规定实现方式。本文将以顺序结构实现栈,链表方式实现队列,感兴趣的小伙伴快跟随小编一起学习一下吧

栈和队列是一种数据结构,只规定了性质,并没有规定实现方式。

本文以顺序结构实现栈,链表方式实现队列。

一、栈的概念

栈:是一种特殊的线性表,其只允许在栈顶进行插入和删除元素操作。

栈中的数据元素遵守后进先出LIFO(Last In First Out)的原则。

压栈:栈的插入操作叫做进栈\压栈\入栈,入数据在栈顶。

出栈:栈的删除操作叫做出栈。出数据也在栈顶。

二、Stack.h

#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>
typedef int STDataType;
typedef struct stack
{
    STDataType* arr;
    int top;//数组元素个数(top-1为最后一个元素的下标)就是顺序表的size
    int capacity;//总容量
}ST;
void StackInit(ST* ps);//初始化
void StackDestroy(ST* ps);//销毁
void StackPush(ST* ps, STDataType x);//压栈
void StackPop(ST* ps);//出栈
bool StackEmpty(ST* ps);//判断栈是不是为空
STDataType StackTop(ST* ps);//访问栈顶元素
int StackSize(ST* ps);//数组元素个数

以顺序结构实现栈,本质上仍是一个顺序表,只是这个顺序表加上了栈先进后出的规则。

数组的头是栈底,数组尾是栈顶。栈主要的压栈、出栈、访问栈顶等接口非常契合顺序表的尾插、尾删、随机访问的特点。

三、Stack.c

1、栈的初始化和销毁

void StackInit(ST* ps)//初始化
{
    assert(ps);
    ps->arr = NULL;
    ps->top = ps->capacity = 0;
}
void StackDestroy(ST* ps)//销毁
{
    assert(ps);
    free(ps->arr);
    ps->arr = NULL;
    ps->top = ps->capacity = 0;
}

和顺序表的初始化、销毁方式一样

2、栈的进栈、出栈

void StackPush(ST* ps, STDataType x)//进栈
{
    assert(ps);
    //判断扩容
    if (ps->top == ps->capacity)
    {
        int newCapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
        STDataType* tmp = (STDataType*)realloc(ps->arr, newCapacity * sizeof(STDataType));
        if (tmp == NULL)
        {
            perror("realloc fail:\n");
            exit(-1);
        }
        ps->arr = tmp;
        ps->capacity = newCapacity;
    }
    ps->arr[ps->top] = x;
    ps->top++;
}
void StackPop(ST* ps)//出栈
{
    assert(ps);
    assert(!StackEmpty(ps));
    ps->top--;
}

进栈需要判断栈的空间,空间不够则需要扩容

出栈时,先判空,再将top--即可

3、栈的判空、访问栈顶元素、栈内元素个数

bool StackEmpty(ST* ps)//判断栈是不是为空
{
    assert(ps);
    return ps->top == 0;
}
STDataType StackTop(ST* ps)//访问栈顶元素
{
    assert(ps);
    assert(!StackEmpty(ps));
    return ps->arr[ps->top - 1];
}
int StackSize(ST* ps)//数组元素个数
{
    assert(ps);
    return ps->top;
}

注意访问栈顶元素这个接口,要先判断栈是不是空。

四、队列的概念

队列:一端进行插入数据操作,另一端进行删除数据操作的特殊线性表,队列具有先进先出FIFO(First In First Out)的特点。

入队列:进行插入操作的一端称为队尾

出队列:进行删除操作的一端称为队头

队列参照现实生活中的排队

五、Queue.h

#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>
typedef int QDataType;
typedef struct QueueNode
{
    struct QueueNode* next;
    QDataType data;
}QNode;
typedef struct Queue
{
    QNode* head;
    QNode* tail;
    int size;//加个size,方便统计长度
}Queue;
void QueueInit(Queue* pq);//初始化
void QueueDestroy(Queue* pq);//销毁
void QueuePush(Queue* pq,QDataType x);//入队(尾插)
bool QueueEmpty(Queue* pq);//判断队列是否为空
void QueuePop(Queue* pq);//出队(头删)
int QueueSize(Queue* pq);//统计队列长度
QDataType QueueFront(Queue* pq);//访问队头数据
QDataType QueueBack(Queue* pq);//访问队尾数据

因为顺序结构不适合头删,这里使用单链表来实现队列。

结构体QNode用于模拟单链表,结构体Queue中存放了单链表的头、尾指针、链表节点个数。使用Queue来操纵单链表。

单链表的头head是队头(头删出数据),tail是队尾(尾插录数据)

六、Queue.c

1、队列的初始化和销毁

void QueueInit(Queue* pq)//初始化
{
    assert(pq);
    pq->head = pq->tail = NULL;
    pq->size = 0;
}
void QueueDestroy(Queue* pq)//销毁
{
    assert(pq);
    QNode* cur = pq->head;
    //逐个销毁释放空间
    while (cur)
    {
        QNode* del = cur;
        cur = cur->next;
        free(del);
    }
    pq->head = pq->tail = NULL;
}

和单链表的销毁方式一样,注意销毁时需要逐个节点销毁。

2、队列的入队、出队

void QueuePush(Queue* pq, QDataType x)//入队,尾插
{
    assert(pq);
    //创建一个新节点
    QNode* newnode = (QNode*)malloc(sizeof(QNode));
    if (newnode == NULL)
    {
        perror("malloc fail:\n");
        exit(-1);
    }
    newnode->data = x;
    newnode->next = NULL;
    //队列为空时的尾插和不为空的尾插
    if (QueueEmpty(pq))
        pq->head=pq->tail = newnode;
    else
    {
        pq->tail->next = newnode;
        pq->tail = newnode;
    }
    pq->size++;
}
void QueuePop(Queue* pq)//出队(头删)
{
    assert(pq);
    assert(!QueueEmpty(pq));
    QNode* next = pq->head->next;
    free(pq->head);
    pq->head = next;
    pq->size--;
}

入队:尾插一个节点

出队:头删一个节点

3、队列的判空

bool QueueEmpty(Queue* pq)//判断队列是否为空
{
    assert(pq);
    return pq->head == NULL;
}

4、访问队头、队尾数据、统计队列长度

int QueueSize(Queue* pq)//统计队列长度
{
    assert(pq);
    return pq->size;
}
QDataType QueueFront(Queue* pq)//访问队头数据
{
    assert(pq);
    assert(!QueueEmpty(pq));
    return pq->head->data;
}
QDataType QueueBack(Queue* pq)//访问队尾数据
{
    assert(pq);
    assert(!QueueEmpty(pq));
    return pq->tail->data;
}

访问接口,注意先判空。

七、力扣中栈和队列OJ题

1、有效的括号

使用队列来解决,创建一个栈,碰到左括号将其进栈,碰到右括号则访问栈顶元素,不相符则为false,迭代比较相符则为true

bool isValid(char * s){
    ST st;
    StackInit(&st);
    while(*s)
    {
        if(*s=='('||*s=='{'||*s=='[')
        {
            StackPush(&st,*s);//压栈
        }
        else//比较时的情况
        {
            if(StackEmpty(&st))
                return false;  
            else if(StackTop(&st)=='('&&*s!=')')//访问栈顶元素
            {
                return false;
            }
            else if(StackTop(&st)=='{'&&*s!='}')
            {
                return false;
            }
            else if(StackTop(&st)=='['&&*s!=']')
            {
                return false;
            }
            StackPop(&st);
        }
        ++s;
    }
    if(!StackEmpty(&st))
        return false;
    StackDestroy(&st);
    return true;
}

注:上述代码还需要将栈的实现的代码拷贝一份上去。

2、用队列实现栈

入栈:选择非空队列进行入栈

出栈:队列中只留一个元素,将其他元素Pop至另一个队列,再对那个遗留的元素执行出队列操作即可模拟出栈动作

typedef struct {
    Queue q1;
    Queue q2;
} MyStack;
MyStack* myStackCreate() {
    MyStack* obj=(MyStack*)malloc(sizeof(MyStack));
    QueueInit(&obj->q1);
    QueueInit(&obj->q2);
    return obj;
}
 
void myStackPush(MyStack* obj, int x) {
    if(!QueueEmpty(&obj->q1))
    {
        QueuePush(&obj->q1,x);//入队,尾插
    }
    else
    {
        QueuePush(&obj->q2,x);//入队,尾插
    }
}
 
int myStackPop(MyStack* obj) {
    Queue* empty=&obj->q1;
    Queue* unEmpty=&obj->q2;
    if(QueueEmpty(&obj->q2))
    {
        empty=&obj->q2;
        unEmpty=&obj->q1;
    }
    while(QueueSize(unEmpty)>1)//将非空元素导入到空队列,留下最后一个
    {
        QueuePush(empty,QueueFront(unEmpty));//入队,尾插
        QueuePop(unEmpty);//出队(头删)
    }
    int top=QueueFront(unEmpty);
    QueuePop(unEmpty);//出队(头删)
    return top;
}
 
int myStackTop(MyStack* obj) {
    if(!QueueEmpty(&obj->q1))
    {
        return QueueBack(&obj->q1);//访问队尾数据
    }
    else
    {
        return QueueBack(&obj->q2);//访问队尾数据
    }
}
 
bool myStackEmpty(MyStack* obj) {
    return QueueEmpty(&obj->q1)&&QueueEmpty(&obj->q2);
}
 
void myStackFree(MyStack* obj) {
    QueueDestroy(&obj->q1);//销毁
    QueueDestroy(&obj->q2);//销毁
    free(obj);
}

注:上述代码还需要将队列的实现的代码拷贝一份上去。

3、用栈实现队列

现在有两个栈,第一个栈用于入栈、出栈至第二个栈的操作,第二个栈仅用于出栈操作。

入栈:在第一个栈中压入数据

出栈:如果第二个栈为空,则第一个栈中 数据全部出栈至第二个栈,由第二个栈专门执行出栈操作。等第二个栈再次为空,再次执行上述动作

MyQueue* myQueueCreate() {
    MyQueue* obj=(MyQueue*)malloc(sizeof(MyQueue));
    StackInit(&obj->st1);
    StackInit(&obj->st2);
    return obj;
}
 
void myQueuePush(MyQueue* obj, int x) {
    StackPush(&obj->st1, x);//压栈
}
 
int myQueuePop(MyQueue* obj) {
    if(StackEmpty(&obj->st2))
    {
        while(!StackEmpty(&obj->st1))
        {
            StackPush(&obj->st2, StackTop(&obj->st1));//压栈
            StackPop(&obj->st1);
        }
    }
    int val=StackTop(&obj->st2);
    StackPop(&obj->st2);
    return val;
}
 
int myQueuePeek(MyQueue* obj) {
    if(StackEmpty(&obj->st2))
    {
        while(!StackEmpty(&obj->st1))
        {
            StackPush(&obj->st2, StackTop(&obj->st1));//压栈
            StackPop(&obj->st1);
        }
    }
    return StackTop(&obj->st2);
}
 
bool myQueueEmpty(MyQueue* obj) {
    return StackEmpty(&obj->st1)&&StackEmpty(&obj->st2);
}
 
void myQueueFree(MyQueue* obj) {
    StackDestroy(&obj->st1);
    StackDestroy(&obj->st2);
    free(obj);
}

注:上述代码还需要将栈的实现的代码拷贝一份上去。

4、设计循环队列

typedef struct {
    int* arr;
    int front;//记录首
    int tail;//记录尾的下一个
    int capacity;//用于处理边界问题的一个变量
} MyCircularQueue;
MyCircularQueue* myCircularQueueCreate(int k) {
    MyCircularQueue* obj=(MyCircularQueue*)malloc(sizeof(MyCircularQueue));
    obj->arr=(int*)malloc(sizeof(int)*(k+1));
    obj->front=obj->tail=0;
    obj->capacity=k+1;//这里一定要写成k+1,写成k的话,后续处理边界问题要额外考虑分支情况
    return obj;
}
bool myCircularQueueIsEmpty(MyCircularQueue* obj) {
    return obj->front==obj->tail;
}
 
bool myCircularQueueIsFull(MyCircularQueue* obj) {
    return (obj->tail+1)%(obj->capacity)==obj->front;
}
bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) {
    if(myCircularQueueIsFull(obj))
        return false;
    obj->arr[obj->tail]=value;
    obj->tail++;
    obj->tail%=obj->capacity;
    return true;
}
 
bool myCircularQueueDeQueue(MyCircularQueue* obj) {
    if(myCircularQueueIsEmpty(obj))
        return false;
    obj->front++;
    obj->front%=obj->capacity;
    return true;
}
 
int myCircularQueueFront(MyCircularQueue* obj) {
    if(myCircularQueueIsEmpty(obj))
        return -1;
    return obj->arr[obj->front];
}
 
int myCircularQueueRear(MyCircularQueue* obj) {
    if(myCircularQueueIsEmpty(obj))
        return -1;
    return obj->arr[(obj->tail-1+obj->capacity)%obj->capacity];
}
void myCircularQueueFree(MyCircularQueue* obj) {
    free(obj->arr);
    obj->arr=NULL;
    free(obj);
}

因为循环队列无法区分队列为空和为满的情况,因为为空和未满,首位下标是一样的。

所以这道题有两种解法,计数确定栈空栈满,或者多开辟一个空间。本题采用后者。

可选的数据结构也有两种,顺序和链表。本题采用顺序。

上表为队列满的情况,无法再执行插入。运用顺序表,本题的难点在于如何处理tail和front在数组尾部的情况。

强烈建议在初始化的接口中将capacity定义为k+1,因为入队出队接口中%capacity后,可以同时满足正常和极端位置下的情况。(详见代码,一读就懂,后续读者可以逝一下将capacity定义为k,感受下区别)

capacity定义为k时的代码如下:

typedef struct {
    int* arr;
    int front;//记录首
    int tail;//记录尾的下一个
    int capacity;//总容量
} MyCircularQueue;
 
 
MyCircularQueue* myCircularQueueCreate(int k) {
    MyCircularQueue* obj=(MyCircularQueue*)malloc(sizeof(MyCircularQueue));
    obj->arr=(int*)malloc(sizeof(int)*(k+1));
    obj->front=obj->tail=0;
    obj->capacity=k;
    return obj;
}
bool myCircularQueueIsEmpty(MyCircularQueue* obj) {
    return obj->front==obj->tail;
}
 
bool myCircularQueueIsFull(MyCircularQueue* obj) {
    return (obj->tail+1)%(obj->capacity+1)==obj->front;
}
bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) {
    if(myCircularQueueIsFull(obj))
        return false;
    obj->arr[obj->tail]=value;
    obj->tail++;
    if(obj->tail>obj->capacity)
        obj->tail=obj->tail%obj->capacity-1;
    return true;
}
 
bool myCircularQueueDeQueue(MyCircularQueue* obj) {
    if(myCircularQueueIsEmpty(obj))
        return false;
    obj->front++;
    if(obj->front>obj->capacity)
        obj->front=obj->front%obj->capacity-1;
    return true;
}
 
int myCircularQueueFront(MyCircularQueue* obj) {
    if(myCircularQueueIsEmpty(obj))
        return -1;
    return obj->arr[obj->front];
}
 
int myCircularQueueRear(MyCircularQueue* obj) {
    if(myCircularQueueIsEmpty(obj))
        return -1;
    if(obj->tail!=0)
        return obj->arr[(obj->tail-1+obj->capacity)%obj->capacity];
    return obj->arr[obj->capacity];
}
void myCircularQueueFree(MyCircularQueue* obj) {
    free(obj->arr);
    obj->arr=NULL;
    free(obj);
}

主要区别就是入队出队代码,常规情况和边界情况不能统一。

以上就是C语言数据结构之栈和队列的实现及应用的详细内容,更多关于C语言 栈 队列的资料请关注脚本之家其它相关文章!

相关文章

  • 浅析C/C++中的可变参数与默认参数

    浅析C/C++中的可变参数与默认参数

    C支持可变参数的函数,这里的意思是C支持函数带有可变数量的参数,最常见的例子就是我们十分熟悉的printf()系列函数。我们还知道在函数调用时参数是自右向左压栈的
    2013-09-09
  • Java C++ 算法题解拓展leetcode670最大交换示例

    Java C++ 算法题解拓展leetcode670最大交换示例

    这篇文章主要介绍了Java C++算法题解拓展leetcode670最大交换示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-09-09
  • C++结构体与类的区别详情

    C++结构体与类的区别详情

    这篇文章主要介绍了C++结构体与类的区别,C++中的struct对C中的struct进行了扩充,它已经不再只是一个包含不同数据类型的数据结构了,它已经获取了太多的功能。下面我们一起进入文章俩姐具体内容,需要的朋友也可以参考一下
    2021-11-11
  • C/C++函数指针深入探究

    C/C++函数指针深入探究

    函数指针是一个指针变量,它可以存储函数的地址,然后使用函数指针,下面这篇文章主要给大家介绍了关于C语言进阶教程之函数指针的相关资料,需要的朋友可以参考下
    2022-08-08
  • C++实现模拟shell命令行(代码解析)

    C++实现模拟shell命令行(代码解析)

    这篇文章主要介绍了C++实现模拟shell命令行,本文通过实例代码进行命令行解析,代码简单易懂,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-12-12
  • C++中getline()和get()的方法浅析

    C++中getline()和get()的方法浅析

    大家都知道作为C++获取输入流的方法,几乎在任何一本资料书上getline()方法和get()方法都作为入门级的方法进行讲述,即便如此,笔者在学习C++的过程中仍经常忘记这二者的使用要点,可能也有C++的初学者对这两个方法还心存疑虑,本篇文章就这两个方法的使用进行简要阐述。
    2016-10-10
  • Qt 鼠标/触屏绘制平滑曲线(支持矢量/非矢量方式)

    Qt 鼠标/触屏绘制平滑曲线(支持矢量/非矢量方式)

    这篇文章主要介绍了Qt 鼠标/触屏绘制平滑曲线(支持矢量/非矢量方式),文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-04-04
  • C语言数据结构之单向链表详解分析

    C语言数据结构之单向链表详解分析

    链表可以说是一种最为基础的数据结构了,而单向链表更是基础中的基础。链表是由一组元素以特定的顺序组合或链接在一起的,不同元素之间在逻辑上相邻,但是在物理上并不一定相邻。在维护一组数据集合时,就可以使用链表,这一点和数组很相似
    2021-11-11
  • Qt实现数据导出到xls的示例代码

    Qt实现数据导出到xls的示例代码

    导入导出数据到csv由于语法简单,适用场景有限,于是本文将为大家介绍Qt如何实现导出数据到xls,感兴趣的小伙伴可以跟随小编一起试一试
    2022-01-01
  • C++核心编程之内存分区详解

    C++核心编程之内存分区详解

    这篇文章主要为大家详细介绍了C++核心编程之内存分区,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下,希望能够给你带来帮助
    2022-03-03

最新评论