LeetCode/NowCoder-栈和队列OJ练习

孜孜不倦:孜孜:勤勉,不懈怠。指工作或学习勤奋不知疲倦。💓💓💓

目录

 说在前面

题目一:括号匹配问题

题目二:用队列实现栈

题目三:用栈实现队列

题目四:设计循环队列

SUMUP结尾


 说在前面

 dear朋友们大家好!💖💖💖我们又见面了,有到了我们数据结构的刷题时间了。我们上次刚学完了栈和队列,现在正好练练手~

 👇👇👇

友友们!🎉🎉🎉点击这里进入力扣leetcode学习🎉🎉🎉


​以下是leetcode题库界面:

​​

 👇👇👇

🎉🎉🎉点击这里进入牛客网NowCoder刷题学习🎉🎉🎉
​以下是NowCoder题库界面:

  ​​​

题目一:括号匹配问题

题目链接:20. 有效的括号 - 力扣(LeetCode)

题目描述:

题目分析:

 思路:由于C语言没有单独提供栈的实现,我们首先需要把我们之前写的栈的实现的接口都复制到题当中,接口如下:

typedef char STDataType;

typedef struct Stack
{
	STDataType* a;
	int top;
	int capacity;
}ST;

//栈的初始化
void STInit(ST* pst)
{
	assert(pst);
	pst->a = NULL;
	//top指的是栈顶数据的下一个位置
	pst->top = pst->capacity = 0;
}

//扩容
static void STCheckCapacity(ST* pst)
{
	if (pst->top == pst->capacity)
	{
		int NewCapacity = pst->capacity == 0 ? 4 : 2 * pst->capacity;
		STDataType* temp = (STDataType*)realloc(pst->a, NewCapacity * sizeof(STDataType));
		if (temp == NULL)
		{
			perror("realloc operation failed");
			exit(1);
		}
		pst->a = temp;
		pst->capacity = NewCapacity;
	}
}

//入栈
void STPush(ST* pst, STDataType x)
{
	assert(pst);
	STCheckCapacity(pst);
	pst->a[pst->top++] = x;
}

//出栈
void STPop(ST* pst)
{
	assert(pst && pst->top);
	pst->top--;
}

//获取栈顶元素
STDataType STTop(ST* pst)
{
	assert(pst && pst->top);
	return pst->a[pst->top - 1];
}

//检测栈是否为空
bool STEmpty(ST* pst)
{
	assert(pst);
	return pst->top == 0;
}

//获取栈中有效元素个数
int STSize(ST* pst)
{
	assert(pst);
	return pst->top + 1;
}

//栈的销毁
void STDestroy(ST* pst)
{
	assert(pst);
	pst->a = NULL;
	pst->top = pst->capacity = 0;
}

在此基础上我们才能完成题解。

这题为什么会想到用栈来解决呢?我们可以认为每次得到一个右括号,都和从右边往左的第一个左括号进行匹配,如果匹配成功,就是对的,如果有哪一次匹配失败了,说明不正确,返回false。那么我们观察左括号很明显就有个特点,就是最后输入的左括号要先和右括号匹配,这和我们栈的LIFO(Last In First Out)是一样的。

比如:

像上面这个例子,我们可以用栈存放左括号。如果是三种左括号的一种,我们就把它压入栈中,如果是三种右开括号的一种,我们取出栈顶的左括号,和它进行匹配。

当放入这三个左括号,我们输入了一个右括号,此时我们需要得到栈顶的括号(STTop)和当前右有括号对比看是否匹配。如果匹配成功,我们还需要将这个数据删除,然后继续这个操作,也就是每次对比都会消耗一个左括号。

     

显然两个左括号都匹配成功,此时再继续压栈。最后两个右括号刚好也和两个左括号匹配成功,所以最后我们可以判断栈是否为空(STEmpty)来判断是否整体都是匹配成功的。

