当前位置: 首页 > backend >正文

数据结构与算法——栈和队列

栈和队列

    • 概念与结构
    • 栈的实现
      • 栈的初始化
      • 栈的销毁
      • 判断栈是否为空
      • 入栈
      • 出栈
      • 取栈顶元素
      • 栈中有效元素个数
  • 队列
    • 概念与结构
    • 队列的实现
      • 队列结点结构
      • 队列结构
      • 初始化队列
      • 队列判空
      • 销毁队列
      • 入队列,队尾
      • 出队列,队头
      • 取队头数据
      • 取队尾数据
      • 队列有效数据个数

概念与结构

栈:⼀种特殊的线性表,其只允许在固定的⼀端进⾏插⼊和删除元素操作。进⾏数据插⼊和删除操作的⼀端称为栈顶,另⼀端称为栈底。栈中的数据元素遵守后进先出的原则。(先进的后出,后进的先出)

压栈:栈的插⼊操作叫做进栈/压栈/⼊栈,⼊数据在栈顶。
出栈:栈的删除操作叫做出栈。出数据也在栈顶。

栈的底层结构选型:链表 or 数组?
  • 使用链表来说,栈顶是经常操作的一端,为了降低时间复杂度,将链表的头看作栈顶,尾看作栈底,入栈的时间复杂度则为O(1),出栈时间复杂度O(1)。

  • 使用数组来说,尾部看作栈顶,头部看作栈顶,入栈、出栈时间复杂度为O(1)。

他们的入栈、出栈操作时间复杂度都一样,但是我们选择数组更好,因为链表是独立的结点,每次操作都要申请一个新的结点,8个字节,空间的消耗更大,所以底层选择数组。所以物理、逻辑结构都是是线性的。

栈的实现

栈的初始化

首先定义栈的结构,然后定义一个初始化函数,因为对形参的改变需要影响实参,所以传址调用,且在函数中用一级指针接收。

头文件

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>//定义栈的结构
typedef int StackDataType;
typedef struct Stack {StackDataType* arr;int top;     //指向栈顶的位置int capacity;//栈的容量
}Stack;//初始化
void StcakInit(Stack* ps);//形参的变化要影响实参,所以要传传址,用一级指针接受

实现文件

#include"Stack.h"//初始化
void StcakInit(Stack* ps) //ps是形参,形参的初始化要改变实参,所以传址
{ps->arr = NULL;ps->top = ps->capacity = 0;
}

测试文件

#include"Stack.h"void test01()
{Stack st;StackInit(&st);//传址,st是实参
}int main()
{test01();return 0;
}

栈的销毁

//销毁栈
void StackDestroy(Stack* ps)
{if (ps->arr)free(ps->arr);ps->arr = NULL;ps->capacity = ps->top = 0;
}

判断栈是否为空

bool 函数是一种返回 布尔值(true 或 false) 的函数,通常用于逻辑判断。在 C 语言中,bool 类型需要包含 <stdbool.h> 头文件才能使用。
检查栈是否为空:

  • 如果 ps->top == 0(栈空),返回 true。

  • 否则返回 false。

//判断栈是否为空
bool StackEmpty(Stack* ps)
{assert(ps);return ps->top == 0; //如果 top == 0,返回 true(栈空);否则返回 false
}

入栈

定义一个入栈的函数,一个保存结构体地址的指针变量,一个需要插入的数据,因为进行了初始化,所以要进行增容,这里需要使用realloc(指向之前分配的内存块的指针,新的内存块大小(字节数)),然后将新增容的空间和重新分配内存块的大小赋值给原来的capacity和arr。

