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

数据结构之,栈与队列

数据结构之,栈与队列

  • 1、栈
    • 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、获取栈中有效元素个数
  • 2、队列
    • 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、销毁队列
  • 3、完整代码

1、栈

1.1、什么是“ 栈 ”

栈,是一种特殊的线性表。

栈,只能在该结构固定的一端,实现数据的添加,与删除。这一端,也叫做栈顶。对应的,另一端叫做栈底

往栈中插入数据,叫做入栈 / 压栈 / 进栈。插入数据在栈顶。如图:
在这里插入图片描述
删除栈中的数据,叫做出栈。删除数据也在栈顶。如图:
在这里插入图片描述
栈中元素的改变,遵循后进先出( Last In First Out )的原则。

1.2、栈的实现

1.2.1、栈的定义

栈的元素,可以用类似于顺序表元素的结构体,也可以用类似于链表节点的结构体。

如果用类似链表的结构:

struct stack
{int data;struct stack* next;
};

我们在之前的学习中就知道,链表头删、头插的时间复杂度为O(1)

那么在栈中,为了降低时间复杂度,我们只能把第一个栈元素,所在的一端,当作栈顶。这样一来,时间复杂度也为O(1)

但是,如果要插入新的栈元素,每次就要申请一个栈元素(结构体)类型大小的空间。

如果用类似顺序表的结构:

struct stack
{int* arr;int size;int capacity;
};

结合之前所学,我们只能将数组的末端,作为栈顶,以降低时间复杂度( O(1) )。

然而,如果我们想在栈中插入数据,只需申请对应数据元素类型大小的空间。

随着要插入数据的不断增多,这种利用数组创建栈元素的方法,在申请次数和开辟空间大小上,将会比上一个结构更少。

所以,我们采用的创建栈的方法:

typedef int STADataType;
typedef struct stack
{STADataType* arr;int top;         //栈顶位置int capacity;    //最大容量
}stack;

1.2.2、栈的初始化

由于栈存在空间不够要扩容,导致栈本身(连同栈中的数组一起)(的地址)改变,那么我们像操作函数中,输入的是栈的地址

代码演示:

void STAInit(stack* pst)
{assert(pst);pst->arr = NULL;pst->top = pst->capacity = 0;
}

1.2.3、栈的销毁

思路:

  1. 首先判断数组成员是否为NULL,再释放空间
  2. 各项参数初始化

代码演示:

void STADestroy(stack* pst)
{if (pst->arr)free(pst->arr);pst->arr = NULL;pst->top = pst->capacity = 0;
}

1.2.4、入栈

已知栈顶为数组末尾。