代码如下:

 bool isValid(char* s) {
     ST st;
     STInit(&st);
     while(*s)
     {
         //左括号入栈
         if(*s == '(' || *s == '[' || *s == '{')
         {
             STPush(&st, *s);
         }
         else//右括号去栈顶左括号尝试匹配
         {
             if(STEmpty(&st))//栈为空
             {
                 STDestroy(&st);
                 return false;
             }
             char top = STTop(&st);
             STPop(&st);
             //匹配失败
             if(*s == ')' && top != '('
             || *s == ']' && top != '['
             || *s == '}' && top != '{')
                 return false;
         }
         s++;
     }
     bool ret = STEmpty(&st);
     STDestroy(&st);
     return ret;
 }

最后有一个地方需要注意,就是如果我们刚开始就进入右括号,此时没有做括号匹配,那么直接就跳出循环,栈也是为空的,就返回了true,这显然是错误的。所以我们在输入右括号的时候也要判断一下栈是否为空,若为空直接返回false。 

题目二:用队列实现栈

题目链接:225. 用队列实现栈 - 力扣(LeetCode)

题目描述:

题目分析:

 思路:和题目一同样的,我们需要将队列的实现的所有接口都复制到题目当中,才能在接下来使用队列,接口如下:

typedef int QDataType;

typedef struct QueueNode
{
	QDataType val;
	struct QueueNode* next;
}QNode;

typedef struct Queue
{
	struct QueueNode* phead;
	struct QueueNode* ptail;
	int size;
}Queue;

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

//队尾入队列
void QueuePush(Queue* pq, QDataType x)
{
	assert(pq);
	QNode* newnode = (QNode*)malloc(sizeof(QNode));
	if (newnode == NULL)
	{
		perror("malloc operation failed");
		exit(1);
	}
	newnode->next = NULL;
	newnode->val = x;
	if (pq->ptail == NULL)
	{
		pq->phead = pq->ptail = newnode;
	}
	else
	{
		pq->ptail->next = newnode;
		pq->ptail = newnode;
	}
	pq->size++;
}

//队头出队列
void QueuePop(Queue* pq)
{
	assert(pq && pq->size);
	if (pq->phead->next == NULL)
	{
		free(pq->phead);
		pq->phead = pq->ptail = NULL;
	}
	else
	{
		QNode* next = pq->phead->next;
		free(pq->phead);
		pq->phead = next;
	}
	pq->size--;
}

//获取队列有效元素个数
int QueueSize(Queue* pq)
{
	assert(pq);
	return pq->size;
}

//获取队列头部元素
QDataType QueueFront(Queue* pq)
{
	assert(pq && pq->phead);
	return pq->phead->val;
}

//获取队列尾部元素
QDataType QueueBack(Queue* pq)
{
	assert(pq && pq->ptail);
	return pq->ptail->val;
}

//检测队列是否为空
bool QueueEmpty(Queue* pq)
{
	assert(pq);
	return pq->size == 0;
}

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

有了上述接口以后,我们思考接下来如何用两个队列实现栈。

我们知道,栈是后入先出的,而队列是先入先出的。函数需要我们实现栈的基本操作:push(压栈)、pop(弹栈)、top(取栈顶元素)、empty(判空)。对于压栈来说,其实可以直接实现,我们把一个队列的队头当做栈底,队尾入队列(QueuePush)就相当于压栈。问题是如何弹栈呢?队列只能是一端入,另一端出,没办法直接实现删除队尾的数据。这个时候,我们可以借助另一个队列。我们可以将队列中的数据从队头开始都入到另一个队列的中,只留下最后一个队尾的数据,然后再删除这个数据就可以了。

这也是这道题中重要的思想 ,去栈顶元素可以直接用QueueBack实现,而判空就是两个队列都为空即为空,同时要注意压栈应该压入到不为空的队列中,栈顶元素返回的也是不为空的队列中的尾元素,请大家注意。

代码如下:

//创建包含两个队列的匿名结构体
typedef struct {
    Queue q1;
    Queue q2;
} MyStack;

//初始化这两个队列
MyStack* myStackCreate() {
    MyStack* pst = (MyStack*)malloc(sizeof(MyStack));
    QueueInit(&pst->q1);
    QueueInit(&pst->q2);
    return pst;
}

