数据结构——栈和队列

在这里插入图片描述

栈和队列的建立

  • 前言
  • 一、栈
    • 1.栈的概念
    • 2.栈的实现
    • 3.代码示例
      • (1)Stack.h
      • (2)Stack.c
      • (3)Test.c
      • (4)运行结果
      • (5)完整代码演示
  • 二、队列
    • 1.队列的概念
    • 2.队列的实现
    • 3.代码示例
      • (1)Queue.h
      • (2)Queue.c
      • (3)Test.c
      • (4)运行结果
      • (5)完整代码演示
  • 三、栈的应用例题
    • 方法一
    • 方法二
  • 总结


前言

今天我们来学习栈和队列的简易建立!
在后面还会有一道栈的应用题,检测大家的用功程度!
加油吧!


一、栈

1.栈的概念

:一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作。 进行数据插入和删除操作的一端称为栈顶,另一端称为栈底
栈中的数据元素遵守后进先出LIFO(Last In First Out)的原则。 压栈:栈的插入操作叫做进栈/压栈/入栈,入数据在栈顶
出栈:栈的删除操作叫做出栈。出数据也在栈顶

模型图示例:
在这里插入图片描述
在这里插入图片描述

2.栈的实现

栈的实现一般可以使用数组或者链表实现,相对而言数组的结构实现更优一些。因为数组在尾上插入数据的代价比较小

图片示例:
在这里插入图片描述
在这里插入图片描述

3.代码示例

(1)Stack.h

1.头文件的声明

//头文件的声明
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>

2.栈的接口定义

//栈的接口定义
typedef int STDataType;
typedef struct Stack
{STDataType* a;int top;int capacity;
}ST;

3.初始化和销毁函数的声明

//初始化
void STInit(ST* ps);
//销毁
void STDestroy(ST* ps);

4.入栈和出栈函数的声明

//插入
void STPush(ST* ps, STDataType x);
//删除
void STPop(ST* ps);

5.查找栈顶元素和长度计算函数以及判空函数的声明

//插入
//查找栈顶元素
STDataType STTop(ST* ps);
//长度计算
int STSize(ST* ps);
//判断是否为空
bool STEmpty(ST* ps);

(2)Stack.c

1.头文件的声明

#include "Stack.h"

2.初始化和销毁函数的定义

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

3.入栈和出栈函数的定义

//插入
void STPush(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->a, sizeof(STDataType) * newCapacity);if (tmp == NULL){perror("realloc fail");exit(-1);}ps->a = tmp;ps->capacity = newCapacity;}ps->a[ps->top] = x;ps->top++;
}//删除栈顶元素
void STPop(ST* ps)
{assert(ps);assert(ps->top > 0);--ps->top;
}

4.查找栈顶元素和长度计算函数以及判空函数的定义

//查找栈顶元素
STDataType STTop(ST* ps)
{assert(ps);assert(ps->top > 0);return ps->a[ps->top - 1];
}//长度计算
int STSize(ST* ps)
{assert(ps);return ps->top;
}//判断是否为空
bool STEmpty(ST* ps)
{assert(ps);return ps->top == 0;
}

(3)Test.c

1.头文件的声明

#include "Stack.h"

2.测试函数的定义

void TestStack()
{ST st;STInit(&st);STPush(&st, 1);STPush(&st, 2);STPush(&st, 3);STPush(&st, 4);STPush(&st, 5);while (!STEmpty(&st)){printf("%d ", STTop(&st));STPop(&st);}printf("\n");STPush(&st, 6);STPush(&st, 7);STPush(&st, 8);STPush(&st, 9);STPush(&st, 10);while (!STEmpty(&st)){printf("%d ", STTop(&st));STPop(&st);}printf("\n");STDestroy(&st);
}

3.主函数的定义

int main()
{TestStack();return 0;
}

(4)运行结果

在这里插入图片描述

(5)完整代码演示

