Note2---栈和队列~~

2023-12-16 12:37:41


前言🥗

之前,我们学习了顺序表和链表的相关知识,也完成了相应的练习,接下来我们要学习的是栈和队列!

他们也可以说是顺序表和链表的延续,主要思想还是和顺序表+链表的思想相关的,也需要借助顺序表和链表来实现栈和队列。


1.栈🎟?

1.1 简单介绍

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

后进先出:可以理解为吃饼干🍪---按照顺序吃饼干,最先吃到的饼干是厂家最后装进去的

压栈:栈的插入操作叫做进栈/压栈/入栈入数据在栈顶

出栈:栈的删除操作叫做出栈出数据也在栈顶


1.2 栈的实现--选择

栈是一端出、入,是为栈顶;一端自动划分为栈尾

那么,我们是通过顺序表还是链表来实现呢?

下面,我们来简单的权衡利弊一下:


1.3?栈的实现--代码

1.3.1 stack.h

#pragma once
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
#include<stdbool.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); 
// 检测栈是否为空,如果为空返回非零结果,如果不为空返回0 
int StackEmpty(Stack* ps); 
// 销毁栈 
void StackDestroy(Stack* ps); 

1.3.2 stack.c

1.3.2.1 关于top如何初始化🌞

我们先来实现初始化:

void StackInit(Stack* ps)
{
	assert(ps);
	ps->_a = NULL;
	ps->_capacity = 0;
	ps->_top = 0;//top类比于顺序表的size,用来计数的
}

按照之前的顺序表的思路,我们很容易就想到了将top(类比于size--->计数)赋值为0

但是,请注意??

这里潜藏着一个问题:

1.3.2.2 解决方案👁?


1.3.2.3 方法1🌺
#include"stack.h"
// 初始化栈 
void StackInit(Stack* ps)
{
	assert(ps);
	ps->_a = NULL;
	ps->_capacity = 0;
	ps->_top = 0;//top类比于顺序表的size,用来计数的
}
// 入栈 
void StackPush(Stack* ps, STDataType data)
{
	assert(ps);
	if (ps->_top == ps->_capacity)
	{
		int newcapacity = ps->_capacity == 0 ? 4 : 2 * ps->_capacity;
		STDataType* tmp = (STDataType*)realloc(ps->_a, sizeof(STDataType) * newcapacity);
		if (tmp == NULL)
		{
			perror("realloc error!\n");
			return;
		}
		ps->_a = tmp;
		ps->_capacity = newcapacity;
	}
	ps->_a[ps->_top] = data;
	ps->_top++;
}
// 出栈 
void StackPop(Stack* ps)
{
	assert(ps);
	assert(ps->_top > 0);//栈不为空
	ps->_top--;
}
// 获取栈顶元素 
STDataType StackTop(Stack* ps)
{
	assert(ps);
	assert(ps->_top > 0);//栈不为空
	return ps->_a[ps->_top-1];
}
// 获取栈中有效元素个数 
int StackSize(Stack* ps)
{
	assert(ps);
	return ps->_top;
}
// 检测栈是否为空,如果为空返回非零结果,如果不为空返回0 
int StackEmpty(Stack* ps)
{
	assert(ps);
	if (ps->_top == 0)
		return 1;
	return 0;
}
// 销毁栈 
void StackDestroy(Stack* ps)
{
	assert(ps);
	free(ps->_a);
	ps->_a = NULL;
	ps->_capacity = ps->_top = 0;
}