压栈,即压入不为空的队列中,如果都为空那就都行
void myStackPush(MyStack* obj, int x) {
    if(!QueueEmpty(&obj->q1))
    {
        QueuePush(&obj->q1, x);
    }
    else
    {
        QueuePush(&obj->q2, x);
    }
}

//弹栈
int myStackPop(MyStack* obj) {
    //假设法,假设q1是空的,若不成立那就q2是空的
    Queue* empty = &obj->q1;
    Queue* noempty = &obj->q2;
    if(!QueueEmpty(empty))
    {
        empty = &obj->q2;
        noempty = &obj->q1;
    }
    //把不空的队列入到空队列中,并留下最后一个
    while(QueueSize(noempty) - 1)
    {
        QueuePush(empty, QueueFront(noempty));
        QueuePop(noempty);
    }
    int top = QueueFront(noempty);
    QueuePop(noempty);
    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);
}

题目三:用栈实现队列

题目链接:232. 用栈实现队列 - 力扣(LeetCode)

题目描述:

题目分析:

 思路1:和队列实现栈是基本类似的,建议大家做了第二题再看这道题。不过这里有个地方不太一样,就是Pop,在队列实现栈中,我们只需要将队列中的数据插入到另外一个队列中,再删除剩下的那一个就行了,但是栈实现队列,将栈中的数据压栈到另外的一个栈,删除栈底的那个数据后,由于顺序反了,我们还需要将另一个栈的数据再放回来才行。

也就是这里会有区别,其他地方其实都和题目二是一样的。

代码如下: 

//创建包含两个栈的匿名结构体
typedef struct {
    ST st1;
    ST st2;
} MyQueue;

//初始化结构体
MyQueue* myQueueCreate() {
    MyQueue* pq = (MyQueue*)malloc(sizeof(MyQueue));
    STInit(&pq->st1);
    STInit(&pq->st2);
    return pq;
}

//队尾入队列
void myQueuePush(MyQueue* obj, int x) {
    if(!STEmpty(&obj->st1))
    {
        STPush(&obj->st1, x);
    }
    else
    {
        STPush(&obj->st2, x);
    }
}

//队头出队列
int myQueuePop(MyQueue* obj) {
    ST* empty = &obj->st1;
    ST* noempty = &obj->st2;
    if(!STEmpty(empty))
    {
        empty = &obj->st2;
        noempty = &obj->st1;
    }
    while(STSize(noempty) > 1)
    {
        STPush(empty, STTop(noempty));
        STPop(noempty);
    }
    int top = STTop(noempty);
    STPop(noempty);
    while(STSize(empty))
    {
        STPush(noempty, STTop(empty));
        STPop(empty);
    }
    return top;
}

//获得队头数据
int myQueuePeek(MyQueue* obj) {
    ST* empty = &obj->st1;
    ST* noempty = &obj->st2;
    if(!STEmpty(empty))
    {
        empty = &obj->st2;
        noempty = &obj->st1;
    }
    while(STSize(noempty) > 1)
    {
        STPush(empty, STTop(noempty));
        STPop(noempty);
    }
    int top = STTop(noempty);
    STPush(empty, STTop(noempty));
    STPop(noempty);
    while(STSize(empty))
    {
        STPush(noempty, STTop(empty));
        STPop(empty);
    }
    return top;
}

//判空
bool myQueueEmpty(MyQueue* obj) {
    return STEmpty(&obj->st1) && STEmpty(&obj->st2);
}