//插入数据-栈顶入数据
void StackPush(Stack* ps, StackDataType x)
{assert(ps);if (ps->top == ps->capacity){//增容int newCapacity = ps->capacity = 0 ? 4 : 2 * ps->capacity;StackDataType * tmp = (StackDataType*)realloc(ps->arr, newCapacity * sizeof(StackDataType));if (tmp == NULL){perror("relloc fail!");exit(1);}ps->arr = tmp;ps->capacity = newCapacity;}ps->arr[ps->top++] = x;
}

出栈

因为栈为空返回的是true,所以这里要取反

//出栈-后进的先出,先进的后出
void StackPop(Stack* ps)
{assert(!StackEmpty(ps));--ps->top;
}

取栈顶元素

不同于出栈的是不会减少栈中的元素个数

//取栈顶元素
StackDataType* StcakTop(Stack* ps)
{assert(!StackEmpty(ps));return ps->arr[ps->top - 1];
}

栈中有效元素个数

int StackSize(ST* ps)
{return ps->top;
}

队列

概念与结构

概念:只允许在⼀端进⾏插⼊数据操作,在另⼀端进⾏删除数据操作的特殊线性表,队列具有先进先出FIFO(First In First Out)

⼊队列:进⾏插⼊操作的⼀端称为队尾
出队列:进⾏删除操作的⼀端称为队头

在这里插入图片描述
队列的底层结构如何选型?

数组:选择最后为队尾时->入队O(1),出队O(N)    选择第一个数据为队尾时->入队O(N),出队O(1)
链表:单链表时,第一个结点为队头,尾结点为队尾时,入队O(N)(因为需要找尾结点),出队O(1)双向链表时,这时入队,出队的时间复杂度都为O(1),但是每个结点的空间消耗的太大。

优化单链表:在定义第一个结点的指针为phead的基础上,定义尾结点的指针为ptail,入队时候直接再ptail后面插入结点,这样也入队,出队时间复杂度都为O(1)。

队列的实现

队列结点结构

typedef int QNDataType;//队列结点的结构
typedef struct QueueNode {QNDataType data;struct QueueNode* next;
}QNode;

队列结构

//队列的结构
typedef struct Queue {QNode* phead;QNode* ptail;
}Q;

初始化队列

//队列的初始化
void QueueInit(Q* pq)
{assert(pq);pq->phead = pq->ptail = NULL;
}

队列判空

//判空
bool QueueEmpty(Q* pq)
{assert(pq);return pq->phead == NULL;
}

销毁队列

pcur从头开始遍历(条件:pcur不为空),然后将pcur的下一个结点存起来。释放pcur,将存起来的结点赋给pcur,最后手动将phead和ptail置空。

//销毁队列
void QueueDestroy(Q* pq)
{assert(pq);QNode* pcur = pq->phead;while(pcur){QNode* next = pcur->next;free(pcur);pcur = next;}pq->phead = pq->ptail = NULL;//pq->size = 0;
}

入队列,队尾

队列不为空:pq->ptail->next = newnode,然后将pq->ptail = pq->ptail->next
在这里插入图片描述
为空的时候:
phead = ptail = newnode

//队尾入数据
void QueuePush(Q* pq, QNDataType x)
{assert(pq);//申请结点空间QNode* newnode = (QNode*)malloc(sizeof(QNode));if (newnode == NULL){perror("malloc fail!");exit(1);}newnode->data = x;newnode->next = NULL;//队列为空if (pq->phead == NULL){pq->phead = pq->ptail = newnode;}else {pq->ptail->next = newnode;}pq->ptail = newnode;
}

出队列,队头

先将phead->next保存下来,然后释放phead,然后phead = phead->next ,只有一个结点(头,尾都指向同一个节点)phead和ptail都需要置空
在这里插入图片描述

//队头出数据
void QueuePop(Q* pq)
{//判空assert(!QueueEmpty(pq));//只有一个结点if (pq->phead == pq->ptail){free(pq->phead);pq->phead = pq->ptail = NULL;}else {QNode* next = pq->phead->next;free(pq->phead);pq->phead = next;}
}

取队头数据

//取队头数据
QNDataType QueueFront(Q* pq)
{assert(!QueueEmpty(pq));return pq->phead->data;
}

取队尾数据

//取队尾数据
QNDataType QueueBack(Q* pq)
{assert(!QueueEmpty(pq));return pq->ptail->data;
}

队列有效数据个数

第一种方式:遍历链表——适用于不会频繁调用队列有效数据个数的场景

//队列有效元素个数
int QueueSize(Q* pq)
{assert(pq);//第一种方式:遍历链表——适用于不会频繁调用队列有效数据个数的场景QNode* pcur = pq->phead;int size = 0;while (pcur){size++;pcur = pcur->next;}return size;}

第二种方式:遍历链表——适用于频繁调用队列有效数据个数的场景(完整代码)
//return pq->size;

#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>typedef int QNDataType;typedef struct QueueNode {QNDataType data;struct QueueNode* next;
} QNode;typedef struct Queue {QNode* phead;QNode* ptail;int size;  // 新增:记录队列元素个数
} Q;void QueueInit(Q* pq);
bool QueueEmpty(const Q* pq);
void QueuePush(Q* pq, QNDataType x);
void QueuePop(Q* pq);
QNDataType QueueFront(const Q* pq);
QNDataType QueueBack(const Q* pq);
int QueueSize(Q* pq);  // 新增:O(1) 方式
void QueueDestroy(Q* pq);
#include "Queue.h"void QueueInit(Q* pq) {assert(pq);pq->phead = pq->ptail = NULL;pq->size = 0;
}bool QueueEmpty(const Q* pq) {assert(pq);return pq->size == 0;  // 直接判断 size
}void QueuePush(Q* pq, QNDataType x) {assert(pq);QNode* newnode = (QNode*)malloc(sizeof(QNode));if (newnode == NULL) {perror("malloc fail!");exit(1);}newnode->data = x;newnode->next = NULL;if (pq->phead == NULL) {pq->phead = pq->ptail = newnode;} else {pq->ptail->next = newnode;pq->ptail = newnode;}pq->size++;  // 更新 size
}void QueuePop(Q* pq) {assert(!QueueEmpty(pq));QNode* next = pq->phead->next;free(pq->phead);pq->phead = next;if (pq->phead == NULL) {pq->ptail = NULL;}pq->size--;  // 更新 size
}QNDataType QueueFront(const Q* pq) {assert(!QueueEmpty(pq));return pq->phead->data;
}QNDataType QueueBack(const Q* pq) {assert(!QueueEmpty(pq));return pq->ptail->data;
}int QueueSize(Q* pq) {assert(pq);return pq->size;  // O(1) 直接返回
}void QueueDestroy(Q* pq) {assert(pq);QNode* pcur = pq->phead;while (pcur) {QNode* next = pcur->next;free(pcur);pcur = next;}pq->phead = pq->ptail = NULL;pq->size = 0;  // 重置 size
}
http://www.xdnf.cn/news/7033.html

