栈和队列介绍与实现

本文详细介绍了栈和队列这两种经典的数据结构,包括它们的概念、链表实现、操作方法以及在函数调用、表达式求值、BFS、线程池等领域的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

栈(Stack)和队列(Queue)是两种经典的数据结构,它们在计算机科学和软件开发中起着重要的作用。本文将介绍栈和队列的概念、实现以及一些常见的应用场景。

目录

一、栈(Stack)的介绍

1.1 栈的概念

1.2 栈的实现

1.2.1 栈的数据结构

1.2.2 栈的初始化

1.2.3 入栈

1.2.4 出栈

1.2.5 获取栈顶元素

1.2.6 获取栈中有效元素个数

1.2.7 检测栈是否为空

1.2.8 销毁栈

1.3 栈的应用

二、队列(Queue)的介绍

2.1 队列的概念

2.2 队列的实现

2.2.1 队列的数据结构

2.2.2 初始化队列

2.2.3 入队列

2.2.4 出队列

2.2.5 获取队头元素

2.2.6 获取队尾元素

2.2.7 获取队列中有效元素个数

2.2.8 检测队列是否为空

2.2.9 销毁队列

2.3 队列的应用

四、总结


 

一、栈(Stack)的介绍

1.1 栈的概念

栈是一种线性数据结构,它具有“后进先出”(Last In First Out,LIFO)的特性。栈可以看作是一种特殊的线性表,只能在表的一端进行插入和删除操作,这一端被称为栈顶。

栈的插入操作称为入栈(Push),删除操作称为出栈(Pop)。栈不支持随机访问,只能访问栈顶元素。

1.2 栈的实现

栈的实现有多种方式,如使用数组、链表等。这里我们以链表实现栈为例进行介绍。

1.2.1 栈的数据结构
// 支持动态增长的栈
typedef int STDataType;
typedef struct Stack
{
	STDataType* _a;
	int _top;		// 栈顶
	int _capacity;  // 容量 
}Stack;
1.2.2 栈的初始化

使用StackInit函数初始化栈,需要传入一个Stack类型的指针,将栈的成员初始化为初始值。

void StackInit(Stack* ps)
{
    assert(ps);
    ps->_a = NULL;
    ps->_top = 0;
    ps->_capacity = 0;
}
1.2.3 入栈

使用StackPush函数将元素入栈,需要传入一个Stack类型的指针和一个STDataType类型的数据。入栈时,将数据放入栈顶,并更新栈顶指针。

void StackPush(Stack* ps, STDataType data)
{
    assert(ps);
    // 如果栈的容量不够,则进行扩容操作
    if (ps->_top == ps->_capacity)
    {
        size_t newCapacity = ps->_capacity == 0 ? 4 : ps->_capacity * 2;
        STDataType* tmp = realloc(ps->_a, newCapacity * sizeof(STDataType));
        if (tmp == NULL)
        {
            // 扩容失败,打印错误信息并退出程序
            printf("realloc failed\n");
            exit(1);
        }
        ps->_a = tmp;
        ps->_capacity = newCapacity;
    }
    ps->_a[ps->_top++] = data;
}
1.2.4 出栈

使用StackPop函数将栈顶元素出栈,需要传入一个Stack类型的指针。出栈时,将栈顶指针减一,即可将栈顶元素出栈。

void StackPop(Stack* ps)
{
    assert(ps);
    if (ps->_top == 0)
    {
        // 如果栈为空,打印错误信息并退出程序
        printf("stack is empty\n");
        exit(1);
    }
    ps->_top--;
}
1.2.5 获取栈顶元素

使用StackTop函数获取栈顶元素,需要传入一个Stack类型的指针。直接返回栈顶元素即可。

STDataType StackTop(Stack* ps)
{
    assert(ps);
    if (ps->_top == 0)
    {
        // 如果栈为空,打印错误信息并退出程序
        printf("stack is empty\n");
        exit(1);
    }
    return ps->_a[ps->_top - 1];
}
1.2.6 获取栈中有效元素个数

使用StackSize函数获取栈中有效元素个数,需要传入一个Stack类型的指针。直接返回栈中有效元素个数即可。

int StackSize(Stack* ps)
{
    assert(ps);
    return ps->_top;
}
1.2.7 检测栈是否为空

使用StackEmpty函数检测栈是否为空,需要传入一个Stack类型的指针。如果栈为空,返回非零结果;如果不为空,返回0。

int StackEmpty(Stack* ps)
{
    assert(ps);
    return ps->_top == 0;
}
1.2.8 销毁栈

使用StackDestroy函数销毁栈,需要传入一个Stack类型的指针。释放栈的内存空间即可。