//销毁队列
void myQueueFree(MyQueue* obj) {
    STDestroy(&obj->st1);
    STDestroy(&obj->st2);
    free(obj);

思路2:除了思路1,我们其实还有另外一种方法,就是将一个栈的栈顶当做队头,入数据,另外一个栈的栈顶当做队尾,出数据:

那么入队列就相当于将数据压入到左边的栈(pushst)中,出队列就相当于是删除右边的栈(popst)的栈顶数据,如果右边的栈中没有数据了,再把左边的栈的数据全部导入到右边,这样往复就可以了。 

代码如下:

//创建包含两个栈的匿名结构体
typedef struct {
    ST pushst;
    ST popst;
} MyQueue;

//初始化结构体内容
MyQueue* myQueueCreate() {
    MyQueue* pst = (MyQueue*)malloc(sizeof(MyQueue));
    STInit(&pst->pushst);
    STInit(&pst->popst);
    return pst;
}

//队尾入队列
void myQueuePush(MyQueue* obj, int x) {
   STPush(&obj->pushst, x);//相当于压入pushst中
}

//取得队头数据
int myQueuePeek(MyQueue* obj) {
    if(STEmpty(&obj->popst))//如果popst中没有数据,就把pushst中的数据导到popst中
    {
        while(!STEmpty(&obj->pushst))
        {
            STPush(&obj->popst, STTop(&obj->pushst));
            STPop(&obj->pushst);
        }
    }
    return STTop(&obj->popst);
}

//队头出队列
int myQueuePop(MyQueue* obj) {
    int front = myQueuePeek(obj);//有数据,直接获取,没数据,先将pushst导入
    STPop(&obj->popst);
    return front;
}

//判空
bool myQueueEmpty(MyQueue* obj) {
    return STEmpty(&obj->pushst) && STEmpty(&obj->popst);
}

//销毁队列
void myQueueFree(MyQueue* obj) {
    STDestroy(&obj->pushst);
    STDestroy(&obj->popst);
    free(obj);
}

 

题目四:设计循环队列

题目链接:622. 设计循环队列 - 力扣(LeetCode)

题目描述:

题目分析:

 思路:创建数组,利用模运算使得操作控制在0~k元素范围内,并保证tail不放数据。

 我们一般的队列是用链表实现的,但是对于循环队列,数组的方法可能更好实现,链表的方法并不比数组的方法简单多少。

//创建包含数组相关内容的匿名结构体
typedef struct {
    int* arr;
    int head;
    int tail;
    int k;
} MyCircularQueue;

//初始化结构体中的各项数据
MyCircularQueue* myCircularQueueCreate(int k) {
    MyCircularQueue* st = (MyCircularQueue*)malloc(sizeof(MyCircularQueue));
    st->arr = (int*)malloc((k + 1) * sizeof(int));
    st->head = 0;
    st->tail = 0;
    st->k = k;
    return st;
}

//如果head和tail位置相同,循环队列为空
bool myCircularQueueIsEmpty(MyCircularQueue* obj) {
    return obj->head == obj->tail;
}

//如果head在tail的下一个位置,循环队列为满
bool myCircularQueueIsFull(MyCircularQueue* obj) {
    return obj->head == (obj->tail + 1) % (obj->k + 1); 
}

//队尾入队列
bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) {
    if(myCircularQueueIsFull(obj))
        return false;
    obj->arr[obj->tail] = value;
    obj->tail++;
    obj->tail %= (obj->k + 1);
    return true;
}

//队头出队列
bool myCircularQueueDeQueue(MyCircularQueue* obj) {
    if(myCircularQueueIsEmpty(obj))
        return false;
    obj->head++;
    obj->head %= (obj->k + 1); 
    return true;
}

//取队头数据
int myCircularQueueFront(MyCircularQueue* obj) {
    if(myCircularQueueIsEmpty(obj))
        return -1;
    else
        return obj->arr[obj->head];
}

//取队尾数据
int myCircularQueueRear(MyCircularQueue* obj) {
    if(myCircularQueueIsEmpty(obj))
        return -1;
    else
        return obj->tail == 0 ? obj->arr[obj->k] : obj->arr[obj->tail - 1];
        //return obj->arr[(obj->tail + obj->k) % (obj->k + 1)];
}

//销毁队列
void myCircularQueueFree(MyCircularQueue* obj) {
    free(obj->arr);
    free(obj);
}