1.3.2.4 方法2🍄
#include"stack.h"
// 初始化栈 
void StackInit(Stack* ps)
{
	assert(ps);
	ps->_a = NULL;
	ps->_capacity = 0;
	ps->_top = -1;//top类比于顺序表的size,用来计数的
}
// 入栈 
void StackPush(Stack* ps, STDataType data)
{
	assert(ps);
	if (ps->_top+1 == ps->_capacity)
	{
		int newcapacity = ps->_capacity == 0 ? 4 : 2 * ps->_capacity;
		STDataType* tmp = (STDataType*)realloc(ps->_a, sizeof(STDataType) * newcapacity);
		if (tmp == NULL)
		{
			perror("realloc error!\n");
			return;
		}
		ps->_a = tmp;
		ps->_capacity = newcapacity;
	}
	ps->_top++;
	ps->_a[ps->_top] = data;
}
// 出栈 
void StackPop(Stack* ps)
{
	assert(ps);
	assert(ps->_top >= 0);//栈不为空
	ps->_top--;
}
// 获取栈顶元素 
STDataType StackTop(Stack* ps)
{
	assert(ps);
	assert(ps->_top >= 0);//栈不为空
	return ps->_a[ps->_top];
}
// 获取栈中有效元素个数 
int StackSize(Stack* ps)
{
	assert(ps);
	return ps->_top+1;
}
// 检测栈是否为空,如果为空返回非零结果,如果不为空返回0 
int StackEmpty(Stack* ps)
{
	assert(ps);
	if (ps->_top == -1)
		return 1;
	return 0;
}
// 销毁栈 
void StackDestroy(Stack* ps)
{
	assert(ps);
	free(ps->_a);
	ps->_a = NULL;
	ps->_capacity = 0;
	ps->_top = -1;
}

1.3.3 test.c

#include"stack.h"
int main()
{
	Stack st;
	StackInit(&st);
	StackPush(&st, 1);
	StackPush(&st, 2);
	StackPush(&st, 3);
	StackPush(&st, 4);
	StackPush(&st, 5);
	while (!StackEmpty(&st))
	{
		printf("%d ", StackTop(&st));
		StackPop(&st);
	}
	printf("\n");//访问一次之后栈就为空了
	StackDestroy(&st);
	return 0;
}

1.3.4 效果展现


1.4 出入栈顺序思考

思考一下,后进先出是绝对的吗?

进去顺序:1 2 3 4 5

出来顺序:5 4 3 2 1

这是一定的吗?还有别的出栈顺序吗?

我们不妨来看看代码的实现效果:??????????????

我们发现,出栈的顺序竟然可以不是54321

我们可以进一个立马出一个??然后再进?????????

所以,后进先出是相对的,不是绝对的!是相对于栈里面现有的数据而言的后进先出??

故,入栈顺序和出栈顺序是一对多的关系??

1.5 选择练手

1.一个栈的初始状态为空。现将元素1、2、3、4、5、A、B、C、D、E依次入栈,然后再依次出栈,则元素出
栈的顺序是( )。
A 12345ABCDE
B EDCBA54321
C ABCDE12345
D 54321EDCBA

很容易地,我们根据后进先出的原则,知道选B

2.若进栈序列为 1,2,3,4 ,进栈过程中可以出栈,则下列不可能的一个出栈序列是()
A 1,4,3,2
B 2,3,4,1
C 3,1,4,2
D 3,4,2,1

我们逐一击破:

A.入1--->出1--->入2 3 4--->出4 3 2

B.入1 2--->出2--->入3 4--->出4 3 1

C.入1 2 3--->出3--->出1不可能!!!

D.入1 2 3--->出3--->入4--->出4--->出2 1

综上,我们选择C

1.6 OJ题练手---括号匹配问题

???????力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台


1.6.1 思路分析


1.6.2 代码实现