void StackDestroy(Stack* ps)
{
    assert(ps);
    free(ps->_a);
    ps->_a = NULL;
    ps->_top = 0;
    ps->_capacity = 0;
}

以上就是栈的概念和实现的代码,下面给出一个栈的示例程序:

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

typedef int STDataType;
typedef struct Stack
{
    STDataType* _a;   // 存储栈中元素的数组
    int _top;         // 栈顶指针,指向栈顶元素的下一个位置
    int _capacity;    // 栈的容量
}Stack;

void StackInit(Stack* ps);  // 初始化栈
void StackPush(Stack* ps, STDataType data);  // 入栈
void StackPop(Stack* ps);  // 出栈
STDataType StackTop(Stack* ps);  // 获取栈顶元素
int StackSize(Stack* ps);  // 获取栈的大小
int StackEmpty(Stack* ps);  // 判断栈是否为空
void StackDestroy(Stack* ps);  // 销毁栈

int main()
{
    Stack s;
    StackInit(&s);  // 初始化栈

    StackPush(&s, 1);  // 入栈
    StackPush(&s, 2);
    StackPush(&s, 3);
    StackPush(&s, 4);
    printf("Stack Size: %d\n", StackSize(&s));  // 输出栈的大小
    printf("Stack Top: %d\n", StackTop(&s));  // 输出栈顶元素

    StackPop(&s);  // 出栈
    printf("Stack Top: %d\n", StackTop(&s));  // 输出栈顶元素

    StackDestroy(&s);  // 销毁栈
    return 0;
}

void StackInit(Stack* ps)
{
    assert(ps);
    ps->_a = NULL;  // 将栈中元素存储的数组置为空
    ps->_top = 0;  // 将栈顶指针置为0,表示空栈
    ps->_capacity = 0;  // 将栈的容量置为0
}

void StackPush(Stack* ps, STDataType data)
{
    assert(ps);
    if (ps->_top == ps->_capacity)  // 如果栈已满,则需要扩容
    {
        size_t newCapacity = ps->_capacity == 0 ? 4 : ps->_capacity * 2;  // 新容量为原容量的两倍,如果原容量为0,则设置为4
        STDataType* tmp = realloc(ps->_a, newCapacity * sizeof(STDataType));  // 重新分配内存
        if (tmp == NULL)
        {
            printf("realloc failed\n");
            exit(1);
        }
        ps->_a = tmp;  // 更新栈中元素存储的数组的地址
        ps->_capacity = newCapacity;  // 更新栈的容量
    }
    ps->_a[ps->_top++] = data;  // 将元素入栈,并更新栈顶指针的位置
}

void StackPop(Stack* ps)
{
    assert(ps);
    if (ps->_top == 0)  // 如果栈为空,则无法出栈
    {
        printf("stack is empty\n");
        exit(1);
    }
    ps->_top--;  // 更新栈顶指针的位置,表示出栈操作
}

STDataType StackTop(Stack* ps)
{
    assert(ps);
    if (ps->_top == 0)  // 如果栈为空,则无法获取栈顶元素
    {
        printf("stack is empty\n");
        exit(1);
    }
    return ps->_a[ps->_top - 1];  // 返回栈顶元素
}

int StackSize(Stack* ps)
{
    assert(ps);
    return ps->_top;  // 返回栈的大小,即栈顶指针的位置
}

int StackEmpty(Stack* ps)
{
    assert(ps);
    return ps->_top == 0;  // 如果栈顶指针的位置为0,则栈为空
}

void StackDestroy(Stack* ps)
{
    assert(ps);
    free(ps->_a);  // 释放栈中元素存储的数组的内存
    ps->_a = NULL;  // 将栈中元素存储的数组置为空
    ps->_top = 0;  // 将栈顶指针置为0
    ps->_capacity = 0;  // 将栈的容量置为0
}

运行结果如下:

 

1.3 栈的应用

栈在计算机科学和软件开发中有许多应用场景,以下是一些常见的应用场景:

  • 函数调用栈:函数的调用和返回过程使用栈来实现,每当一个函数被调用时,就会将函数的参数和返回地址等信息存入栈中,当函数返回时,再从栈中取出这些信息。

  • 表达式求值:在编译器和解释器中,栈常用于将中缀表达式转换为后缀表达式,并进行求值操作。

  • 括号匹配:栈可以用于判断括号是否匹配,每当遇到左括号时,就将其入栈,当遇到右括号时,就从栈中取出一个左括号并进行匹配。

  • 浏览器的前进和后退功能:浏览器的前进和后退功能可以使用两个栈来实现,一个栈用于存储已访问的网页,另一个栈用于存储已后退的网页。

二、队列(Queue)的介绍

2.1 队列的概念