这道题锻炼并加深了对模运算的理解,对上面每个函数体中的模运算大家一定要理解清楚。那链表的方式为什么复杂呢?原因就是因为,在数组中我们取尾的前一个只要下标-1就可以了,最多再加上模运算,但是对于链表是不好找前一个尾节点的前一个节点的。当然有解决办法,如改用双向链表,或者记录tail的前一个节点prev,也可以重新遍历链表。但是这些解决方法无一不大大增加了代码的复杂度且降低了可读性,所以对于循环链表来说,数组的解决方法是更好的。

SUMUP结尾

数据结构就像数学题,需要刷题才能对它有感觉。之后还会更新数据结构相关的练习题、面试题,希望大家一起学习,共同进步~

如果大家觉得有帮助,麻烦大家点点赞,如果有错误的地方也欢迎大家指出~

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

不同网段的通信过程

这里的AA和HH指的是mac地址,上面画的是路由器 底下的这个pc1,或者其他的连接在这里的pc,他们的默认网关就是路由器的这个192.168.1.1/24这个接口 来看看通信的过程 1、先判断(和之前一样) 2、去查默认网关&#xf…

Matlab读取Swarm球谐系数,并绘制EWH全球格网图(存在疑问)

ICGEM官网下载 COST-G发布的4040的球谐系数 close all; clearvars -except; % addpath(E:\Code\Tool\Function\GRACE_functions); dir_degree_1 E:\Code\GRACE_data\Degree_1\deg1_coef.txt; dir_c20 E:\Code\GRACE_data\Degree_2\C20_RL06.txt; myDir_Swarm E:…

微服务项目收获和总结---第5天(定时发布)

延迟任务 目录 延迟任务技术对比: Redis实现定时任务:​编辑新增任务:取消任务:拉取任务:Zset定时刷新数据到List中:分布式锁实现定时任务只刷新一次: 技术对比: Redis实现定时任…

万界星空科技定制化MES系统帮助实现数字化生产

由于不同企业的生产流程、需求和目标各异,MES管理系统的个性化和定制化需求也不同。有些企业需要将MES管理系统与ERP等其他管理系统进行集成,以实现全面的信息共享和协同工作。有些企业需要将MES管理系统与SCADA等控制系统进行集成,以实现实时…

极验3逆向 JS逆向最新点选验证码 逆向分析详解

目录 声明! 一、请求流程分析 二、w参数生成位置 三、主要问题 四、结果展示 原创文章,请勿转载! 本文内容仅限于安全研究,不公开具体源码。维护网络安全,人人有责。 声明! 本文章中所有内容仅供学习交流…

K8s种的service配置

什么是service 官方的解释是:   k8s中最小的管理单元是pod;而service是 将运行在一个或一组 Pod 上的网络应用程序公开为网络服务的方法;   Kubernetes 中 Service 的一个关键目标是让你无需修改现有应用以使用某种服务发现机制。 你可以在 Pod 集合中运行代码…

MySQL(三)查询

1、单表和多表查询 1.1 算术运算符、比较运算符及特殊运算符 1)MySQL的算术运算符 select 0.1+0.3333,0.1-0.3333,0.1*0.3333,1/2,1%2; select 1/0,100%0; select 3%2,mod(3,2); 2)MySQL的比较运算符 select 1=0,1=1,null=null; select 1<>0,1<>1,null<&…

音乐系统java在线音乐网站基于springboot+vue的音乐系统带万字文档

文章目录 音乐系统一、项目演示二、项目介绍三、万字项目文档四、部分功能截图五、部分代码展示六、底部获取项目源码和万字论文参考&#xff08;9.9&#xffe5;带走&#xff09; 音乐系统 一、项目演示 在线音乐系统 二、项目介绍 基于springbootvue的前后端分离在线音乐系…

英特尔LLM技术挑战记录

英特尔技术介绍&#xff1a; Flash Attention Flash Attention 是一种高效的注意力机制实现&#xff0c;旨在优化大规模 Transformer 模型中的自注意力计算。在深度学习和自然语言处理领域&#xff0c;自注意力是 Transformer 架构的核心组件&#xff0c;用于模型中不同输入元…

Java 文件操作和输入输出流

