目录
一、线性表的定义与实现
1.1线性表的定义
线性表:零个或多个数据元素的有限序列。
“线性表”是由同一类型的数据结构构成的有序序列的线性结构。线性表中的元素的个数n称为线性表的长度;当一个线性表中没有元素(n=0)时,称为空表。
表的起始位置称为表头,表的结束位置称为表尾。线性表中元素ai-1为ai的直接前驱,元素ai+1为ai的直接后继。在一个线性表中,ai有且仅有一个直接前驱,有且仅有一个直接后继。第一个元素无前驱,最后一个元素无后继。
1.线性表强调是有限的
2.线性表为一个序列,即元素之间是有顺序的。
1.2线性表的顺序存储结构
1.2.1线性表的顺序存储结构的定义
线性表的顺序存储,是在内存中用地址连续的一块存储空间顺序存放线性表的各元素。
在程序设计语言中,一维数组在内存中占用的存储空间就是一组连续的存储空间,因此,用一维数组来表示顺序存储的数据区域。以Data[MAXSIZE]为例:
顺序存储结构:
#define MAXSIZE 20//存储空间初始分配量
typedef int ElementType;//ElementType类型根据实际情况而定
typedef struct
{
ElementType Data[MAXSIZE];//数组存储数据元素
int length;//线性表当前长度
}SqList;
或
#define MAXSIZE 20//存储空间初始分配量
typedef int ElementType;//ElementType类型根据实际情况而定
typedef struct
{
ElementType *elem;//存储空间的基地址-即首元素地址
int length;//线性表当前长度
}SqList;
描述线性表的顺序存储结构需要三个属性:
1.存储空间的起始位置:数组Data的首元素地址。
2.线性表的最大存储容量:数组最大长度MAXSIZE。
3.线性表的当前长度:length。
可以利用SqList定义线性表L或者线性表的指针p:
SqList L,*p;
通过L或p可以访问相应线性表的内容。
1.2.2线性表的顺序存储结构的相关操作
1.初始化
顺序表的初始化即构造一个空表。首先动态分配表解构所需要的存储空间,将表长置0;
typedef struct
{
ElementType Data[MAXSIZE];//数组存储数据元素
int length;//线性表当前长度
}SqList;
SqList* Inite()
{
SqList* p;
p = new SqList;
p->length = 0;
return p;
}
int main()
{
SqList * L1 = Inite();
}
或
typedef struct
{
ElementType* elem;//存储空间的基地址-即首元素地址
int length;//线性表当前长度
}SqList;
int Inite(SqList & L)
{//申请分配空间
//L.elem = (ElementType*)malloc(MAXSIZE * (sizeof(ElementType)));
L.elem = new ElementType[MAXSIZE];
if (!L.elem)//空间分配失败返回NULL
{
exit(ERROR);
}
L.length = 0;
return OK;
}
int main()
{
SqList L;
Inite(L);
}
2.赋值
int Value(SqList* L)
{
int num = 0,i=0;
cin >> num;//输入元素个数
if (L->length == MAXSIZE||num>MAXSIZE)//线性表已满或者元素个数多
return ERROR;
while (num--)
{
cin >> L->Data[i];
i++;
L->length++;
}
return OK;
}
3.查找
在线性表中找与给定值x相同的数据元素。找到返回其下标,未找到返回-1。
int Find(ElementType X, SqList* L)//使用指针传输效率高
{
int i = 0;
while (i < L->length)
{
if (L->Data[i] == X)
return i;
else
i++;
}
return -1;
}
4.插入
int Insert(int i, ElementType e, SqList* L)
{
if (L->length == MAXSIZE)//表满,不能插入
return -1;
if (i<1 || i>L->length)//插入位置不合法
return -1;
if (i <= L->length)//插入位置不在表尾
{
for (int k = L->length - 1; k >= i - 1; k--)
L->Data[k + 1] = L->Data[k];//将要插入位置后的数据元素向后移动
}
L->Data[i - 1] = e;
L->length++;
return OK;
}
5.删除
将第i个元素从表中去掉
int Delete(int i , SqList* L)
{
if (i<1 || i>L->length)//删除位置不合理
return ERROR;
for (int j = i; j < L->length; j++)//将后边元素向前移
L->Data[j - 1] = L->Data[j];
L->length--;
return OK;
}
综合代码:
#include <iostream>
using namespace std;
#define MAXSIZE 20//存储空间初始分配量
#define OK 1
#define ERROR 0
typedef int ElementType;//ElementType类型根据实际情况而定
typedef struct
{
ElementType Data[MAXSIZE];//数组存储数据元素
int length;//线性表当前长度
}SqList;
SqList* Inite()
{
SqList* p;
p = new SqList;
p->length = 0;
return p;
}
int Value(SqList* L)
{
int num = 0,i=0;
cin >> num;//输入元素个数
if (L->length == MAXSIZE||num>MAXSIZE)//线性表已满或者元素个数多
return ERROR;
while (num--)
{
cin >> L->Data[i];
i++;
L->length++;
}
return OK;
}
int Find(ElementType X, SqList L)//相当于原L的复制品
{
int i = 0;
while (i < L.length)
{
if (L.Data[i] == X)
return i;
else
i++;
}
return -1;
}
int Insert(int i, ElementType e, SqList* L)
{
if (L->length == MAXSIZE)//表满,不能插入
return -1;
if (i<1 || i>L->length)//插入位置不合理
return -1;
if (i <= L->length)//插入位置不在表尾
{
for (int k = L->length - 1; k >= i - 1; k--)
L->Data[k + 1] = L->Data[k];//将要插入位置后的数据元素向后移动
}
L->Data[i - 1] = e;
L->length++;
return OK;
}
int Delete(int i , SqList* L)
{
if (i<1 || i>L->length)//删除位置不合理
return ERROR;
for (int j = i; j < L->length; j++)//将后边元素向前移
L->Data[j - 1] = L->Data[j];
L->length--;
return OK;
}
void Print(SqList L)
{
for (int i = 0; i < L.length; i++)
{
cout << L.Data[i]<<" ";
}
printf("\n");
}
int main()
{
SqList * L = Inite();
Value(L);
int i = 0,k=0;
ElementType e;
cin >> i >> e;
Insert(i,e,L);
cin >> k;
Delete(k, L);
Print(*L);
return 0;
}
顺序存储的缺点:插入删除时需要大量移动元素,故引入链式存储结构
1.3链式存储结构
补充:
1.3.1头结点与头指针的异同
头指针:
1.头指针是指链表指向第一个结点的指针,若链表有头结点,则是指向头结点的指针。
2.头指针具有标识作用,所以常以头指针冠以链表的名字。
3.无论链表是否为空,头指针均不为空。头指针是链表的必要元素。
头结点:
1.头结点是为了操作的统一和方便而设立的,放在第一个元素之前,其数据域一般无意义。
2.有了头结点,对在第一元素节点前插入结点和删除第一节点,其操作就统一了。
3.头结点不是链表的必要元素。
1.4单链表结构与顺序存储结构的优缺点
1.若线性表需要频繁查找,很少进行插入删除操作,宜采用顺序存储结构
2.若需要频繁进行插入,删除操作,宜采用单链表结构。
3.线性表元素确定时,宜采用顺序存储结构;线性表中元素个数变化较大或不知道元素个数时,最好采用单链表结构。
二、广义表
广义表是线性表的推广,也称为列表,与线性表一样,也是由n个元素组成的有序序列。
一般形式:
LS=(a1,a2,...,ai...,an);
在广义表的定义中,ai可为单个元素,也可以是广义表,分别称为广义表LS的原子和子表。习惯上,用大写字母表示广义表的名称,用小写字母表示原子。
由此,广义表的定义是一个递归的定义,例子:
- A = ():A 表示空表,其长度为0。
- B = (e)--B 长度为1,,只有一个原子 e。
- C = (a,(b,c,d)) --C 的长度为2,两个元素分别是原子 a 和子表 (b,c,d)。
- D = (A,B,C)--D的长度为3,3个元素均是广义表。 D = ((),(e),(b,c,d)) 。
- E = (a,E)--这是一个递归表,其长度为2。E相当于一个无限的广义表E = (a,(a,(a,…)))。
由于广义表中的元素可以具有不同的结构(单元素或者广义表),因此不适合采用顺序存储方式表示,通常采用链式存储表示。
分析知,广义表的结点有两种情况:
1.单元素,需要有一个域来存储该单元素的值。
2.广义表,需要有一个域来指向另一个链表。
上述两个域可能只需要其中一种,所以,可以利用共用体来实现这两个域的复用。
typedef struct LGNode
{
int Tag;//标志域:0表示该节点是单元素,1表示该结点是广义表
union
{
ElementType atom;//原子结点的值域
struct
{
struct GLNode* hp;
}ptr;//ptr是表节点的指针域,ptr.hp分别指向表头和表尾
};
struct GLNode* tp;//tp指向后继结点
}GLNode,*GList;
两个最重要的运算
1.取表头GetHead(LS):取出的表头为非空广义表的第一个元素,可以是一个单原子也可以是一个子广义表。
2.取表尾GetTail(LS):取出的表尾为除去表头后其余元素构成的表,即表尾一定是一个广义表。
例如:
B = (e),D=(A,B,C)
GetHead(B)=e,GetHead(D)=A
GetTail(B)=(),GetTail(D)=(B,C) 继续分解:GetHead(B,C)=B,GetTail(B,C)=(C)。
注:对广义表()和(())不同,前者为空表,长度为0;后者不为空表长度为1,可分解得到表头和表尾均为空表
广义表的长度和深度
广义表的长度,指的是广义表中所包含的数据元素的个数
。
计算元素个数时,广义表中存储的每个原子算作一个数据
,同样每个子表也只算作是一个数据
广义表的深度,可以通过观察该表中所包含括号的层数间接得到。
eg:A=(a,b,c,(d,(e,f))->长度为4,深度为3
三、栈
栈和队列也是线性表,它们是操作受限的线性表。
栈是限定仅在表尾进行插入和删除操作的线性表。
允许插入和删除的一段称为栈顶(top),另一端称为栈底(bottom),不含元素的栈称为空栈。栈又称为后进先出(Last In First Out,简称LIFO线性表)的线性表。
3.1栈的顺序存储
3.1.1.定义:
#define MAXSIZE 100//存储数据元素的最大数量
typedef struct
{
ElementType data[MAXSIZE];
int top;//栈顶指针
}SqStack;
3.1.2.栈的操作
1.入栈操作Push
首先判断栈是否已满,若不满,栈顶指针+1,新元素e入栈
int Push(SqStack* s, ElementType e)
{
if (s->top == MAXSIZE - 1)//栈已满
return ERROR;
s->top++;
s->data[s->top] = e;
return OK;
}
2.出栈操作Pop
首先判别栈是否空,若不空,返回栈顶元素,栈顶指针-1
int Pop(SqStack* s)
{
if (s->top == 0)//栈空
return ERROR;
ElementType e;
e = s->data[s->top];
s->top--;
return e;
}
综合:
#include <iostream>
using namespace std;
#define OK 1
#define ERROR -1
#define MAXSIZE 100//存储数据元素的最大数量
typedef int ElementType;
typedef struct
{
ElementType data[MAXSIZE];
int top;//栈顶指针
}SqStack;
SqStack* Inite()
{
SqStack* s;
s = new SqStack;
if (!s)//分配空间失败
return ERROR;
s->top = 0;
return s;
}
int Push(SqStack* s, ElementType e)
{
if (s->top == MAXSIZE - 1)//栈已满
return ERROR;
s->top++;
s->data[s->top] = e;
return OK;
}
int Pop(SqStack* s)
{
if (s->top == 0)//栈空
return ERROR;
ElementType e;
e = s->data[s->top];
s->top--;
return e;
}
void Print(SqStack s)
{
int num = s.top;
while (num!=0)
{
cout << s.data[num] << " ";
num--;
}
cout << endl;
return;
}
int main()
{
SqStack *s=Inite();
ElementType e;
int num = 0;
cin >> num;//输入元素个数
while (num--)
{
cin >> e;
Push(s, e);
}
Print(*s);
cout<<Pop(s)<<endl;
Print(*s);
return 0;
}
输入:
5
1 2 3 4 5
输出:
5 4 3 2 1
5
4 3 2 1
3.1.3两栈共享空间
数组有两个端点,两个栈有两个栈底,让一个栈的栈底为数组的起始端,即下标为0处,另一个栈为数组的末端,即下标为MAXSIZE-1处。两个栈如果增加元素,就是两端向中间延伸;当两个栈的栈顶指针相遇,表示两个栈都满了
1.定义:
typedef struct
{
ElementType data[MAXSIZE];
int top1;//栈顶指针
int top2;
}SqDStack;
对两个栈的初始化方法:top1=-1;top=MAXSIZE;
2. 入栈
int Push(SqDStack* s, ElementType e,int stacknum)
{
if (s->top1+1==s->top2)//栈已满
return ERROR;
if (stacknum == 1)
{
s->top1++;
s->data[s->top1] = e;
}
else if (stacknum == 2)
{
s->top2--;
s->data[s->top2] = e;
}
else
cout << "栈号输入错误" << endl;
return OK;
}
3.出栈
int Pop(SqDStack* s,int stacknum)
{
ElementType e=0;
if (stacknum == 1)
{
if (s->top1 == -1)//栈1空
return ERROR;
e = s->data[s->top1];
s->top1--;
}
else if (stacknum == 2)
{
if (s->top2 == MAXSIZE)
return ERROR;
e = s->data[s->top2];
s->top2++;
}
else
cout << "栈号输入错误" << endl;
return e;
}
综合:
#include <iostream>
using namespace std;
#define OK 1
#define ERROR 0
#define MAXSIZE 100//存储数据元素的最大数量
typedef int ElementType;
typedef struct
{
ElementType data[MAXSIZE];
int top1;//栈顶指针
int top2;
}SqDStack;
SqDStack* Inite()
{
SqDStack* s;
s = new SqDStack;
if (!s)//分配空间失败
return ERROR;
s->top1 = -1;
s->top2 = MAXSIZE;
return s;
}
int Push(SqDStack* s, ElementType e,int stacknum)
{
if (s->top1+1==s->top2)//栈已满
return ERROR;
if (stacknum == 1)
{
s->top1++;
s->data[s->top1] = e;
}
else if (stacknum == 2)
{
s->top2--;
s->data[s->top2] = e;
}
else
cout << "栈号输入错误" << endl;
return OK;
}
int Pop(SqDStack* s,int stacknum)
{
ElementType e=0;
if (stacknum == 1)
{
if (s->top1 == -1)//栈1空
return ERROR;
e = s->data[s->top1];
s->top1--;
}
else if (stacknum == 2)
{
if (s->top2 == MAXSIZE)
return ERROR;
e = s->data[s->top2];
s->top2++;
}
else
cout << "栈号输入错误" << endl;
return e;
}
void Print(SqDStack s,int stacknum)
{
int num;
if (stacknum == 1)
{
num = s.top1;
while (num != -1)
{
cout << s.data[num] << " ";
num--;
}
}
else if (stacknum == 2)
{
num = s.top2;
while (num != MAXSIZE)
{
cout << s.data[num] << " ";
num++;
}
}
else
cout << "栈号输入错误" << endl;
cout << endl;
return;
}
int main()
{
SqDStack *s=Inite();
ElementType e;
for(int i=0;i<3;i++)
{
cin >> e;
Push(s,e,1);
}
for (int i = 0; i < 3; i++)
{
cin >> e;
Push(s, e, 2);
}
Print(*s,1);
Print(*s,2);
cout<<Pop(s,1)<<"\n"<<Pop(s,2) << endl;
Print(*s,1);
Print(*s,2);
return 0;
}
输入:
1 2 3 4 5 6
输出:
3 2 1
6 5 4
3
6
2 1
5 4
3.3链式存储结构
栈的链式存储结构与单链表的类似,但其操作受限。
综合代码:
#include <iostream>
using namespace std;
#define OK 1
#define ERROR 0
typedef int ElementType;
typedef struct StackNode
{
ElementType data;
struct StackNode* next;
}StackNode,*LinkStack;
int Inite(LinkStack &s)
{
s= NULL;//构造一个空栈,栈顶指针置空
return OK;
}
int Push(LinkStack& s, ElementType e)
{
StackNode *p = new StackNode;//生成新节点
p->data = e;
p->next = s;
s = p;
return OK;
}
int Pop(LinkStack& s)
{
if (s == NULL)//空栈
return ERROR;
ElementType e;
StackNode* p = new StackNode;
e = s->data;
p = s;//p临时保留栈顶元素,以备释放
s = s->next;
delete p;
return OK;
}
void Print(LinkStack s)
{
StackNode* p = new StackNode;
p = s;
while (p != NULL)
{
cout << p->data << " ";
p = p->next;
}
cout << endl;
return;
}
int main()
{
LinkStack s;
Inite(s);
ElementType e;
for (int i = 0; i < 3; i++)
{
cin >> e;
Push(s, e);
}
Print(s);
cout<<Pop(s);
Print(s);
return 0;
}
补充:栈的一种类型题:判断哪种入栈出栈序列不符合
eg: 将ABCD这4个字符按顺序压入堆栈,可以产生CBAD、ADBC序列吗?
对序列判断,如果最前面出栈的元素是后面压入的,那么提前压入的元素出栈的顺序就一定只能是压入元素顺序的逆序。
上面那句话不太好理解,有一个小技巧,将字符按输入顺序竖着排列,从序列开始划线,可以往上隔着字母划线,不能向下隔字母划线,线一旦中断,就说明这个序列不可能出现。举个例子:CBAD
序列ADBC:D-B段隔着C不能划线,线中断,故该序列不能实现
四、队列
队列是只允许在一端进行插入操作、而在另一端进行删除操作的线性表。
队列是一种先进先出(First In First Out)的线性表,简称FIFO。允许插入的一端称为队尾,允许删除的一端称为队头。
4.1队列的顺序存储
随着入队、出队操作的进行,会出现“假溢出”的现象。
为解决队尾假溢出现象,一般在队列的顺序存储结构中采用循环队列的方式:
队列的头尾相接的顺序存储结构称为循环队列。
又出现一个问题,当front和rear值相等时,队列可能为空或者为满,无法判别。
有两个办法:
一、设置一个标志变量flag,当front=rear,且flag=0时队列为空,当front=rear,且flag=1时为队列满。
二、当队列为空时,front=rear;当队列为满时,条件修改为(rear+1)%数组长度=front。
一般选用方法2。
4.1.1队列的顺序存储结构定义及初始化:
typedef struct
{
ElementType* base;//存储空间基地址
int front;//队头指针
int rear;//尾指针
}SqQueue;
int Inite(SqQueue & Q)
{
Q.base = new ElementType[MAXSIZE];//为队列分配数组空间
if (!Q.base) exit(ERROR);
Q.front = Q.rear = 0;
return OK;
}
或:
typedef struct
{
ElementType data[MAXSIZE];
int front;//队头指针
int rear;//尾指针
}SqQueue;
int Inite(SqQueue* Q)
{
Q->front = 0;
Q->rear = 0;
return OK;
}
4.1.2队列的操作
1.循环队列的插入--入队
int EnQueue(SqQueue* Q, ElementType e)
{
//判断队是否满
if ((Q->rear + 1) % MAXSIZE == Q->front)
return ERROR;
Q->data[Q->rear] = e;
Q->rear = (Q->rear + 1) % MAXSIZE;//rear后移一位
return OK;
}
2.循环队列的删除--出队
ElementType DeQueue(SqQueue* Q)
{
//判断队是否为空
if (Q->front == Q->rear)
return ERROR;
ElementType e;
e = Q->data[Q->front];
Q->front = (Q->front + 1) % MAXSIZE;//front后移一位
return e;
}
3.求循环队列的长度
int QueueLength(SqQueue Q)
{
return (Q.rear - Q.front + MAXSIZE) % MAXSIZE;
}
综合:
#include <iostream>
using namespace std;
#define OK 1
#define ERROR 0
#define MAXSIZE 100
typedef int ElementType;
typedef struct
{
ElementType data[MAXSIZE];
int front;//队头指针
int rear;//尾指针
}SqQueue;
int Inite(SqQueue* Q)
{
Q->front = 0;
Q->rear = 0;
return OK;
}
int EnQueue(SqQueue* Q, ElementType e)
{
//判断队是否满
if ((Q->rear + 1) % MAXSIZE == Q->front)
return ERROR;
Q->data[Q->rear] = e;
Q->rear = (Q->rear + 1) % MAXSIZE;//rear后移一位
return OK;
}
ElementType DeQueue(SqQueue* Q)
{
//判断队是否为空
if (Q->front == Q->rear)
return ERROR;
ElementType e;
e = Q->data[Q->front];
Q->front = (Q->front + 1) % MAXSIZE;//front后移一位
return e;
}
int QueueLength(SqQueue Q)
{
return (Q.rear - Q.front + MAXSIZE) % MAXSIZE;
}
void Print(SqQueue Q)
{
while (Q.front != Q.rear)
{
cout << Q.data[Q.front] << " ";
Q.front = (Q.front + 1) % MAXSIZE;
}
cout << endl;
return;
}
int main()
{
SqQueue Q;
Inite(&Q);
ElementType e;
for (int i = 0; i < 5; i++)
{
cin >> e;
EnQueue(&Q, e);
}
Print(Q);
cout <<"长度1:"<< QueueLength(Q) << endl;
cout<<DeQueue(&Q)<<endl;
cout << "长度2:" << QueueLength(Q) << endl;
Print(Q);
return 0;
}
4.2队列的链式存储
为了操作的方便,我们将队头指针指向链队列的头结点,而队尾指针指向终端结点。
4.1.1队列的链式存储定义:
typedef struct QNode//结点结构
{
ElementType data;
struct QNode* next;
}QNode,*QueuePtr;
typedef struct//队列的链表结构
{
QueuePtr front;
QueuePtr rear;
}LinkQueue;
4.2.2队列的链式存储的操作
1.初始化
int Inite(LinkQueue* Q)
{
Q->front = Q->rear = new QNode;//生成新节点作为头结点,队头和队尾都指向此节点
Q->front->next = NULL;//头结点的指针域值空
return OK;
}
2.入队
int EnQueue(LinkQueue* Q, ElementType e)
{
QueuePtr p = new QNode;//为元素申请新节点
p->data = e;
p->next = NULL;
Q->rear->next = p;//将新节点插入队尾
Q->rear = p;//修改队尾指针
return OK;
}
3.出队
ElementType DeQueue(LinkQueue* Q)
{
if (Q->front == Q->rear)//队列为空
return ERROR;
ElementType e;
QueuePtr p = new QNode;
p = Q->front->next;//p指向将要删除的队头元素
e = p->data;
Q->front->next = p->next;//修改头结点的指针域
if (Q->rear == p)//最后一个元素被删,队尾指针指向头结点
Q->rear = Q->front;
delete p;//释放原头结点元素的空间
return e;
}
综合:
#include <iostream>
using namespace std;
#define OK 1
#define ERROR 0
#define MAXSIZE 100
typedef int ElementType;
typedef struct QNode//结点结构
{
ElementType data;
struct QNode* next;
}QNode, * QueuePtr;
typedef struct//队列的链表结构
{
QueuePtr front;
QueuePtr rear;
}LinkQueue;
int Inite(LinkQueue* Q)
{
Q->front = Q->rear = new QNode;//生成新节点作为头结点,队头和队尾都指向此节点
Q->front->next = NULL;//头结点的指针域值空
return OK;
}
int EnQueue(LinkQueue* Q, ElementType e)
{
QueuePtr p = new QNode;//为元素申请新节点
p->data = e;
p->next = NULL;
Q->rear->next = p;//将新节点插入队尾
Q->rear = p;//修改队尾指针
return OK;
}
ElementType DeQueue(LinkQueue* Q)
{
if (Q->front == Q->rear)//队列为空
return ERROR;
ElementType e;
QueuePtr p = new QNode;
p = Q->front->next;//p指向将要删除的队头元素
e = p->data;
Q->front->next = p->next;//修改头结点的指针域
if (Q->rear == p)//最后一个元素被删,队尾指针指向头结点
Q->rear = Q->front;
delete p;//释放原头结点元素的空间
return e;
}
void Print(LinkQueue Q)
{
ElementType e;
QueuePtr p = new QNode;
p = Q.front;
do
{
p=p->next;
cout <<p->data<< " ";
}while (p != Q.rear);
cout << endl;
return;
}
int main()
{
LinkQueue Q;
Inite(&Q);
ElementType e;
for (int i = 0; i < 5; i++)
{
cin >> e;
EnQueue(&Q, e);
}
Print(Q);
cout<<DeQueue(&Q)<<endl;
Print(Q);
return 0;
}
输入:
1 2 3 4 5
输出:
1 2 3 4 5
1
2 3 4 5
参考书籍:严蔚敏、李冬梅、吴伟民老师的《数据结构》、程杰老师的《大话数据结构》、陈越老师的《数据结构》。有问题欢迎指正。