1.Stack.h

#pragma once
//头文件的声明
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>//栈的接口定义
typedef int STDataType;
typedef struct Stack
{STDataType* a;int top;int capacity;
}ST;//初始化
void STInit(ST* ps);
//销毁
void STDestroy(ST* ps);
//插入
void STPush(ST* ps, STDataType x);
//删除
void STPop(ST* ps);
//查找栈顶元素
STDataType STTop(ST* ps);
//长度计算
int STSize(ST* ps);
//判断是否为空
bool STEmpty(ST* ps);

2.Stack.c

#include "Stack.h"//初始化
void STInit(ST* ps)
{assert(ps);ps->a = NULL;ps->capacity = 0;ps->top = 0;
}//销毁
void STDestroy(ST* ps)
{assert(ps);free(ps->a);ps->a = NULL;ps->top = ps->capacity = 0;
}//插入
void STPush(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->a, sizeof(STDataType) * newCapacity);if (tmp == NULL){perror("realloc fail");exit(-1);}ps->a = tmp;ps->capacity = newCapacity;}ps->a[ps->top] = x;ps->top++;
}//删除栈顶元素
void STPop(ST* ps)
{assert(ps);assert(ps->top > 0);--ps->top;
}//查找栈顶元素
STDataType STTop(ST* ps)
{assert(ps);assert(ps->top > 0);return ps->a[ps->top - 1];
}//长度计算
int STSize(ST* ps)
{assert(ps);return ps->top;
}//判断是否为空
bool STEmpty(ST* ps)
{assert(ps);return ps->top == 0;
}

3.Test.c

#include "Stack.h"void TestStack1()
{ST st;STInit(&st);STPush(&st, 1);STPush(&st, 2);STPush(&st, 3);STPush(&st, 4);STPush(&st, 5);while (!STEmpty(&st)){printf("%d ", STTop(&st));STPop(&st);}printf("\n");STPush(&st, 6);STPush(&st, 7);STPush(&st, 8);STPush(&st, 9);STPush(&st, 10);while (!STEmpty(&st)){printf("%d ", STTop(&st));STPop(&st);}printf("\n");STDestroy(&st);
}int main()
{TestStack1();return 0;
}

二、队列

1.队列的概念

队列:只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出 FIFO(First In First Out)
入队列:进行插入操作的一端称为队尾 出队列:进行删除操作的一端称为队头

模型图示例:
在这里插入图片描述

2.队列的实现

队列也可以数组和链表的结构实现,使用链表的结构实现更优一些,因为如果使用数组的结构,出队列在数组头上出数据,效率会比较低

图片示例:
在这里插入图片描述

3.代码示例

(1)Queue.h

1.头文件的声明

#pragma once
//头文件的声明
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>

2.队列接口的定义

//链表接口定义
typedef int QDataType;
typedef struct QueueNode
{struct QueueNode* next;QDataType data;
}QNode;//队列接口定义
typedef struct Queue
{QNode* head;QNode* tail;int size;
}Que;

3.初始化和销毁函数的声明

//队列初始化
void QueueInit(Que* pq);
//队列销毁
void QueueDestroy(Que* pq);

4.入队列和出队列函数的声明

//插入
void QueuePush(Que* pq, QDataType x);
//删除
void QueuePop(Que* pq);

5.查找队头、查找队尾函数的声明

//查找队头元素
QDataType QueueFront(Que* pq);
//查找队尾元素
QDataType QueueBack(Que* pq);

6.判空以及长度计算函数的声明

//判断是否为空
bool QueueEmpty(Que* pq);
//计算长度
int QueueSize(Que* pq);

(2)Queue.c

1.头文件的声明

#include "Queue.h"

2.初始化和销毁函数的定义