typedef int STDataType;
typedef struct Stack
{
    STDataType* _a;
    int _top;       // 栈顶
    int _capacity;  // 容量 
}Stack;
void StackInit(Stack* ps)
{
    assert(ps);
    ps->_a=NULL;
    ps->_capacity=ps->_top=0;
}
void StackPush(Stack* ps, STDataType data)
{
    assert(ps);
    if(ps->_top==ps->_capacity)
    {
        int newcapacity=ps->_capacity==0?4:2*ps->_capacity;
        STDataType* tmp=(STDataType*)realloc(ps->_a,sizeof(STDataType)*newcapacity);
        if(tmp==NULL)
        {
            perror("realloc error!\n");
            return ;
        }
        ps->_a=tmp;
        ps->_capacity=newcapacity;
    }
    ps->_a[ps->_top]=data;
    ps->_top++;
}
void StackPop(Stack* ps)
{
    assert(ps);
    assert(ps->_top>0);
    ps->_top--;

}
STDataType StackTop(Stack* ps)
{
    assert(ps);
    assert(ps->_top>0);
    return ps->_a[ps->_top-1];
}
int StackSize(Stack* ps)
{
    assert(ps);
    return ps->_top;
}
bool StackEmpty(Stack* ps)
{
    assert(ps);
    return ps->_top==0;
}
void StackDestroy(Stack* ps)
{
    assert(ps);
    free(ps->_a);
    ps->_a=NULL;
    ps->_capacity=ps->_top=0;
}
bool isValid(char* s) {
    Stack st;
    StackInit(&st);
         while(*s)
         {
             //左括号入栈
             if((*s=='(')||(*s=='[')||(*s=='{'))
             {
                 StackPush(&st,*s);
             }
             else
             {
             //没有左括号的情况
                 if(StackEmpty(&st))
                {
                    StackDestroy(&st);
                    return false;
                }
             //左括号出栈
                 char top=StackTop(&st);
                 StackPop(&st);  
                 if((*s==')'&& top!='(')
                 ||(*s==']'&& top!='[')
                 ||(*s=='}'&& top!='{'))
                 {
                     StackDestroy(&st);
                     return false;
                 }        
             }
             s++;
         }
    //可能没有右括号,此时栈不为空
    bool ret=StackEmpty(&st);
    StackDestroy(&st);
    return ret;
}

2.队列

2.1 简单介绍

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

队列:进行插入操作的一端称为队尾?

队列:进行删除操作的一端称为队头

这个方式可以类比于我们日常排队结账,只能排到队尾,不能直接站到队头,收银台也只在队头,结完账就离开了队伍

先进先出:先来的先结账,后来的后结账,公平公正

2.2 出入队队顺序思考

其实,有了上面的类比之后,我们就可以很好的理解到先进先出是绝对的,毕竟你也不想排队的时候还有人插队吧!

先进先出是绝对的(公平公正),入队和出队的顺序是一一对应的

2.3 队列的实现---选择

队列一端入数据,一端出数据,那么是顺序表实现还是链表实现?是单链表还是双链表?

下面我们来看看其中的优缺点:


2.4 队列的实现---代码

2.4.1 Queue.h

2.4.1.1 怎么优化+优化方法


改进之后:

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>
// 链式结构:表示队列 
typedef int QDataType;
typedef struct QListNode
{
	struct QListNode* _next;
	QDataType _data;
}QNode;

// 队列的结构 
typedef struct Queue
{
	QNode* _head;
	QNode* _tail;
    int size;
}Queue;

// 初始化队列 
void QueueInit(Queue* q);
// 队尾入队列 
void QueuePush(Queue* q, QDataType data);
// 队头出队列 
void QueuePop(Queue* q);
// 获取队列头部元素 
QDataType QueueFront(Queue* q);
// 获取队列队尾元素 
QDataType QueueBack(Queue* q);
// 获取队列中有效元素个数 
int QueueSize(Queue* q);
// 检测队列是否为空,如果为空返回非零结果,如果非空返回0 
int QueueEmpty(Queue* q);
// 销毁队列 
void QueueDestroy(Queue* q);

2.4.2 Queue.c