在 Java 编程中&#xff0c;文件操作和输入输出流是非常常见和重要的任务&#xff0c;它们允许你读取和写入文件、处理数据流等。 文件操作概述 文件操作是指对文件进行创建、读取、写入、删除等操作的过程。在 Java 中&#xff0c;文件操作通常涉及到使用文件对象、输入输出…

聚合网卡和Wondershaper限速的一些问题(速度减半问题)

首先我们来了解一下聚合网卡&#xff1a; 聚合网卡&#xff0c;又称为链路聚合组&#xff08;LAG, Link Aggregation Group&#xff09;、端口汇聚&#xff08;Port Trunking&#xff09;、以太通道&#xff08;Ethernet Bonding&#xff09;等&#xff0c;是一种网络技术&…

python基础知识:py文件转换为jupyter文件

搜索了很多&#xff0c;都没什么用&#xff0c;会出现一些json错误&#xff0c;最终直接新建文件成功: 在自己电脑安装Anaconda&#xff0c;安装jupyter notebook&#xff0c;输入命令打开jupyter notebook&#xff1a; 在Anoconda命令行中cd到自己要转换文件的地址&#xff0…

【css3】01-css3新特性样式篇

目录 1 背景 1.1 设置背景图片的定位 1.2 背景裁切-规定背景的绘制区域 1.3 设置背景图片尺寸 2 边框 2.1 盒子阴影box-shadow 2.2 边框图片border-image 3 文本 -文字阴影text-shadow 1 背景 1.1 设置背景图片的定位 background-origin&#xff1a;规定背景图片的定位…

大型央企国企信创化与数字化转型规划实施方案(71页PPT)

方案介绍&#xff1a; 随着全球信息技术的迅猛发展&#xff0c;数字化转型已成为企业提升竞争力、实现可持续发展的必经之路。作为国家经济的重要支柱&#xff0c;大型央企国企在信创化与数字化转型方面承载着重要的责任和使命。本方案旨在通过系统性的规划和实施&#xff0c;…

OrangePi AIpro测评:智能与创新的完美结合

OrangePi AIpro上手指南 简介 香橙派与华为合作发布的香橙派AiPro为Ai主力&#xff0c;为边缘设备的Ai计算提供了可能。 集成图形处理器&#xff0c;拥有8GB/16GB LPDDR4X&#xff08;我这个是8G内存版本的&#xff09;&#xff0c;可以外接32GB/64GB/128GB/256GB eMMC模块&a…

Nacos 2.x 系列【9】配置中心

文章目录 1. 概述1.1 配置1.2 配置中心 2. 案例演示2.1 环境搭建2.2 自定义参数配置2.2 服务配置 1. 概述 1.1 配置 在系统开发过程中&#xff0c;开发者通常会将一些需要变更的参数、变量等从代码中分离出来独立管理&#xff0c;以独立的配置文件的形式存在。 在实际开发中…

华为OD机试【计算最接近的数】(java)(100分)

1、题目描述 给定一个数组X和正整数K&#xff0c;请找出使表达式X[i] - X[i1] … - X[i K 1]&#xff0c;结果最接近于数组中位数的下标i&#xff0c;如果有多个i满足条件&#xff0c;请返回最大的i。 其中&#xff0c;数组中位数&#xff1a;长度为N的数组&#xff0c;按照元…

922. 按奇偶排序数组 II - 力扣

1. 题目 给定一个非负整数数组 nums&#xff0c; nums 中一半整数是 奇数 &#xff0c;一半整数是 偶数 。 对数组进行排序&#xff0c;以便当 nums[i] 为奇数时&#xff0c;i 也是 奇数 &#xff1b;当 nums[i] 为偶数时&#xff0c; i 也是 偶数 。 你可以返回 任何满足上述…

FreeRtos进阶——消息队列的操作逻辑

消息队列&#xff08;queue&#xff09; 在不同的任务之间&#xff0c;如果我们需要互相之间通信&#xff0c;使用全局变量进行通信&#xff0c;是一种不安全的通信的方式。为保证线程安全&#xff0c;我们需要引入消息队列的通信方式。 粗暴的消息队列 为保证线程的安全&am…