队列是一种线性数据结构,它具有“先进先出”(First In First Out,FIFO)的特性。队列可以看作是一种特殊的线性表,只能在表的一端进行插入操作,称为队尾,只能在表的另一端进行删除操作,称为队首。

队列的插入操作称为入队(Enqueue),删除操作称为出队(Dequeue)。队列不支持随机访问,只能访问队首和队尾元素。

2.2 队列的实现

队列的实现同样有多种方式,如使用数组、链表等。这里我们以链表实现队列为例进行介绍。

2.2.1 队列的数据结构
// 链式结构:表示队列 
typedef struct QListNode 
{ 
	struct QListNode* _next; 
	QDataType _data; 
}QNode; 

// 队列的结构 
typedef struct Queue 
{ 
	QNode* _front; 
	QNode* _rear; 
}Queue; 
2.2.2 初始化队列

使用QueueInit函数初始化队列,需要传入一个Queue类型的指针,将队列的成员初始化为初始值。

void QueueInit(Queue* q)
{
    assert(q);
    q->_front = NULL;
    q->_rear = NULL;
}
2.2.3 入队列

使用QueuePush函数将元素入队列,需要传入一个Queue类型的指针和一个QDataType类型的数据。入队列时,创建一个新的QNode节点,将数据放入新节点,并更新队列的_rear指针。

void QueuePush(Queue* q, QDataType data)
{
    assert(q);
    QNode* newNode = (QNode*)malloc(sizeof(QNode));
    if (newNode == NULL)
    {
        printf("malloc failed\n");
        exit(1);
    }
    newNode->_data = data;
    newNode->_next = NULL;
    if (q->_rear == NULL)
    {
        q->_front = q->_rear = newNode;
    }
    else
    {
        q->_rear->_next = newNode;
        q->_rear = newNode;
    }
}
2.2.4 出队列

使用QueuePop函数将队头元素出队列,需要传入一个Queue类型的指针。出队列时,更新队列的_front指针,并释放出队列节点的内存。

void QueuePop(Queue* q)
{
    assert(q);
    if (q->_front == NULL)
    {
        printf("queue is empty\n");
        exit(1);
    }
    QNode* toDelete = q->_front;
    q->_front = q->_front->_next;
    if (q->_front == NULL)
    {
        q->_rear = NULL;
    }
    free(toDelete);
}
2.2.5 获取队头元素

使用QueueFront函数获取队头元素,需要传入一个Queue类型的指针。直接返回队头元素即可。

QDataType QueueFront(Queue* q)
{
    assert(q);
    if (q->_front == NULL)
    {
        printf("queue is empty\n");
        exit(1);
    }
    return q->_front->_data;
}
2.2.6 获取队尾元素

使用QueueBack函数获取队尾元素,需要传入一个Queue类型的指针。直接返回队尾元素即可。

QDataType QueueBack(Queue* q)
{
    assert(q);
    if (q->_rear == NULL)
    {
        printf("queue is empty\n");
        exit(1);
    }
    return q->_rear->_data;
}
2.2.7 获取队列中有效元素个数

使用QueueSize函数获取队列中有效元素个数,需要传入一个Queue类型的指针。遍历队列,并计数有效元素的个数,即可得到队列中有效元素个数。

int QueueSize(Queue* q)
{
    assert(q);
    int size = 0;
    QNode* cur = q->_front;
    while (cur != NULL)
    {
        size++;
        cur = cur->_next;
    }
    return size;
}
2.2.8 检测队列是否为空

使用QueueEmpty函数检测队列是否为空,需要传入一个Queue类型的指针。如果队列为空,返回非零结果;如果非空,返回0。

int QueueEmpty(Queue* q)
{
    assert(q);
    return q->_front == NULL;
}
2.2.9 销毁队列

使用QueueDestroy函数销毁队列,需要传入一个Queue类型的指针。遍历队列,释放每个节点的内存空间,并最后释放队列的内存空间。

void QueueDestroy(Queue* q)
{
    assert(q);
    QNode* cur = q->_front;
    while (cur != NULL)
    {
        QNode* toDelete = cur;
        cur = cur->_next;
        free(toDelete);
    }
    q->_front = NULL;
    q->_rear = NULL;
}

以上就是队列的概念和实现的代码,下面给出一个队列的示例程序:

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

typedef int QDataType;
typedef struct QListNode
{
    struct QListNode* _next;
    QDataType _data;
}QNode;

typedef struct Queue
{
    QNode* _front;
    QNode* _rear;
}Queue;


void QueueInit(Queue* q)
{
    assert(q);
    q->_front = NULL;
    q->_rear = NULL;
}


