【数据结构】顺序表:与时俱进的结构解析与创新应用

 欢迎来到白刘的领域   Miracle_86.-CSDN博客

         系列专栏   数据结构与算法

先赞后看,已成习惯

   创作不易,多多支持!

目录

一、数据结构的概念

二、顺序表(Sequence List)

2.1 线性表的概念以及结构

2.2 顺序表分类

2.2.1 顺序表和数组的区别

2.2.2 顺序表的分类

2.3 顺序表的实现

2.3.1 初始化

 2.3.2 销毁

2.3.3 尾插

2.3.4 头插

2.3.5 尾删

2.3.6 头删

2.3.7 在指定位置插入

2.3.8 在指定位置删除

2.3.9 查找

 2.4 总代码

2.4.1 SeqList.h

2.4.2 SeqList.c


一、数据结构的概念

数据结构是计算机存储、组织数据的方式。数据结构是指相互之间存在一种或多种特定关系的数据元素的集合。数据结构反映数据的内部构成,即数据由哪部分构成,以什么方式构成,以及数据之间存在的相互关系。

数据结构可以分为两类:线性数据结构和非线性数据结构。线性数据结构以线性的方式组织数据,例如数组、链表和栈。非线性数据结构是一种通过多个节点之间的关联来组织数据的方式,例如树和图。数据结构的选取和设计对于解决特定问题和提高算法效率非常重要。

我们可以将这个词拆分成“数据”和“结构”两部分。

什么是数据?数据可以是数字、文字、图像等。比如超市购物时的价格、数量,天气预报中的温度、湿度,或是社交媒体上的浏览量、点赞数,都是数据。它们为我们提供信息,帮助我们做出决策,让生活更加便捷。

什么是结构?当我们想要使用大量使用同类型的数据时,通过手动定义大量的独立的变量对于程序来说,可读性非常差,我们可以借助数组这样的数据结构将大量的数据组织在一起,结构也可以理解为组织数据的方式。

比如,想要在世界上找到“喜羊羊”的这只羊很难,但是我告诉你在青青草原找,那就特别好找。青青草原这一块地方就可以看成一个结构,有效地将羊群组织起来。

总结:

1.数据结构可以存储数据。

2.存储的数据能够方便查找。

那为什么我们需要数据结构呢?

在生活中,假如我们开了个餐馆,不借助排队的方式来管理客户,会导致客户就餐感受差、等餐时间长、餐厅营业混乱等情况。同理,程序中如果不对数据进行管理,可能会导致数据丢失、操作数据困难、野指针等情况。

良好的数据结构和算法设计对于确保程序的稳定性和效率至关重要。通过合理地组织和管理数据,我们可以提高程序的性能,减少错误,并为用户提供更好的体验。

我们曾学过最基础的数据结构:数组。

 

C语言中的百宝箱——数组(1)-CSDN博客

C语言中的百宝箱——数组(2)-CSDN博客

既然我们已经学过数组这种数据结构了,那我们还需要学其它的数据结构嘛?存在即合理,显然数组是有一定的局限性的。

假定数组有10个空间,已经使用了5个,向数组中插入数据的步骤应该是:

1. 求数组的长度(这里是10)。

2. 求数组的有效数据个数(这里是5)。

3. 判断数组是否已满(如果有效数据个数等于数组长度,则数组已满)。

4. 如果数组未满,向下标为有效数据个数的位置插入数据。

注意:在插入数据之前,必须判断数组是否已满。如果数组已满,则不能再继续插入数据,否则可能会导致数据覆盖或程序崩溃等严重问题。

假设数据量非常大,频繁的获取数组有效数据个数确实可能会影响程序执行效率。

所以我们可以得出结论:最基础的数据结构能提供的操作不能满足复杂算法的实现。

二、顺序表(Sequence List)

2.1 线性表的概念以及结构