void QueueInit(Que* pq)
{assert(pq);pq->head = pq->tail = NULL;pq->size = 0;
}void QueueDestroy(Que* pq)
{assert(pq);QNode* cur = pq->head;while (cur){QNode* next = cur->next;free(cur);cur = next;}pq->head = pq->tail = NULL;pq->size = 0;
}

3.入队列和出队列函数的定义

void QueuePush(Que* pq, QDataType 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->tail == NULL){pq->head = pq->tail = newnode;}else{pq->tail->next = newnode;pq->tail = newnode;}pq->size++;
}void QueuePop(Que* pq)
{assert(pq);//判断队列指针指向是否为空assert(!QueueEmpty(pq));//判断队列里面的数据是否为空if (pq->head->next == NULL){free(pq->head);pq->head = pq->tail = NULL;}else{QNode* next = pq->head->next;free(pq->head);pq->head = next;}pq->size--;
}

4.查找队头、查找队尾函数的定义

//查找队头元素
QDataType QueueFront(Que* pq)
{assert(pq);assert(!QueueEmpty(pq));return pq->head->data;
}//查找队尾元素
QDataType QueueBack(Que* pq)
{assert(pq);assert(!QueueEmpty(pq));return pq->tail->data;
}

5.判空以及长度计算函数的定义

//判断是否为空
bool QueueEmpty(Que* pq)
{assert(pq);return pq->head == NULL;
}//长度计算
int QueueSize(Que* pq)
{assert(pq);return pq->size;
}

(3)Test.c

1.头文件的声明

#include "Queue.h"

2.测试函数的定义

void QueueTest() {Que pq;QueueInit(&pq);QueuePush(&pq, 1);QueuePush(&pq, 2);QueuePush(&pq, 3);QueuePush(&pq, 4);QueuePush(&pq, 5);while (!QueueEmpty(&pq)) {printf("%d ", QueueFront(&pq));QueuePop(&pq);}QueueDestroy(&pq);
}

3.主函数的定义

int main() {QueueTest();return 0;
}

(4)运行结果

在这里插入图片描述

(5)完整代码演示

1.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;
}Que;//队列初始化
void QueueInit(Que* pq);
//队列销毁
void QueueDestroy(Que* pq);
//插入
void QueuePush(Que* pq, QDataType x);
//删除
void QueuePop(Que* pq);
//查找队头元素
QDataType QueueFront(Que* pq);
//查找队尾元素
QDataType QueueBack(Que* pq);
//判断是否为空
bool QueueEmpty(Que* pq);
//计算长度
int QueueSize(Que* pq);

2.Queue.c

#include "Queue.h"void QueueInit(Que* pq)
{assert(pq);pq->head = pq->tail = NULL;pq->size = 0;
}void QueueDestroy(Que* pq)
{assert(pq);QNode* cur = pq->head;while (cur){QNode* next = cur->next;free(cur);cur = next;}pq->head = pq->tail = NULL;pq->size = 0;
}void QueuePush(Que* pq, QDataType 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->tail == NULL){pq->head = pq->tail = newnode;}else{pq->tail->next = newnode;pq->tail = newnode;}pq->size++;
}void QueuePop(Que* pq)
{assert(pq);//判断队列指针指向是否为空assert(!QueueEmpty(pq));//判断队列里面的数据是否为空if (pq->head->next == NULL){free(pq->head);pq->head = pq->tail = NULL;}else{QNode* next = pq->head->next;free(pq->head);pq->head = next;}pq->size--;
}//查找队头元素
QDataType QueueFront(Que* pq)
{assert(pq);assert(!QueueEmpty(pq));return pq->head->data;
}//查找队尾元素
QDataType QueueBack(Que* pq)
{assert(pq);assert(!QueueEmpty(pq));return pq->tail->data;
}//判断是否为空
bool QueueEmpty(Que* pq)
{assert(pq);return pq->head == NULL;
}//长度计算
int QueueSize(Que* pq)
{assert(pq);return pq->size;
}

3.Test.c