#include"Queue.h"
// 初始化队列 
void QueueInit(Queue* q)
{
	assert(q);
	q->_head = q->_tail = NULL;
	q->size = 0;
}
// 队尾入队列 
void QueuePush(Queue* q, QDataType data)
{
	assert(q);
	QNode* node = (QNode*)malloc(sizeof(QNode));
	if (node == NULL)
	{
		perror("malloc error!\n");
		return;
	}
	node->_data = data;
	node->_next = NULL;
	if (q->_head == NULL)
	{
		q->_head = q->_tail = node;
	}
	else
	{
		q->_tail->_next = node;
		q->_tail = node;
	}
	q->size++;
}
// 队头出队列 
void QueuePop(Queue* q)
{
	assert(q);
	//不为空
	assert(q->_head);
	//只有1给节点,直接free
	if (q->_head->_next == NULL)
	{
		free(q->_head);
		q->_head = NULL;
	}
	else
	{
		QNode* del = q->_head;
		q->_head = q->_head->_next;
		free(del);
		del = NULL;
	}
	q->size--;
}
// 获取队列头部元素 
QDataType QueueFront(Queue* q)
{
	assert(q);
	assert(q->_head);
	return q->_head->_data;
}
// 获取队列队尾元素 
QDataType QueueBack(Queue* q)
{
	assert(q);
	assert(q->_tail);
	return q->_tail->_data;
}
// 获取队列中有效元素个数 
int QueueSize(Queue* q)
{
	assert(q);
	return q->size;
}
// 检测队列是否为空,如果为空返回非零结果,如果非空返回0 
int QueueEmpty(Queue* q)
{
	assert(q);
	if (q->_head == NULL)
		return 1;
	return 0;
}
// 销毁队列 
void QueueDestroy(Queue* q)
{
	assert(q);
	QNode* del = q->_head;
	while (del)
	{
		QNode* next = del->_next;
		free(del);
		del = NULL;
		del = next;
	}
	q->_head = q->_tail = NULL;
	q->size = 0;
}

2.4.3 test.c

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

2.4.4 效果实现


3.栈和队列面试题

3.1 OJ题练手---用队实现栈

力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台


3.1.1 思路分析


3.1.2 代码实现

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

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

void QueueInit(Queue* q)
{
    assert(q);
    q->_front=q->_rear=NULL;
    q->size=0;
}
void QueuePush(Queue* q, QDataType data)
{
    assert(q);
    QNode* node=(QNode*)malloc(sizeof(QNode));
    node->_data=data;
    node->_next=NULL;
    if (q->_front == NULL)
	{
		q->_front = q->_rear = node;
	}
	else
	{
		q->_rear->_next = node;
		q->_rear = node;
	}
    q->size++;
}
void QueuePop(Queue* q)
{
    assert(q);
    assert(q->_front);
    if(q->_front->_next==NULL)
    {
        free(q->_front);
        q->_front=NULL;
    }
    else{
        QNode* del = q->_front;
        q->_front = q->_front->_next;
        free(del);
        del = NULL;
    }
    q->size--;
} 
QDataType QueueFront(Queue* q)
{
    assert(q);
    assert(q->_front);
    return q->_front->_data;
} 
QDataType QueueBack(Queue* q)
{
    assert(q);
    assert(q->_rear);
    return q->_rear->_data;
} 
int QueueSize(Queue* q)
{
    assert(q);
    return q->size;
} 
int QueueEmpty(Queue* q)
{
    assert(q);
    if(q->_front==NULL)
    {
        return 1;
    }
    return 0;
} 
void QueueDestroy(Queue* q)
{
    assert(q);
    QNode* del=q->_front;
    while(del)
    {
        QNode* next=del->_next;
        q->_front=q->_front->_next;
        free(del);
        del=NULL;
        del=next;
    }
}

typedef struct {
    Queue q1;
    Queue q2;   
} MyStack;