思路:

  1. 判断指针有效性
  2. 判断是否需要扩容。如果需要:
    • 重新确定最大容量capacity
      • 如果为0,赋值一个初始值(比如4
      • 如果不为0,2倍扩容
    • realloc()
    • 更改参数
  3. 先赋值,后top++。

代码实现:

void STAPush(stack* pst, STADataType val)
{assert(pst);if (pst->top == pst->capacity){int newcapacity = (capacity == 0) ? 4 : 2*capacity;STADataType* tmp = (STADataType*)realloc(newcapacity * sizeof(STADataType));if (tmp == NULL){perror("realloc failed\n");exit(1);}pst = tmp;pst->capacity = newcapacity;}pst->arr[pst->top++] = val;
}

1.2.5、判断栈是否为空

在出栈之前,我们有必要判断当前栈是否可以出栈,即判断栈是否为空

判断的依据是top是否为0。这里我们借助布尔类型值,更方便理解。

代码演示:

#include<stdbool.h>
bool STAEmpty(stack* pst)
{assert(pst);return (pst->top == 0);
}

这里如果栈为空,返回ture;否则返回false

1.2.6、出栈

思路:

  1. 判断栈是否为空
  2. 如果不为空,直接top--就可以。这个位置还有有效值,无需担心,后续添加一个新元素就可以覆盖这个位置。

代码演示:

void STAPop(stack* pst)
{assert(!STAEmpty(pst));pst->top--;
}

1.2.7、取栈顶元素

很简单,就是返回top下标的前一个下标对应的元素:

STADataType STATop(stack* pst)
{assert(!STAEmpty(pst));return pst->arr[pst->top - 1];
}

1.2.8、获取栈中有效元素个数

就是返回top

int STASize(stack* pst)
{assert(pst);return pst->top;
}

2、队列

提到了栈,就不得不提到队列。

2.1、什么是队列

队列,也是线性表的一种。

队列,只能在其一端插入数据,而在另一端删除数据
队列的简单图示
插入数据的操作,为入队列,入队列的一端为队尾。

删除数据的操作,为出队列,出队列的一端为队头。

2.2、队列的实现

2.2.1、队列的定义

这时,我们又回到了类似的问题:
实现队列,是用数组呢,还是用链表呢?

从空间复杂度看,
如果我们使用数组,那么,入队列和出队列,必有一种操作,其空间复杂度为O(N)(遍历数组,挪数腾位),似乎不太理想。
而如果我们使用链表,只要我们能够实现快速找到队头节点,和队尾节点的方法,我们就能够打下空间复杂度:

typedef int QueueDataType;
typedef struct ListNode
{QueueDataType data;struct ListNode* next;
}ListNode;
typedef struct Queue
{ListNode* phead;//定位队头节点ListNode* ptail;//定位队尾节点int size;       //统计节点个数
}Queue;

2.2.2、队列的初始化

代码演示:

void QueueInit(Queue* pq)
{assert(pq);pq->size = 0;pq->phead = pq->ptail = NULL;
}

2.2.3、入队列

入队列,有点像链表的尾插:

  1. 创建新节点
  2. ptail后插入
  3. size++

只不过,我们还要另外判断一下,当前节点是否为空。

代码演示:

void QueuePush(Queue* pq, QueueDataType val)
{assert(pq);//创建新节点QueueNode* newnode = (QueueNode*)malloc(sizeof(QueueNode));if (newnode == NULL){perror("malloc failed\n");exit(1);}newnode->data = val;newnode->next = NULL;//判断节点是否为空if (pq->phead == NULL){pq->phead = pq->ptail = newnode;}else{pq->ptail = newnode;pq->ptail = pq->ptail->next;}//节点个数记得更改pq->size++;
}

2.2.4、判断队列是否为空

在出队列之前,有必要判断队列是否可以删除数据。

判断的依据是,phead是否为NULL,或者size是否为0

bool QueueEmpty(Queue* pq)
{assert(pq);return pq->size == 0;
}

2.2.5、出队列

phead所在位置为队首。

先存下一个节点,然后释放,再更新、phead

void QueuePop(Queue* pq)
{//判断是否可删assert(!QueueEmpty(pq));//判断是否只剩下一个节点if (pq->phead == pq->ptail){//此时,头尾指向同一个节点,就全部初始化free(pq->phead);pq->phead = pq->ptail = NULL;}else{//正常出队列QueueNode* next = pq->phead->next;free(pq->phead);pq->phead = next;}//节点个数记得更改pq->size--;
}

2.2.6、返回队首和队尾

首先也是要判断队列是否为空。

然后返回pheadptail中对应的数据即可。

返回队首:

QueueDataType QueueHead(Queue* pq)
{assert(!QueueEmpty(pq));return pq->phead->data;
}

返回队尾:

QueueDataType QueueTail(Queue* pq)
{assert(!QueueEmpty(pq));return pq->ptail->data;
}

2.2.7、返回节点个数

这一步操作,回答了我们为什么要在队列中添加size成员。

即降低时间复杂度。

int QueueSize(Queue* pq)
{assert(pq);return pq->size;
}

2.2.8、销毁队列

这里的队列,由一个个节点组成。那么我们就可以利用之前销毁链表的做法:

  1. 存放下一个节点
  2. 释放当前节点
  3. 节点向前

代码演示:

void QueueDestroy(Queue* pq)
{assert(pq);//遍历QueueNode* pcur = pq->phead;while (pcur){QueueNode* next = pcur->next;free(pcur);pcur = pcur->next;}//出循环后,记得头尾指针及时置空pq->phead = pq->ptail = NULL;pq->size = 0;
}

3、完整代码

栈:

//stack.h
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<errno.h>
#include<stdbool.h>//定义栈
typedef int STADataType;
typedef struct stack
{STADataType* arr;int top;         //定义栈顶的位置int capacity;    //定义最大容量
}stack;//栈的初始化
void StackInit(stack* pst);//栈的销毁
void StackDestroy(stack* pst);//入栈
void StackPush(stack* pst, STADataType val);//判断栈是否为空
bool STAEmpty(stack* pst);//出栈
void StackPop(stack* pst);//取栈顶元素
STADataType STATop(stack* pst);//获取栈中有效元素个数
int STASize(stack* pst);
//stack.c
#include"stack.h"//栈的初始化
void StackInit(stack* pst)
{//判断assert(pst);//各个成员初始化pst->arr = NULL;pst->capacity = pst->top = 0;
}//栈的销毁
void StackDestroy(stack* pst)
{//首先判断数组成员是否为NULL,再释放if (pst->arr)free(pst->arr);pst->arr = NULL;pst->capacity = pst->top = 0;
}//入栈
void StackPush(stack* pst, STADataType val)
{//判断assert(pst);//首先判断空间够不够,不够,扩容//扩容前,还先判断capacity是否为0if (pst->top == pst->capacity){int newcapacity = (pst->capacity == 0) ? 4 : 2 * pst->capacity;STADataType* tmp = (STADataType*)realloc(pst->arr, newcapacity * sizeof(STADataType));//申请失败的判断if (tmp == NULL){perror("realloc falled\n");exit(1);}pst->arr = tmp;pst->capacity = newcapacity;}//放数pst->arr[pst->top++] = val;//先放数,后top++
}//判断栈是否为空
bool STAEmpty(stack* pst)
{assert(pst);return pst->top == 0;
}//出栈
void StackPop(stack* pst)
{//判断当前栈是否可删assert(!STAEmpty(pst));pst->top--;
}//取栈顶元素
STADataType STATop(stack* pst)
{assert(pst);return pst->arr[pst->top - 1];
}//获取栈中有效元素个数
int STASize(stack* pst)
{assert(pst);return pst->top;
}
//test.c
#include"stack.h"void test0()
{stack st;StackInit(&st);StackPush(&st, 1);StackPush(&st, 2);StackPush(&st, 3);StackPush(&st, 4);StackPush(&st, 5);//StackPop(&st);//printf("%d\n", STATop(&st));//printf("%d\n", STASize(&st));
}int main()
{test0();return 0;
}

队列:

//Queue.h
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>//利用节点创建队列,就定义节点与队列
typedef int QueueDataType;
typedef struct QueueNode
{QueueDataType data;struct QueueNode* next;
}QueueNode;
typedef struct Queue
{QueueNode* phead;QueueNode* ptail;int size;
}Queue;//队列初始化
void QueueInit(Queue* pq);//入队列
void QueuePush(Queue* pq, QueueDataType val);//判断队列是否为空
bool QueueEmpty(Queue* pq);//出队列
void QueuePop(Queue* pq);//获取队首元素
QueueDataType QueueHead(Queue* pq);//获取队尾元素
QueueDataType QueueTail(Queue* pq);//返回队列有效节点个数
int QueueSize(Queue* pq);//销毁队列
void QueueDestroy(Queue* pq);
//Queue.c
#include"Queue.h"//队列初始化
void QueueInit(Queue* pq)
{//判断assert(pq);//置空pq->phead = pq->ptail = NULL;pq->size = 0;
}//入队列
void QueuePush(Queue* pq, QueueDataType val)
{//判断assert(pq);//创建新节点,节点在堆上申请QueueNode* newnode = (QueueNode*)malloc(sizeof(QueueNode));//判断是否申请失败if (newnode == NULL){perror("malloc failed\n");exit(1);}newnode->data = val;newnode->next = NULL;//入队列之前,判断队列是否为空if (pq->phead == NULL){//为空,注意头尾和一pq->phead = pq->ptail = newnode;}else {//否则,正常入队列pq->ptail->next = newnode;pq->ptail = pq->ptail->next;}pq->size++;
}//判断队列是否为空
bool QueueEmpty(Queue* pq)
{assert(pq);return pq->phead == NULL;
}//出队列
void QueuePop(Queue* pq)
{//首先判断能不能出队列assert(!QueueEmpty(pq));//如果能//先判断是否只剩下一个节点if (pq->phead == pq->ptail){//直接释放,置空free(pq->phead);pq->phead = pq->ptail = NULL;}else{//先存下第一个节点的下一个节点QueueNode* next = pq->phead->next;//再释放free(pq->phead);//最后,更新第一个节点pq->phead = next;}pq->size--;
}//获取队首元素
QueueDataType QueueHead(Queue* pq)
{//首先判断队列是否为空assert(!QueueEmpty(pq));//然后返回return pq->phead->data;
}//获取队尾元素
QueueDataType QueueTail(Queue* pq)
{//首先判断队列是否为空assert(!QueueEmpty(pq));//然后返回return pq->ptail->data;
}//返回队列有效节点个数
int QueueSize(Queue* pq)
{assert(pq);//可以直接遍历,但是时间复杂度较大//可以在队列的定义中,加入统计节点个数的变量size,每次操作过后再变化return pq->size;
}//销毁队列
void QueueDestroy(Queue* pq)
{assert(pq);//遍历,存下一个节点,释放前一个结点QueueNode* pcur = pq->phead;while (pcur){QueueNode* next = pcur->next;free(pcur);pcur = next;}//野指针置空pq->phead = pq->ptail = NULL;pq->size = 0;
}
//test.c
#include"Queue.h"int main()
{Queue queue;QueueInit(&queue);QueuePush(&queue, 1);QueuePush(&queue, 2);QueuePush(&queue, 3);QueuePush(&queue, 4);QueuePop(&queue);//QueuePop(&queue);//QueuePop(&queue);//QueuePop(&queue);//QueuePop(&queue);printf("%d\n", QueueHead(&queue));printf("%d\n", QueueTail(&queue));printf("%d\n", QueueSize(&queue));QueueDestroy(&queue);return 0;
}
http://www.dtcms.com/a/589166.html

相关文章:

  • 【数据结构】基于哈夫曼树的数据压缩算法
  • SQLAlchemy2.0使用
  • 利用binlog2sql数据闪回实战
  • 东莞网站建设曼哈顿信科网站建设的总体设计概图
  • 算法:矩形区域不超过k的数值和
  • 算法30.0
  • 算法基础篇:(四)基础算法之前缀和
  • Nginx优化与防盗链
  • Vue-vuex 核心概念和 API
  • 分治归并算法第一弹
  • 【数据结构】哈夫曼树技术详解:原理、算法与应用
  • 贵阳网站备案在哪里网站红色
  • 个人网站公司网站区别经营区别数字货币怎么推广赚钱
  • 3GPP 各主要 Release(版本)及其发布时间和主要内容
  • RK3588同时硬解和GPU绘制16路1080P/通用其他RK系列板子/嵌入式监控系统/支持国产硬件和系统
  • BB ACS355变频器家装EMC电源滤波安装与使用指南
  • ARMV9.7 FEAT_SME2p3 视频编解码器新增指令扩展
  • 基础开发工具(下)
  • 一文详解分布式事务
  • HarmonyOS DataShareExtension深度解析:构建安全高效的数据共享架构
  • 团风做网站网站建站前期准备工作
  • .net 网站开发架构企业网站设置
  • 面试题 16.25. LRU 缓存
  • st表详解
  • 企业网站优化甲薇g71679做同等效果下拉词做网站白云
  • 9、webgl 基本概念 + 复合变换 + 平面内容复习
  • gRPC C++库架构与异步编程实践
  • 做网站如何收益发送wordpress
  • 人工智能备考——4部分总结
  • Vite.js 快速入门指南 (React + JavaScript 版)