#include "Queue.h"void QueueTest() {Que pq;QueueInit(&pq);QueuePush(&pq, 1);QueuePush(&pq, 2);QueuePush(&pq, 3);QueuePush(&pq, 4);QueuePush(&pq, 5);while (!QueueEmpty(&pq)) {printf("%d ", QueueFront(&pq));QueuePop(&pq);}QueueDestroy(&pq);
}int main() {QueueTest();return 0;
}

三、栈的应用例题

题目:括号匹配问题
题目链接
在这里插入图片描述

提示:
. 1 <= s.length <= 104
. s 仅由括号 ‘()[]{}’ 组成

解题思路:
这道题我有两种解法!
建栈法和暴力破解法!

方法一

首先第一种就是利用栈来解决:
1.左括号,入栈;
2.右括号与栈中的栈顶括号进行匹配;

图例:
在这里插入图片描述
代码示例:

引用之前栈的建立函数:

//栈的接口定义
typedef int STDataType;
typedef struct Stack
{STDataType* a;int top;int capacity;
}ST;//初始化
void STInit(ST* ps);
//销毁
void STDestroy(ST* ps);
//插入
void STPush(ST* ps, STDataType x);
//删除
void STPop(ST* ps);
//查找栈顶元素
STDataType STTop(ST* ps);
//长度计算
int STSize(ST* ps);
//判断是否为空
bool STEmpty(ST* ps);//初始化
void STInit(ST* ps)
{assert(ps);ps->a = NULL;ps->capacity = 0;ps->top = 0;
}//销毁
void STDestroy(ST* ps)
{assert(ps);free(ps->a);ps->a = NULL;ps->top = ps->capacity = 0;
}//插入
void STPush(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->a, sizeof(STDataType) * newCapacity);if (tmp == NULL){perror("realloc fail");exit(-1);}ps->a = tmp;ps->capacity = newCapacity;}ps->a[ps->top] = x;ps->top++;
}//删除栈顶元素
void STPop(ST* ps)
{assert(ps);assert(ps->top > 0);--ps->top;
}//查找栈顶元素
STDataType STTop(ST* ps)
{assert(ps);assert(ps->top > 0);return ps->a[ps->top - 1];
}//长度计算
int STSize(ST* ps)
{assert(ps);return ps->top;
}//判断是否为空
bool STEmpty(ST* ps)
{assert(ps);return ps->top == 0;
}

功能函数的定义