MyStack* myStackCreate() {
    MyStack* st=(MyStack*)malloc(sizeof(MyStack));
    QueueInit(&st->q1);
    QueueInit(&st->q2);
    return st;
}
//不为空,则存储数据
void myStackPush(MyStack* obj, int x) {
    if(!QueueEmpty(&obj->q1))
    {
        QueuePush(&obj->q1,x);
    }
    else
    {
        QueuePush(&obj->q2,x);
    }
}
//将n-1个元素删除
int myStackPop(MyStack* obj) {
    //假设法
    Queue* empty=&obj->q1;
    Queue* unempty=&obj->q2;
    if(!QueueEmpty(&obj->q1))
    {
        empty=&obj->q2;
        unempty=&obj->q1;
    }
    while(QueueSize(unempty)>1)
    {
        QueuePush(empty,QueueFront(unempty));
        QueuePop(unempty);
    }
    //只剩1个元素
    int top=QueueFront(unempty);
    QueuePop(unempty);
    return top;
}

int myStackTop(MyStack* obj) {
    if(!QueueEmpty(&obj->q1))
    {
        return QueueBack(&obj->q1);
    }
    else{
        return QueueBack(&obj->q2);
    }
}

bool myStackEmpty(MyStack* obj) {
   if(QueueEmpty(&obj->q1)&&QueueEmpty(&obj->q2))
   {
       return true;
   }
   else
   {
       return false;
   }
}

void myStackFree(MyStack* obj) {
    QueueDestroy(&obj->q1);
    QueueDestroy(&obj->q2);
    free(obj);
}

3.2 用栈实现队列

力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台


3.2.1 思路分析


3.2.2 代码实现

typedef int STDataType;
typedef struct Stack
{
	STDataType* _a;
	int _top;		// 栈顶
	int _capacity;  // 容量 
}Stack;
// 初始化栈 
void StackInit(Stack* ps)
{
    assert(ps);
    ps->_a=NULL;
    ps->_capacity=ps->_top=0;
}
// 入栈 
void StackPush(Stack* ps, STDataType data)
{
    assert(ps);
    if(ps->_capacity==ps->_top)
    {
        int newcapacity=ps->_capacity==0?4:2*ps->_capacity;
        STDataType* tmp=(STDataType*)realloc(ps->_a,sizeof(STDataType)*newcapacity);
        if(tmp==NULL)
        {
            perror("realloc error!\n");
            return ;
        }
        ps->_a=tmp;
        ps->_capacity=newcapacity;
    }
    ps->_a[ps->_top]=data;
    ps->_top++;
} 
// 出栈 
void StackPop(Stack* ps)
{
    assert(ps);
    assert(ps->_top);
    ps->_top--;
}
// 获取栈顶元素 
STDataType StackTop(Stack* ps)
{
    assert(ps);
    assert(ps->_top);
    return ps->_a[ps->_top-1];
} 
// 获取栈中有效元素个数 
int StackSize(Stack* ps)
{
    assert(ps);
    return ps->_top;
}
// 检测栈是否为空,如果为空返回非零结果,如果不为空返回0 
int StackEmpty(Stack* ps)
{
    assert(ps);
    if(ps->_top==0)
        return 1;
    return 0;
} 
// 销毁栈 
void StackDestroy(Stack* ps)
{
    assert(ps);
    free(ps->_a);
    ps->_a=NULL;
    ps->_capacity=ps->_top=0;
} 

typedef struct {
    Stack pushst;
    Stack popst;
} MyQueue;


MyQueue* myQueueCreate() {
    MyQueue* obj=(MyQueue*)malloc(sizeof(MyQueue));
    StackInit(&obj->pushst);
    StackInit(&obj->popst);
    return obj;
}

void myQueuePush(MyQueue* obj, int x) {
    StackPush(&obj->pushst,x);
}

int myQueuePop(MyQueue* obj) {
    int top=myQueuePeek(obj);
    StackPop(&obj->popst);
    return top;
}

int myQueuePeek(MyQueue* obj) {
    if(StackEmpty(&obj->popst))
    {
        while(!StackEmpty(&obj->pushst))
        {
            StackPush(&obj->popst,StackTop(&obj->pushst));
            StackPop(&obj->pushst);
        }
    }
    return StackTop(&obj->popst);
}