void QueuePush(Queue* q, QDataType data)
{
    assert(q);
    QNode* newNode = (QNode*)malloc(sizeof(QNode));  // 创建一个新节点
    if (newNode == NULL)
    {
        printf("malloc failed\n");
        exit(1);
    }
    newNode->_data = data;
    newNode->_next = NULL;
    if (q->_rear == NULL)  // 如果队列为空,将新节点设置为队列的首尾节点
    {
        q->_front = q->_rear = newNode;
    }
    else  // 如果队列不为空,将新节点插入到队列的尾部,并更新队尾指针
    {
        q->_rear->_next = newNode;
        q->_rear = newNode;
    }
}

void QueuePop(Queue* q)
{
    assert(q);
    if (q->_front == NULL)
    {
        printf("queue is empty\n");
        exit(1);
    }
    QNode* toDelete = q->_front;  // 获取队列的首节点
    q->_front = q->_front->_next;  // 更新队列的首节点指针
    if (q->_front == NULL)  // 如果队列只有一个节点,删除后会导致队列为空,需要同时更新队尾指针
    {
        q->_rear = NULL;
    }
    free(toDelete);  // 释放被删除节点的内存
}

QDataType QueueFront(Queue* q)
{
    assert(q);
    if (q->_front == NULL)
    {
        printf("queue is empty\n");
        exit(1);
    }
    return q->_front->_data;  // 返回队列的首节点的数据
}

QDataType QueueBack(Queue* q)
{
    assert(q);
    if (q->_rear == NULL)
    {
        printf("queue is empty\n");
        exit(1);
    }
    return q->_rear->_data;  // 返回队列的尾节点的数据
}

int QueueSize(Queue* q)
{
    assert(q);
    int size = 0;
    QNode* cur = q->_front;  // 从队列的首节点开始遍历
    while (cur != NULL)  // 遍历直到队列的末尾
    {
        size++;
        cur = cur->_next;  // 移动到下一个节点
    }
    return size;  // 返回队列的大小
}

int QueueEmpty(Queue* q)
{
    assert(q);
    return q->_front == NULL;  // 如果队列的首节点为空,则队列为空
}

void QueueDestroy(Queue* q)
{
    assert(q);
    QNode* cur = q->_front;  // 从队列的首节点开始遍历
    while (cur != NULL)  // 遍历直到队列的末尾
    {
        QNode* toDelete = cur;  // 记录当前节点
        cur = cur->_next;  // 移动到下一个节点
        free(toDelete);  // 释放当前节点的内存
    }
    q->_front = NULL;  // 将队列的首尾指针置空
    q->_rear = NULL;
}

int main() {
    Queue queue;
    QueueInit(&queue);  // 初始化队列

    printf("Pushing elements into queue...");  // 将元素依次插入队列
    for (int i = 0; i < 5; i++) {
        QueuePush(&queue, i);
    }
    printf("Done.\n");

    printf("Queue size: %d\n", QueueSize(&queue));  // 输出队列的大小
    printf("Front element: %d\n", QueueFront(&queue));  // 输出队列的首节点数据
    printf("Back element: %d\n", QueueBack(&queue));  // 输出队列的尾节点数据

    printf("Popping elements from queue...");  // 将元素依次从队列中删除并输出
    while (!QueueEmpty(&queue)) {
        printf("%d ", QueueFront(&queue));
        QueuePop(&queue);
    }
    printf("Done.\n");

    QueueDestroy(&queue);  // 销毁队列并释放内存

    return 0;
}

运行结果如下:

2.3 队列的应用

队列在计算机科学和软件开发中也有许多应用场景,以下是一些常见的应用场景:

  • 广度优先搜索(BFS):在图论和树的算法中,广度优先搜索(BFS)通常使用队列来实现,用于按层次遍历图或树的节点。

  • 线程池:线程池常常使用队列来存储待执行的任务,每当有新的任务提交时,就将其插入到队列的末尾,然后线程池中的线程按照一定的策略从队列中取出任务进行执行。

  • 消息队列:在分布式系统和消息中间件中,消息队列常常用于存储和传递消息,生产者将消息插入到队列的尾部,消费者从队列的头部获取消息进行处理。

  • 打印队列:在操作系统中,打印队列用于存储待打印的文件,打印任务按照先进先出的顺序进行执行。

 

四、总结

本文介绍了栈和队列的概念、实现以及一些常见的应用场景。栈具有“后进先出”(LIFO)的特性,适用于函数调用、表达式求值、括号匹配等场景。队列具有“先进先出”(FIFO)的特性,适用于广度优先搜索、线程池、消息队列等场景。栈和队列的实现可以使用数组、链表等数据结构,本文以数组为例进行了详细的代码实现和结果演示。希望通过阅读本文,读者能够更好地理解和应用栈和队列。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值