bool isValid(char* s) {ST st;STInit(&st);char topVal;while (*s ) {if (*s == '(' || *s == '[' || *s == '{') {STPush(&st, *s);}else {//数量不匹配if (STEmpty(&st)){STDestroy(&st);return false;}topVal = STTop(&st);STPop(&st);if ((*s == ']' && topVal != '[')|| (*s == ']' && topVal != '[')|| (*s == ']' && topVal != '[')) {STDestroy(&st);return false;}}++s;}bool ret = STEmpty(&st);STDestroy(&st);return ret;
}

方法二

第二种方法就是例子中如果不存在无效括号的话,那么至少有一个是左右括号相邻的;
所以先找到相邻且匹配的括号将其移除,然后慢慢一点一点全部都移除的话则说明全部括号有效!

图例
在这里插入图片描述

代码演示:

bool isValid(char* s) {char* p = s;while (*p) {p = s;while (*p) {if (*p + 1 == *(p + 1) || *p + 2 == *(p + 1)) {//查看assii码表了解符号的大小char* move = p;while (true) {*move = *(move + 2);if(*move=='\0')break;move++;}break;}else {p++;}}if (*p == '\0' && *s != '\0')return false;}return true;
}

总结

今天的内容有点多,希望大家仔细观看,细细揣摩!
好好学习,天天向上!
不变的真理!

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

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

相关文章

容器和云原生(三):kubernetes搭建与使用

目录 单机K8S docker containerd image依赖 kubeadm初始化 验证 crictl工具 K8S核心组件 上文安装单机docker是很简单docker&#xff0c;但是生产环境需要多个主机&#xff0c;主机上启动多个docker容器&#xff0c;相同容器会绑定形成1个服务service&#xff0c;微服务…

在vue中使用codemirror格式化JSON

1. 下载指定版本的包 (避免引发不必要的错误) yarn add codemirror^5.64.02. 导入需要的文件 import CodeMirror from codemirrorimport codemirror/addon/lint/lint.cssimport codemirror/addon/fold/foldgutter.cssimport codemirror/lib/codemirror.cssimport codemirror/t…

【C++/C 实现球球大作战】

目录 1.引言2.游戏设计&#xff1a;概述游戏的玩法和操作方式。3.游戏实现&#xff08;1&#xff09;函数 GameInit() 初始化游戏的函数。&#xff08;2&#xff09;函数 GameDraw() 用于绘制游戏场景的函数。&#xff08;3&#xff09;函数 keyControl(int speed) 负责处理键盘…

《动手学深度学习》-20卷积层里的填充和步幅

沐神版《动手学深度学习》学习笔记&#xff0c;记录学习过程&#xff0c;详细的内容请大家购买书籍查阅。 b站视频链接 开源教程链接 卷积层里的填充和步幅 应用5x5大小的卷积核&#xff0c;输入32x32&#xff0c;输出会变为28x28。 更大的卷积核更快地减小输出大小。 导致网…

【数据治理】什么是数据库归档

文章目录 前言什么是数据归档 前言 如果您的日常工作中需要对数据库进行管理&#xff0c;那您肯定已经或即将遭遇这样的困惑&#xff1a;随着业务的蓬勃发展&#xff0c;数据库文件的大小逐渐增大&#xff0c;您需要为在线业务提供越来越大的高性能磁盘容量&#xff0c;但数据…

Angular中 ng-template 和 ng-content 有何区别?

在Angular中&#xff0c;ng-template 和 ng-content 都是用于管理和展示内容的指令&#xff0c;但它们在使用和功能上有一些区别。让我为你解释一下它们的区别&#xff0c;并提供一些示例来说明。 ng-template: ng-template 是一个用来定义可重用模板的容器。它本身不会被渲染…

微信公众号网页开发调用扫一扫及苹果手机(iOS)无反应问题解决方案

二维码大家都很常见&#xff0c;使用场景也很多&#xff0c;但是日常使用中有两种场景比较常见。 1、二维码背后的内容是一个网址&#xff0c;扫描后直接跳转到对应的网址&#xff0c;比如&#xff1a;宣传海报&#xff0c;跳转到直播间、微官网或者微信公众号。 2、二维码背后…

鲁图中大许少辉博士八一新书《乡村振兴战略下传统村落文化旅游设计》山东省图书馆典藏

鲁图中大许少辉博士八一新书《乡村振兴战略下传统村落文化旅游设计》山东省图书馆典藏

ubuntu 安装 postgresql以及 wal回滚

安装 sudo apt install postgresql postgresql-contrib设置远程连接 修改/etc/postgresql/12/main/postgresql.conf **将listen_addresses 改成 ***修改/etc/postgresql/12/main/pg_hba.conf 找到如下信息 #IPv4 local connections: 修改为 host all all 0.0.0.0/0 md5 重启…

Git问题:解决“ssh:connect to host github.com port 22: Connection timed out”

操作系统 Windows11 使用Git IDEA 连接方式&#xff1a;SSH 今天上传代码出现如下报错&#xff1a;ssh:connect to host github.com port 22: Connection timed out 再多尝试几次&#xff0c;依然是这样。 解决 最终发现两个解决方案&#xff1a;&#xff08;二选一&#xf…

反转链表II

江湖一笑浪滔滔&#xff0c;红尘尽忘了 题目 示例 思路 链表这部分的题&#xff0c;不少都离不开单链表的反转&#xff0c;参考&#xff1a;反转一个单链表 这道题加上哨兵位的话会简单很多&#xff0c;如果不加的话&#xff0c;还需要分情况一下&#xff0c;像是从头节点开始…

剑指Offer51.数组中的逆序对 C++

1、题目描述 在数组中的两个数字&#xff0c;如果前面一个数字大于后面的数字&#xff0c;则这两个数字组成一个逆序对。输入一个数组&#xff0c;求出这个数组中的逆序对的总数。 示例 1: 输入: [7,5,6,4] 输出: 5 2、VS2019上运行 使用方法一&#xff1a;归并排序 #inclu…

创建型(一) - 简单工厂模式、工厂方法模式和抽象工厂模式

本文使用了王争老师设计模式课程中的例子&#xff0c;写的很清晰&#xff0c;而且中间穿插了代码优化。 由于设计模式就是解决问题的一种思路&#xff0c;所以每个设计模式会从问题出发&#xff0c;这样比较好理解设计模式出现的意义。 一、简单工厂模式 解决问题&#xff1a…

淘宝Tmall1688京东API接口系列,海量数据值得get!

Api接口也就是所谓的应用程序接口&#xff0c;api接口的全称是Application Program Interface&#xff0c;通过API接口可以实现计算机软件之间的相互通信&#xff0c;开发人员可以通过API接口程序开发应用程序&#xff0c;可以减少编写无用程序&#xff0c;减轻编程任务&#x…

Docker入门知识讲解与实践

文章目录 Dockeryum在线安装安装yum-utils下载aliyun的repo源下载Docker配置加速器 Docker基本操作拉取镜像UbuntuCentos 创建两个容器容器的停止/重启查看容器退出容器交互式非交互式 查看容器内部信息查看Docker相关命令帮助docker rundocker image Docker save与Docker expo…

HJ53 杨辉三角的变形

以上三角形的数阵&#xff0c;第一行只有一个数1&#xff0c;以下每行的每个数&#xff0c;是恰好是它上面的数、左上角数和右上角的数&#xff0c;3个数之和&#xff08;如果不存在某个数&#xff0c;认为该数就是0&#xff09;。 求第n行第一个偶数出现的位置。如果没有偶数…

2023年计算机设计大赛国三 数据可视化 (源码可分享)

2023年暑假参加了全国大学生计算机设计大赛&#xff0c;并获得了国家三等奖&#xff08;国赛答辩出了点小插曲&#xff09;。在此分享和记录本次比赛的经验。 目录 一、作品简介二、作品效果图三、设计思路四、项目特色 一、作品简介 本项目实现对农产品近期发展、电商销售、灾…

金九银十面试题之《JVM》

&#x1f42e;&#x1f42e;&#x1f42e; 辛苦牛&#xff0c;掌握主流技术栈&#xff0c;包括前端后端&#xff0c;已经7年时间&#xff0c;曾在税务机关从事开发工作&#xff0c;目前在国企任职。希望通过自己的不断分享&#xff0c;可以帮助各位想或者已经走在这条路上的朋友…

删除链表的中间节点

题目&#xff1a; 示例&#xff1a; 思路&#xff1a; 这个题类似于寻找链表中间的数字&#xff0c;slow和fast都指向head&#xff0c;slow走一步&#xff0c;fast走两步&#xff0c;也许你会有疑问&#xff0c;节点数的奇偶不考虑吗&#xff1f;while执行条件写成fast&&…

Docker 的数据管理 网络通信

目录 1.管理容器数据的方式 数据卷 数据卷的容器 2.操作命令 3.Docker 镜像的创建 1.管理容器数据的方式 数据卷 可以独立于容器生命周期存储的机制 可提供持久化 数据共享 docker run -v /var/www:/data1 --name web1 -it centos:7 /bin/bash 数据卷的容器 用来提供持久化数…