bool myQueueEmpty(MyQueue* obj) {
    return StackEmpty(&obj->pushst)&&StackEmpty(&obj->popst);
}

void myQueueFree(MyQueue* obj) {
    StackDestroy(&obj->popst);
    StackDestroy(&obj->pushst);
    free(obj);
}

3.3 设计循环队列

力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台


3.3.1 思路分析


所以到底是数组实现还是链表实现呢?

使用数组or链表都一样,都有麻烦的地方
数组:
判断满没满需要取余%,而且数组要借助下标,要自己设想它逻辑循环(是一个环)--解决循环问题
(单向)循环链表:
虽然不需要取余%但是取尾数据不好找(我们多一个空位,需要知道空位前面的数据,因为前面的数据才是真正的有数据的尾节点),所以还需要一个指向空位的前一个的指针,或者双向循环链表(3个指针)


3.3.2 代码实现

typedef struct {
    int* a;
    int front;//数组下标
    int back;//数组下标
    int k;
} MyCircularQueue;


MyCircularQueue* myCircularQueueCreate(int k) {
    MyCircularQueue* obj=(MyCircularQueue*)malloc(sizeof(MyCircularQueue));
    obj->a=(int*)malloc(sizeof(int)*(k+1));
    obj->front=0;
    obj->back=0;
    obj->k=k;
    return obj;
}
//先判断空和满--->便于插入和删除的时候的判断调用
bool myCircularQueueIsEmpty(MyCircularQueue* obj) {
    return obj->back==obj->front;
}

bool myCircularQueueIsFull(MyCircularQueue* obj) {
    return (obj->back+1)%(obj->k+1)==obj->front;
}

bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) {
    if(myCircularQueueIsFull(obj))
    {
        return false;
    }
    obj->a[obj->back++]=value;
    //正常来说没满,直接插入数据之后,back++就行了
    //但是当back走到尾了,还能back++嘛?不能,back要%回到头
    obj->back %=(obj->k+1);
    return true;
}

bool myCircularQueueDeQueue(MyCircularQueue* obj) {
    if(myCircularQueueIsEmpty(obj))
    {
        return false;
    }
    (obj->front)++;//置为空,没有意义
    //同样的,删除的时候正常情况下front++
    //但是front走到尾了,还能++往后走嘛?不能,要%回到头再开始
    obj->front %=(obj->k+1);
    return true;
}

int myCircularQueueFront(MyCircularQueue* obj) {
    if(myCircularQueueIsEmpty(obj))
    {
        return -1;
    }
    return obj->a[obj->front];
}

int myCircularQueueRear(MyCircularQueue* obj) {
    if(myCircularQueueIsEmpty(obj))
    {
        return -1;
    }
    //return obj->a[obj->back-1];//back指向的是下一个空位
    //取尾的时候,正常情况下,之际返回back-1就行了
    //但是当back%回到头时,此时back==0,还能(back-1)取-1嘛?
    //不能,当back==0时,直接返回k(k就是数组下标---最后一个数据)
    if(obj->back==0)
    {
        return obj->a[obj->k];
    }
    else
    {
        return obj->a[obj->back-1];
    }
}

void myCircularQueueFree(MyCircularQueue* obj) {
    free(obj->a);
    free(obj);
}

后语🍵

本次的学习到这里就结束了,通过今天的学习,我们堆数据结构的栈和队列有了一定的了解,也进行了适量的练习。但是我们还是需要课后巩固练习的,加深理解和印象。下篇文章,小江将会带领大家一起学习了解二叉树的相关知识点!!!


本次的分享到这里就结束了!!!

PS:小江目前只是个新手小白。欢迎大家在评论区讨论哦!有问题也可以讨论的!

如果对你有帮助的话,记得点赞👍+收藏??+关注?

文章来源:https://blog.csdn.net/2301_79184587/article/details/134438809
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。