相关文章:

  • Python字符串格式化(一):三种经典格式化方法
  • 从零开始实现大语言模型(十六):加载开源大语言模型参数
  • 《Python星球日记》 第87天:什么是大语言模型 LLM?
  • 1_Spring 【IOC容器的创建】
  • 深入了解linux系统—— 基础IO(下)
  • 【QGIS二次开发】地图编辑-08
  • tauri2项目使用sidcar嵌入可执行文件并使用命令行调用
  • 实战设计模式之状态模式
  • 互联网大厂Java面试场景:从Spring Boot到分布式缓存技术的探讨
  • 十一、STM32入门学习之FREERTOS移植
  • React 19 中的useRef得到了进一步加强。
  • ngx_http_proxy_protocol_vendor_module 模块
  • 【Linux】进程的基本概念
  • 虚幻引擎5-Unreal Engine笔记之Pawn与胶囊体的关系
  • 【android bluetooth 协议分析 01】【HCI 层介绍 5】【SetEventMask命令介绍】
  • Elasticsearch 初步认识
  • 用 UniApp 构建习惯打卡 App —— HabitLoop 开发记
  • 【cursor】有效解决
  • Denoising Score Matching with Langevin Dynamics
  • 【HarmonyOS 5开发入门】DevEco Studio安装配置完全指南
  • Flink 的窗口机制
  • 【ant design】ant-design-vue 4.0实现主题色切换
  • 【软考 McCabe度量法】
  • 深入理解指针(6)
  • 基因编辑根治胰腺癌-陈墨仙
  • Raft 协议:分布式一致性算法的核心思想
  • 欢乐熊大话蓝牙知识4:GATT 协议全解:蓝牙传数据到底怎么传?
  • 费马小定理
  • 数学复习笔记 16
  • 【Linux网络编程】Socket编程:协议理论入门