线性表(linear list)是n个具有相同特性的数据元素的有限序列。线性表是一种在实际中广泛使用的数据结构,常见的线性表包括顺序表(Sequence List、链表、栈、队列、字符串等。

线性表在逻辑上是线性结构,也就是说它表现为一条连续的直线。然而,在物理结构上,线性表并不一定是连续的。线性表在物理上存储时,通常以数组和链式结构的形式存在。数组存储方式使得元素在内存中连续排列,便于通过下标快速访问;而链式存储方式则通过指针或引用将元素链接在一起,允许在动态环境中灵活地进行插入和删除操作。

如何来理解逻辑结构和物理结构呢?

简单来说,逻辑结构关注的是数据元素之间的逻辑关系,它描述的是数据应该如何被组织,而不关心数据具体是如何在计算机内存中存放的。例如,我们可以说一个列表是一系列有序的元素,这就是它的逻辑结构。

而物理结构,或者叫存储结构,则关心数据在计算机内存中的具体存放方式。比如,这个列表是用数组的形式连续存储在内存中,还是用链表的形式分散存储在内存中,这就是物理结构所关心的问题。

2.2 顺序表分类

2.2.1 顺序表和数组的区别

其实顺序表的底层逻辑是数组,对数组进行了封装,并且实现了常用的增删查改等接口。

2.2.2 顺序表的分类

顺序表分为静态顺序表和动态顺序表,顾名思义,一个静态动不了,一个动态可调节。

• 静态顺序表

刚刚我们说了,顺序表是基于数组实现的,那静态顺序表就是基于定长数组实现的。

//静态顺序表
typedef int SLDataType;
#define N 7
typedef struct SeqList{
    SLDataType a[N];
    int size;
}SL;

• 动态顺序表:

动态一词,我们不由得想起之前提到的动态内存管理,没错动态顺序表确实是需要动态申请的。

内存地产风云录:malloc、free、calloc、realloc演绎动态内存世界的楼盘开发与交易大戏-CSDN博客

// 动态顺序表 -- 按需申请
typedef struct SeqList
{
 SLDataType* a;
 int size; // 有效数据个数
 int capacity; // 空间容量
}SL;

 

2.3 顺序表的实现

还记得扫雷嘛各位,当时我们创建了三个文件,分别是:game.c,game.h和test.c。game.c用来存放函数的实现,game.h用来存放头文件以及宏,而test.c用来测试。写完函数进行测试,这是一个良好的习惯,今天我们实现顺序表也会这样操作,但这里不介绍test.c文件,着重讲解函数的实现。

首先,我们需要定义一个动态顺序表,我们将定义放在头文件中。

//定义顺序表中的数据类型,可以随时修改int
typedef int SLDataType;

//动态
typedef struct SeqList
{
	SLDataType *arr;
	int size; //有效数据个数
	int capacity; //空间大小
}SL;
2.3.1 初始化

接下来我们需要对这个顺序表进行初始化(initialize)。

这个函数没什么好解释的,只需要将顺序表的成员,指针指向NULL,数变成0。唯一一点重要的是:我们传参的时候,需要传顺序表的地址。

要区别传值和传址,我们在这篇文章中解释过:

灵魂指针,教给(一)-CSDN博客

实参传递给形参的时候,形参会单独创建⼀份临时空间来接收实参,对形参的修改不影响实参。如果我们想修改顺序表里的值,那必然得传它的地址。代码如下:

//初始化,注意传址
void SLInit(SL* ps)
{
	ps->arr = NULL;
	ps->capacity = ps->size = 0;

}
 2.3.2 销毁

这个函数也很简单,我们既然是动态申请的空间,就需要将其free掉。

函数实现:

//销毁
void SLDestroy(SL* ps)
{
	if (ps->arr)
	{
		free(ps->arr);
	}
	ps->arr = NULL;
	ps->capacity = ps->size = 0;
}
2.3.3 尾插

我们在尾插的时候,试想一下,如果空间不够,是不是就没位置插了,这个时候我们要重新开辟空间,并且,我们在初始化后,空间是0,肯定需要申请。所以这里我们需要一个函数来进行扩容:

//扩容
void SLCheckCapacity(SL* ps)
{
	assert(ps);
	//先判断空间是否足够,不够需要扩容

	if (ps->capacity == ps->size) //不够扩容
	{
		//申请空间
		//三目判断初始空间大小
		int NewCapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
		SLDataType* tmp = (SLDataType*)realloc(ps->arr, NewCapacity * sizeof(SLDataType));
		if (tmp == NULL)
		{
			perror("realloc fail");
			exit(1);
		}
		ps->arr = tmp;
		ps->capacity = NewCapacity;
	}
}

首先我们用assert断言防止空指针,之后我们用到了if语句来判断空间是否足够,如果不够则进入if扩容。判断条件我们仔细想想,什么情况是空间不够,是不是当空间大小和有效数据个数相等时,就没法插入了,所以我们将这个作为判断条件。

扩容搞定后,我们就可以进行插入操作了,在尾插中,我们需要传入两个参数:一个是顺序表的地址,另一个是要插入的元素。

尾插其实很简单,我们只需要将最后的位置赋值为要插入的元素就可以了,最后别忘了既然插进去了一个元素,有效数据个数肯定要++。

//尾插
void SLPushBack(SL* ps, SLDataType x)
{
	SLCheckCapacity(ps);
	 //足够尾插
	ps->arr[ps->size++] = x;
	/*ps->arr[ps->size] = x;
	ps->size++;*/
	
}
2.3.4 头插

尾插我们直接在尾巴后面插入元素即可,头插意味着我们要在头部插入一个元素,那么我们就需要在头部为其留出位置,同时原有元素还不可以改变。有了位置再进行插入,思路清晰,直接上代码。

//头插
void SLPushFront(SL* ps, SLDataType x)
{
	SLCheckCapacity(ps);
	//整体挪动,再插入
	for (int i = ps->size; i > 0; i--)
	{
		ps->arr[i] = ps->arr[i - 1];
	}
	ps->arr[0] = x;
	ps->size++;
}

这里注意,for循环遍历时,从后往前遍历,防止覆盖。

2.3.5 尾删

大家想想,怎么删,将最后一个元素置为-1?还是别的什么数字?其实我们根本不需要管最后一个元素,我们直接将size进行 - - 即可。因为不影响其它功能,所以我们不用考虑那么复杂。

//尾删
void SLPopBack(SL* ps)
{
	assert(ps);
	assert(ps->size);
	//删完数据,size--
	//ps->arr[ps->size - 1] = -1;要不要这行都可以
	ps->size--;
}
2.3.6 头删

头删也很简单,直接覆盖即可。

//头删
void SLPopFront(SL* ps)
{
	assert(ps);
	assert(ps->size);
	//直接覆盖即可
	for (int i = 1; i < ps->size; i++)
	{
		ps->arr[i - 1] = ps->arr[i];
	}
	ps->size--;
}
2.3.7 在指定位置插入

我们效仿头插遍历,为元素留位置,在指定位置插入也是如此。

//在指定位置插入
void SLInsert(SL* ps, int pos, SLDataType x)//注意pos是下标
{
	assert(ps);
	assert(pos >= 0 && pos <= ps->size);
	SLCheckCapacity(ps);
	for (int i = ps->size; i > pos; i--)
	{
		ps->arr[i] = ps->arr[i - 1];//arr[pos+1]=arr[pos];
	}
	ps->arr[pos] = x;
	ps->size++;
}
2.3.8 在指定位置删除

同样是遍历,同样是覆盖,不多解释,直接上代码。

//在指定位置删除
void SLErase(SL* ps, int pos)
{
	assert(ps);
	assert(pos >= 0 && pos < ps->size);
	for (int i = pos; i < ps->size - 1; i++)
	{
		ps->arr[i] = ps->arr[i + 1];//arr[size-2]=arr[size-1]
	}
	ps->size--;
}
2.3.9 查找

其实查找也是遍历,我们可以发现,但凡我们弄清原理之后,顺序表还是蛮简单的。

//查找
int SLFind(SL* ps, SLDataType x)
{
	assert(ps);
	for (int i = 0; i < ps->size; i++)
	{
		if (ps->arr[i] == x)
		{
			return i;
		}
	}
	return -1;
}

 2.4 总代码

2.4.1 SeqList.h
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
//定义顺序表的结构

//#define N 100


//静态顺序表
//struct SeqList
//{
//	int arr[N];
//	int size;//有效数据个数
//};

//定义顺序表中的数据类型,可以随时修改int
typedef int SLDataType;

//动态
typedef struct SeqList
{
	SLDataType *arr;
	int size; //有效数据个数
	int capacity; //空间大小
}SL;

//typedef struct SeqList SL;

//初始化
void SLInit(SL* ps);
//销毁
void SLDestroy(SL* ps);
//打印
void SLPrint(SL* ps);
//扩容
void SLCheckCapacity(SL* ps);
//尾部插入删除 / 头部插入删除
void SLPushBack(SL* ps, SLDataType x);
void SLPushFront(SL* ps, SLDataType x);

void SLPopBack(SL* ps);
void SLPopFront(SL* ps);
//指定位置之前插入/删除数据
void SLInsert(SL* ps, int pos, SLDataType x);
void SLErase(SL* ps, int pos);
//查找
int SLFind(SL* ps, SLDataType x);
2.4.2 SeqList.c
#define _CRT_SECURE_NO_WARNINGS 1
#include"SeqList.h"

//初始化,注意传址
void SLInit(SL* ps)
{
	ps->arr = NULL;
	ps->capacity = ps->size = 0;

}
//销毁
void SLDestroy(SL* ps)
{
	if (ps->arr)
	{
		free(ps->arr);
	}
	ps->arr = NULL;
	ps->capacity = ps->size = 0;
}
//打印
void SLPrint(SL* ps)
{
	for (int i = 0; i < ps->size; i++)
	{
		printf("%d ", ps->arr[i]);
	}
	printf("\n");
}
//扩容
void SLCheckCapacity(SL* ps)
{
	assert(ps);
	//先判断空间是否足够,不够需要扩容

	if (ps->capacity == ps->size) //不够扩容
	{
		//申请空间
		//三目判断初始空间大小
		int NewCapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
		SLDataType* tmp = (SLDataType*)realloc(ps->arr, NewCapacity * sizeof(SLDataType));
		if (tmp == NULL)
		{
			perror("realloc fail");
			exit(1);
		}
		ps->arr = tmp;
		ps->capacity = NewCapacity;
	}
}
//尾插
void SLPushBack(SL* ps, SLDataType x)
{
	SLCheckCapacity(ps);
	 //足够尾插
	ps->arr[ps->size++] = x;
	/*ps->arr[ps->size] = x;
	ps->size++;*/
	
}
//头插
void SLPushFront(SL* ps, SLDataType x)
{
	SLCheckCapacity(ps);
	//整体挪动,再插入
	for (int i = ps->size; i > 0; i--)
	{
		ps->arr[i] = ps->arr[i - 1];
	}
	ps->arr[0] = x;
	ps->size++;
}
//尾删
void SLPopBack(SL* ps)
{
	assert(ps);
	assert(ps->size);
	//删完数据,size--
	//ps->arr[ps->size - 1] = -1;要不要这行都可以
	ps->size--;
}
//头删
void SLPopFront(SL* ps)
{
	assert(ps);
	assert(ps->size);
	//直接覆盖即可
	for (int i = 1; i < ps->size; i++)
	{
		ps->arr[i - 1] = ps->arr[i];
	}
	ps->size--;
}
//在指定位置插入
void SLInsert(SL* ps, int pos, SLDataType x)//注意pos是下标
{
	assert(ps);
	assert(pos >= 0 && pos <= ps->size);
	SLCheckCapacity(ps);
	for (int i = ps->size; i > pos; i--)
	{
		ps->arr[i] = ps->arr[i - 1];//arr[pos+1]=arr[pos];
	}
	ps->arr[pos] = x;
	ps->size++;
}
//在指定位置删除
void SLErase(SL* ps, int pos)
{
	assert(ps);
	assert(pos >= 0 && pos < ps->size);
	for (int i = pos; i < ps->size - 1; i++)
	{
		ps->arr[i] = ps->arr[i + 1];//arr[size-2]=arr[size-1]
	}
	ps->size--;
}
//查找
int SLFind(SL* ps, SLDataType x)
{
	assert(ps);
	for (int i = 0; i < ps->size; i++)
	{
		if (ps->arr[i] == x)
		{
			return i;
		}
	}
	return -1;
}

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

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

相关文章

华为开源自研AI框架昇思MindSpore应用案例:数据处理性能优化

如果你对MindSpore感兴趣&#xff0c;可以关注昇思MindSpore社区 数据是整个深度学习中最重要的一环&#xff0c;因为数据的好坏决定了最终结果的上限&#xff0c;模型的好坏只是去无限逼近这个上限&#xff0c;所以高质量的数据输入&#xff0c;会在整个深度神经网络中起到积极…

嵌入式Linux开发实操(十八):Linux音频ALSA开发

应用程序程序员应该使用库API,而不是内核API。alsa库提供了内核API 100%的功能,但增加了可用性方面的主要改进,使应用程序代码更简单、更美观。未来的修复程序或兼容性代码可能会放在库代码中,而不是放在内核驱动程序中。 使用ALSA API和libasound进行简单的声音播放: /*…

用全连接对手写数字识别案例(附解决TensorFlow2.x没有examples问题)

数据集介绍 数据集直接调用可能出现问题&#xff0c;建议从官网直接下载下来&#xff0c;下载存在这四个文件 手写数字识别数据集下载&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/1nqhP4yPNcqefKYs91jp9ng?pwdxe1h 提取码&#xff1a;xe1h 55000行训练数据集&a…

CentOS如何使用Docker部署Plik服务并实现公网访问本地设备上传下载文件

文章目录 1. Docker部署Plik2. 本地访问Plik3. Linux安装Cpolar4. 配置Plik公网地址5. 远程访问Plik6. 固定Plik公网地址7. 固定地址访问Plik 本文介绍如何使用Linux docker方式快速安装Plik并且结合Cpolar内网穿透工具实现远程访问&#xff0c;实现随时随地在任意设备上传或者…

Asciinema:一款强大的终端录屏工具

最近看见一个好的终端录屏工具&#xff0c;现在记录一下并进行分享。 终端录屏工具asciinema是一个免费和开源的解决方案&#xff0c;用于记录终端会话并在网上分享。它支持在终端内直接录制&#xff0c;提供播放、复制粘贴和嵌入功能。安装方面&#xff0c;支持多种操作系统&…

Git 原理及使用 (带动图演示)

文章目录 &#x1f308; Ⅰ Git 安装&#x1f319; 01. Linux - centos &#x1f308; Ⅱ Git 工作区、暂存区和版本库&#x1f319; 01. 认识工作区、暂存区和版本库&#x1f319; 02. 使用 Git 管理工作区的文件 &#x1f308; Ⅲ Git 基本操作&#x1f319; 01. 创建本地仓库…

使用代理绕过网站的反爬机制

最近在尝试收集一些网络指标的数据&#xff0c; 所以&#xff0c; 我又开始做爬虫了。 :) 我们在做爬虫的过程中经常会遇到这样的情况&#xff0c;最初爬虫正常运行&#xff0c;正常抓取数据&#xff0c;一切看起来都是那么的美好&#xff0c;然而一杯茶的功夫可能就会出现错误…

YOLOv9改进策略 | Conv篇 | 利用YOLO-MS的MSBlock二次创新RepNCSPELAN4(全网独家创新)

一、本文介绍 本文给大家带来的改进机制是利用YOLO-MS提出的一种针对于实时目标检测的MSBlock模块(其其实不能算是Conv但是其应该是一整个模块)&#xff0c;我们将其用于RepNCSPELAN中组合出一种新的结构&#xff0c;来替换我们网络中的模块可以达到一种轻量化的作用&#xff…

Vue3+TS版本Uniapp:项目前置操作

作者&#xff1a;前端小王hs 阿里云社区博客专家/清华大学出版社签约作者✍/CSDN百万访问博主/B站千粉前端up主 环境&#xff1a;使用vscode进行开发 如果一开始是使用的HbuilderX&#xff0c;请看hbuilderX创建的uniapp项目转移到vscode 为什么选择vscode&#xff1f;有更好…

Ceph 分布式文件系统 搭建及使用

一、Ceph 介绍 在当今数据爆炸式增长的时代&#xff0c;企业对于可靠、可扩展的存储解决方案的需求日益迫切。Ceph 作为一种开源的、可伸缩的分布式存储解决方案&#xff0c;正逐渐成为企业级存储领域的热门选择。Ceph是一种由Radicalbit公司开发的开源分布式存储系统&#xf…

学习微服务nacos遇到的问题

在学习微服务注册到nacos的时候&#xff0c;所有过程都正确了&#xff0c;注册也成功了&#xff0c;但是访问不了调用的地址报错出现问题。 一、引入依赖 在cloud-demo父工程的pom文件中的<dependencyManagement>中引入SpringCloudAlibaba的依赖 1、springboot <pa…

【系统分析师】软件工程

文章目录 1、信息系统生命周期2、软件开发模型2.1 原型及其演化2.2 增量模型和螺旋模型2.3 V模型、喷泉模型、快速应用开发2.4 构件组装模型2.5 统一过程-UP2.6 敏捷方法 3、逆向工程4、净室软件工程 【写在前面】 记录了一系列【系统分析师】文章&#xff0c;点击下面的链接&a…

Python 全栈安全(二)

原文&#xff1a;annas-archive.org/md5/712ab41a4ed6036d0e8214d788514d6b 译者&#xff1a;飞龙 协议&#xff1a;CC BY-NC-SA 4.0 第二部分&#xff1a;认证与授权 本书的第二部分是最具商业价值的部分。我这样说是因为它充满了大多数系统需要具备的实用工作流示例&#xf…

Linux 系统systemd(pid=1)占用80端口导致web程序无法启动

背景 linux系统内安装了例如nginx的网站程序&#xff0c;但是无法正常启动&#xff0c;netstat 查看下 80端口被 systemd 占用。 处理方法 注意务必组好快照备份后再操作。 做好备份后将/usr/lib/systemd/system 内http相关的配置文件重命名后重启主机恢复正常。 重启后正常 sy…

分类神经网络2:ResNet模型复现

目录 ResNet网络架构 ResNet部分实现代码 ResNet网络架构 论文原址&#xff1a;https://arxiv.org/pdf/1512.03385.pdf 残差神经网络(ResNet)是由微软研究院的何恺明、张祥雨、任少卿、孙剑等人提出的&#xff0c;通过引入残差学习解决了深度网络训练中的退化问题&#xff…

【FFmpeg】视频与图片互相转换 ( 视频与 JPG 静态图片互相转换 | 视频与 GIF 动态图片互相转换 )

文章目录 一、视频与 JPG 静态图片互相转换1、视频转静态图片2、视频转多张静态图片3、多张静态图片转视频 二、视频与 GIF 动态图片互相转换1、视频转成 GIF 动态图片2、 GIF 动态图片转成视频 一、视频与 JPG 静态图片互相转换 1、视频转静态图片 执行 ffmpeg -i input.mp4 …

云原生Kubernetes: K8S 1.29版本 部署ingress-nginx

目录 一、实验 1.环境 2. K8S 1.29版本 部署ingress-nginx 二、问题 1.kubectl 如何强制删除 Pod、Namespace 资源 2.创建pod失败 3.pod报错ImagePullBackOff 4.docker如何将镜像上传到官方仓库 5.创建ingress报错 一、实验 1.环境 &#xff08;1&#xff09;主机 表…

数据结构 - 顺序表

一. 线性表的概念 线性表&#xff08;linear list&#xff09;是n个具有相同特性的数据元素的有限序列。 线性表是一种在实际中广泛使用的数据结构&#xff0c;常见的线性表&#xff1a;顺序表、链表、栈、队列、字符串... 线性表在逻辑上是线性结构&#xff0c;也就说是连续的…

SpringBoot 集成 WebSocket

前言 最近在做一个 WebSocket 通信服务的软件&#xff0c;所以必须跟着学一学。 1、WebSocket 概述 一般情况下&#xff0c;我们的服务器和服务器之间可以发送请求&#xff0c;但是服务器是不能向浏览器去发送请求的。因为设计之初并没有想到以后会出现服务端频繁向客户端发送…

【代码随想录刷题记录】LeetCode34在排序数组中查找元素的第一个和最后一个位置

题目地址 最近忙活实验&#xff0c;实在没空刷题&#xff0c;这个题对我来说难度还蛮大的&#xff0c;尤其是理解那个找左边界和找右边界的条件&#xff0c;后来我按照自己的理解写了出来&#xff08;感觉给的答案解释起来有点反认识规律&#xff09;&#xff0c;所以我从0开始…